forked from Fediversity/fediversity.eu
separate templating from file system outputs
This commit is contained in:
parent
24bd786896
commit
c8caf09ebf
|
@ -7,16 +7,24 @@ let
|
||||||
templates = import ./templates.nix { inherit lib; };
|
templates = import ./templates.nix { inherit lib; };
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options.templates = mkOption {
|
options.templates =
|
||||||
description = ''
|
let
|
||||||
Collection of named functions to convert page contents to files
|
# arbitrarily nested attribute set where the leaves are of type `type`
|
||||||
|
# NOTE: due to how `either` works, the first match is significant,
|
||||||
|
# so if `type` happens to be an attrset, the typecheck will consider
|
||||||
|
# `type`, not `attrsOf`
|
||||||
|
recursiveAttrs = type: with types; attrsOf (either type (recursiveAttrs type));
|
||||||
|
in
|
||||||
|
mkOption {
|
||||||
|
description = ''
|
||||||
|
Collection of named functions to convert document contents to a string representation
|
||||||
|
|
||||||
Each template function takes the complete site `config` and the page data structure.
|
Each template function takes the complete site `config` and the document's data structure.
|
||||||
'';
|
'';
|
||||||
type = with types; attrsOf (functionTo (functionTo options.files.type));
|
type = recursiveAttrs (with types; functionTo (functionTo str));
|
||||||
};
|
};
|
||||||
|
|
||||||
config.templates =
|
config.templates.html =
|
||||||
let
|
let
|
||||||
commonmark = name: markdown: pkgs.runCommand "${name}.html"
|
commonmark = name: markdown: pkgs.runCommand "${name}.html"
|
||||||
{
|
{
|
||||||
|
@ -26,37 +34,27 @@ in
|
||||||
'';
|
'';
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
page = lib.mkDefault (config: page: {
|
page = lib.mkDefault (config: page: templates.html {
|
||||||
# TODO: create static redirects from `tail page.locations`
|
head = ''
|
||||||
# TODO: reconsider using `page.outPath` and what to put into `locations`.
|
<title>${page.title}</title>
|
||||||
# maybe we can avoid having ".html" suffixes there.
|
<meta name="description" content="${page.description}" />
|
||||||
# since templates can output multiple files, `html` is merely one of many things we *could* produce.
|
<link rel="canonical" href="${page.outPath}" />
|
||||||
# TODO: maybe it would even make sense to split routing and rendering altogether
|
'';
|
||||||
${page.outPath} = builtins.toFile "${page.name}.html" (templates.html {
|
body = ''
|
||||||
head = ''
|
${templates.nav { menu = { menu = config.menus.main; }; }}
|
||||||
<title>${page.title}</title>
|
${builtins.readFile (commonmark page.name page.body)}
|
||||||
<meta name="description" content="${page.description}" />
|
'';
|
||||||
<link rel="canonical" href="${page.outPath}" />
|
|
||||||
'';
|
|
||||||
body = ''
|
|
||||||
${templates.nav { menu = { menu = config.menus.main; }; }}
|
|
||||||
${builtins.readFile (commonmark page.name page.body)}
|
|
||||||
'';
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
article = lib.mkDefault (config: page: {
|
article = lib.mkDefault (config: page: templates.html {
|
||||||
# TODO: create static redirects from `tail page.locations`
|
head = ''
|
||||||
${page.outPath} = builtins.toFile "${page.name}.html" (templates.html {
|
<title>${page.title}</title>
|
||||||
head = ''
|
<meta name="description" content="${page.description}" />
|
||||||
<title>${page.title}</title>
|
<meta name="author" content="${with lib; if isList page.author then join ", " page.author else page.author}" />
|
||||||
<meta name="description" content="${page.description}" />
|
'';
|
||||||
<meta name="author" content="${with lib; if isList page.author then join ", " page.author else page.author}" />
|
body = ''
|
||||||
'';
|
${templates.nav { menu = { menu = config.menus.main; }; }}
|
||||||
body = ''
|
${builtins.readFile (commonmark page.name page.body)}
|
||||||
${templates.nav { menu = { menu = config.menus.main; }; }}
|
'';
|
||||||
${builtins.readFile (commonmark page.name page.body)}
|
|
||||||
'';
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -71,25 +69,24 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
config.files =
|
config.files =
|
||||||
|
# TODO: create static redirects from `tail page.locations`
|
||||||
let
|
let
|
||||||
pages = lib.concatMapAttrs
|
pages = lib.attrValues config.pages;
|
||||||
(name: page: page.template config page)
|
collections = with lib; concatMap (collection: collection.entry) (attrValues config.collections);
|
||||||
config.pages;
|
collections' = with lib; map
|
||||||
collections =
|
(
|
||||||
let
|
entry: recursiveUpdate entry {
|
||||||
byCollection = with lib; mapAttrs
|
locations = map (l: "${entry.collection.name}/${l}") entry.locations;
|
||||||
(_: collection:
|
}
|
||||||
map (entry: entry.template config entry) collection.entry
|
)
|
||||||
)
|
collections;
|
||||||
config.collections;
|
|
||||||
in
|
|
||||||
with lib; concatMapAttrs
|
|
||||||
(collection: entries:
|
|
||||||
foldl' (acc: entry: acc // entry) { } entries
|
|
||||||
)
|
|
||||||
byCollection;
|
|
||||||
in
|
in
|
||||||
pages // collections;
|
with lib; foldl
|
||||||
|
(acc: elem: acc // {
|
||||||
|
"${head elem.locations}" = builtins.toFile "${elem.name}.html" elem.outputs.html;
|
||||||
|
})
|
||||||
|
{ }
|
||||||
|
(pages ++ collections');
|
||||||
|
|
||||||
options.build = mkOption {
|
options.build = mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
|
|
|
@ -16,12 +16,15 @@ in
|
||||||
config.content-types = {
|
config.content-types = {
|
||||||
document = { name, config, ... }: {
|
document = { name, config, ... }: {
|
||||||
options = {
|
options = {
|
||||||
|
|
||||||
name = mkOption {
|
name = mkOption {
|
||||||
description = "Symbolic name, used as a human-readable identifier";
|
description = "Symbolic name, used as a human-readable identifier";
|
||||||
type = types.str;
|
type = types.str;
|
||||||
default = name;
|
default = name;
|
||||||
};
|
};
|
||||||
|
# TODO: reconsider using `page.outPath` and what to put into `locations`.
|
||||||
|
# maybe we can avoid having ".html" suffixes there.
|
||||||
|
# since templates can output multiple files, `html` is merely one of many things we *could* produce.
|
||||||
|
# TODO: make `apply` configurable so one can programmatically modify locations
|
||||||
locations = mkOption {
|
locations = mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
List of historic output locations for the resulting file
|
List of historic output locations for the resulting file
|
||||||
|
@ -44,17 +47,11 @@ in
|
||||||
type = types.str;
|
type = types.str;
|
||||||
default = lib.head config.locations;
|
default = lib.head config.locations;
|
||||||
};
|
};
|
||||||
# TODO: maybe it would even make sense to split routing and rendering altogether.
|
outputs = mkOption {
|
||||||
# in that case, templates would return strings, and a different
|
|
||||||
# piece of the machinery resolves rendering templates to files
|
|
||||||
# using `locations`.
|
|
||||||
# then we'd have e.g. `templates.html` and `templates.atom` for
|
|
||||||
# different output formats.
|
|
||||||
template = mkOption {
|
|
||||||
description = ''
|
description = ''
|
||||||
Function that converts the page contents to files
|
Representations of the document in different formats
|
||||||
'';
|
'';
|
||||||
type = with types; functionTo (functionTo options.files.type);
|
type = with types; attrsOf str;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -85,12 +82,17 @@ in
|
||||||
type = types.str;
|
type = types.str;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
config.template = cfg.templates.page;
|
config.outputs.html = cfg.templates.html.page cfg config;
|
||||||
};
|
};
|
||||||
|
|
||||||
article = { config, collectionName, ... }: {
|
article = { config, collection, ... }: {
|
||||||
imports = [ cfg.content-types.page ];
|
imports = [ cfg.content-types.page ];
|
||||||
options = {
|
options = {
|
||||||
|
collection = mkOption {
|
||||||
|
description = "Collection this article belongs to";
|
||||||
|
type = options.collections.type.nestedTypes.elemType;
|
||||||
|
default = collection;
|
||||||
|
};
|
||||||
date = mkOption {
|
date = mkOption {
|
||||||
description = "Publication date";
|
description = "Publication date";
|
||||||
type = with types; nullOr str;
|
type = with types; nullOr str;
|
||||||
|
@ -103,8 +105,10 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
config.name = lib.slug config.title;
|
config.name = lib.slug config.title;
|
||||||
config.outPath = "${collectionName}/${lib.head config.locations}";
|
# TODO: this should be covered by the TBD `link` function instead,
|
||||||
config.template = lib.mkForce cfg.templates.article;
|
# taking a historical list of collection names into account
|
||||||
|
config.outPath = "${collection.name}/${lib.head config.locations}";
|
||||||
|
config.outputs.html = lib.mkForce (cfg.templates.html.article cfg config);
|
||||||
};
|
};
|
||||||
|
|
||||||
named-link = { ... }: {
|
named-link = { ... }: {
|
||||||
|
|
|
@ -42,11 +42,15 @@ in
|
||||||
description = "Type of entries in the collection";
|
description = "Type of entries in the collection";
|
||||||
type = types.deferredModule;
|
type = types.deferredModule;
|
||||||
};
|
};
|
||||||
|
name = mkOption {
|
||||||
|
description = "Symbolic name, used as a human-readable identifier";
|
||||||
|
type = types.str;
|
||||||
|
default = name;
|
||||||
|
};
|
||||||
entry = mkOption {
|
entry = mkOption {
|
||||||
description = "An entry in the collection";
|
description = "An entry in the collection";
|
||||||
type = types.collection (types.submodule ({
|
type = types.collection (types.submodule ({
|
||||||
_module.args.collection = config.entry;
|
_module.args.collection = config;
|
||||||
_module.args.collectionName = name;
|
|
||||||
imports = [ config.type ];
|
imports = [ config.type ];
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
Reference in a new issue