| # Copyright lowRISC contributors. | 
 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. | 
 | # SPDX-License-Identifier: Apache-2.0 | 
 | """ | 
 | Generate HTML documentation from validated dashboard Hjson tree | 
 | """ | 
 |  | 
 | import html | 
 | import logging as log | 
 | import os.path | 
 | import re | 
 | import sys | 
 | from pathlib import Path | 
 |  | 
 | import dashboard.dashboard_validate as dashboard_validate | 
 | import hjson | 
 | import mistletoe as mk | 
 |  | 
 | REPO_TOP = Path(__file__).parent.parent.parent.resolve().absolute() | 
 |  | 
 |  | 
 | def genout(outfile, msg): | 
 |     outfile.write(msg) | 
 |  | 
 |  | 
 | STAGE_STRINGS = { | 
 |     # Life Stages | 
 |     'L0': 'Specification', | 
 |     'L1': 'Development', | 
 |     'L2': 'Signed Off', | 
 |     # Design Stages | 
 |     'D0': 'Initial Work', | 
 |     'D1': 'Functional', | 
 |     'D2': 'Feature Complete', | 
 |     'D2S': 'Security Countermeasures Complete', | 
 |     'D3': 'Design Complete', | 
 |     # Verification Stages | 
 |     'V0': 'Initial Work', | 
 |     'V1': 'Under Test', | 
 |     'V2': 'Testing Complete', | 
 |     'V2S': 'Testing Complete, With Security Countermeasures Verified', | 
 |     'V3': 'Verification Complete', | 
 |     # DIF Stages (S for Software) | 
 |     'S0': 'Initial Work', | 
 |     'S1': 'Functional', | 
 |     'S2': 'Complete', | 
 |     'S3': 'Stable', | 
 | } | 
 |  | 
 |  | 
 | def convert_stage(stagestr): | 
 |     return STAGE_STRINGS.get(stagestr, "UNKNOWN") | 
 |  | 
 | def get_doc_url(base, url): | 
 |     """ Produce a URL to a document. | 
 |  | 
 |     Relative `url`s are relative to `base`, absolute `url`s are relative to the | 
 |     repository root. | 
 |     """ | 
 |     assert isinstance(url, str) and len(url) > 0 | 
 |     if url[0] == '/': | 
 |         return url | 
 |     else: | 
 |         return '/' + base + '/' + url | 
 |  | 
 |  | 
 | # Link module name with its design spec doc. | 
 | def get_linked_design_spec(obj): | 
 |     result = "" | 
 |     if 'design_spec' in obj: | 
 |         result = "<span title='Design Spec'><a href='{}'>".format( | 
 |             get_doc_url(obj['_ip_desc_hjson_dir'], obj['design_spec'])) | 
 |         result += "<code>{}</code></a></span>".format(html.escape(obj['name'])) | 
 |     else: | 
 |         result = html.escape(obj['name']) | 
 |  | 
 |     return result | 
 |  | 
 |  | 
 | # Provide the link to the DV document. | 
 | def get_linked_dv_doc(obj): | 
 |     if 'dv_doc' in obj: | 
 |         return "<span title='DV Document'><a href=\"{}\">DV</a></span>".format( | 
 |             get_doc_url(obj['_ip_desc_hjson_dir'], obj['dv_doc'])) | 
 |     else: | 
 |         return "" | 
 |  | 
 |  | 
 | # Link the version to the commit id (if available). | 
 | def get_linked_version(rev): | 
 |     version = html.escape(rev['version']) | 
 |     tree = rev['commit_id'] if 'commit_id' in rev else 'master' | 
 |     url = "https://github.com/lowrisc/opentitan/tree/{}".format(tree) | 
 |     return "<span title='{}'><a href=\"{}\">{}</a></span>".format( | 
 |         tree, url, version) | 
 |  | 
 |  | 
 | # Link D/V stages with the checklist table. | 
 | def get_linked_checklist(obj, rev, stage, is_latest_rev=True): | 
 |     if not stage or stage not in rev: | 
 |         return "" | 
 |  | 
 |     url = "" | 
 |     in_page_ref = "" | 
 |     if rev[stage] not in ["D0", "V0"]: | 
 |         # if in D0 or V0 stage, there is no in-page reference. | 
 |         in_page_ref = "#{}".format(html.escape(rev[stage]).lower()) | 
 |  | 
 |     # If the checklist is available, the commit id is available, and it is not | 
 |     # the latest revision, link to the committed version of the checklist. | 
 |     # Else, if checklist is available, then link to the current version of the | 
 |     # checklist html. | 
 |     # Else, link to the template. | 
 |     if 'hw_checklist' in obj and 'commit_id' in rev and not is_latest_rev: | 
 |         url = "https://github.com/lowrisc/opentitan/tree/{}/{}.md{}".format( | 
 |             rev['commit_id'], obj['hw_checklist'], in_page_ref) | 
 |     elif 'hw_checklist' in obj: | 
 |         url = get_doc_url(obj['_ip_desc_hjson_dir'], | 
 |                           obj['hw_checklist'] + in_page_ref) | 
 |     else: | 
 |         # There is no checklist available, so point to the template. | 
 |         # doc/project/hw_checklist.md.tpl is a symlink to ip_checklist.md.tpl, | 
 |         # and github doesn't auto-render symlinks, so we have to use the url | 
 |         # where the symlink points to. | 
 |         url = "https://github.com/lowrisc/opentitan/tree/master/" | 
 |         url += "util/uvmdvgen/checklist.md.tpl" | 
 |  | 
 |     return "<a href=\"{}\">{}</a>".format(url, html.escape(rev[stage])) | 
 |  | 
 |  | 
 | # Link S stages with the checklist table. | 
 | def get_linked_sw_checklist(obj, rev, stage, is_latest_rev=True): | 
 |     if not stage or stage not in rev: | 
 |         return "" | 
 |  | 
 |     url = "" | 
 |     in_page_ref = "" | 
 |     if rev[stage] not in ["S0"]: | 
 |         # if in D0 or V0 stage, there is no in-page reference. | 
 |         in_page_ref = "#{}".format(html.escape(rev[stage]).lower()) | 
 |  | 
 |     # If the checklist is available, the commit id is available, and it is not | 
 |     # the latest revision, link to the committed version of the checklist. | 
 |     # Else, if checklist is available, then link to the current version of the | 
 |     # checklist html. | 
 |     # Else, link to the template. | 
 |     if 'sw_checklist' in obj and 'commit_id' in rev and not is_latest_rev: | 
 |         url = "https://github.com/lowrisc/opentitan/tree/{}/{}.md{}".format( | 
 |             rev['commit_id'], obj['sw_checklist'], in_page_ref) | 
 |     elif 'sw_checklist' in obj: | 
 |         url = get_doc_url(obj['_ip_desc_hjson_dir'], | 
 |                           obj['sw_checklist'] + in_page_ref) | 
 |     else: | 
 |         # There is no checklist available, so point to the template. | 
 |         url = "https://github.com/lowrisc/opentitan/tree/master/" | 
 |         url += "doc/project/sw_checklist.md.tpl" | 
 |  | 
 |     return "<a href=\"{}\">{}</a>".format(url, html.escape(rev[stage])) | 
 |  | 
 |  | 
 | # Link development stages in "L# : D# : V# : S#" format. | 
 | # Hover text over each L, D, V, S indicates the stage mapping. | 
 | # D, V, and S stages link to actual checklist items. | 
 | def get_development_stage(obj, rev, is_latest_rev=True): | 
 |     if "life_stage" not in rev: | 
 |         return " " | 
 |  | 
 |     life_stage = rev['life_stage'] | 
 |     life_stage_html = "<span title='{}'>{}</span>".format( | 
 |         html.escape(convert_stage(life_stage)), html.escape(life_stage)) | 
 |  | 
 |     if life_stage != 'L0' and 'design_stage' in rev: | 
 |         design_stage = rev['design_stage'] | 
 |         design_stage_html = "<span title='{}'>{}</span>".format( | 
 |             html.escape(convert_stage(design_stage)), | 
 |             get_linked_checklist(obj, rev, 'design_stage', is_latest_rev)) | 
 |     else: | 
 |         design_stage_html = "-" | 
 |  | 
 |     if life_stage != 'L0' and 'verification_stage' in rev: | 
 |         verification_stage = rev['verification_stage'] | 
 |         verification_stage_html = "<span title='{}'>{}</span>".format( | 
 |             html.escape(convert_stage(verification_stage)), | 
 |             get_linked_checklist(obj, rev, 'verification_stage', | 
 |                                  is_latest_rev)) | 
 |     else: | 
 |         verification_stage_html = "-" | 
 |  | 
 |     if life_stage != 'L0' and 'dif_stage' in rev: | 
 |         dif_stage = rev['dif_stage'] | 
 |         dif_stage_html = "<span title='{}'>{}</span>".format( | 
 |             html.escape(convert_stage(dif_stage)), | 
 |             get_linked_sw_checklist(obj, rev, 'dif_stage', is_latest_rev)) | 
 |     else: | 
 |         dif_stage_html = "-" | 
 |  | 
 |     return [ | 
 |         life_stage_html, design_stage_html, verification_stage_html, | 
 |         dif_stage_html | 
 |     ] | 
 |  | 
 |  | 
 | # Create dashboard of hardware IP development status | 
 | def gen_dashboard_html(hjson_path, outfile): | 
 |     with hjson_path: | 
 |         prjfile = open(str(hjson_path)) | 
 |         try: | 
 |             obj = hjson.load(prjfile) | 
 |         except ValueError: | 
 |             raise SystemExit(sys.exc_info()[1]) | 
 |     if dashboard_validate.validate(obj) == 0: | 
 |         log.info("Generated dashboard object for " + str(hjson_path)) | 
 |     else: | 
 |         log.fail("hjson file import failed\n") | 
 |  | 
 |     ip_desc_hjson_dir = hjson_path.parent.relative_to(REPO_TOP) | 
 |     obj['_ip_desc_hjson_dir'] = str(ip_desc_hjson_dir) | 
 |  | 
 |     # If `revisions` field doesn't exist, the tool assumes the Hjson | 
 |     # as the previous project format, which has only one version entry. | 
 |     if "revisions" not in obj: | 
 |         print_version1_format(obj, outfile) | 
 |     else: | 
 |         print_multiversion_format(obj, outfile) | 
 |         return | 
 |  | 
 |  | 
 | # Version 1 (single version) format | 
 | def print_version1_format(obj, outfile): | 
 |     # yapf: disable | 
 |     genout(outfile, "      <tr>\n") | 
 |     genout(outfile, "        <td class=\"fixleft\">" + | 
 |                     get_linked_design_spec(obj) + "</td>\n") | 
 |     genout(outfile, "        <td class=\"dv-doc\">" + | 
 |                     get_linked_dv_doc(obj) + "</td>\n") | 
 |     genout(outfile, "        <td class=\"version\">" + | 
 |                     get_linked_version(obj) + "</td>\n") | 
 |  | 
 |     for stage_html in get_development_stage(obj, obj): | 
 |         genout(outfile, | 
 |                "        <td class=\"hw-stage\">" + stage_html + "</td>\n") | 
 |  | 
 |     if 'notes' in obj: | 
 |         genout(outfile, | 
 |                "        <td>" + mk.markdown(obj['notes']).rstrip() + "</td>\n") | 
 |     else: | 
 |         genout(outfile, | 
 |                "        <td><p> </p></td>\n") | 
 |     genout(outfile, "      </tr>\n") | 
 |     # yapf: enable | 
 |  | 
 |  | 
 | def print_multiversion_format(obj, outfile): | 
 |     # Sort the revision list based on the version field. | 
 |     # TODO: If minor version goes up gte than 10? | 
 |     revisions = sorted(obj["revisions"], key=lambda x: x["version"]) | 
 |     latest_rev = len(revisions) - 1 | 
 |     outstr = "" | 
 |     for i, rev in enumerate(revisions): | 
 |         outstr += "      <tr>\n" | 
 |  | 
 |         # If only one entry in `revisions`, no need of `rowspan`. | 
 |         if len(revisions) == 1: | 
 |             outstr += "        <td class='fixleft'>" | 
 |             outstr += get_linked_design_spec(obj) + "</td>\n" | 
 |             outstr += "        <td class='dv-doc'>" | 
 |             outstr += get_linked_dv_doc(obj) + "</td>\n" | 
 |         # Print out the module name in the first entry only | 
 |         elif i == 0: | 
 |             outstr += "        <td class='fixleft' rowspan='{}'>".format( | 
 |                 len(revisions)) | 
 |             outstr += get_linked_design_spec(obj) + "</td>\n" | 
 |             outstr += "        <td class='hw-stage' rowspan='{}'>".format( | 
 |                 len(revisions)) | 
 |             outstr += get_linked_dv_doc(obj) + "</td>\n" | 
 |  | 
 |         # Version | 
 |         outstr += "        <td class=\"version\">" | 
 |         outstr += get_linked_version(rev) + "</td>\n" | 
 |  | 
 |         # Development Stage | 
 |         for stage_html in get_development_stage(obj, rev, (i == latest_rev)): | 
 |             outstr += "        <td class=\"hw-stage\"><span class='hw-stage'>" | 
 |             outstr += stage_html | 
 |             outstr += "</span></td>\n" | 
 |  | 
 |         # Notes | 
 |         if 'notes' in rev and rev['notes'] != '': | 
 |             outstr += "        <td>" + mk.markdown( | 
 |                 rev['notes']).rstrip() + "</td>\n" | 
 |         else: | 
 |             outstr += "        <td><p> </p></td>\n" | 
 |         outstr += "      </tr>\n" | 
 |  | 
 |     genout(outfile, outstr) | 
 |  | 
 |  | 
 | # Create table of hardware specifications | 
 | def gen_specboard_html(hjson_path, rel_hjson_path, outfile): | 
 |     with hjson_path: | 
 |         prjfile = open(str(hjson_path)) | 
 |         try: | 
 |             obj = hjson.load(prjfile) | 
 |         except ValueError: | 
 |             raise SystemExit(sys.exc_info()[1]) | 
 |     if dashboard_validate.validate(obj) == 0: | 
 |         log.info("Generated dashboard object for " + str(hjson_path)) | 
 |     else: | 
 |         log.error("hjson file import failed") | 
 |  | 
 |     # create design spec and DV doc references, check for existence below | 
 |     design_spec_md = re.sub(r'/data/', '/doc/', | 
 |                             re.sub(r'\.prj\.hjson', '.md', str(hjson_path))) | 
 |     dv_doc_md = re.sub( | 
 |         r'/data/', '/doc/dv', | 
 |         re.sub(r'\.prj\.hjson', 'index.md', str(hjson_path))) | 
 |     design_spec_html = re.sub( | 
 |         r'/data/', '/doc/', | 
 |         re.sub(r'\.prj\.hjson', '.html', str(rel_hjson_path))) | 
 |     dv_doc_html = re.sub( | 
 |         r'/data/', '/doc/dv', | 
 |         re.sub(r'\.prj\.hjson', 'index.html', str(rel_hjson_path))) | 
 |  | 
 |     # yapf: disable | 
 |     genout(outfile, "      <tr>\n") | 
 |     genout(outfile, "        <td class=\"fixleft\">" + | 
 |                     html.escape(obj['name']) + "</td>\n") | 
 |     if os.path.exists(design_spec_md): | 
 |         genout(outfile, "        <td class=\"fixleft\"><a href=\"" + | 
 |                html.escape(design_spec_html) + "\">" + | 
 |                "design spec</a>\n") | 
 |     else: | 
 |         genout(outfile, "        <td> </td>\n") | 
 |     if os.path.exists(dv_doc_md): | 
 |         genout(outfile, "        <td class=\"fixleft\"><a href=\"" + | 
 |                html.escape(dv_doc_html) + "\">" + | 
 |                "DV document</a>\n") | 
 |     else: | 
 |         genout(outfile, "        <td> </td>\n") | 
 |  | 
 |     genout(outfile, "      </tr>\n") | 
 |     # yapf: enable | 
 |     return |