Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions runparams.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,9 @@ def check_input_path(p):
desc_list = ["JSON dump", "MkDocs", "PlantUML", "RDF", "TeX", "Web pages"]

if opts.verbose:
self.log.basicConfig(level=logging.INFO)
logging.basicConfig(level=logging.INFO)
if opts.debug:
self.log.basicConfig(level=logging.DEBUG)
logging.basicConfig(level=logging.DEBUG)

self.input_path = Path(opts.input_dir)
check_input_path(self.input_path)
Expand Down
67 changes: 64 additions & 3 deletions spec_parser/mkdocs.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ def gen_mkdocs(model, outpath, cfg):
jinja.globals["class_link"] = class_link
jinja.globals["property_link"] = property_link
jinja.globals["ext_property_link"] = ext_property_link
jinja.globals["type_link"] = lambda x, showshort=False: type_link(x, model, showshort=showshort)
jinja.globals["type_link"] = lambda x, showshort=False: type_link(
x, model, showshort=showshort
)
jinja.globals["not_none"] = lambda x: str(x) if x is not None else ""
jinja.globals["get_subclass_tree"] = lambda x: get_subclass_tree(x, model)
jinja.globals["get_class_url"] = get_class_url

p = outpath

Expand All @@ -37,8 +41,23 @@ def _generate_in_dir(dirname, group, tmplfname):
d.mkdir(exist_ok=True)
f = d / f"{s.name}.md"

context = vars(s).copy()

# For classes, compute direct and nested subclasses
if dirname == "Classes" and hasattr(s, "subclasses"):
# Calculate direct subclasses (they are already stored in s.subclasses)

# Create a map of class to its direct subclasses (as Class objects, not just names)
direct_subclasses = []
for subclass_name in s.subclasses:
subclass = model.classes.get(subclass_name)
if subclass:
direct_subclasses.append(subclass)

context["direct_subclasses"] = direct_subclasses

template = jinja.get_template(tmplfname)
page = template.render(vars(s))
page = template.render(context)
f.write_text(page)

_generate_in_dir("Classes", model.classes, "class.md.j2")
Expand All @@ -52,7 +71,10 @@ def _gen_filelist(nsname, itemslist, heading):
nameslist = [c.name for c in itemslist.values()]
if nameslist:
ret.append(f" - {heading}:")
ret.extend(f" - '{n}': model/{nsname}/{heading}/{n}.md" for n in sorted(nameslist))
ret.extend(
f" - '{n}': model/{nsname}/{heading}/{n}.md"
for n in sorted(nameslist)
)
return ret

files = dict()
Expand Down Expand Up @@ -136,3 +158,42 @@ def type_link(name, model, *, showshort=False):
return f"[{name}](../{dirname}/{name}.md)"
else:
return f"{name}"


def get_subclass_tree(class_name, model):
"""Build a nested structure representing the subclass tree for a given class.

Returns a list of dictionaries, each with 'name' and 'children' keys.
"""
result = []
cls = model.classes.get(class_name)

if not cls or not hasattr(cls, "subclasses") or not cls.subclasses:
return result

for subclass_name in cls.subclasses:
subclass_info = {
"name": subclass_name,
"children": get_subclass_tree(subclass_name, model),
}
result.append(subclass_info)

return result


def get_class_url(class_name):
"""Generate a URL for a class based on its fully qualified name.

Args:
class_name: Fully qualified class name like "/Namespace/ClassName"

Returns:
URL string that works in MkDocs
"""
parts = class_name.split("/")
if len(parts) >= 3:
namespace = parts[1]
class_name = parts[2]
# Format for MkDocs
return f"../../{namespace}/Classes/{class_name}"
return "#"
14 changes: 14 additions & 0 deletions spec_parser/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,20 @@ def _tsort_recursive(inh, cn, visited, stack):
c.inheritance_stack.append(pcn)
pcn = self.classes[pcn].fqsupercname

# build subclass trees
# Initialize subclasses dict for each class
for c in self.classes.values():
c.subclasses = []

# Populate subclasses
for c in self.classes.values():
if c.fqsupercname:
self.classes[c.fqsupercname].subclasses.append(c.fqname)

# Sort subclasses by name for consistent display
for c in self.classes.values():
c.subclasses.sort()

# add inherited properties to classes
for cn in stack:
c = self.classes[cn]
Expand Down
153 changes: 153 additions & 0 deletions spec_parser/templates/mkdocs/class.md.j2
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,159 @@
{% endif %}
{% endfor %}

{% if subclasses %}
## Subclass tree

{% set current_ns = fqname.split('/')[1] %}
{% set current_name = fqname.split('/')[2] %}

<ul class="tree-view">
<li>
<span class="tree-caret tree-caret-down"></span>
<span class="tree-text current-class">/{{current_ns}}/{{current_name}}</span>
{% set subclass_tree = get_subclass_tree(fqname) %}
{% if subclass_tree %}
<ul class="nested active">
{% macro render_tree_node(node) %}
<li>
{% set node_ns = node.name.split('/')[1] %}
{% set node_name = node.name.split('/')[2] %}
{% if node.children %}
<span class="tree-caret"></span>
<a href="/spdx-spec/model/{{node_ns}}/Classes/{{node_name}}" class="tree-text">/{{node_ns}}/{{node_name}}</a>
<ul class="nested">
{% for child in node.children %}
{{ render_tree_node(child) }}
{% endfor %}
</ul>
{% else %}
<span class="tree-leaf"></span>
<a href="/spdx-spec/model/{{node_ns}}/Classes/{{node_name}}" class="tree-text">/{{node_ns}}/{{node_name}}</a>
{% endif %}
</li>
{% endmacro %}

{% for node in subclass_tree %}
{{ render_tree_node(node) }}
{% endfor %}
</ul>
{% endif %}
</li>
</ul>

<style>
/* Tree View Styles */
.tree-view, .tree-view ul, .tree-view li {
list-style: none !important;
list-style-type: none !important;
list-style-image: none !important;
margin: 0;
padding: 0;
}

.tree-view {
padding-left: 0;
font-family: inherit;
}

.tree-view ul {
padding-left: 20px;
margin: 5px 0;
}

.tree-view li {
margin: 5px 0;
position: relative;
}

/* Override any markdown list styles */
.tree-view li::before,
.tree-view li::marker,
.tree-view li::after {
content: none !important;
display: none !important;
}

.tree-caret,
.tree-leaf {
display: inline-block;
width: 16px;
height: 16px;
text-align: center;
vertical-align: middle;
}

.tree-caret {
cursor: pointer;
user-select: none;
}

.tree-caret::before {
content: "▶";
color: #555;
display: inline-block;
transition: transform 0.2s;
font-size: 0.8em;
}

.tree-caret-down::before {
transform: rotate(90deg);
}

.tree-text {
vertical-align: middle;
}

.current-class {
font-weight: bold;
}

.nested {
display: none;
}

.active {
display: block;
}

/* Fix MkDocs link styles in tree */
.tree-view a {
color: inherit;
text-decoration: none;
}

.tree-view a:hover {
text-decoration: underline;
}
</style>

<script>
// Tree view behavior - optimized
(function() {
function initTreeView() {
document.querySelectorAll('.tree-caret').forEach(function(toggler) {
toggler.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
this.classList.toggle('tree-caret-down');
this.parentElement.querySelector('.nested').classList.toggle('active');
});
});
}

// Run when DOM is ready
if (document.readyState !== 'loading') {
initTreeView();
} else {
document.addEventListener('DOMContentLoaded', initTreeView);
}

// Also run when MkDocs finishes loading
document.addEventListener('DOMContentSwapComplete', initTreeView);
})();
</script>
{% endif %}

{% endblock %}

{% block extra %}
Expand Down