|
#!/usr/bin/env python3 |
|
|
|
import os |
|
import sys |
|
import re |
|
from xml.etree.ElementTree import Element, SubElement, Comment, tostring |
|
from xml.dom import minidom |
|
import pprint |
|
from datetime import datetime |
|
|
|
# usage directions |
|
if len(sys.argv) < 2: |
|
print('usage: python3 mkdoc.py [directory to scan for asm files] > index.html') |
|
exit(0) |
|
|
|
# scan files |
|
files = [] |
|
for walk in os.walk(sys.argv[1]): |
|
for file in walk[2]: |
|
if file[-4:].lower() == '.asm': |
|
files.append('%s/%s' % (walk[0], file)) |
|
|
|
re_nl = re.compile(r'\n') |
|
|
|
# define documented sections |
|
documents = { |
|
'sections': {}, |
|
'constants': {}, |
|
'labels': {}, |
|
'paragraphs': [], |
|
'project': {} |
|
} |
|
# over all files |
|
for file in files: |
|
# one file |
|
with open(file, 'r') as source: |
|
# read docs and the thing below it |
|
source_txt = source.read() |
|
for doc in re.finditer(r";;--\n(.*?);;--\n(.+?)??$", source_txt, re.MULTILINE | re.DOTALL): |
|
# separate what is documented with the docs themselves |
|
bottom_line = doc.group(2) or '' |
|
doc_content = re.sub(r';;\s+', '', doc.group(1)) |
|
|
|
# initialize keys |
|
keys = {'desc':'', 'file': file} |
|
|
|
# figure out which line number are we looking at |
|
# https://stackoverflow.com/a/44478696 |
|
|
|
# if there is anything defined here |
|
if bottom_line != '': |
|
keys['line'] = len(re_nl.findall(source_txt, 0, doc.start(2)))+1 |
|
|
|
# start of doc itself |
|
keys['doc_line'] = len(re_nl.findall(source_txt, 0, doc.start(1)))+1 |
|
|
|
# should this key be placed in the primary project key? |
|
as_project = False |
|
|
|
# process documentation and tags used |
|
# valid tags at the moment (square brackets denote arguments): |
|
# @project [project name] |
|
# @author [function or project author] |
|
# @version [project version] XXX |
|
# @date [addition date] XXX |
|
# @url [project URL] XXX |
|
# @return [register or wram value] [describe the return value here] |
|
# @returns [describe the return value here] |
|
# @param [register or wram value] [describe the parameter here] |
|
for line in doc_content.split('\n'): |
|
has_tag = re.match(r'@(project|author|version|date|url|return\s+\w+|returns|param\s+\w+)\s+(.+)', line) |
|
if has_tag: |
|
if has_tag.group(1) == 'project': |
|
as_project = True |
|
keys[has_tag.group(1)] = has_tag.group(2) |
|
else: |
|
keys['desc'] += ' ' + line |
|
|
|
# insert it into the documented code dict depending |
|
# on what is below the doc |
|
is_label = re.match('^(\w+):', bottom_line) |
|
is_equ = re.match('^(\w+)\s+[Ee][Qq][Uu]\s+(.+)', bottom_line) |
|
is_section = re.match('^[Ss][Ee][Cc][Tt][Ii][Oo][Nn]\s+["\'](.+)["\']', bottom_line) |
|
|
|
if as_project: |
|
documents['project'] = keys |
|
else: |
|
if is_label: |
|
documents['labels'][is_label.group(1)] = keys |
|
elif is_equ: |
|
keys['value'] = is_equ.group(2) |
|
documents['constants'][is_equ.group(1)] = keys |
|
elif is_section: |
|
documents['sections'][is_section.group(1)] = keys |
|
else: |
|
documents['paragraphs'].append(keys) |
|
|
|
################################################################### |
|
# try to make a web page |
|
################################################################### |
|
|
|
def make_safe_item_name(string): |
|
return string.replace(' ','-') |
|
|
|
def make_paragraphs(parent, string): |
|
for paragraph in string.split('<p>'): |
|
if paragraph.strip() != '': |
|
desc = SubElement(parent, 'p') |
|
desc.text = paragraph.strip() |
|
|
|
html = Element('html') |
|
head = SubElement(html, 'head') |
|
body = SubElement(html, 'body') |
|
|
|
sc_left = SubElement(html, 'div', {'class': 'navigation-container'}) |
|
header = SubElement(sc_left, 'header') |
|
sc_right = SubElement(html, 'div', {'class': 'content-container'}) |
|
main = SubElement(sc_right, 'main') |
|
|
|
# add meta tag |
|
SubElement(head, 'meta', {'charset': 'utf-8'}) |
|
|
|
# add style tag |
|
SubElement(head, 'link', {'rel': 'stylesheet', 'href':'style.css'}) |
|
|
|
# parse info variables |
|
project_title = documents['project'].get('project', '(no project name)') |
|
project_desc = documents['project'].get('desc', None) |
|
project_author = documents['project'].get('author', None) |
|
project_url = documents['project'].get('url', None) |
|
|
|
# render info to page |
|
page_title = SubElement(head, 'title') |
|
page_title.text = project_title |
|
|
|
project_h1 = SubElement(header, 'h1', {'id':'project-info'}) |
|
project_h1.text = project_title |
|
|
|
if project_author: |
|
SubElement(project_h1, 'br') |
|
author = SubElement(project_h1, 'small') |
|
author.text = 'by ' + project_author |
|
|
|
info_header = SubElement(main, 'h2') |
|
info_header.text = 'Project info' |
|
|
|
if project_desc: |
|
desc_container = SubElement(main, 'div', {'id':'project-description'}) |
|
make_paragraphs(desc_container, project_desc) |
|
|
|
if project_url: |
|
url_ = SubElement(desc_container, 'span') |
|
url_.text = 'For more info, see: ' |
|
url = SubElement(url_, 'a', {'href': project_url}) |
|
url.text = project_url |
|
|
|
main_menu = SubElement(header, 'ul', {'id':'nav-menu'}) |
|
for section in documents.keys(): |
|
# exclude these |
|
if section == 'project': |
|
break |
|
if section == 'paragraphs': |
|
break |
|
menu_item = SubElement(main_menu, 'li') |
|
menu_item_ = SubElement(menu_item, 'a', {'href': '#%s' % section}) |
|
menu_item_.text = section.capitalize() |
|
|
|
if type(documents[section]) is dict: |
|
if len(documents[section]) > 0: |
|
item_entry_container = SubElement(menu_item, 'ul') |
|
for item in documents[section].keys(): |
|
item_entry = SubElement(item_entry_container, 'li') |
|
item_entry = SubElement(item_entry, 'a', {'href': '#%s-%s' % (section, make_safe_item_name(item))}) |
|
item_entry.text = item |
|
|
|
# individual annotations |
|
reorganized_annotations = {} |
|
for annotation in documents['paragraphs']: |
|
if annotation['file'] not in reorganized_annotations: |
|
reorganized_annotations[annotation['file']] = [] |
|
new_key = {} |
|
new_key['line'] = annotation['doc_line'] |
|
new_key['desc'] = annotation['desc'] |
|
reorganized_annotations[annotation['file']].append(new_key) |
|
|
|
if len(reorganized_annotations) > 0: |
|
menu_item = SubElement(main_menu, 'li') |
|
menu_item_ = SubElement(menu_item, 'a', {'href': '#annotations'}) |
|
menu_item_.text = 'Annotations' |
|
|
|
item_entry_container = SubElement(menu_item, 'ul') |
|
counter = 0 |
|
for filename in reorganized_annotations.keys(): |
|
counter += 1 |
|
item_entry = SubElement(item_entry_container, 'li') |
|
item_entry = SubElement(item_entry, 'a', {'href': '#%s-%d' % ('annotations', counter)}) |
|
item_entry.text = filename |
|
|
|
# Sections |
|
for section_name, content in documents.items(): |
|
# exclude these |
|
if section_name == 'project': |
|
break |
|
if section_name == 'paragraphs': |
|
section = SubElement(main, 'section', {'id': 'annotations'}) |
|
section_title = SubElement(section, 'h2') |
|
section_title.text = 'Annotations' |
|
|
|
counter = 0 |
|
for filename, annotations in reorganized_annotations.items(): |
|
counter += 1 |
|
entry_title = SubElement(section, 'h3', {'id': '%s-%d' % ('annotations', counter)}) |
|
entry_title.text = filename |
|
dl = SubElement(section, 'dl') |
|
for i in annotations: |
|
a = SubElement(dl, 'dt') |
|
a.text = 'Line %d' % i['line'] |
|
a = SubElement(dl, 'dd') |
|
make_paragraphs(a, i['desc']) |
|
break |
|
section = SubElement(main, 'section', {'id': section_name}) |
|
section_title = SubElement(section, 'h2') |
|
section_title.text = section_name.capitalize() |
|
if type(content) is dict: |
|
for name, params in content.items(): |
|
entry_title = SubElement(section, 'h3', {'id': '%s-%s' % (section_name, make_safe_item_name(name))}) |
|
entry_title.text = name |
|
|
|
deflist = SubElement(section, 'dl') |
|
|
|
# description item |
|
defitem = SubElement(deflist, 'dt') |
|
defitem.text = 'Description' |
|
defitem = SubElement(deflist, 'dd') |
|
|
|
desctext = params.get('desc', 'No description defined.') |
|
make_paragraphs(defitem, desctext) |
|
|
|
# params |
|
func_params = {} |
|
for key, key_contents in params.items(): |
|
if key.startswith('param'): |
|
func_params[key.replace('param', '').strip()] = key_contents |
|
|
|
if len(func_params) > 0: |
|
# parameters item |
|
defitem = SubElement(deflist, 'dt') |
|
defitem.text = 'Parameters' |
|
defitem = SubElement(deflist, 'dd') |
|
itemlist = SubElement(defitem, 'ul') |
|
for param_name, param_desc in func_params.items(): |
|
item_ = SubElement(itemlist, 'li') |
|
item_var = SubElement(item_, 'var', {'style': 'param-name'}) |
|
item_var.text = param_name |
|
item_var.tail = ' - ' |
|
item_var = SubElement(item_, 'span', {'style': 'param-desc'}) |
|
item_var.text = param_desc.strip() |
|
|
|
# returns |
|
return_params = {} |
|
for key, key_contents in params.items(): |
|
if key.startswith('return'): |
|
return_params[key.replace('return', '').strip()] = key_contents |
|
|
|
if len(return_params) > 0: |
|
# parameters item |
|
defitem = SubElement(deflist, 'dt') |
|
defitem.text = 'Returns' |
|
defitem = SubElement(deflist, 'dd') |
|
itemlist = SubElement(defitem, 'ul') |
|
for param_name, param_desc in return_params.items(): |
|
item_ = SubElement(itemlist, 'li') |
|
item_var = SubElement(item_, 'var', {'style': 'param-name'}) |
|
item_var.text = param_name |
|
item_var.tail = ' - ' |
|
item_var = SubElement(item_, 'span', {'style': 'param-desc'}) |
|
item_var.text = param_desc.strip() |
|
|
|
# output |
|
returntext = params.get('returns', None) |
|
|
|
if returntext: |
|
defitem = SubElement(deflist, 'dt') |
|
defitem.text = 'Output' |
|
defitem = SubElement(deflist, 'dd') |
|
|
|
make_paragraphs(defitem, returntext) |
|
|
|
# in file |
|
defitem = SubElement(deflist, 'dt') |
|
defitem.text = 'Defined in' |
|
defitem = SubElement(deflist, 'dd') |
|
defitem.text = "%s line %d" % ( |
|
params.get('file', '???'), |
|
params.get('line', 0) |
|
) |
|
|
|
SubElement(sc_right, 'hr') |
|
|
|
footer = SubElement(sc_right, 'footer') |
|
footer.text = "ASMDOC generated by mkdoc.py on %s" % datetime.now().strftime("%Y-%m-%d %H:%M:%S") |
|
|
|
|
|
################################################################### |
|
# render |
|
################################################################### |
|
prettified = \ |
|
minidom.parseString(tostring(html, 'utf-8')) \ |
|
.toprettyxml(indent=" ") |
|
prettified = "<!DOCTYPE html>\n" + "\n".join(prettified.split("\n")[1:]) |
|
print(prettified) |
|
|
|
#pp = pprint.PrettyPrinter(indent=4) |
|
#pp.pprint(documents) |