This commit is contained in:
dusk 2023-04-06 01:43:12 +03:00
parent d30b078411
commit ea64bc70fc
Signed by: dusk
GPG Key ID: 1D8F8FAF2294D6EA
14 changed files with 557 additions and 478 deletions

52
css.nix
View File

@ -1,19 +1,43 @@
{utils}: let
inherit (utils) mapAttrsToList concatStringsSep isList toString map;
{lib, ...}: let
l = lib // builtins;
t = l.types;
evalCssValue = value:
if isList value
then concatStringsSep ", " (map toString value)
else toString value;
evalInner = inner: concatStringsSep " " (mapAttrsToList (name: value: "${name}: ${evalCssValue value};") inner);
css = maybeAttrs:
if isList maybeAttrs
then concatStringsSep " " maybeAttrs
else concatStringsSep " " (mapAttrsToList (name: inner: "${name} { ${evalInner inner} }") maybeAttrs);
in {
inherit css;
if l.isList value
then l.concatStringsSep ", " (l.map toString value)
else l.toString value;
evalInner = inner:
l.concatStringsSep
" "
(
l.mapAttrsToList
(name: value: "${name}: ${evalCssValue value};")
inner
);
eval = maybeAttrs:
if l.isList maybeAttrs
then l.concatStringsSep " " maybeAttrs
else
l.concatStringsSep
" "
(
l.mapAttrsToList
(name: inner: "${name} { ${evalInner inner} }")
maybeAttrs
);
css = {
__functor = self: arg: eval arg;
media = rule: inner: ''
@media (${rule}) { ${css inner} }
@media (${rule}) { ${eval inner} }
'';
};
in {
options = {
html-nix.lib.css = l.mkOption {
type = t.functionTo t.str;
};
};
config = {
html-nix.lib.css = css;
};
}

8
default.nix Normal file
View File

@ -0,0 +1,8 @@
{
imports = [
./html.nix
./css.nix
./pkgs-lib.nix
./templaters/default.nix
];
}

15
examples/flake.nix Normal file
View File

@ -0,0 +1,15 @@
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
parts.url = "github:hercules-ci/flake-parts";
};
outputs = inp:
inp.parts.lib.mkFlake {inputs = inp;} {
systems = ["x86_64-linux"];
imports = [
../default.nix
./site.nix
];
};
}

View File

@ -1,27 +0,0 @@
{
tags,
pkgs,
}:
with pkgs.htmlNix; let
index = with tags;
html [
(body [
(p "Hello world!")
(mkLink "./ex.html" "say bye")
])
];
ex = with tags;
html [
(body [
(p "Bye world!")
(mkLink "./index.html" "go back")
])
];
site = {
"index.html" = index;
"ex.html" = ex;
};
in
mkServeFromSite site

View File

@ -1,12 +1,12 @@
{
pkgs,
lib,
}: let
inherit (pkgs) htmlNix;
perSystem = {config, ...}: let
html-nix = config.html-nix;
siteServe = html-nix.mkServeFromSite (html-nix.mkSiteFrom {
src = ./site;
in
htmlNix.mkServeFromSite (htmlNix.mkSiteFrom {
inherit src;
templater = lib.templaters.basic;
templater = html-nix.lib.templaters.basic;
local = true;
})
});
in {
apps.site.program = "${siteServe}/bin/serve";
};
}

View File

@ -1,8 +0,0 @@
tags:
with tags;
html [
(body [
(p "Hello,")
(p "world!")
])
]

View File

@ -1,27 +1,28 @@
{
"nodes": {
"flakeUtils": {
"examples": {
"inputs": {
"nixpkgs": "nixpkgs",
"parts": "parts"
},
"locked": {
"lastModified": 1644229661,
"narHash": "sha256-1YdnJAsNy69bpcjuoKdOYQX0YxZBiCYZo4Twxerqv7k=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "3cecb5b042f7f209c56ffd8371b2711a290ec797",
"type": "github"
"lastModified": 1,
"narHash": "sha256-agjsV0F6nVUimGUciKlOSF35XvTOQKKEez0xZ8X0JBI=",
"path": "./examples",
"type": "path"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
"path": "./examples",
"type": "path"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1644972330,
"narHash": "sha256-6V2JFpTUzB9G+KcqtUR1yl7f6rd9495YrFECslEmbGw=",
"lastModified": 1680668850,
"narHash": "sha256-mQMg13yRsS0LXVzaeoSPwqgPO6yhkGzGewPgMSqXSv8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "19574af0af3ffaf7c9e359744ed32556f34536bd",
"rev": "4a65e9f64e53fdca6eed31adba836717a11247d2",
"type": "github"
},
"original": {
@ -31,10 +32,99 @@
"type": "github"
}
},
"nixpkgs-lib": {
"locked": {
"dir": "lib",
"lastModified": 1680213900,
"narHash": "sha256-cIDr5WZIj3EkKyCgj/6j3HBH4Jj1W296z7HTcWj1aMA=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "e3652e0735fbec227f342712f180f4f21f0594f2",
"type": "github"
},
"original": {
"dir": "lib",
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-lib_2": {
"locked": {
"dir": "lib",
"lastModified": 1680213900,
"narHash": "sha256-cIDr5WZIj3EkKyCgj/6j3HBH4Jj1W296z7HTcWj1aMA=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "e3652e0735fbec227f342712f180f4f21f0594f2",
"type": "github"
},
"original": {
"dir": "lib",
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1680668850,
"narHash": "sha256-mQMg13yRsS0LXVzaeoSPwqgPO6yhkGzGewPgMSqXSv8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "4a65e9f64e53fdca6eed31adba836717a11247d2",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"parts": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1680392223,
"narHash": "sha256-n3g7QFr85lDODKt250rkZj2IFS3i4/8HBU2yKHO3tqw=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "dcc36e45d054d7bb554c9cdab69093debd91a0b5",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"parts_2": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib_2"
},
"locked": {
"lastModified": 1680392223,
"narHash": "sha256-n3g7QFr85lDODKt250rkZj2IFS3i4/8HBU2yKHO3tqw=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "dcc36e45d054d7bb554c9cdab69093debd91a0b5",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"root": {
"inputs": {
"flakeUtils": "flakeUtils",
"nixpkgs": "nixpkgs"
"examples": "examples",
"nixpkgs": "nixpkgs_2",
"parts": "parts_2"
}
}
},

View File

@ -1,57 +1,17 @@
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
flakeUtils.url = "github:numtide/flake-utils";
parts.url = "github:hercules-ci/flake-parts";
examples.url = "path:./examples";
};
outputs = {
self,
flakeUtils,
nixpkgs,
}: let
utils = import ./utils.nix;
lib = {
# Convert Nix expressions to HTML
tags = import ./tags.nix {inherit utils;};
# Convert Nix expressions to CSS
css = import ./css.nix {inherit utils;};
# Various site templaters
templaters = {
# Basic templater with purecss, mobile responsive layout and supports posts
basic = import ./templaters/basic.nix;
outputs = inp:
inp.parts.lib.mkFlake {inputs = inp;} {
debug = true;
systems = ["x86_64-linux"];
flake = {
flakeModule = ./default.nix;
inherit (inp.examples) apps;
};
};
outputs = flakeUtils.lib.eachDefaultSystem (system: let
pkgs = nixpkgs.legacyPackages.${system};
in {
lib = {
pkgsLib = import ./pkgs-lib.nix {
inherit pkgs;
utils = utils // {inherit (lib) tags css;};
};
};
apps = with flakeUtils.lib; {
site = mkApp {
drv = import ./examples/site.nix {inherit lib pkgs;};
name = "serve";
};
basicServe = mkApp {
drv = import ./examples/serve.nix {
inherit (lib) tags;
inherit pkgs;
};
name = "serve";
};
};
examples = {
tags = import ./examples/tags.nix lib.tags;
};
});
in
outputs // {lib = outputs.lib // lib;};
}

49
html.nix Normal file
View File

@ -0,0 +1,49 @@
{lib, ...}: let
l = lib // builtins;
evalAttrs = attrs:
l.concatStrings
(
l.mapAttrsToList
(name: value: " ${name}=\"${value}\"")
attrs
);
evalChildren = children:
if l.isList children
then l.concatStrings children
else children;
tag = name: maybeAttrs:
if l.isAttrs maybeAttrs
then (children: "<${name}${evalAttrs maybeAttrs}>${evalChildren children}</${name}>")
else tag name {} maybeAttrs;
noChildrenTag = name: attrs: "<${name} ${evalAttrs attrs}>";
tagsToGen =
(l.map (n: "h${toString n}") (l.range 1 6))
++ ["ul" "li" "html" "head" "body" "div" "p"]
++ ["a" "title" "code" "pre" "nav" "article" "script"];
tags = l.genAttrs tag tagsToGen;
noChildrenTagsToGen = ["link" "meta"];
noChildrenTags = l.genAttrs noChildrenTag noChildrenTagsToGen;
in {
options = {
html-nix.lib.html = l.mkOption {
type = l.types.raw;
};
};
config = {
html-nix.lib.html =
tags
// noChildrenTags
// {
inherit tag;
mkLink = url: tags.a {href = url;};
mkStylesheet = url:
noChildrenTags.link {
rel = "stylesheet";
href = url;
};
};
};
}

View File

@ -1,7 +1,26 @@
{
utils,
pkgs,
lib,
flake-parts-lib,
...
}: let
l = lib // builtins;
in {
options = {
perSystem =
flake-parts-lib.mkPerSystemOption
({...}: {
html-nix.lib = {
mkServeFromSite = l.mkOption {
type = with l.types; functionTo package;
};
mkSiteFrom = l.mkOption {
type = with l.types; functionTo attrs;
};
};
});
};
config = {
perSystem = {pkgs, ...}: let
pkgBin = name: "${pkgs.${name}}/bin/${name}";
mkServePathScript = path:
@ -10,53 +29,51 @@
'';
mkSitePath = site: let
inherit (utils) recursiveAttrPaths concatStringsSep map;
inherit (pkgs.lib) mapAttrsRecursive init last getAttrFromPath;
convertToPath = path: value:
if builtins.isPath value
then value
else pkgs.writeText (concatStringsSep "-" path) value;
fileAttrPaths = recursiveAttrPaths site;
texts = mapAttrsRecursive convertToPath site;
mkCreateFileCmd = path: value: let p = concatStringsSep "/" (init path); in "mkdir -p \"$out/${p}\" && ln -s \"${value}\" \"$out/${p}/${last path}\"";
createFileCmds = map (path: mkCreateFileCmd path (getAttrFromPath path texts)) fileAttrPaths;
else pkgs.writeText (l.concatStringsSep "-" path) value;
fileAttrPaths = l.recursiveAttrPaths site;
texts = l.mapAttrsRecursive convertToPath site;
mkCreateFileCmd = path: value: let
p = l.concatStringsSep "/" (l.init path);
in "mkdir -p \"$out/${p}\" && ln -s \"${value}\" \"$out/${p}/${l.last path}\"";
createFileCmds =
l.map
(path: mkCreateFileCmd path (l.getAttrFromPath path texts))
fileAttrPaths;
in
pkgs.runCommandLocal "site-path" {} ''
mkdir -p $out
${concatStringsSep "\n" createFileCmds}
${l.concatStringsSep "\n" createFileCmds}
'';
parseMarkdown = name: contents:
pkgs.runCommandLocal name {} ''
printf ${pkgs.lib.escapeShellArg contents} | ${pkgBin "pandoc"} -f gfm > $out
printf ${l.escapeShellArg contents} | ${pkgBin "pandoc"} -f gfm > $out
'';
in {
inherit mkServePathScript mkSitePath parseMarkdown;
html-nix.lib = {
mkServeFromSite = site: mkServePathScript (mkSitePath site);
mkSiteFrom = {
src,
templater,
local ? false,
}: let
inherit (utils) readDir readFile fromTOML mapAttrsToList sort elemAt;
inherit (pkgs.lib) nameValuePair head splitString pipe removeSuffix mapAttrs';
postsRendered = let
path = src + "/posts";
in
pipe (readDir path) [
(mapAttrsToList (
l.pipe (l.readDir path) [
(l.mapAttrsToList (
name: _:
nameValuePair
(head (splitString "." name))
(readFile (parseMarkdown name (readFile (path + "/${name}"))))
l.nameValuePair
(l.head (l.splitString "." name))
(l.readFile (parseMarkdown name (l.readFile (path + "/${name}"))))
))
(sort (
(l.sort (
p: op: let
extractDate = name: splitString "-" (head (splitString "_" name));
getPart = name: el: removeSuffix "0" (elemAt (extractDate name) el);
extractDate = name: l.splitString "-" (l.head (l.splitString "_" name));
getPart = name: el: l.removeSuffix "0" (l.elemAt (extractDate name) el);
d = getPart p.name;
od = getPart op.name;
in
@ -66,22 +83,22 @@ in {
pagesRendered = let
path = src + "/pages";
in
mapAttrs'
l.mapAttrs'
(
name: _:
nameValuePair
(head (splitString "." name))
(readFile (parseMarkdown name (readFile (path + "/${name}"))))
l.nameValuePair
(l.head (l.splitString "." name))
(l.readFile (parseMarkdown name (l.readFile (path + "/${name}"))))
)
(readDir path);
siteConfig = fromTOML (readFile (src + "/config.toml"));
(l.readDir path);
siteConfig = l.fromTOML (l.readFile (src + "/config.toml"));
baseurl =
if local
then "http://localhost:8080"
else siteConfig.baseurl or (throw "Need baseurl");
context = {
inherit utils pkgs baseurl;
inherit lib baseurl;
config = siteConfig;
posts = postsRendered;
pages = pagesRendered;
@ -94,4 +111,7 @@ in {
};
in
(templater context).site;
};
};
};
}

View File

@ -1,34 +0,0 @@
{utils}: let
inherit (utils) concatStrings mapAttrsToList genAttrs isAttrs isList range toString;
evalAttrs = attrs: concatStrings (mapAttrsToList (name: value: " ${name}=\"${value}\"") attrs);
evalChildren = children:
if isList children
then concatStrings children
else children;
tag = name: maybeAttrs:
if isAttrs maybeAttrs
then (children: "<${name}${evalAttrs maybeAttrs}>${evalChildren children}</${name}>")
else tag name {} maybeAttrs;
noChildrenTag = name: attrs: "<${name} ${evalAttrs attrs}>";
tagsToGen =
(map (n: "h${toString n}") (range 1 6))
++ ["ul" "li" "html" "head" "body" "div" "p"]
++ ["a" "title" "code" "pre" "nav" "article" "script"];
tags = genAttrs tag tagsToGen;
noChildrenTagsToGen = ["link" "meta"];
noChildrenTags = genAttrs noChildrenTag noChildrenTagsToGen;
in
tags
// noChildrenTags
// {
inherit tag;
mkLink = url: tags.a {href = url;};
mkStylesheet = url:
noChildrenTags.link {
rel = "stylesheet";
href = url;
};
}

View File

@ -1,27 +1,23 @@
{
utils,
posts,
pkgs,
config,
pages,
site,
baseurl,
lib,
...
} @ context: let
inherit (utils) readFile mapAttrsToList mapAttrs tags fetchGit map elemAt foldl' concatStrings genAttrs toString;
inherit (pkgs.lib) optionalString optionalAttrs optional length splitString nameValuePair toInt range mapAttrs' singleton;
inherit (builtins) listToAttrs;
}: let
l = lib // builtins;
t = l.types;
inherit (config.html-nix.lib) html css;
stylesheets = map tags.mkStylesheet [
"https://unpkg.com/purecss@2.0.6/build/pure-min.css"
"https://unpkg.com/purecss@2.0.6/build/grids-responsive-min.css"
"${baseurl}/site.css"
func = ctx: let
stylesheets = l.map html.mkStylesheet [
"https://unpkg.com/purecss@3.0.0/build/pure-min.css"
"https://unpkg.com/purecss@3.0.0/build/grids-responsive-min.css"
"${ctx.baseurl}/site.css"
];
parsePostName = name: let
parts = splitString "_" name;
id = elemAt parts 1;
date = elemAt parts 0;
parts = l.splitString "_" name;
id = l.elemAt parts 1;
date = l.elemAt parts 0;
in {
inherit id date;
formatted = "${date} - ${id}";
@ -34,7 +30,7 @@
parsed = parsePostName name;
inherit (parsed) id date;
in
with tags;
with html;
article [
(a {
href = "#${id}";
@ -44,45 +40,43 @@
value
];
pagesSection =
pagesSection = with html;
[
(tags.div {class = "pure-u-1";} (tags.a {
href = "${baseurl}/";
(div {class = "pure-u-1";} (a {
href = "${ctx.baseurl}/";
class = "pagelink";
} "home"))
]
++ (map
++ (l.map
(name:
tags.div {class = "pure-u-1";} (tags.a {
href = "${baseurl}/${name}/";
div {class = "pure-u-1";} (a {
href = "${ctx.baseurl}/${name}/";
class = "pagelink";
}
name))
(mapAttrsToList (name: _: name) pages))
(l.mapAttrsToList (name: _: name) pages))
++ [
(tags.div {class = "pure-u-1";} (tags.a {
href = "${baseurl}/posts/";
(div {class = "pure-u-1";} (a {
href = "${ctx.baseurl}/posts/";
class = "pagelink";
} "posts"))
];
postsRendered = map renderPost posts;
postsLinks = with tags;
postsLinks = with html;
singleton
(ul (
map
l.map
(
post:
li (
a {href = "${baseurl}/${post.name}";}
a {href = "${ctx.baseurl}/${post.name}";}
(parsePostName post.name).formatted
)
)
posts
));
postsSectionContent = with tags;
postsSectionContent = with html;
[
(a {
href = "#posts";
@ -91,20 +85,20 @@
]
++ postsLinks;
sidebarSection = optionalString ((length pagesSection) > 0) (
with tags;
sidebarSection = l.optionalString ((l.length pagesSection) > 0) (
with html;
nav {class = "sidebar";} [
(div {class = "pure-g";} pagesSection)
]
);
mkPage = content:
with tags; ''
with html; ''
<!DOCTYPE html>
${html [
(head (stylesheets
++ [
(title config.title)
(title ctx.config.title)
(meta {
name = "viewport";
content = "width=device-width, initial-scale=1";
@ -118,15 +112,15 @@
]}
'';
indexPage = mkPage (context.indexContent or postsSectionContent);
indexPage = mkPage (ctx.indexContent or postsSectionContent);
pagesAndPosts =
pages
// listToAttrs (
map (post: nameValuePair post.name (renderPost post)) posts
ctx.pages
// l.listToAttrs (
map (post: l.nameValuePair post.name (renderPost post)) ctx.posts
);
stylesheet = with utils.css; let
stylesheet = let
marginMobile = {
margin-left = "3%";
margin-right = "3%";
@ -135,9 +129,20 @@
css [
(css (
(
mapAttrs'
(name: value: nameValuePair value {content = "\"${concatStrings (map (_: "#") (range 1 (toInt name)))} \"";})
(genAttrs (n: "h${toString n}:before") (map toString (range 1 6)))
l.mapAttrs'
(
name: value:
l.nameValuePair
value
{
content = "\"${l.concatStrings (l.map (_: "#") (l.range 1 (l.toInt name)))} \"";
}
)
(
l.genAttrs
(n: "h${l.toString n}:before")
(l.map l.toString (l.range 1 6))
)
)
// {
body = {
@ -177,7 +182,7 @@
};
}
))
(media "max-width: 48em" {
(css.media "max-width: 48em" {
"nav.sidebar" =
{
position = "relative";
@ -195,13 +200,23 @@ in {
inherit stylesheets sidebarSection mkPage stylesheet;
site =
site
ctx.site
// {
"index.html" = indexPage;
"posts"."index.html" = mkPage postsSectionContent;
"404.html" = mkPage (tags.h1 "No such page");
"404.html" = mkPage (html.h1 "No such page");
"site.css" = stylesheet;
}
// (mapAttrs (name: value: {"index.html" = mkPage value;}) pagesAndPosts)
// optionalAttrs (context ? resources) {inherit (context) resources;};
// (l.mapAttrs (name: value: {"index.html" = mkPage value;}) pagesAndPosts)
// l.optionalAttrs (ctx ? resources) {inherit (ctx) resources;};
};
in {
options = {
html-nix.lib.templaters.basic = l.mkOption {
type = t.functionTo t.attrs;
};
};
config = {
html-nix.lib.templaters.basic = func;
};
}

5
templaters/default.nix Normal file
View File

@ -0,0 +1,5 @@
{
imports = [
./basic.nix
];
}

View File

@ -1,38 +0,0 @@
let
inherit (builtins) isAttrs isList map any concatMap concatStringsSep listToAttrs genList;
mapAttrsToList = f: attrs: map (name: f name attrs.${name}) (builtins.attrNames attrs);
in
{
inherit mapAttrsToList;
recursiveAttrPaths = set: let
flattenIfHasList = x:
if (isList x) && (any isList x)
then concatMap flattenIfHasList x
else [x];
recurse = path: set: let
g = name: value:
if isAttrs value
then recurse (path ++ [name]) value
else path ++ [name];
in
mapAttrsToList g set;
in
flattenIfHasList (recurse [] set);
concatStrings = concatStringsSep "";
genAttrs = f: names:
listToAttrs (map (n: {
name = n;
value = f n;
})
names);
range = first: last:
if first > last
then []
else genList (n: first + n) (last - first + 1);
}
// builtins