Skip to content

Commit 5088191

Browse files
authored
overview: render service demos (#668)
1 parent bbf2cda commit 5088191

File tree

6 files changed

+205
-42
lines changed

6 files changed

+205
-42
lines changed

flake.nix

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,13 @@
119119
rec {
120120
packages = ngipkgs // {
121121
overview = import ./overview {
122-
inherit lib lib' self;
122+
inherit
123+
lib
124+
lib'
125+
self
126+
nixpkgs
127+
system
128+
;
123129
pkgs = pkgs // ngipkgs;
124130
projects = ngiProjects;
125131
options = optionsDoc.optionsNix;
@@ -191,7 +197,8 @@
191197

192198
devShells.default = pkgs.mkShell {
193199
inherit (checks."infra/pre-commit") shellHook;
194-
buildInputs = checks."infra/pre-commit".enabledPackages;
200+
# TODO use devmode
201+
buildInputs = checks."infra/pre-commit".enabledPackages ++ [ pkgs.darkhttpd ];
195202
};
196203

197204
formatter = pkgs.writeShellApplication {

overview/default.nix

Lines changed: 125 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
lib,
33
lib',
44
options,
5+
nixpkgs,
56
pkgs,
67
projects,
78
self,
9+
system,
810
}:
911
let
1012
inherit (builtins)
@@ -14,6 +16,7 @@ let
1416
concatStringsSep
1517
filter
1618
isList
19+
isInt
1720
readFile
1821
substring
1922
toJSON
@@ -30,17 +33,35 @@ let
3033
mapAttrsToList
3134
optionalString
3235
recursiveUpdate
36+
filterAttrs
3337
mapAttrs'
3438
nameValuePair
3539
take
3640
drop
41+
splitString
42+
intersperse
3743
;
3844

3945
empty =
4046
xs:
4147
assert isList xs;
4248
xs == [ ];
43-
heading = i: text: "<h${toString i}>${text}</h${toString i}>";
49+
heading =
50+
i: anchor: text:
51+
assert (isInt i && i > 0);
52+
if i == 1 then
53+
''
54+
<h1>${text}</h1>
55+
''
56+
else
57+
''
58+
<a class="heading" href="#${anchor}">
59+
<h${toString i} data-url="#${anchor}">
60+
${text}
61+
<span class="anchor"/>
62+
</h${toString i}>
63+
</a>
64+
'';
4465

4566
# Splits a compressed date up into ISO 8601
4667
lastModified =
@@ -71,7 +92,7 @@ let
7192
);
7293
in
7394
filter (option: any ((flip hasPrefix) (join "." option.loc)) spec) (attrValues options);
74-
examples = project: attrValues project.nixos.examples;
95+
examples = project: attrValues (filterAttrs (name: _: name != "demo") project.nixos.examples);
7596
};
7697

7798
# This doesn't actually produce a HTML string but a Jinja2 template string
@@ -116,28 +137,26 @@ let
116137
prefixLength = 2;
117138
in
118139
optionalString (!empty projectOptions) ''
119-
<section><details><summary>${heading 3 "Options"}</summary><dl>
140+
${heading 2 "options" "Options"}
141+
<section><details><summary><code>services.cryptpad</code></summary><dl>
120142
${concatLines (map (one prefixLength) projectOptions)}
121143
</dl></details></section>
122144
'';
123145
};
124146

125147
examples = rec {
126148
one = example: ''
127-
<li>
149+
<section><details><summary>${example.description}</summary>
128150
129-
${example.description}
151+
{{ include_code("nix", "${example.module}")}}
130152
131-
<pre><code>${readFile example.module}</code></pre>
132-
133-
</li>
153+
</details></section>
134154
'';
135155
many =
136156
examples:
137157
optionalString (!empty examples) ''
138-
<section><details><summary>${heading 3 "Examples"}</summary><ul>
158+
${heading 2 "examples" "Examples"}
139159
${concatLines (map one examples)}
140-
</ul></details></section>
141160
'';
142161
};
143162

@@ -176,8 +195,11 @@ let
176195
# The indivdual page of a project
177196
projects.one = name: project: ''
178197
<article class="page-width">
179-
${heading 1 name}
198+
${heading 1 null name}
180199
${render.metadata.one project.metadata}
200+
${optionalString (project.nixos.examples ? demo) (
201+
render.serviceDemo.one project.nixos.modules.services project.nixos.examples.demo
202+
)}
181203
${render.options.many (pick.options project)}
182204
${render.examples.many (pick.examples project)}
183205
</article>
@@ -221,12 +243,56 @@ let
221243
'';
222244
many = projects: concatLines (mapAttrsToList one projects);
223245
};
246+
247+
demoGlue.one = exampleText: ''
248+
# default.nix
249+
{
250+
ngipkgs ? import (fetchTarball "https://github.com/ngi-nix/ngipkgs/tarball/main") { },
251+
}:
252+
ngipkgs.demo (
253+
${toString (intersperse "\n " (splitString "\n" exampleText))}
254+
)
255+
'';
256+
257+
serviceDemo.one =
258+
services: example:
259+
let
260+
demoSystem = import (nixpkgs + "/nixos/lib/eval-config.nix") {
261+
inherit system;
262+
modules = (attrValues services) ++ [ example.module ];
263+
};
264+
openPorts = demoSystem.config.networking.firewall.allowedTCPPorts;
265+
# The port that is forwarded to the host so that the user can access the demo service.
266+
servicePort = (builtins.head openPorts) + 10000;
267+
in
268+
''
269+
${heading 2 "demo" "Run a demo deployment locally"}
270+
271+
<ol>
272+
<li><strong>Install Nix on your platform.</strong></li>
273+
<li>
274+
<strong>Download this Nix file to your computer.</strong>
275+
It obtains the NGIpkgs source code and declares a basic service configuration
276+
to be run in a virtual machine.
277+
{{ include_code("nix", "default.nix", relative_path=True) }}
278+
</li>
279+
<li>
280+
<strong>Build the virtual machine</strong> defined in <code>default.nix</code> and run it:
281+
<pre><code>nix-build && ./result</code></pre>
282+
Building <strong>will</strong> take a while.
283+
</li>
284+
<li>
285+
<strong>Access the service</strong> with a web browser:
286+
<a href="http://localhost:${toString servicePort}">http://localhost:${toString servicePort}</a>
287+
</li>
288+
</ol>
289+
'';
224290
};
225291

226292
# The top-level overview for all projects
227293
index = ''
228294
<section class="page-width">
229-
${heading 1 "NGIpkgs"}
295+
${heading 1 null "NGIpkgs"}
230296
231297
<p>
232298
NGIpkgs is collection of software applications funded by the <a href="https://www.ngi.eu/ngi-projects/ngi-zero/">Next Generation Internet</a> initiative and packaged for <a href="https://nixos.org">NixOS</a>.
@@ -267,26 +333,30 @@ let
267333
<footer>Version: ${version}, Last Modified: ${lastModified}</footer>
268334
'';
269335

270-
# Every HTML page that we generate
271-
pages =
272-
{
273-
"index.html" = {
274-
pagetitle = "NGIpkgs software repository";
275-
content = index;
276-
summary = ''
277-
NGIpkgs is collection of software applications funded by the Next
278-
Generation Internet initiative and packaged for NixOS.
279-
'';
280-
};
336+
# HTML project pages
337+
projectPages = mapAttrs' (
338+
name: project:
339+
nameValuePair "project/${name}" {
340+
pagetitle = "NGIpkgs | ${name}";
341+
content = render.projects.one name project;
342+
summary = project.metadata.summary or null;
343+
demoFile =
344+
if project.nixos.examples ? demo then
345+
(pkgs.writeText "default.nix" (render.demoGlue.one (readFile project.nixos.examples.demo.module)))
346+
else
347+
null;
281348
}
282-
// mapAttrs' (
283-
name: project:
284-
nameValuePair "project/${name}/index.html" {
285-
pagetitle = "NGIpkgs | ${name}";
286-
content = render.projects.one name project;
287-
summary = project.metadata.summary or null;
288-
}
289-
) projects;
349+
) projects;
350+
351+
# The summary page at the overview root
352+
indexPage = {
353+
pagetitle = "NGIpkgs software repository";
354+
content = index;
355+
summary = ''
356+
NGIpkgs is collection of software applications funded by the Next
357+
Generation Internet initiative and packaged for NixOS.
358+
'';
359+
};
290360

291361
htmlFile =
292362
path:
@@ -313,10 +383,19 @@ let
313383
'';
314384

315385
# Ensure that directories exist and render the jinja2 template that we composed with Nix so far
316-
writeHtmlCommand = path: htmlFile: ''
317-
mkdir -p "$out/$(dirname '${path}')"
318-
python3 ${./render-template.py} '${htmlFile}' "$out/${path}"
319-
'';
386+
writeProjectCommand =
387+
path: page:
388+
''
389+
mkdir -p "$out/${path}"
390+
''
391+
+ optionalString (page.demoFile != null) ''
392+
cp '${page.demoFile}' "$out/${path}/default.nix"
393+
chmod +w "$out/${path}/default.nix"
394+
nixfmt "$out/${path}/default.nix"
395+
''
396+
+ ''
397+
python3 ${./render-template.py} '${htmlFile path page}' "$out/${path}/index.html"
398+
'';
320399

321400
fonts =
322401
pkgs.runCommand "fonts"
@@ -331,6 +410,12 @@ let
331410
done
332411
'';
333412

413+
highlightingCss =
414+
pkgs.runCommand "pygments-css-rules.css" { nativeBuildInputs = [ pkgs.python3Packages.pygments ]; }
415+
''
416+
pygmentize -S default -f html -a .code > $out
417+
'';
418+
334419
in
335420
pkgs.runCommand "overview"
336421
{
@@ -341,17 +426,20 @@ pkgs.runCommand "overview"
341426
ps: with ps; [
342427
jinja2
343428
markdown-it-py
429+
pygments
344430
]
345431
))
432+
nixfmt-rfc-style
346433
];
347434
}
348435
(
349436
''
350437
mkdir -pv $out
351-
cp -v ${./style.css} $out/style.css
438+
cat ${./style.css} ${highlightingCss} > $out/style.css
352439
ln -s ${fonts} $out/fonts
440+
python3 ${./render-template.py} '${htmlFile "" indexPage}' "$out/index.html"
353441
''
354-
+ (concatLines (mapAttrsToList (path: v: writeHtmlCommand path (htmlFile path v)) pages))
442+
+ (concatLines (mapAttrsToList (path: page: writeProjectCommand path page) projectPages))
355443
+ ''
356444
357445
vnu -Werror --format json $out/*.html | jq

overview/render-template.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,35 @@
11
import sys
2+
import os
3+
24
from jinja2 import Template
35
from markdown_it import MarkdownIt
6+
from pygments import highlight
7+
from pygments.lexers import get_lexer_by_name
8+
from pygments.formatters import HtmlFormatter
49

510
jinja2_template_file = sys.argv[1]
611
output_file = sys.argv[2]
712

13+
# Include code from a file and highlight it in HTML
14+
def include_code(language, file_path, relative_path=False):
15+
if relative_path:
16+
file_path = os.path.dirname(output_file) + "/" + file_path
17+
with open(file_path) as file:
18+
code = file.read()
19+
try:
20+
lexer = get_lexer_by_name(language, stripall=True)
21+
formatter = HtmlFormatter(cssclass="code")
22+
return highlight(code, lexer, formatter)
23+
except Exception:
24+
print("Fallback: Return the code without highlighting")
25+
return f'<pre><code>{code}</code></pre>'
26+
827
md = MarkdownIt("commonmark")
928

1029
with open(jinja2_template_file) as file:
1130
jinja2_template = Template(file.read())
1231

13-
rendered = jinja2_template.render(markdown_to_html=md.render)
32+
rendered = jinja2_template.render(markdown_to_html=md.render, include_code=include_code)
1433

1534
with open(output_file, "w") as file:
1635
file.write(rendered)

overview/style.css

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@
274274

275275
body {
276276
font-family: "IBM Plex Sans";
277+
padding-bottom: 25em;
277278
}
278279
body > section {
279280
padding: 0.5cm 0 0.5cm 0;
@@ -282,6 +283,7 @@ body > section {
282283
code, pre {
283284
font-family: "IBM Plex Mono";
284285
background: #eee;
286+
overflow-x: auto;
285287
}
286288

287289
details > summary > h2, details > summary > h3 {
@@ -293,6 +295,24 @@ h1 {
293295
font-weight: 700;
294296
}
295297

298+
a.heading {
299+
text-decoration: none;
300+
color: inherit;
301+
}
302+
303+
h2:hover .anchor {
304+
visibility: visible;
305+
opacity: 1;
306+
}
307+
308+
.anchor {
309+
visibility: hidden;
310+
opacity: 0;
311+
transition: opacity 0.3s ease;
312+
font-size: 75%;
313+
content: "¶";
314+
}
315+
296316
.page-width {
297317
max-width: 55rem;
298318
margin: auto;
@@ -330,7 +350,7 @@ article.project .deliverable-tag {
330350

331351
.option-name {
332352
font-weight: 600;
333-
font-family: "IBMPlexMono";
353+
font-family: "IBM Plex Mono";
334354
}
335355

336356
.option-readonly {

0 commit comments

Comments
 (0)