KiBot/kibot/out_navigate_results.py

291 lines
12 KiB
Python

# -*- coding: utf-8 -*-
# Copyright (c) 2022 Salvador E. Tropea
# Copyright (c) 2022 Instituto Nacional de Tecnología Industrial
# License: GPL-3.0
# Project: KiBot (formerly KiPlot)
# The Assembly image is a composition from Pixlok and oNline Web Fonts
# The rest are KiCad icons
import os
from shutil import copy2
from math import ceil
from .gs import GS
import pprint
from .optionable import BaseOptions
from .kiplot import config_output, get_output_dir
from .registrable import RegOutput
from .macros import macros, document, output_class # noqa: F401
from . import log, __version__
logger = log.get_logger()
CAT_IMAGE = {'PCB': 'pcbnew.svg',
'Schematic': 'eeschema.svg',
'fabrication': 'fabrication.svg',
'export': 'export.svg',
'assembly': 'assembly_simple.svg',
'repair': 'repair.svg',
'docs': 'project.svg',
'BoM': 'bom.svg',
'3D': '3d.svg',
'gerber': 'gerber.svg',
'drill': 'load_drill.svg'}
EXT_IMAGE = {'gbr': 'file_gbr.svg',
'gtl': 'file_gbr.svg',
'gtp': 'file_gbr.svg',
'gbo': 'file_gbr.svg',
'gto': 'file_gbr.svg',
'gbs': 'file_gbr.svg',
'gbl': 'file_gbr.svg',
'gts': 'file_gbr.svg',
'gml': 'file_gbr.svg',
'gm1': 'file_gbr.svg',
'gbrjob': 'file_gerber_job.svg',
'brd': 'file_brd.svg',
'dxf': 'file_dxf.svg',
'cad': 'file_cad.svg',
'drl': 'file_drl.svg',
'pdf': 'file_pdf.svg',
'txt': 'file_txt.svg',
'pos': 'file_pos.svg',
'csv': 'file_csv.svg',
'svg': 'file_svg.svg',
'eps': 'file_eps.svg',
'png': 'file_png.svg',
'jpg': 'file_jpg.svg',
'plt': 'file_plt.svg',
'ps': 'file_ps.svg',
'step': 'file_stp.svg',
'stp': 'file_stp.svg',
'html': 'file_html.svg',
'xml': 'file_xml.svg',
'tsv': 'file_tsv.svg',
'xlsx': 'file_xlsx.svg',
'xyrs': 'file_xyrs.svg'}
for i in range(31):
n = str(i)
EXT_IMAGE['gl'+n] = 'file_gbr.svg'
EXT_IMAGE['g'+n] = 'file_gbr.svg'
EXT_IMAGE['gp'+n] = 'file_gbr.svg'
BIG_ICON = 256
MID_ICON = 64
OUT_COLS = 10
STYLE = """
.cat-table { margin-left: auto; margin-right: auto; }
.cat-table td { padding: 20px 24px; }
.nav-table { margin-left: auto; margin-right: auto; }
.nav-table td { padding: 20px 24px; }
.output-table {
width: 1280px;
margin-left: auto;
margin-right: auto;
border-collapse:
collapse;
margin-top: 5px;
margin-bottom: 4em;
font-size: 0.9em;
font-family: sans-serif;
min-width: 400px;
border-radius: 5px 5px 0 0;
overflow: hidden;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.15);
}
.output-table thead tr { background-color: #0e4e8e; color: #ffffff; text-align: left; }
.output-table th { padding: 10px 12px; }
.output-table td { padding: 5px 7px; }
.out-cell { width: 128px; text-align: center }
.out-img { text-align: center; margin-left: auto; margin-right: auto; }
.cat-img { text-align: center; margin-left: auto; margin-right: auto; }
.td-small { text-align: center; font-size: 0.6em; }
.generator { text-align: right; font-size: 0.6em; }
a:link, a:visited { text-decoration: none;}
a:hover, a:active { text-decoration: underline;}
"""
class Navigate_ResultsOptions(BaseOptions):
def __init__(self):
with document:
self.output = GS.def_global_output
""" Filename for the output (%i=html, %x=navigate) """
self.link_from_root = ''
""" The name of a file to create at the main output directory linking to the home page """
super().__init__()
self._expand_id = 'navigate'
self._expand_ext = 'html'
def add_to_tree(self, cat, out, o_tree):
# Add `out` to `o_tree` in the `cat` category
cat = cat.split('/')
node = o_tree
for c in cat:
if c not in node:
# New one
node[c] = {}
node = node[c]
node[out.name] = out
def copy(self, img):
copy2(os.path.join(self.img_src_dir, img), os.path.join(self.out_dir, img))
def get_image_for_cat(self, cat):
if cat in CAT_IMAGE:
img = 'images/'+CAT_IMAGE[cat]
cat_img = '<img src="{}" alt="{}" width="{}" height="{}">'.format(img, cat, BIG_ICON, BIG_ICON)
cat = ('<table class="cat-img"><tr><td>{}<br>{}</td></tr></table>'.
format(cat_img, cat))
self.copy(img)
return cat
def get_image_for_file(self, file):
ext = os.path.splitext(file)[1][1:].lower()
img = 'images/'+EXT_IMAGE.get(ext, 'unknown.svg')
ext_img = '<img src="{}" alt="{}" width="{}" height="{}">'.format(img, file, MID_ICON, MID_ICON)
file = ('<table class="out-img"><tr><td>{}</td></tr><tr><td class="td-small">{}</td></tr></table>'.
format(ext_img, file))
self.copy(img)
return file
def add_back_home(self, f, prev):
if prev is not None:
prev += '.html'
f.write('<table class="nav-table">')
f.write(' <tr>')
f.write(' <td><a href="{}"><img src="images/back.svg" width="{}" height="{}" alt="go back"></a></td>'.
format(prev, MID_ICON, MID_ICON))
f.write(' <td><a href="{}"><img src="images/home.svg" width="{}" height="{}" alt="go home"></a></td>'.
format(self.home, MID_ICON, MID_ICON))
f.write(' </tr>')
f.write('</table>')
f.write('<p class="generator">Generated by <a href="https://github.com/INTI-CMNB/KiBot/">KiBot</a> v{}</p>'.
format(__version__))
def write_head(self, f, title):
f.write('<!DOCTYPE html>\n')
f.write('<html lang="en">\n')
f.write('<head>\n')
f.write(' <title>{}</title>\n'.format(title if title else 'Main page'))
f.write(' <meta charset="UTF-8">\n') # UTF-8 encoding for unicode support
f.write(' <link rel="stylesheet" href="styles.css">\n')
f.write(' <link rel="icon" href="favicon.ico">\n')
f.write('</head>\n')
f.write('<body>\n')
def generate_cat_page_for(self, name, node, prev, category):
logger.debug('- Categories: '+str(node.keys()))
with open(os.path.join(self.out_dir, name), 'wt') as f:
self.write_head(f, category)
name, ext = os.path.splitext(name)
# Limit to 5 categories by row
c_cats = len(node)
rows = ceil(c_cats/5.0)
by_row = c_cats/rows
acc = 0
f.write('<table class="cat-table">\n<tr>\n')
for cat, content in node.items():
if acc >= by_row:
# Flush the table and create another
acc = 0
f.write('</tr>\n</table>\n<table class="cat-table">\n<tr>\n')
pname = name+'_'+cat+ext
self.generate_page_for(content, pname, name, category+'/'+cat)
f.write(' <td><a href="{}">{}</a></td>\n'.format(pname, self.get_image_for_cat(cat)))
acc += 1
f.write('</tr>\n</table>\n')
self.add_back_home(f, prev)
f.write('</body>\n</html>\n')
def generate_end_page_for(self, name, node, prev, category):
logger.debug('- Outputs: '+str(node.keys()))
with open(os.path.join(self.out_dir, name), 'wt') as f:
self.write_head(f, category)
name, ext = os.path.splitext(name)
for oname, out in node.items():
f.write('<table class="output-table">\n')
oname = oname.replace('_', ' ')
oname = oname[0].upper()+oname[1:]
if out.comment:
oname += ': '+out.comment
f.write('<thead><tr><th colspan="{}">{}</th></tr></thead>\n'.format(OUT_COLS, oname))
config_output(out)
out_dir = get_output_dir(out.dir, out, dry=True)
f.write('<tbody><tr>\n')
targets = out.get_targets(out_dir)
if len(targets) == 1:
tg = os.path.relpath(os.path.abspath(targets[0]), start=self.out_dir)
f.write('<td class="out-cell" colspan="{}"><a href="{}">{}</a></td>\n'.
format(OUT_COLS, tg, self.get_image_for_file(os.path.basename(tg))))
else:
c = 0
for tg in targets:
if c == OUT_COLS:
f.write('</tr>\n<tr>\n')
c = 0
tg = os.path.relpath(os.path.abspath(tg), start=self.out_dir)
f.write('<td class="out-cell"><a href="{}">{}</a></td>\n'.
format(tg, self.get_image_for_file(os.path.basename(tg))))
c = c+1
for _ in range(c, OUT_COLS):
f.write('<td class="out-cell"></td>\n')
f.write('</tr></tbody>\n')
f.write('</table>\n')
self.add_back_home(f, prev)
f.write('</body>\n</html>\n')
def generate_page_for(self, node, name, prev=None, category=''):
logger.debug('Generating page for '+name)
if isinstance(list(node.values())[0], dict):
self.generate_cat_page_for(name, node, prev, category)
else:
self.generate_end_page_for(name, node, prev, category)
def run(self, name):
self.out_dir = os.path.dirname(name)
self.img_src_dir = os.path.dirname(__file__)
self.img_dst_dir = os.path.join(self.out_dir, 'images')
os.makedirs(self.img_dst_dir, exist_ok=True)
name = os.path.basename(name)
# Create a tree with all the outputs
o_tree = {}
for o in RegOutput.get_outputs():
cat = o._category
if cat is None:
continue
if isinstance(cat, str):
cat = [cat]
for c in cat:
self.add_to_tree(c, o, o_tree)
logger.debug('Collected outputs:\n'+pprint.pformat(o_tree))
with open(os.path.join(self.out_dir, 'styles.css'), 'wt') as f:
f.write(STYLE)
# Create the pages
self.home = name
self.copy('images/back.svg')
self.copy('images/home.svg')
copy2(os.path.join(self.img_src_dir, 'images', 'favicon.ico'), os.path.join(self.out_dir, 'favicon.ico'))
self.generate_page_for(o_tree, name)
# Link it?
if self.link_from_root:
redir_file = os.path.join(GS.out_dir, self.link_from_root)
rel_start = os.path.relpath(os.path.join(self.out_dir, name), start=GS.out_dir)
logger.debug('Creating redirector: {} -> {}'.format(redir_file, rel_start))
with open(redir_file, 'wt') as f:
f.write('<html>\n<head>\n<meta http-equiv="refresh" content="0; {}"/>'.format(rel_start))
f.write('</head>\n</html>')
@output_class
class Navigate_Results(BaseOutput): # noqa: F821
""" Navigate Results
Generates a web page to navigate the generated outputs """
def __init__(self):
super().__init__()
with document:
self.options = Navigate_ResultsOptions
""" [dict] Options for the `navigate_results` output """
@staticmethod
def get_conf_examples(name, layers, templates):
outs = BaseOutput.simple_conf_examples(name, 'Web page to browse the results', 'Browse') # noqa: F821
outs[0]['options'] = {'link_from_root': 'index.html'}
return outs