From 697f456becfcadd832944432f7a406bdbde02dd3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Hugo=20H=C3=B6rnquist?= <hugo@lysator.liu.se>
Date: Sat, 1 Jul 2023 18:29:15 +0200
Subject: [PATCH] work

---
 muppet/breadcrumbs.py    | 27 ++++++++++++++--
 muppet/format.py         | 36 ++++++++++++---------
 muppet/gather.py         |  3 +-
 muppet/markdown.py       |  5 ++-
 muppet/output.py         | 22 ++++++++++---
 muppet/puppet/parser.py  |  1 +
 static-src/style.scss    | 68 ++++++++++++++++++++++++++++++++++++++--
 templates/base.html      |  3 +-
 templates/code_page.html |  4 ++-
 9 files changed, 141 insertions(+), 28 deletions(-)

diff --git a/muppet/breadcrumbs.py b/muppet/breadcrumbs.py
index ae88d3c..70e6d4b 100644
--- a/muppet/breadcrumbs.py
+++ b/muppet/breadcrumbs.py
@@ -33,7 +33,21 @@ class Breadcrumb:
     text: str
 
 
-def breadcrumbs(*items: str | Tuple[str, str]) -> list[Breadcrumb]:
+@dataclass
+class Breadcrumbs:
+    """
+    A complete set of breadcrumbs.
+
+    Basically a non-empty list, with the last item being guaranteed.
+    This since the trailing item shouldn't be a link, and with this we
+    have guaranteed correct data when rendering the template.
+    """
+
+    crumbs: list[Breadcrumb]
+    final: str
+
+
+def breadcrumbs(*items: str | Tuple[str, str]) -> Breadcrumbs:
     """
     Generate a breadcrumb trail.
 
@@ -47,7 +61,7 @@ def breadcrumbs(*items: str | Tuple[str, str]) -> list[Breadcrumb]:
     """
     url = '/'
     result = []
-    for item in items:
+    for item in items[:-1]:
         if isinstance(item, str):
             url += item + '/'
             text = item
@@ -56,4 +70,11 @@ def breadcrumbs(*items: str | Tuple[str, str]) -> list[Breadcrumb]:
             text = item[0]
         url = re.sub('/+', '/', url)
         result.append(Breadcrumb(ref=url, text=text))
-    return result
+
+    final = items[-1]
+    if isinstance(final, str):
+        finaltxt = final
+    else:
+        finaltxt = final[0]
+
+    return Breadcrumbs(result, finaltxt)
diff --git a/muppet/format.py b/muppet/format.py
index 8c52eb4..bf107ad 100644
--- a/muppet/format.py
+++ b/muppet/format.py
@@ -1022,7 +1022,7 @@ def parse(form: Any, indent: int, context: list[str]) -> Tag:
                 return tag(f'[|[{form}]|]', 'parse-error')
 
 
-def print_docstring(name: str, docstring: dict[str, Any]) -> str:
+def format_docstring(name: str, docstring: dict[str, Any]) -> Tuple[str, str]:
     """
     Format docstrings as they appear in some puppet types.
 
@@ -1049,8 +1049,6 @@ def print_docstring(name: str, docstring: dict[str, Any]) -> str:
 
     # param_defaults = d_type['defaults']
 
-    out += f'<h2><code>{name}</code></h2>\n'
-
     for t in tags:
         text = html.escape(t.get('text') or '')
         if t['tag_name'] == 'summary':
@@ -1063,6 +1061,7 @@ def print_docstring(name: str, docstring: dict[str, Any]) -> str:
         if t['tag_name'] == 'example':
             if name := t.get('name'):
                 out += f'<h3>{name}</h3>\n'
+            # TODO highlight?
             out += f'<pre class="example"><code class="puppet">{text}</code></pre>\n'
 
     # out += '<dl>'
@@ -1081,9 +1080,10 @@ def print_docstring(name: str, docstring: dict[str, Any]) -> str:
         out += markdown(docstring['text'])
         out += '</div>'
 
-    return out
+    return (name, out)
 
 
+# TODO @option tags
 def build_param_dict(docstring: dict[str, Any]) -> dict[str, str]:
     """
     Extract all parameter documentation from a docstring dict.
@@ -1112,7 +1112,7 @@ def build_param_dict(docstring: dict[str, Any]) -> dict[str, str]:
         return {}
 
 
-def format_class(d_type: dict[str, Any]) -> str:
+def format_class(d_type: dict[str, Any]) -> Tuple[str, str]:
     """Format Puppet class."""
     t = parse_puppet(d_type['source'])
     data = parse(t, 0, ['root'])
@@ -1120,12 +1120,13 @@ def format_class(d_type: dict[str, Any]) -> str:
     out = ''
     name = d_type['name']
     # print(name, file=sys.stderr)
-    out += print_docstring(name, d_type['docstring'])
+    name, body = format_docstring(name, d_type['docstring'])
+    out += body
 
     out += '<pre class="highlight-muppet"><code class="puppet">'
     out += render(renderer, data)
     out += '</code></pre>'
-    return out
+    return name, out
 
 
 def format_type() -> str:
@@ -1133,35 +1134,37 @@ def format_type() -> str:
     return 'TODO format_type not implemented'
 
 
-def format_type_alias(d_type: dict[str, Any]) -> str:
+def format_type_alias(d_type: dict[str, Any]) -> Tuple[str, str]:
     """Format Puppet type alias."""
     renderer = HTMLRenderer()
     out = ''
     name = d_type['name']
     # print(name, file=sys.stderr)
-    out += print_docstring(name, d_type['docstring'])
+    title, body = format_docstring(name, d_type['docstring'])
+    out += body
     out += '\n'
     out += '<pre class="highlight-muppet"><code class="puppet">'
     t = parse_puppet(d_type['alias_of'])
     data = parse(t, 0, ['root'])
     out += render(renderer, data)
     out += '</code></pre>\n'
-    return out
+    return title, out
 
 
-def format_defined_type(d_type: dict[str, Any]) -> str:
+def format_defined_type(d_type: dict[str, Any]) -> Tuple[str, str]:
     """Format Puppet defined type."""
     renderer = HTMLRenderer(build_param_dict(d_type['docstring']))
     out = ''
     name = d_type['name']
     # print(name, file=sys.stderr)
-    out += print_docstring(name, d_type['docstring'])
+    title, body = format_docstring(name, d_type['docstring'])
+    out += body
 
     out += '<pre class="highlight-muppet"><code class="puppet">'
     t = parse_puppet(d_type['source'])
     out += render(renderer, parse(t, 0, ['root']))
     out += '</code></pre>\n'
-    return out
+    return title, out
 
 
 def format_resource_type(r_type: dict[str, Any]) -> str:
@@ -1206,8 +1209,11 @@ def format_puppet_function(function: dict[str, Any]) -> str:
         signature['signature']
         signature['docstring']
     if t in ['ruby3x', 'ruby4x']:
-        # TODO manual highlight here
-        out += f'<pre><code class="ruby">{function["source"]}</code></pre>\n'
+        # TODO syntax highlighting
+        s = '<pre class="highlight-muppet"><code class="ruby">'
+        s += function["source"]
+        s += '</code></pre>\n'
+        out += s
     elif t == 'puppet':
         out += '<pre class="highlight-muppet"><code class="puppet">'
         try:
diff --git a/muppet/gather.py b/muppet/gather.py
index 1d3fc48..4795553 100644
--- a/muppet/gather.py
+++ b/muppet/gather.py
@@ -103,7 +103,8 @@ def get_module(cache: Cache,
     except FileNotFoundError:
         metadata = {}
 
-    doc_files = glob(os.path.join(os.path.abspath(path), '*.md'))
+    doc_files = glob(os.path.join(os.path.abspath(path), '*.md')) \
+        + glob(os.path.join(os.path.abspath(path), 'LICENSE'))
 
     return ModuleEntry(name=name,
                        path=path,
diff --git a/muppet/markdown.py b/muppet/markdown.py
index df54fee..dd7e1ea 100644
--- a/muppet/markdown.py
+++ b/muppet/markdown.py
@@ -43,7 +43,10 @@ def markdown(text: str) -> str:
         .enable('table') \
         .enable('strikethrough')
 
-    return md.render(text)
+    output = md.render(text)
+    if not isinstance(output, str):
+        raise ValueError(f"Unexpected markdown output: expected str, got {type(output)}")
+    return output
 
 
 # header_text.downcase().replace(' ', '-')
diff --git a/muppet/output.py b/muppet/output.py
index 7e2266b..727fe37 100644
--- a/muppet/output.py
+++ b/muppet/output.py
@@ -298,9 +298,19 @@ def setup_module(base: str, module: ModuleEntry, *, path_base: str) -> None:
 
         with open(os.path.join(dir, 'source.pp.html'), 'w') as f:
             template = jinja.get_template('code_page.html')
+            crumbs = breadcrumbs(
+                    ('Environment', ''),
+                    module.name,
+                    (puppet_class['name'],
+                     'manifests/' + '/'.join(puppet_class['name'].split('::')[1:])),
+                    'This',
+                    )
+
             with open(module.file(puppet_class['file']), 'r') as g:
-                f.write(template.render(content=highlight(g.read(), 'puppet'),
-                                        path_base=path_base))
+                f.write(template.render(title='',
+                                        content=highlight(g.read(), 'puppet'),
+                                        path_base=path_base,
+                                        breadcrumbs=crumbs))
 
         with open(os.path.join(dir, 'source.json'), 'w') as f:
             json.dump(puppet_class, f, indent=2)
@@ -316,7 +326,9 @@ def setup_module(base: str, module: ModuleEntry, *, path_base: str) -> None:
                     (puppet_class['name'],
                      'manifests/' + '/'.join(puppet_class['name'].split('::')[1:])),
                     )
-            f.write(template.render(content=format_class(puppet_class),
+            title, body = format_class(puppet_class)
+            f.write(template.render(title=title,
+                                    content=body,
                                     path_base=path_base,
                                     breadcrumbs=crumbs))
 
@@ -336,7 +348,9 @@ def setup_module(base: str, module: ModuleEntry, *, path_base: str) -> None:
 
         template = jinja.get_template('code_page.html')
         with open(os.path.join(dir, 'index.html'), 'w') as f:
-            f.write(template.render(content=format_type_alias(type_alias),
+            title, body = format_type_alias(type_alias)
+            f.write(template.render(title=title,
+                                    content=body,
                                     path_base=path_base))
 
     # data['data_type_aliases']
diff --git a/muppet/puppet/parser.py b/muppet/puppet/parser.py
index 5ab7e1b..d1f95c6 100644
--- a/muppet/puppet/parser.py
+++ b/muppet/puppet/parser.py
@@ -13,6 +13,7 @@ from typing import Any, TypeAlias, Union
 from ..cache import Cache
 
 
+# TODO cache path
 cache = Cache('/home/hugo/.cache/puppet-doc')
 
 
diff --git a/static-src/style.scss b/static-src/style.scss
index 2f0fba2..a4846ba 100644
--- a/static-src/style.scss
+++ b/static-src/style.scss
@@ -1,3 +1,5 @@
+$border-radius: 1ex;
+
 /* -------------------------------------------------- */
 
 .parse-error {
@@ -9,6 +11,12 @@
 
 $header-height: 2em;
 
+h1 {
+	text-align: center;
+	margin: 0;
+	border-bottom: 1px solid black;
+}
+
 h2, h3 {
 	position: sticky;
 	background: white;
@@ -72,18 +80,38 @@ code.json {
 .example {
 	background: lightgray;
 	padding: 1em;
-	border-radius: 1ex;
+	border-radius: $border-radius;
 }
 
 .comment {
+	/*
 	border-left: 1ex;
 	border-left-style: dotted;
-	display: inline-block;
 	padding-left: 1em;
+	*/
+	display: inline-block;
 
 	font-family: sans;
 	font-size: 80%;
 
+	p {
+		$line-height: 1.2em;
+
+		line-height: $line-height;
+		padding-left: 20px;
+		/* https://stackoverflow.com/questions/52748260/add-a-prefix-character-for-each-new-line-using-css */
+		background:
+			repeating-linear-gradient(
+				to bottom,
+				transparent 0px,
+				transparent 5px,
+				#000 5px,
+				#000 calc(1.2em - 2px),
+				transparent calc(1.2em - 2px),
+				transparent 1.2em)
+			4px 0/2px 100% no-repeat;
+	}
+
 	p:first-child {
 		margin-top: 0;
 	}
@@ -93,6 +121,42 @@ code.json {
 	}
 }
 
+.alternatives {
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	padding: 0;
+	margin-top: 0;
+
+	li {
+		padding: 0;
+		display: inline-block;
+		background-color: lightgrey;
+		border-left: 1px solid black;
+		border-right: 1px solid black;
+
+		&:first-child {
+			border-radius: 0 0 0 $border-radius;
+			border-left: none;
+		}
+
+		&:last-child {
+			border-radius: 0 0 $border-radius 0;
+			border-right: none;
+		}
+
+		a {
+			display: block;
+			padding: 1em;
+			color: black;
+		}
+
+		.selected {
+			background-color: grey;
+		}
+	}
+}
+
 /* -------------------------------------------------- */
 
 @import "colorscheme_default";
diff --git a/templates/base.html b/templates/base.html
index c218717..c9cbb88 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -35,9 +35,10 @@ Parameters:
 		<header>
 			{% if breadcrumbs %}
 				<ul class="breadcrumb">
-					{%- for item in breadcrumbs -%}
+					{%- for item in breadcrumbs.crumbs -%}
 						<li><a href="{{ path_base }}{{ item.ref }}">{{ item.text }}</a></li>
 					{%- endfor -%}
+					<li>{{ breadcrumbs.final }}</li>
 				</ul>
 			{% endif %}
 		</header>
diff --git a/templates/code_page.html b/templates/code_page.html
index 0b588cc..989fc5a 100644
--- a/templates/code_page.html
+++ b/templates/code_page.html
@@ -2,12 +2,14 @@
 A page containing puppet code.
 
 Parameters:
+	title:
 	content:
 		Content of page
 #}
 {% extends "base.html" %}
 {% block content %}
-	<ul>
+	<h1><code>{{ title }}</code></h1>
+	<ul class="alternatives">
 		<li><a href="index.html">Rendered</a></li>
 		<li><a href="source.pp.html">Source</a></li>
 		<li><a href="source.pp.txt">Raw Source</a></li>
-- 
GitLab