feat: working version

This commit is contained in:
Ferrari Martino Giordano
2024-03-08 17:18:38 +01:00
parent e964b7347f
commit 65e399fa70
6 changed files with 439 additions and 165 deletions
+1
View File
@@ -1 +1,2 @@
output/ output/
__pycache__
+1
View File
@@ -0,0 +1 @@
from autodoc.autodoc import *
+29 -15
View File
@@ -4,20 +4,20 @@ Main entry point for the MARTe Autodoc project"
Author: Martino Ferrari Author: Martino Ferrari
Email: martinogiordano.ferrari@iter.org Email: martinogiordano.ferrari@iter.org
""" """
from argparse import ArgumentParser import json
import logging import logging
import sys
import os import os
import pprint import pprint
import sys
from argparse import ArgumentParser
from MARTe.StandardParser import StandardParser from MARTe.StandardParser import ParseException, StandardParser
from MARTe.StandardParser import ParseException
import json
import autodoc import autodoc
__version__ = "0.0.0" __version__ = "0.0.0"
def __args__(): def __args__():
"""Parse arguments.""" """Parse arguments."""
parser = ArgumentParser( parser = ArgumentParser(
@@ -25,11 +25,25 @@ def __args__():
description="MARTe configuration auto documentation tool", description="MARTe configuration auto documentation tool",
) )
parser.add_argument("filename", help="Configuration file to document", type=str) parser.add_argument("filename", help="Configuration file to document", type=str)
parser.add_argument("--type", choices=["json", "marte"], default="marte", help="Configuration file type (default: `marte`)") parser.add_argument(
parser.add_argument("--loglevel", choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], default="ERROR", help="Logging level (default: `ERROR`)") "--type",
choices=["json", "marte"],
default="marte",
help="Configuration file type (default: `marte`)",
)
parser.add_argument(
"--loglevel",
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
default="ERROR",
help="Logging level (default: `ERROR`)",
)
parser.add_argument("--version", action="version", version=__version__) parser.add_argument("--version", action="version", version=__version__)
parser.add_argument(
"-d", "--destination", help="Destination folder", type=str, default="."
)
return parser.parse_args() return parser.parse_args()
def main(): def main():
"""Main function.""" """Main function."""
args = __args__() args = __args__()
@@ -39,12 +53,14 @@ def main():
logging.error(f"Specified file `{args.filename}` does not exists") logging.error(f"Specified file `{args.filename}` does not exists")
sys.exit(1) sys.exit(1)
config = {} config = {}
if args.type == 'marte': if args.type == "marte":
parser = StandardParser() parser = StandardParser()
try: try:
config = parser.parse_file(args.filename) config = parser.parse_file(args.filename)
except ParseException as e: except ParseException as e:
logging.error(f"Impossible to parse file `{args.filename}` at position ({e.line},{e.col}): {e.msg}`") logging.error(
f"Impossible to parse file `{args.filename}` at position ({e.line},{e.col}): {e.msg}`"
)
sys.exit(1) sys.exit(1)
except Exception as e: except Exception as e:
logging.error(f"Sometihng went wrong with file `{args.filename}`") logging.error(f"Sometihng went wrong with file `{args.filename}`")
@@ -55,11 +71,9 @@ def main():
if not config: if not config:
logging.error(f"Impossible to load configuration `{args.filename}`") logging.error(f"Impossible to load configuration `{args.filename}`")
sys.exit(1) sys.exit(1)
print(f"# `{os.path.splitext(os.path.basename(args.filename))[0]}` Documentation") filename = os.path.splitext(os.path.basename(args.filename))[0]
for label, obj in config.items(): autodoc.document(filename, args, config)
if 'Class' in obj:
autodoc.document(label, obj)
if __name__ == '__main__':
if __name__ == "__main__":
main() main()
Binary file not shown.
Binary file not shown.
+362 -104
View File
@@ -2,134 +2,392 @@
Autodoc functions Autodoc functions
""" """
import logging import logging
import os
import sys
__PATH__ = "."
def __doc_app_state__(state, tab=''): def __sig__(obj, label, type):
if '+Threads' in state: return (obj, label, type)
for i, tname in enumerate(state['+Threads']):
if tname[0] == '+':
ch = '├─●' if i < len(state['+Threads']) - 1 else '╰─●'
print(f'{tab} {ch} {tname[1:]}')
def __doc_state__(label, state, app, full=False): def __edge__(src, trg):
if '+Threads' in state: return (src, trg)
for tname, obj in state['+Threads'].items():
if tname[0] == '+':
print() def __sig_name__(name):
print(f"#### Thread `{tname[1:]}`\n") return name.replace(".", "_")
print(f'Visual representation of the thread `{tname[1:]}`:\n')
print('```graphviz')
print('digraph structs {') def __type_bytes__(mtype):
print(' rankdir=LR;') if mtype.startswith("uint"):
print(' node [shape=record];') return int(mtype[4:]) // 8
datasources = {} if mtype.startswith("int"):
if 'Functions' in obj: return int(mtype[3:]) // 8
fns = obj['Functions'] if mtype.startswith("float"):
for fn in fns: return int(mtype[5:]) // 8
obj = app['+Functions'][f'+{fn}'] if mtype == "bool":
node = f' fn{fn} [label="{fn}&#92;n{obj["Class"]}' return 1
if full: logging.debug(f"Type `{mtype}` unknow, size 1 byte")
node += ' | ' return 1
outputs = ''
inputs = ''
def __type_size__(mtype):
return __type_bytes__(mtype[0]) * mtype[1]
def __num_bytes__(sig):
noe = 1 if "NumberOfElements" not in sig else int(sig["NumberOfElements"])
nod = 1 if "NumberOfDimensions" not in sig else int(sig["NumberOfDimensions"])
bytes = __type_bytes__(sig["Type"])
return noe * nod * bytes
def __doc_app_state__(state, tab=""):
res = ""
if "+Threads" in state:
for i, tname in enumerate(state["+Threads"]):
if tname[0] == "+":
ch = "├─●" if i < len(state["+Threads"]) - 1 else "╰─●"
res += f"{tab} {ch} {tname[1:]}\n"
return res
def __doc_obj__(label, obj, id):
node = f' {id} [label="{label}&#92;n{obj["Class"]} | '
return node
def __sig_type__(sig):
type_name = sig["Type"]
noe = sig["NumberOfElements"] if "NumberOfElements" in sig else 1
nod = sig["NumberOfDimensions"] if "NumberOfDimensions" in sig else 1
dim = int(noe) * int(nod)
return (type_name, dim)
def __sig_alias__(name, signal):
return name if "Alias" not in signal else signal["Alias"]
def __doc_common_gam__(name, obj, edges):
inputs = ""
outputs = ""
if "InputSignals" in obj: if "InputSignals" in obj:
for i, (l, sig) in enumerate(obj["InputSignals"].items()): for i, (l, sig) in enumerate(obj["InputSignals"].items()):
if sig['DataSource'] not in datasources: alias = __sig_alias__(l, sig)
datasources[sig['DataSource']] = {} mtype = __sig_type__(sig)
sn = sig['Alias'] if 'Alias' in sig else l datasrc = sig["DataSource"]
if sn not in datasources[sig['DataSource']]: edges.append(
datasources[sig['DataSource']][sn] = [f'fn{fn}:in{i}'] __edge__(
else: __sig__(datasrc, alias, mtype),
datasources[sig['DataSource']][sn].append(f'fn{fn}:in{i}') __sig__(name, l, mtype),
)
)
if i != 0: if i != 0:
inputs += ' | ' inputs += " | "
inputs += f'<in{i}> {l} ({sig["Type"]})' inputs += f"<in_{__sig_name__(l)}> {l}"
if "OutputSignals" in obj: if "OutputSignals" in obj:
for i, (l, sig) in enumerate(obj["OutputSignals"].items()): for i, (l, sig) in enumerate(obj["OutputSignals"].items()):
if sig['DataSource'] not in datasources: alias = __sig_alias__(l, sig)
datasources[sig['DataSource']] = {} mtype = __sig_type__(sig)
if l not in datasources[sig['DataSource']]: datasrc = sig["DataSource"]
datasources[sig['DataSource']][l] = [f'fn{fn}:out{i}'] edges.append(
else: __edge__(
datasources[sig['DataSource']][l].append(f'fn{fn}:out{i}') __sig__(name, l, mtype),
__sig__(datasrc, alias, mtype),
)
)
if i != 0: if i != 0:
outputs += ' | ' outputs += " | "
outputs += f'<out{i}> {l} ({sig["Type"]})' outputs += f"<out_{__sig_name__(l)}> {l}"
node += '{ { '+inputs+' } | { '+ outputs +' } }"];' return "{ {" + inputs + " } | { " + outputs + "} }", edges
else:
node += '"];'
def __doc_triggered_iogam__(name, obj, edges):
node = ""
if "InputSignals" in obj: if "InputSignals" in obj:
for i, (l, sig) in enumerate(obj["InputSignals"].items()): for i, (l, sig) in enumerate(obj["InputSignals"].items()):
if sig['DataSource'] not in datasources: alias = __sig_alias__(l, sig)
datasources[sig['DataSource']] = [] mtype = __sig_type__(sig)
if (fn, 'in') not in datasources[sig['DataSource']]: datasrc = sig["DataSource"]
datasources[sig['DataSource']].append((fn, 'in')) edges.append(
__edge__(
__sig__(datasrc, alias, mtype),
__sig__(name, l, mtype),
)
)
if i > 1:
node += " | "
node += f"<in_{__sig_name__(l)}> "
if i == 0:
node += "Trigger | { { "
else:
node += f"{l}"
node += " } | { "
if "OutputSignals" in obj: if "OutputSignals" in obj:
for i, (l, sig) in enumerate(obj["OutputSignals"].items()): for i, (l, sig) in enumerate(obj["OutputSignals"].items()):
if sig['DataSource'] not in datasources: alias = __sig_alias__(l, sig)
datasources[sig['DataSource']] = [] mtype = __sig_type__(sig)
if (fn, 'out') not in datasources[sig['DataSource']]: datasrc = sig["DataSource"]
datasources[sig['DataSource']].append((fn, 'out')) edges.append(
print(node) __edge__(
for l, obj in datasources.items(): __sig__(name, l, mtype),
logging.debug(f"datasource: {l}") __sig__(datasrc, alias, mtype),
ds = app['+Data'][f'+{l}'] )
arrows = '' )
node = f' ds{l} [label="{l}&#92;n{ds["Class"]}' if i != 0:
if full: node += " | "
node += ' | ' node += f"<out_{__sig_name__(l)}> {l} "
for i, s in enumerate(obj): node += " } }"
return node, edges
def __doc_iogam__(id, obj, edges):
node = ""
if "InputSignals" in obj and "OutputSignals" in obj:
inputs, outputs = obj["InputSignals"], obj["OutputSignals"]
output_names = list(outputs.keys())
input_names = list(inputs.keys())
j = 0
isize = 0
osize = 0
i = 0
irow = ""
orow = ""
while i < len(inputs):
in_label = input_names[i]
input = inputs[in_label]
if irow:
irow += " | "
irow += f"<in_{__sig_name__(in_label)}> {in_label}"
isize += __num_bytes__(input)
i += 1
alias = __sig_alias__(in_label, input)
mtype = __sig_type__(input)
datasrc = input["DataSource"]
edges.append(
__edge__(
__sig__(datasrc, alias, mtype),
__sig__(id, in_label, mtype),
)
)
while osize < isize and j < len(outputs):
out_label = output_names[j]
output = outputs[out_label]
osize += __num_bytes__(output)
if orow:
orow += " | "
orow += f"<out_{__sig_name__(out_label)}> {out_label}"
j += 1
alias = __sig_alias__(out_label, output)
mtype = __sig_type__(output)
datasrc = output["DataSource"]
edges.append(
__edge__(
__sig__(id, out_label, mtype),
__sig__(datasrc, alias, mtype),
)
)
if isize == osize and irow and orow:
if node:
node += " | "
node += "{ {" + irow + "} | {" + orow + "} }"
irow = ""
orow = ""
if irow or orow:
if node:
node += " | "
node += "{ {" + irow + "} | {" + orow + "} }"
return node, edges
def __doc_constgam__(id, obj, edges):
node = ""
if "OutputSignals" in obj:
for i, (l, sig) in enumerate(obj["OutputSignals"].items()):
datasrc = sig["DataSource"]
alias = __sig_alias__(l, sig)
mtype = __sig_type__(sig)
edges.append(
__edge__(__sig__(id, l, mtype), __sig__(datasrc, alias, mtype))
)
if i != 0:
node += " | "
node += f'<out_{__sig_name__(l)}> {sig["Default"]} '
node += ""
return node, edges
def __doc_gam__(label, obj, edges):
id = f"fn{label}"
cls = obj["Class"]
node = f' {id} [label="{label}&#92;n{obj["Class"]} | '
if cls == "ConstantGAM":
sigs, datasources = __doc_constgam__(id, obj, edges)
elif cls == "IOGAM":
sigs, datasources = __doc_iogam__(id, obj, edges)
elif cls == "TriggeredIOGAM":
sigs, datasources = __doc_triggered_iogam__(id, obj, edges)
else:
sigs, datasources = __doc_common_gam__(id, obj, edges)
if sigs:
node += sigs + '"];'
else:
node = ""
return node, datasources
def __process_gamds__(name, signals):
node = ""
for i, sig_name in enumerate(signals):
sig_type = signals[sig_name]
node += f'{name}_{__sig_name__(sig_name)}[ label=" {name}::{sig_name} ({sig_type[0]}[{sig_type[1]}])", color=blue];\n'
return node
def __process_ds__(name, obj, edges):
cls = obj["Class"]
sigs = {}
node = ""
for edge in edges:
if edge[0][0] == name:
sigs[edge[0][1]] = edge[0][2]
if edge[1][0] == name:
sigs[edge[1][1]] = edge[1][2]
if sigs:
if cls == "GAMDataSource":
node = __process_gamds__(name, sigs)
else:
order = []
if "Signals" in obj:
for sig in obj["Signals"]:
if sig in sigs:
order.append(sig)
else:
order = list(sigs.keys())
node = f'{name} [label="{name}&#92;n{cls} | '
for i, sig_name in enumerate(order):
sig_type = sigs[sig_name]
if i > 0: if i > 0:
node += " | " node += " | "
node += f'<sig{i}> {s}' node += f"<{__sig_name__(sig_name)}> {sig_name} ({sig_type[0]}[{sig_type[1]}])"
for c in obj[s]: node += '", color=blue];\n'
if c.split(':')[1].startswith('in'): return node
arrows += f' ds{l}:sig{i} -> {c};\n'
def __process_edges__(edges, datasources):
arrows = ""
for source, target in edges:
src_obj = source[0]
src_sig = __sig_name__(source[1])
trg_obj = target[0]
trg_sig = __sig_name__(target[1])
a = f"{src_obj}"
b = f"{trg_obj}"
if src_obj in datasources:
if datasources[src_obj] == "GAMDataSource":
a += f"_{src_sig}"
else: else:
arrows += f' {c} -> ds{l}:sig{i};\n' a += f":{src_sig}"
else: else:
for (fn, dir) in obj: a += f":out_{src_sig}"
if dir == 'in': if trg_obj in datasources:
arrows += f' ds{l} -> fn{fn};\n' if datasources[trg_obj] == "GAMDataSource":
b += f"_{trg_sig}"
else: else:
arrows += f' fn{fn} -> ds{l};\n' b += f":{trg_sig}"
node += '" color=blue];' else:
print(node) b += f":in_{trg_sig}"
print(arrows) arrows += f"{a} -> {b};\n"
print('}\n```') return arrows
def __doc_state__(label, state, app):
mdstate = ""
graph = f"digraph {label[1:]}" + "{\n"
graph += "grapn [ranksep=4];\n"
graph += "rankdir = LR;\n"
graph += "beautify = true;\n"
graph += "node [shape=record];\n"
graph += "\n"
arrows = ""
ext = ""
edges = []
datasources = {}
if "+Threads" in state:
for tname, obj in state["+Threads"].items():
if tname[0] == "+":
# graph += f"subgraph cluster_{tname[1:]}" + "{\n"
if "Functions" in obj:
fns = obj["Functions"]
for fn in fns:
obj = app["+Functions"][f"+{fn}"]
node, edges = __doc_gam__(fn, obj, edges)
graph += f"{node}\n"
# graph += f' label = "{tname[1:]}";\n'
# graph += "}\n"
if "+Data" in app:
for key, obj in app["+Data"].items():
if key[0] == "+":
datasources[key[1:]] = obj["Class"]
node = __process_ds__(key[1:], obj, edges)
graph += node
arrows = __process_edges__(edges, datasources)
graph += "\n" + ext + "\n" + arrows
graph += "}\n"
fname = f"{label[1:]}"
with open(os.path.join(__PATH__, f"{fname}.dot"), "w") as file:
file.write(graph)
os.system(
f"dot -Tsvg -o{os.path.join(__PATH__, fname)}.svg {os.path.join(__PATH__, fname)}.dot"
)
mdstate += f"![{fname}]({fname}.svg)\n"
return mdstate
def __doc_application__(label, app): def __doc_application__(label, app):
print() mdapp = f'\n## Real-time application "{label[1:]}"\n\n'
print(f"## Real-time application: `{label[1:]}`") mdapp += f"Below are descrbed the states and threads of the real-time application {label[1:]}.\n"
print() mdapp += "```\n"
print(f"Below are descrbed the states and threads of the real-time application {label[1:]}.") mdapp += f'{""*(len(label)-1)}\n'
print("```") mdapp += f"{label[1:]}\n"
print(f'{""*(len(label)-1)}') mdapp += f'╰┬{""*(len(label)-2)}\n'
print(f'{label[1:]}') states = app["+States"]
print(f'╰┬{""*(len(label)-2)}')
states = app['+States']
for i, (label, obj) in enumerate(states.items()): for i, (label, obj) in enumerate(states.items()):
ch = '├─▷' if i < len(states) - 1 else '╰─▷' ch = "├─▷" if i < len(states) - 1 else "╰─▷"
tab = '' if i < len(states) - 1 else ' ' tab = "" if i < len(states) - 1 else " "
if label[0] == '+': if label[0] == "+":
print(f' {ch} {label[1:]}') mdapp += f" {ch} {label[1:]}\n"
__doc_app_state__(obj, tab) mdapp += __doc_app_state__(obj, tab)
print("```") mdapp += "```\n"
for label, obj in states.items(): for label, obj in states.items():
if label[0] == '+': if label[0] == "+":
print() mdapp += f'\n### State "{label[1:]}"\n'
print(f'### State `{label[1:]}`') mdapp += __doc_state__(label, obj, app)
__doc_state__(label, obj, app) return mdapp
def document(fname, args, config):
def document(label, obj):
"""Document MARTe object.""" """Document MARTe object."""
if obj['Class'] == 'RealTimeApplication': global __PATH__
__doc_application__(label, obj) __PATH__ = os.path.join(args.destination, fname)
if obj['Class'] == 'StateMachine': if not os.path.isdir(__PATH__):
print() os.mkdir(__PATH__)
print(f"## State-Machine `{label[1:]}`")
mddoc = f"# {fname}\n"
for label, obj in config.items():
if "Class" in obj:
if obj["Class"] == "RealTimeApplication":
mddoc += __doc_application__(label, obj)
if obj["Class"] == "StateMachine":
mddoc += f'\n## State-Machine "{label[1:]}"'
with open(os.path.join(__PATH__, f"{fname}.md"), "w") as file:
file.write(mddoc)