''
- ;
- in
- ''
-
- '';
-
- };
-
options.files = mkOption {
description = ''
Files that make up the site, mapping from output path to contents
- By default, all elements in `option`{pages} are converted to files using their template or the default template.
Add more files to the output by assigning to this attribute set.
'';
- # TODO: this should be attrsOf string-coercible instead.
- # we can convert this to file at the very end.
type = with types; attrsOf path;
};
- config.files =
- # TODO: create static redirects from `tail page.locations`
- let
- pages = lib.attrValues config.pages;
- collections = with lib; concatMap (collection: collection.entry) (attrValues config.collections);
- in
- with lib; foldl
- (acc: elem: acc // {
- # TODO: we may or may not want to enforce the mapping of file types to output file name suffixes
- "${head elem.locations}.html" = builtins.toFile "${elem.name}.html" elem.outputs.html;
- })
- { }
- (pages ++ collections);
-
options.build = mkOption {
description = ''
The final output of the web site
diff --git a/website/presentation/templates.nix b/website/presentation/templates.nix
new file mode 100644
index 00000000..c76cd3a4
--- /dev/null
+++ b/website/presentation/templates.nix
@@ -0,0 +1,50 @@
+{ config, options, lib, pkgs, ... }:
+let
+ inherit (lib)
+ mkOption
+ types
+ ;
+ # TODO: optionally run the whole thing through the validator
+ # https://github.com/validator/validator
+ render-html = document:
+ let
+ eval = lib.evalModules {
+ class = "DOM";
+ modules = [ document (import ./dom.nix) ];
+ };
+ in
+ toString eval.config;
+in
+{
+ config.templates.html = {
+ markdown = { name, body }:
+ let
+ commonmark = pkgs.runCommand "${name}.html"
+ {
+ buildInputs = [ pkgs.cmark ];
+ } ''
+ cmark ${builtins.toFile "${name}.md" body} > $out
+ '';
+ in
+ builtins.readFile commonmark;
+ nav = { menu, page }:
+ let
+ render-item = item:
+ if item ? menu then ''
+
''
+ ;
+ in
+ ''
+
+ '';
+ };
+}
diff --git a/website/structure/collections.nix b/website/structure/collections.nix
new file mode 100644
index 00000000..b171c1f3
--- /dev/null
+++ b/website/structure/collections.nix
@@ -0,0 +1,75 @@
+{ config, options, lib, pkgs, ... }:
+let
+ inherit (lib)
+ mkOption
+ types
+ ;
+ cfg = config;
+in
+{
+ options.collections = mkOption {
+ description = ''
+ Named collections of unnamed pages
+
+ Define the content type of a new collection `example` to be `article`:
+
+ ```nix
+ config.collections.example.type = config.types.article;
+ ```
+
+ Add a new entry to the `example` collection:
+
+ ```nix
+ config.collections.example.entry = {
+ # contents here
+ }
+ ```
+ '';
+ type = with types; attrsOf (submodule ({ name, config, ... }: {
+ options = {
+ type = mkOption {
+ description = "Type of entries in the collection";
+ type = types.deferredModule;
+ };
+ name = mkOption {
+ description = "Symbolic name, used as a human-readable identifier";
+ type = types.str;
+ default = name;
+ };
+ prefixes = mkOption {
+ description = ''
+ List of historic output locations for files in the collection
+
+ The first element is the canonical location.
+ All other elements are used to create redirects to the canonical location.
+
+ The default entry is the symbolic name of the collection.
+ When changing the symbolic name, append the old one to your custom list and use `lib.mkForce` to make sure the default element will be overridden.
+ '';
+ type = with types; nonEmptyListOf str;
+ example = [ "." ];
+ default = [ config.name ];
+ };
+ entry = mkOption {
+ description = "An entry in the collection";
+ type = types.collection (types.submodule ({
+ imports = [ config.type ];
+ _module.args.collection = config;
+ process-locations = ls: with lib; concatMap (l: map (p: "${p}/${l}") config.prefixes) ls;
+ }));
+ };
+ };
+ }));
+ };
+ config.files =
+ # TODO: create static redirects from `tail .locations`
+ let
+ collections = with lib; concatMap (collection: collection.entry) (attrValues config.collections);
+ in
+ with lib; foldl
+ (acc: elem: acc // {
+ "${head elem.locations}.html" = builtins.toFile "${elem.name}.html" elem.outputs.html;
+ })
+ { }
+ collections;
+}
diff --git a/website/structure/default.nix b/website/structure/default.nix
index 46a1543b..4e1cdb9d 100644
--- a/website/structure/default.nix
+++ b/website/structure/default.nix
@@ -14,73 +14,53 @@ in
type = with types; attrsOf deferredModule;
};
- # TODO: enable i18n, e.g. via a nested attribute for language-specific content
- options.pages = mkOption {
- description = ''
- Collection of pages on the site
- '';
- type = with types; attrsOf (submodule config.content-types.page);
- };
-
- options.collections = mkOption {
- description = ''
- Named collections of unnamed pages
-
- Define the content type of a new collection `example` to be `article`:
-
- ```nix
- config.collections.example.type = config.types.article;
- ```
-
- Add a new entry to the `example` collection:
-
- ```nix
- config.collections.example.entry = {
- # contents here
- }
- ```
- '';
- type = with types; attrsOf (submodule ({ name, config, ... }: {
- options = {
- type = mkOption {
- description = "Type of entries in the collection";
- type = types.deferredModule;
- };
- name = mkOption {
- description = "Symbolic name, used as a human-readable identifier";
- type = types.str;
- default = name;
- };
- prefixes = mkOption {
- description = ''
- List of historic output locations for files in the collection
-
- The first element is the canonical location.
- All other elements are used to create redirects to the canonical location.
-
- The default entry is the symbolic name of the collection.
- When changing the symbolic name, append the old one to your custom list and use `lib.mkForce` to make sure the default element will be overridden.
- '';
- type = with types; nonEmptyListOf str;
- example = [ "." ];
- default = [ config.name ];
- };
- entry = mkOption {
- description = "An entry in the collection";
- type = types.collection (types.submodule ({
- imports = [ config.type ];
- _module.args.collection = config;
- process-locations = ls: with lib; concatMap (l: map (p: "${p}/${l}") config.prefixes) ls;
- }));
- };
+ config.content-types.document = { name, config, options, link, ... }: {
+ config._module.args.link = config.link;
+ options = {
+ name = mkOption {
+ description = "Symbolic name, used as a human-readable identifier";
+ type = types.str;
+ default = name;
};
- }));
- };
+ locations = mkOption {
+ description = ''
+ List of historic output locations for the resulting file
- options.menus = mkOption {
- description = ''
- Collection navigation menus
- '';
- type = with types; attrsOf (submodule config.content-types.navigation);
+ Elements are relative paths to output files, without suffix.
+ The suffix will be added depending on output file type.
+
+ The first element is the canonical location.
+ All other elements are used to create redirects to the canonical location.
+
+ The default entry is the symbolic name of the document.
+ When changing the symbolic name, append the old one to your custom list and use `lib.mkForce` to make sure the default element will be overridden.
+ '';
+ type = with types; nonEmptyListOf str;
+ apply = config.process-locations;
+ example = [ "about/overview" "index" ];
+ default = [ config.name ];
+ };
+ process-locations = mkOption {
+ description = "Function to post-process the output locations of contained document";
+ type = types.functionTo options.locations.type;
+ default = lib.id;
+ };
+ link = mkOption {
+ description = "Helper function for transparent linking to other pages";
+ type = with types; functionTo str;
+ # TODO: we may want links to other representations,
+ # and currently the mapping of output types to output file
+ # names is soft.
+ default = target: with lib; "${relativePath (head config.locations) (head target.locations)}.html";
+ };
+ outputs.html = mkOption {
+ # TODO: make this of type DOM and convert to string at the output.
+ # the output aggregator then only needs something string-coercible
+ description = ''
+ Representations of the document in different formats
+ '';
+ type = with types; str;
+ };
+ };
};
}
diff --git a/website/structure/document.nix b/website/structure/document.nix
deleted file mode 100644
index dd3718d5..00000000
--- a/website/structure/document.nix
+++ /dev/null
@@ -1,58 +0,0 @@
-{ lib, ... }:
-let
- inherit (lib)
- mkOption
- types
- ;
-in
-{
- content-types.document = { name, config, options, link, ... }: {
- config._module.args.link = config.link;
- options = {
- name = mkOption {
- description = "Symbolic name, used as a human-readable identifier";
- type = types.str;
- default = name;
- };
- locations = mkOption {
- description = ''
- List of historic output locations for the resulting file
-
- Elements are relative paths to output files, without suffix.
- The suffix will be added depending on output file type.
-
- The first element is the canonical location.
- All other elements are used to create redirects to the canonical location.
-
- The default entry is the symbolic name of the document.
- When changing the symbolic name, append the old one to your custom list and use `lib.mkForce` to make sure the default element will be overridden.
- '';
- type = with types; nonEmptyListOf str;
- apply = config.process-locations;
- example = [ "about/overview" "index" ];
- default = [ config.name ];
- };
- process-locations = mkOption {
- description = "Function to post-process the output locations of contained document";
- type = types.functionTo options.locations.type;
- default = lib.id;
- };
- link = mkOption {
- description = "Helper function for transparent linking to other pages";
- type = with types; functionTo str;
- # TODO: we may want links to other representations,
- # and currently the mapping of output types to output file
- # names is soft.
- default = target: with lib; "${relativePath (head config.locations) (head target.locations)}.html";
- };
- outputs.html = mkOption {
- # TODO: make this of type DOM and convert to string at the output.
- # the output aggregator then only needs something string-coercible
- description = ''
- Representations of the document in different formats
- '';
- type = with types; str;
- };
- };
- };
-}
diff --git a/website/structure/navigation.nix b/website/structure/navigation.nix
index 5f47d239..91cc74cc 100644
--- a/website/structure/navigation.nix
+++ b/website/structure/navigation.nix
@@ -16,7 +16,14 @@ let
];
in
{
- content-types.named-link = { ... }: {
+ options.menus = mkOption {
+ description = ''
+ Collection navigation menus
+ '';
+ type = with types; attrsOf (submodule config.content-types.navigation);
+ };
+
+ config.content-types.named-link = { ... }: {
options = {
label = mkOption {
description = "Link label";
@@ -29,7 +36,7 @@ in
};
};
- content-types.navigation = { name, config, ... }: {
+ config.content-types.navigation = { name, config, ... }: {
options = {
name = mkOption {
description = "Symbolic name, used as a human-readable identifier";
diff --git a/website/structure/page.nix b/website/structure/page.nix
index 748dd3a7..8eef14e6 100644
--- a/website/structure/page.nix
+++ b/website/structure/page.nix
@@ -15,7 +15,24 @@ let
toString eval.config;
in
{
- content-types.page = { name, config, ... }: {
+ # TODO: enable i18n, e.g. via a nested attribute for language-specific content
+ options.pages = mkOption {
+ description = ''
+ Collection of pages on the site
+ '';
+ type = with types; attrsOf (submodule config.content-types.page);
+ };
+ config.files = with lib;
+ foldl'
+ (acc: elem: acc // {
+ # TODO: create static redirects from `tail page.locations`
+ # TODO: the file name could correspond to the canonical location in the HTML representation
+ "${head elem.locations}.html" = builtins.toFile "${elem.name}.html" elem.outputs.html;
+ })
+ { }
+ (attrValues config.pages);
+
+ config.content-types.page = { name, config, ... }: {
imports = [ cfg.content-types.document ];
options = {
title = mkOption {