forked from fediversity/fediversity
		
	separate templating from file system outputs
This commit is contained in:
		
							parent
							
								
									59a2fed5e2
								
							
						
					
					
						commit
						829a796f16
					
				
					 3 changed files with 76 additions and 71 deletions
				
			
		|  | @ -7,16 +7,24 @@ let | |||
|   templates = import ./templates.nix { inherit lib; }; | ||||
| in | ||||
| { | ||||
|   options.templates = mkOption { | ||||
|     description = '' | ||||
|       Collection of named functions to convert page contents to files | ||||
|   options.templates = | ||||
|     let | ||||
|       # 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. | ||||
|     ''; | ||||
|     type = with types; attrsOf (functionTo (functionTo options.files.type)); | ||||
|   }; | ||||
|         Each template function takes the complete site `config` and the document's data structure. | ||||
|       ''; | ||||
|       type = recursiveAttrs (with types; functionTo (functionTo str)); | ||||
|     }; | ||||
| 
 | ||||
|   config.templates = | ||||
|   config.templates.html = | ||||
|     let | ||||
|       commonmark = name: markdown: pkgs.runCommand "${name}.html" | ||||
|         { | ||||
|  | @ -26,37 +34,27 @@ in | |||
|       ''; | ||||
|     in | ||||
|     { | ||||
|       page = lib.mkDefault (config: page: { | ||||
|         # TODO: create static redirects from `tail page.locations` | ||||
|         # 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: maybe it would even make sense to split routing and rendering altogether | ||||
|         ${page.outPath} = builtins.toFile "${page.name}.html" (templates.html { | ||||
|           head = '' | ||||
|             <title>${page.title}</title> | ||||
|             <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)} | ||||
|           ''; | ||||
|         }); | ||||
|       page = lib.mkDefault (config: page: templates.html { | ||||
|         head = '' | ||||
|           <title>${page.title}</title> | ||||
|           <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: { | ||||
|         # TODO: create static redirects from `tail page.locations` | ||||
|         ${page.outPath} = builtins.toFile "${page.name}.html" (templates.html { | ||||
|           head = '' | ||||
|             <title>${page.title}</title> | ||||
|             <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; }; }} | ||||
|             ${builtins.readFile (commonmark page.name page.body)} | ||||
|           ''; | ||||
|         }); | ||||
|       article = lib.mkDefault (config: page: templates.html { | ||||
|         head = '' | ||||
|           <title>${page.title}</title> | ||||
|           <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; }; }} | ||||
|           ${builtins.readFile (commonmark page.name page.body)} | ||||
|         ''; | ||||
|       }); | ||||
|     }; | ||||
| 
 | ||||
|  | @ -71,25 +69,24 @@ in | |||
|   }; | ||||
| 
 | ||||
|   config.files = | ||||
|     # TODO: create static redirects from `tail page.locations` | ||||
|     let | ||||
|       pages = lib.concatMapAttrs | ||||
|         (name: page: page.template config page) | ||||
|         config.pages; | ||||
|       collections = | ||||
|         let | ||||
|           byCollection = with lib; mapAttrs | ||||
|             (_: collection: | ||||
|               map (entry: entry.template config entry) collection.entry | ||||
|             ) | ||||
|             config.collections; | ||||
|         in | ||||
|         with lib; concatMapAttrs | ||||
|           (collection: entries: | ||||
|             foldl' (acc: entry: acc // entry) { } entries | ||||
|           ) | ||||
|           byCollection; | ||||
|       pages = lib.attrValues config.pages; | ||||
|       collections = with lib; concatMap (collection: collection.entry) (attrValues config.collections); | ||||
|       collections' = with lib; map | ||||
|         ( | ||||
|           entry: recursiveUpdate entry { | ||||
|             locations = map (l: "${entry.collection.name}/${l}") entry.locations; | ||||
|           } | ||||
|         ) | ||||
|         collections; | ||||
|     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 { | ||||
|     description = '' | ||||
|  |  | |||
|  | @ -16,12 +16,15 @@ in | |||
|   config.content-types = { | ||||
|     document = { name, config, ... }: { | ||||
|       options = { | ||||
| 
 | ||||
|         name = mkOption { | ||||
|           description = "Symbolic name, used as a human-readable identifier"; | ||||
|           type = types.str; | ||||
|           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 { | ||||
|           description = '' | ||||
|             List of historic output locations for the resulting file | ||||
|  | @ -44,17 +47,11 @@ in | |||
|           type = types.str; | ||||
|           default = lib.head config.locations; | ||||
|         }; | ||||
|         # TODO: maybe it would even make sense to split routing and rendering altogether. | ||||
|         #       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 { | ||||
|         outputs = mkOption { | ||||
|           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; | ||||
|         }; | ||||
|       }; | ||||
|       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 ]; | ||||
|       options = { | ||||
|         collection = mkOption { | ||||
|           description = "Collection this article belongs to"; | ||||
|           type = options.collections.type.nestedTypes.elemType; | ||||
|           default = collection; | ||||
|         }; | ||||
|         date = mkOption { | ||||
|           description = "Publication date"; | ||||
|           type = with types; nullOr str; | ||||
|  | @ -103,8 +105,10 @@ in | |||
|         }; | ||||
|       }; | ||||
|       config.name = lib.slug config.title; | ||||
|       config.outPath = "${collectionName}/${lib.head config.locations}"; | ||||
|       config.template = lib.mkForce cfg.templates.article; | ||||
|       # TODO: this should be covered by the TBD `link` function instead, | ||||
|       #       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 = { ... }: { | ||||
|  |  | |||
|  | @ -42,11 +42,15 @@ in | |||
|             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; | ||||
|           }; | ||||
|           entry = mkOption { | ||||
|             description = "An entry in the collection"; | ||||
|             type = types.collection (types.submodule ({ | ||||
|               _module.args.collection = config.entry; | ||||
|               _module.args.collectionName = name; | ||||
|               _module.args.collection = config; | ||||
|               imports = [ config.type ]; | ||||
|             })); | ||||
|           }; | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Valentin Gagarin
							Valentin Gagarin