From 95100bdfd1d8eb8c0399a34f0aa9b71bbae62f86 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Hugo=20H=C3=B6rnquist?= <hugo@lysator.liu.se>
Date: Mon, 25 Sep 2023 19:20:14 +0200
Subject: [PATCH] General work.

---
 muppet/output/__init__.py   | 85 ++++++++++++++++++-------------------
 muppet/output/docstring.py  | 14 +-----
 muppet/output/util.py       | 13 ++++++
 templates/code_page.html    |  6 +++
 templates/module_index.html |  2 +-
 5 files changed, 63 insertions(+), 57 deletions(-)

diff --git a/muppet/output/__init__.py b/muppet/output/__init__.py
index a1fb41e..d03a471 100644
--- a/muppet/output/__init__.py
+++ b/muppet/output/__init__.py
@@ -14,7 +14,6 @@ import json
 from glob import glob
 from typing import (
     Optional,
-    Protocol,
     Sequence,
     cast,
 )
@@ -47,6 +46,7 @@ from muppet.cache import AbstractCache
 
 from .docstring import format_docstring
 from .puppet_source import hyperlink_puppet_source
+from .util import HtmlSerializable
 
 
 jinja = Environment(
@@ -58,18 +58,6 @@ jinja = Environment(
 logger = logging.getLogger(__name__)
 
 
-class HtmlSerializable(Protocol):
-    """Classes which can be serialized as HTML."""
-
-    def to_html(self) -> str:  # pragma: no cover
-        """Return HTML string."""
-        ...
-
-    def to_html_list(self) -> str:  # pragma: no cover
-        """Return HTML suitable for a list."""
-        ...
-
-
 class Templates:
     """
     Namespace for templates.
@@ -95,7 +83,9 @@ class Templates:
     def code_page(self, *,
                   title: str,
                   content: str,
-                  breadcrumbs: Optional[Breadcrumbs] = None
+                  breadcrumbs: Optional[Breadcrumbs] = None,
+                  left_sidebar: Optional[str] = None,
+                  right_sidebar: Optional[str] = None,
                   ) -> str:  # pragma: no cover
         """
         Template for a page containing puppet code.
@@ -111,6 +101,8 @@ class Templates:
                 title=title,
                 content=content,
                 path_base=self.path_base,
+                left_sidebar=left_sidebar,
+                right_sidebar=right_sidebar,
                 breadcrumbs=breadcrumbs)
 
     def content(self, *,
@@ -148,7 +140,7 @@ class Templates:
     def module_index(self, *,
                      # content: list[],  # something with to_html_list and to_html
                      content: Sequence[HtmlSerializable],
-                     module_author: str,
+                     module_author: Optional[str],
                      module_name: str,
                      doc_files: list[tuple[str, str]],
                      breadcrumbs: Optional[Breadcrumbs] = None,
@@ -196,7 +188,6 @@ class Templates:
 
 @dataclass
 class ResourceTypeOutput:
-    """Basic HTML implementation."""
 
     title: str
     module_name: str
@@ -211,6 +202,7 @@ class ResourceTypeOutput:
         If the node has a link, create a hyperlink, otherwise return
         it's title directly.
         """
+        # TODO make links absolute to root, to allow inclusion anywhere
         if self.link:
             return f'<a href="{ self.link }">{ self.title }</a>'
         else:
@@ -223,6 +215,7 @@ class ResourceTypeOutput:
             .get_template('snippets/ResourceType-index-entry.html') \
             .render(item=self,
                     module_name=self.module_name,
+                    # TODO don't hardcode prefix
                     prefix='/code/muppet-strings/output')
 
     def to_html_list(self) -> str:
@@ -253,6 +246,7 @@ class IndexItem:
 
     def base(self) -> str:
         """Return link to self."""
+        # TODO make links absolute to root, to allow inclusion anywhere
         return f'<a href="{self.file}">{ self.name }</a>'
 
     def to_html(self) -> str:
@@ -350,7 +344,6 @@ class IndexCategory:
 
 @dataclass
 class ResourceIndex:
-    """Placeholder."""
 
     title: str
     children: list[HtmlSerializable]
@@ -430,8 +423,23 @@ class PuppetModule:
         except FileNotFoundError:
             self.metadata = {}
 
+        self.module_toc: str = ''.join([
+            '<ul class="toc">',
+            *(e.to_html_list() for e in self._build_module_toc()),
+            '</ul>'])
+
     def _build_module_toc(self) -> Sequence[ResourceIndex | IndexCategory]:
-        """Build the TOC of the module."""
+        """
+        Build the TOC of the module.
+
+        This method looks into the strings output of the current
+        object, and creates a TOC tree which more or less has the
+        strings output keys as top level entries, and all
+        resources/classes/... of that type as child entries.
+
+        :returns:
+            A list of categories.
+        """
         content: list[ResourceIndex | IndexCategory] = []
 
         if puppet_classes := self.strings_output.puppet_classes:
@@ -522,16 +530,12 @@ class PuppetModule:
         with open(os.path.join(destination, 'index.html'), 'w') as f:
             f.write(self.templates.module_index(
                 module_name=self.name,
-                module_author='TODO',  # module.metadata['author'],
+                module_author=self.metadata.get('author'),
                 breadcrumbs=crumbs,
                 content=toc,
                 doc_files=doc_files_toc,
                 # left_sidebar=(),
-                right_sidebar=''.join([
-                    '<ul class="toc">',
-                    *(e.to_html_list() for e in toc),
-                    '</ul>',
-                ])))
+                right_sidebar=self.module_toc))
 
     def _generate_classes(self, destination: str) -> None:
         for puppet_class in self.strings_output.puppet_classes \
@@ -559,20 +563,14 @@ class PuppetModule:
                     )
 
             with open(os.path.join(dir, 'source.pp.html'), 'w') as f:
-
                 with open(self.file(puppet_class.file), 'r') as g:
                     f.write(self.templates.code_page(
+                        # TODO handle title?
                         title='',
+                        left_sidebar=self.module_toc,
                         content=highlight(g.read(), 'puppet'),
                         breadcrumbs=crumbs))
 
-            # TODO reimplement this?
-            # with open(os.path.join(dir, 'source.json'), 'w') as f:
-            #     json.dump(puppet_class, f, indent=2)
-
-            # with open(os.path.join(dir, 'source.pp.html'), 'w') as f:
-            #     f.write(format_class(puppet_class))
-
             crumbs = breadcrumbs(
                     ('Environment', ''),
                     self.name,
@@ -583,8 +581,9 @@ class PuppetModule:
             title, body = format_class(puppet_class)
             with open(os.path.join(dir, 'index.html'), 'w') as f:
                 f.write(self.templates.code_page(
-                    title=self.name,
+                    title=title,
                     content=body,
+                    left_sidebar=self.module_toc,
                     breadcrumbs=crumbs))
 
             # puppet_class['file']
@@ -601,10 +600,6 @@ class PuppetModule:
             with open(os.path.join(dir, 'source.pp.txt'), 'w') as f:
                 f.write(type_alias.alias_of)
 
-            # TODO reimplement this?
-            # with open(os.path.join(dir, 'source.json'), 'w') as f:
-            #     json.dump(type_alias, f, indent=2)
-
             title, body = format_type_alias(type_alias, type_alias.name)
             with open(os.path.join(dir, 'index.html'), 'w') as f:
                 f.write(self.templates.code_page(
@@ -854,7 +849,9 @@ def type_aliases_index(alias_list: list[DataTypeAlias]) -> IndexCategory:
 
 def resource_type_index(resource_types: list[ResourceType],
                         module_name: str) -> list[HtmlSerializable]:
-    """Generate index for all known resource types."""
+    """
+    Generate index of all known resource types.
+    """
     lst: list[HtmlSerializable] = []
 
     for resource_type in resource_types:
@@ -910,7 +907,7 @@ def format_class(d_type: DefinedType | PuppetClass) -> tuple[str, str]:
     out = ''
     logger.info("Formatting class %s", d_type.name)
     # print(name, file=sys.stderr)
-    name, body = format_docstring(d_type.name, d_type.docstring)
+    body = format_docstring(d_type.name, d_type.docstring)
     out += body
 
     # ------ Old ---------------------------------------
@@ -947,7 +944,7 @@ def format_class(d_type: DefinedType | PuppetClass) -> tuple[str, str]:
         else:
             out += d_type.source
         out += '</code></pre>'
-    return name, out
+    return d_type.name, out
 
 
 def build_param_dict(docstring: DocString) -> dict[str, str]:
@@ -985,7 +982,7 @@ def format_type_alias(d_type: DataTypeAlias, file: str) -> tuple[str, str]:
     name = d_type.name
     logger.info("Formatting type alias %s", name)
     # print(name, file=sys.stderr)
-    title, body = format_docstring(name, d_type.docstring)
+    body = format_docstring(name, d_type.docstring)
     out += body
     out += '\n'
     out += '<pre class="highlight-muppet"><code class="puppet">'
@@ -995,7 +992,7 @@ def format_type_alias(d_type: DataTypeAlias, file: str) -> tuple[str, str]:
         logger.error("Parsing %(name)s failed: %(err)s",
                      {'name': d_type.alias_of, 'err': e})
     out += '</code></pre>\n'
-    return title, out
+    return d_type.name, out
 
 
 def format_defined_type(d_type: DefinedType, file: str) -> tuple[str, str]:
@@ -1005,7 +1002,7 @@ def format_defined_type(d_type: DefinedType, file: str) -> tuple[str, str]:
     name = d_type.name
     logger.info("Formatting defined type %s", name)
     # print(name, file=sys.stderr)
-    title, body = format_docstring(name, d_type.docstring)
+    body = format_docstring(name, d_type.docstring)
     out += body
 
     out += '<pre class="highlight-muppet"><code class="puppet">'
@@ -1015,7 +1012,7 @@ def format_defined_type(d_type: DefinedType, file: str) -> tuple[str, str]:
         logger.error("Parsing %(name)s failed: %(err)s",
                      {'name': d_type.source, 'err': e})
     out += '</code></pre>\n'
-    return title, out
+    return d_type.name, out
 
 
 def format_resource_type(r_type: ResourceType) -> str:
diff --git a/muppet/output/docstring.py b/muppet/output/docstring.py
index b85676c..8b0ea37 100644
--- a/muppet/output/docstring.py
+++ b/muppet/output/docstring.py
@@ -33,10 +33,6 @@ from muppet.puppet.strings import (
 )
 
 
-# TODO what even is this for?
-param_doc: dict[str, str] = {}
-
-
 @dataclass
 class GroupedTags:
     """
@@ -106,7 +102,7 @@ def parse_author(author: str) -> str:
         return html.escape(m['any'])
 
 
-def format_docstring(name: str, docstring: DocString) -> tuple[str, str]:
+def format_docstring(name: str, docstring: DocString) -> str:
     """
     Format docstrings as they appear in some puppet types.
 
@@ -116,16 +112,10 @@ def format_docstring(name: str, docstring: DocString) -> tuple[str, str]:
     * puppet_type_aliases, and
     * defined_types
     """
-    global param_doc
-
     # The api tag is ignored, since it instead is shown from context
 
     out = ''
 
-    param_doc = {tag.name: tag.text or ''
-                 for tag in docstring.tags
-                 if isinstance(tag, DocStringParamTag)}
-
     grouped_tags = GroupedTags.from_taglist(docstring.tags)
 
     # --------------------------------------------------
@@ -255,4 +245,4 @@ def format_docstring(name: str, docstring: DocString) -> tuple[str, str]:
     # since
     # _other
 
-    return (name, out)
+    return out
diff --git a/muppet/output/util.py b/muppet/output/util.py
index b1d69f5..2c3c3a3 100644
--- a/muppet/output/util.py
+++ b/muppet/output/util.py
@@ -7,6 +7,7 @@ useful than other.
 The aim is to only have pure functions here.
 """
 
+from typing import Protocol
 from muppet.parser_combinator import (
     MatchCompound,
     MatchObject,
@@ -42,3 +43,15 @@ def inner_text(obj: MatchObject | list[MatchObject]) -> str:
             return ''.join(inner_text(x) for x in xs)
         case _:
             raise ValueError('How did we get here')
+
+
+class HtmlSerializable(Protocol):
+    """Classes which can be serialized as HTML."""
+
+    def to_html(self) -> str:  # pragma: no cover
+        """Return HTML string."""
+        ...
+
+    def to_html_list(self) -> str:  # pragma: no cover
+        """Return HTML suitable for a list."""
+        ...
diff --git a/templates/code_page.html b/templates/code_page.html
index 5b2d364..6aecc8a 100644
--- a/templates/code_page.html
+++ b/templates/code_page.html
@@ -9,9 +9,15 @@ Parameters:
 {% extends "base.html" %}
 {% block left_sidebar %}
 	{# Class list, basically the right sidebar from module index #}
+	{% if left_sidebar %}
+		{{ left_sidebar }}
+	{% endif %}
 {% endblock %}
 {% block right_sidebar %}
 	{# All defined variables #}
+	{% if right_sidebar %}
+		{{ right_sidebar }}
+	{% endif %}
 {% endblock %}
 {% block content %}
 	<h1><code>{{ title }}</code></h1>
diff --git a/templates/module_index.html b/templates/module_index.html
index 7f76a36..d00e73a 100644
--- a/templates/module_index.html
+++ b/templates/module_index.html
@@ -26,7 +26,7 @@ Parameters:
 	{% endif %}
 {% endblock %}
 {% block content %}
-<h1>{{ module_author }} / {{ module_name.title() }}</h1>
+	<h1>{% if module_author %}{{ module_author }} / {% endif %}{{ module_name.title() }}</h1>
 
 <ul>
 	{% for name, path in doc_files %}
-- 
GitLab