blob: d54df80d59b9404bdd83a7baf7ca0ef87b75eea5 [file] [log] [blame]
# 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
import hjson
import mistletoe as mk
import dashboard.dashboard_validate as dashboard_validate
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',
'D3': 'Design Complete',
# Verification Stages
'V0': 'Initial Work',
'V1': 'Under Test',
'V2': 'Testing Complete',
'V3': 'Verification Complete',
# DIF Stages (S for Software)
'S0': 'Initial Work',
'S1': 'Functional',
'S2': 'Complete',
'S3': 'Stable',
}
# TODO: This is relative to the dashboard, which is currently located at
# hw/_index.md.
docs_server = "../.."
def convert_stage(stagestr):
return STAGE_STRINGS.get(stagestr, "UNKNOWN")
# Link module name with its design spec doc.
def get_linked_design_spec(obj):
result = ""
if 'design_spec' in obj.keys():
url = docs_server + "/" + html.escape(obj['design_spec'])
result = "<span title='Design Spec'><a href='{}'>".format(url)
result += "<code>{}</code></a></span>".format(html.escape(obj['name']))
else:
result = html.escape(obj['name'])
return result
# Provide the link to the DV plan.
def get_linked_dv_plan(obj):
if 'dv_plan' in obj.keys():
url = docs_server + "/" + html.escape(obj['dv_plan'])
return "<span title='DV Plan'><a href=\"{}\">DV</a></span>".format(url)
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 = "{}/{}{}".format(docs_server, html.escape(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 = "{}/{}{}".format(docs_server, html.escape(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 "&nbsp;"
life_stage = rev['life_stage']
life_stage_mapping = convert_stage(life_stage)
separator = " : "
if life_stage != 'L0' and 'design_stage' in rev:
design_stage = rev['design_stage']
design_stage_mapping = convert_stage(design_stage)
else:
design_stage = None
if life_stage != 'L0' and 'verification_stage' in rev:
verification_stage = rev['verification_stage']
verification_stage_mapping = convert_stage(verification_stage)
else:
verification_stage = None
if life_stage != 'L0' and 'dif_stage' in rev:
dif_stage = rev['dif_stage']
dif_stage_mapping = convert_stage(dif_stage)
else:
dif_stage = None
result = "<span title='{}'>{}</span>".format(
html.escape(life_stage_mapping), html.escape(life_stage))
if design_stage:
result += separator
result += "<span title='{}'>{}</span>".format(
html.escape(design_stage_mapping),
get_linked_checklist(obj, rev, 'design_stage', is_latest_rev))
if verification_stage:
result += separator
result += "<span title='{}'>{}</span>".format(
html.escape(verification_stage_mapping),
get_linked_checklist(obj, rev, 'verification_stage',
is_latest_rev))
if dif_stage:
result += separator
result += "<span title='{}'>{}</span>".format(
html.escape(dif_stage_mapping),
get_linked_sw_checklist(obj, rev, 'dif_stage',
is_latest_rev))
return result
# 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")
# If `revisions` field doesn't exist, the tool assumes the Hjson
# as the previous project format, which has only one version entry.
if not "revisions" 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):
life_stage = obj['life_stage']
life_stage_mapping = convert_stage(obj['life_stage'])
# yapf: disable
genout(outfile, " <tr>\n")
name = html.escape(obj['name'])
genout(outfile, " <td class=\"fixleft\">" +
get_linked_design_spec(obj) + "</td>\n")
genout(outfile, " <td class=\"hw-stage\">" +
get_linked_dv_plan(obj) + "</td>\n")
genout(outfile, " <td class=\"hw-stage\">" +
get_linked_version(obj) + "</td>\n")
genout(outfile, " <td class=\"hw-stage\"><span class='hw-stage'>" +
get_development_stage(obj, obj) + "</span></td>\n")
if 'notes' in obj:
genout(outfile,
" <td>" + mk.markdown(obj['notes']).rstrip() + "</td>\n")
else:
genout(outfile,
" <td><p>&nbsp;</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='hw-stage'>"
outstr += get_linked_dv_plan(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_plan(obj) + "</td>\n"
# Version
outstr += " <td class=\"hw-stage\">"
outstr += get_linked_version(rev) + "</td>\n"
# Development Stage
outstr += " <td class=\"hw-stage\"><span class='hw-stage'>"
outstr += get_development_stage(obj, rev, (i == latest_rev))
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>&nbsp;</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 plan references, check for existence below
design_spec_md = re.sub(r'/data/', '/doc/',
re.sub(r'\.prj\.hjson', '.md', str(hjson_path)))
dv_plan_md = re.sub(
r'/data/', '/doc/',
re.sub(r'\.prj\.hjson', '_dv_plan.md', str(hjson_path)))
design_spec_html = re.sub(
r'/data/', '/doc/',
re.sub(r'\.prj\.hjson', '.html', str(rel_hjson_path)))
dv_plan_html = re.sub(
r'/data/', '/doc/',
re.sub(r'\.prj\.hjson', '_dv_plan.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>&nbsp;</td>\n")
if os.path.exists(dv_plan_md):
genout(outfile,
" <td class=\"fixleft\"><a href=\"" +
html.escape(dv_plan_html) + "\">" +
"DV plan</a>\n")
else:
genout(outfile,
" <td>&nbsp;</td>\n")
genout(outfile, " </tr>\n")
# yapf: enable
return