diff --git a/website/presentation/default.nix b/website/presentation/default.nix index 39ec3593..3e9e266b 100644 --- a/website/presentation/default.nix +++ b/website/presentation/default.nix @@ -4,19 +4,10 @@ let mkOption types ; - templates = import ./templates.nix { inherit lib; }; - # 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 { + imports = lib.nixFiles ./.; + options.templates = let # arbitrarily nested attribute set where the leaves are of type `type` @@ -33,65 +24,15 @@ in type = recursiveAttrs (with types; functionTo (coercedTo attrs toString str)); }; - 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 '' -
  • ${item.menu.label} - ${lib.indent " " (item.menu.outputs.html page)} -
  • - '' - else if item ? page then ''
  • ${item.page.title}
  • '' - else ''
  • ${item.link.label}
  • '' - ; - 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 '' +
  • ${item.menu.label} + ${lib.indent " " (item.menu.outputs.html page)} +
  • + '' + else if item ? page then ''
  • ${item.page.title}
  • '' + else ''
  • ${item.link.label}
  • '' + ; + 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 {