diff options
| -rw-r--r-- | content/basics.html.nix | 26 | ||||
| -rw-r--r-- | content/default.nix | 18 | ||||
| -rw-r--r-- | content/education.html.nix | 39 | ||||
| -rw-r--r-- | content/experience.html.nix | 26 | ||||
| -rw-r--r-- | content/languages.html.nix | 10 | ||||
| -rw-r--r-- | content/publications.html.nix | 64 | ||||
| -rw-r--r-- | default.nix | 44 | ||||
| -rw-r--r-- | flake.lock | 90 | ||||
| -rw-r--r-- | flake.nix | 21 | ||||
| -rw-r--r-- | index.html.nix | 52 | ||||
| -rw-r--r-- | lib/default.nix | 5 | ||||
| -rw-r--r-- | lib/html.nix | 219 | ||||
| -rw-r--r-- | static/icon.png | bin | 0 -> 704 bytes |
13 files changed, 614 insertions, 0 deletions
diff --git a/content/basics.html.nix b/content/basics.html.nix new file mode 100644 index 0000000..d7f7a90 --- /dev/null +++ b/content/basics.html.nix @@ -0,0 +1,26 @@ +{ html, data, ... }: + +let basics = data.basics; +in { + title = "About me"; + priority = 0; + body = with html; + with data.basics; + lines [ + (div { class = "row"; } [ + (div { class = "col"; } [ (imgWith { src = avatar; }) ]) + (div { class = "col"; } (dl [ + (dt "${icon "las la-at"} e-mail") + (dd (for email (email: "${mailto email.address} (${email.name}) ${br}"))) + (dt "${icon "las la-key"} keys") + (dd (for keys.pgp (name: path: href path name))) + (dt "${icon "las la-map-marker"} address") + (dd (with location; '' + ${number} ${street}${br} + ${postalCode} ${city} + '')) + ])) + ]) + description + ]; +} diff --git a/content/default.nix b/content/default.nix new file mode 100644 index 0000000..0b0d0ca --- /dev/null +++ b/content/default.nix @@ -0,0 +1,18 @@ +{ html, make, ... }: + +let + sectionTemplate = section: { + inherit (section) title priority; + body = html.section { id = section.title; } [ + (html.h1 section.title) + section.body + ]; + }; + makeSection = path: sectionTemplate (make path { }); +in builtins.map makeSection [ + ./basics.html.nix + ./education.html.nix + ./experience.html.nix + # ./languages.html.nix + ./publications.html.nix +] diff --git a/content/education.html.nix b/content/education.html.nix new file mode 100644 index 0000000..e1027e3 --- /dev/null +++ b/content/education.html.nix @@ -0,0 +1,39 @@ +{ html, data, lib, ... }: + +let education = data.education; +in { + title = "Education"; + priority = 30; + body = with html; + dl (for (sort.reverse.byPath [ "date" "start" ] education) (item: + with item; + lines [ + (dt [ + (with institution; "${studyType} @ ${href url name}, ${location}") + br + (with date; small (timerange start end)) + ]) + (dd [ + (lib.optionalString (lib.hasAttr "years" item) (lines + (for (sort.reverse.byPath [ "date" "start" ] years) (year: + with year; + details [ + (summary [ + (with program; + "${studyType} @ ${ + href url (abbr { title = name; } acronym) + }") + br + (with date; small (timerange start end)) + ]) + description + (lines (for courses (category: list: + details [ + (summary "${category} courses") + (lib.concatStringsSep " · " (lib.naturalSort list)) + ]))) + ])))) + description + ]) + ])); +} diff --git a/content/experience.html.nix b/content/experience.html.nix new file mode 100644 index 0000000..8637802 --- /dev/null +++ b/content/experience.html.nix @@ -0,0 +1,26 @@ +{ html, data, lib, ... }: + +let experience = data.experience; +in { + title = "Experience"; + priority = 20; + body = with html; + dl (for (sort.reverse.byPath [ "date" "start" ] experience) (item: + with item; + lines [ + (dt [ + (with institution; "${position} @ ${href url name}, ${location}") + br + (small (lib.concatStringsSep " · " + ([ (with date; timerange start end) ] + ++ lib.optional (lib.hasAttr "supervisors" item) + "supervised by ${ + lib.concatStringsSep " " + (for supervisors (supervisor: with supervisor; href url name)) + }" ++ lib.optional (lib.hasAttr "assets" item) + (lib.concatStringsSep " " (for assets + (asset: with asset; href "#Publications#${id}" "${icon "las la-paperclip"} ${name}")))))) + ]) + (dd description) + ])); +} diff --git a/content/languages.html.nix b/content/languages.html.nix new file mode 100644 index 0000000..36780db --- /dev/null +++ b/content/languages.html.nix @@ -0,0 +1,10 @@ +{ html, data, ... }: + +let languages = data.languages; +in { + title = "Languages"; + priority = 40; + body = with html; + lines (for languages + (language: with language; "${icon} ${name} (${proficiency})")); +} diff --git a/content/publications.html.nix b/content/publications.html.nix new file mode 100644 index 0000000..04bb502 --- /dev/null +++ b/content/publications.html.nix @@ -0,0 +1,64 @@ +{ html, data, lib, ... }: + +let + publications = data.publications; + attrValsOpt = attrs: attrSet: + lib.attrVals (builtins.filter (attr: lib.hasAttr attr attrSet) attrs) + attrSet; + format = publication: + with html; + with publication; + { + inherit id title url year abstract cite; + } // (let + authorsOther = lib.remove data.basics.name + (builtins.map (author: "${author.given} ${author.family}") author); + in lib.optionalAttrs (authorsOther != [ ]) { + authors = "With ${lib.concatStringsSep ", " authorsOther}"; + }) // lib.optionalAttrs (publication ? container-title) { + published = "In ${em container-title}, " + lib.concatStringsSep ", " + (attrValsOpt [ "volume" "issue" "publisher" ] publication); + } // lib.optionalAttrs (publication ? ISBN) { + isbn = "${small "ISBN"}: ${ISBN}"; + } // lib.optionalAttrs (publication ? ISSN) { + issn = "${small "ISSN"}: ${ISSN}"; + } // lib.optionalAttrs (publication ? DOI) { + doi = "${small "DOI"}: ${href "https://doi.org/${DOI}" (code DOI)}"; + }; +in { + title = "Publications"; + priority = 10; + body = with html; + dl (for (sort.reverse.byPath [ "issued" "date-parts" ] publications) + (publication: + let formatted = format publication; + in with formatted; + lines [ + (dt { id = "Publications#${id}"; } + "${href { target = "_blank"; } url title} (${year})") + (dd [ + (lib.concatStringsSep ". " + (attrValsOpt [ "authors" "published" "isbn" "issn" "doi" ] + formatted)) + (details [ + (summary "More") + (dl [ + (dt "Abstract.") + (dd (blockquote abstract)) + (dt "Cite.") + (let + citeWith = title: attr: + details [ + (summary title) + (pre (code (lib.getAttr attr cite))) + ]; + in dd [ + (citeWith "BibLaTeX" "biblatex") + (citeWith "BibTeX" "bibtex") + (citeWith "CSL JSON" "csljson") + ]) + ]) + ]) + ]) + ])); +} diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..7e7098a --- /dev/null +++ b/default.nix @@ -0,0 +1,44 @@ +{ pkgs, html, data }: + +let + commonArgs = { + inherit data html make; + inherit (pkgs) lib; + }; + make = path: overrides: + let f = import path; + in f ((builtins.intersectAttrs (builtins.functionArgs f) commonArgs) + // overrides); + lineAwesomeCSS = { fontsRelativeDirectory ? "./webfonts" }: + pkgs.stdenv.mkDerivation rec { + name = "line-awesome-css"; + version = "v1.2.1"; + + src = pkgs.fetchurl { + url = + "https://raw.githubusercontent.com/icons8/line-awesome/${version}/dist/line-awesome/css/line-awesome.min.css"; + sha256 = "sha256:zmGhjPCE8VADeYNABEZD8ymsX5AEWsstnneDaL15mFQ="; + }; + + phases = [ "installPhase" ]; + installPhase = '' + cp $src $out + substituteInPlace $out --replace '../fonts' '${fontsRelativeDirectory}' + ''; + }; + + webpage = { line-awesome, line-awesome-css ? lineAwesomeCSS + , source ? builtins.toFile "index.html" (make ./index.html.nix { }) + , files ? data.files, + icon ? ./static/icon.png }: + pkgs.runCommand "webpage" { } '' + mkdir "$out" + ln -sT "${source}" "$out/index.html" + mkdir "$out/static" + ln -sT "${icon}" "$out/static/icon.png" + ln -sT "${files}" "$out/static/files" + mkdir -p "$out/static/css/fonts/line-awesome" + ln -sT "${line-awesome}/share/fonts/woff2" "$out/static/css/fonts/line-awesome/webfonts" + ln -sT "${line-awesome-css {}}" "$out/static/css/fonts/line-awesome/line-awesome.min.css" + ''; +in pkgs.callPackage webpage { } diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..c128a4a --- /dev/null +++ b/flake.lock @@ -0,0 +1,90 @@ +{ + "nodes": { + "data": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1668186470, + "narHash": "sha256-v1bDqQjsnwPpFgAS7LImXzbDKvydsxCeIeM0AsJgFAw=", + "owner": "qaristote", + "repo": "info", + "rev": "6bdc8dd0c582edcaed9295628956fa966d3c44e3", + "type": "github" + }, + "original": { + "owner": "qaristote", + "repo": "info", + "type": "github" + } + }, + "flake-utils": { + "locked": { + "lastModified": 1667395993, + "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "locked": { + "lastModified": 1667395993, + "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1668086072, + "narHash": "sha256-msFoXI5ThCmhTTqgl27hpCXWhaeqxphBaleJAgD8JYM=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "72d8853228c9758820c39b8659415b6d89279493", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1668086072, + "narHash": "sha256-msFoXI5ThCmhTTqgl27hpCXWhaeqxphBaleJAgD8JYM=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "72d8853228c9758820c39b8659415b6d89279493", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "root": { + "inputs": { + "data": "data", + "flake-utils": "flake-utils_2", + "nixpkgs": "nixpkgs_2" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..570cde9 --- /dev/null +++ b/flake.nix @@ -0,0 +1,21 @@ +{ + description = "Source code of Quentin Aristote's personal webpage."; + + inputs = { + flake-utils.url = "github:numtide/flake-utils"; + data.url = "github:qaristote/info"; + }; + + outputs = { self, nixpkgs, flake-utils, data }: + { lib = import ./lib { inherit (nixpkgs) lib; }; } // + flake-utils.lib.eachDefaultSystem (system: + let pkgs = nixpkgs.legacyPackages.${system}; + in rec { + packages.webpage = import ./default.nix { + inherit pkgs; + inherit (self.lib.pp) html; + data = data.formatWith."${system}" self.lib.pp.html; + }; + defaultPackage = packages.webpage; + }); +} diff --git a/index.html.nix b/index.html.nix new file mode 100644 index 0000000..473c01a --- /dev/null +++ b/index.html.nix @@ -0,0 +1,52 @@ +{ html, make, ... }: + +let sections = html.sort.byKey "priority" (make ./content { }); +in with html; +html.html { lang = "en"; } [ + (head [ + # Basic page needs + (metaWith { charset = "utf-8"; }) + (title "Quentin Aristote") + (metaWith { + name = "description"; + content = "Personal webpage of Quentin Aristote"; + }) + (metaWith { + name = "author"; + content = "Quentin Aristote"; + }) + (metaWith { + http-equiv = "x-ua-compatible"; + content = "ie=edge"; + }) + # Mobile specific needs + (metaWith { + name = "viewport"; + content = "width=device-width, initial-scale=1"; + }) + # Font + (linkWith { + rel = "stylesheet"; + href = "/static/css/fonts/line-awesome/line-awesome.min.css"; + }) + # CSS + (linkWith { + rel = "stylesheet"; + href = "https://classless.de/classless.css"; + }) + # Favicon + (linkWith { + rel = "icon"; + type = "image/png"; + href = "static/icon.png"; + }) + ]) + (body [ + (header [ + (nav (ul ([ (li "Quentin Aristote") ] ++ for sections + (section: li (href "#${section.title}" section.title))))) + ]) + (main { role = "main"; } (for sections (section: section.body))) + (footer [ ]) + ]) +] diff --git a/lib/default.nix b/lib/default.nix new file mode 100644 index 0000000..2e3127d --- /dev/null +++ b/lib/default.nix @@ -0,0 +1,5 @@ +{ lib }: + +{ + pp.html = import ./html.nix { inherit lib; }; +} diff --git a/lib/html.nix b/lib/html.nix new file mode 100644 index 0000000..aa1c839 --- /dev/null +++ b/lib/html.nix @@ -0,0 +1,219 @@ +{ lib, ... }: + +let + comment = content: "<!-- ${content} -->"; + lines = lib.concatStringsSep "\n"; + sortByPath = cmp: keys: + lib.sort + (x: y: cmp (lib.getAttrFromPath keys x) (lib.getAttrFromPath keys y)); + sortByKey = cmp: key: sortByPath cmp [ key ]; + for = iterable: f: + if lib.isList iterable then + builtins.map f iterable + else + lib.mapAttrsToList f iterable; + + setAttr = attr: value: ''${attr}="${value}"''; + tagWithAttrs = tag: attrs: + "<${tag}${ + lib.concatMapStrings (x: " ${x}") (lib.mapAttrsToList setAttr attrs) + }>"; + lineOrLines = f: content: + if lib.isList content then f (lines content) else f content; + tryOverride = f: arg: + if lib.isAttrs arg then + tryOverride (attrs: content: f (arg // attrs) content) + else + f { } arg; + container = tag: + tryOverride (attrs: + lineOrLines (content: "${tagWithAttrs tag attrs}${content}</${tag}>")); + + empty = tagWithAttrs; + + tagsContainer = [ + # Main root + # https://developer.mozilla.org/en-US/docs/Web/HTML/Element#main_root + "html" + # Document metadata + # https://developer.mozilla.org/en-US/docs/Web/HTML/Element#document_metadata + "head" + "style" + "title" + # Sectioning root + # https://developer.mozilla.org/en-US/docs/Web/HTML/Element#sectioning_root + "body" + # Content sectioning + # https://developer.mozilla.org/en-US/docs/Web/HTML/Element#content_sectioning + "address" + "article" + "aside" + "footer" + "header" + "h1" + "h2" + "h3" + "h4" + "h5" + "h6" + "main" + "nav" + "section" + # Text content + # https://developer.mozilla.org/en-US/docs/Web/HTML/Element#text_content + "blockquote" + "dd" + "div" + "dl" + "dt" + "figcaption" + "figure" + "li" + "menu" + "ol" + "p" + "pre" + "ul" + # Inline text semantics + # https://developer.mozilla.org/en-US/docs/Web/HTML/Element#inline_text_semantics + "a" + "abbr" + "b" + "bdi" + "bdo" + "cite" + "code" + "data" + "dfn" + "em" + "i" + "kbd" + "mark" + "q" + "rp" + "rt" + "ruby" + "s" + "samp" + "small" + "span" + "strong" + "sub" + "time" + "u" + "var" + # Image and multimedia + # https://developer.mozilla.org/en-US/docs/Web/HTML/Element#image_and_multimedia + "audio" + "map" + "track" + "video" + # Embedded content + # https://developer.mozilla.org/en-US/docs/Web/HTML/Element#embedded_content + "iframe" + "object" + "picture" + "portal" + "source" + # SVG and MathML + # https://developer.mozilla.org/en-US/docs/Web/HTML/Element#svg_and_mathml + "svg" + "math" + # Scripting + # https://developer.mozilla.org/en-US/docs/Web/HTML/Element#scripting + "canvas" + "noscript" + "script" + # Demarcating edits + # https://developer.mozilla.org/en-US/docs/Web/HTML/Element#demarcating_edits + "del" + "ins" + # Table content + # https://developer.mozilla.org/en-US/docs/Web/HTML/Element#table_content + "caption" + "colgroup" + "table" + "tbody" + "td" + "tfoot" + "th" + "thead" + "tr" + # Forms + # https://developer.mozilla.org/en-US/docs/Web/HTML/Element#forms + "button" + "datalist" + "fieldset" + "form" + "label" + "legend" + "meter" + "optgroup" + "option" + "output" + "progress" + "select" + "textarea" + # Interactive elements + # https://developer.mozilla.org/en-US/docs/Web/HTML/Element#interactive_elements + "details" + "dialog" + "summary" + # Web components + # https://developer.mozilla.org/en-US/docs/Web/HTML/Element#web_components + "slot" + "template" + ]; + tagsContainerFuns = + builtins.foldl' (module: tag: module // { "${tag}" = container tag; }) { } + tagsContainer; + + tagsEmpty = [ + "area" + "base" + "br" + "col" + "embed" + "hr" + "img" + "input" + "keygen" + "link" + "meta" + "param" + "source" + "track" + "wbr" + ]; + tagsEmptyFuns = builtins.foldl' (module: tag: + let tagWith = empty tag; + in module // { + "${tag}With" = tagWith; + "${tag}" = tagWith { }; + }) { } tagsEmpty; + + file = path: "/static/files/${path}"; + href = tryOverride (attrs: url: content: + tagsContainerFuns.a ({ href = url; } // attrs) content); + icon = + tryOverride (attrs: id: tagsContainerFuns.i (attrs // { class = id; }) ""); + mailto = tryOverride (attrs: address: href attrs "mailto:${address}" address); + timerange = start: end: + "${tagsContainerFuns.time { date = start; } start} - ${ + tagsContainerFuns.time { date = end; } end + }"; +in tagsContainerFuns // tagsEmptyFuns // { + inherit for comment container empty file href icon lines mailto timerange; +} // { + sort = let + lt = x: y: x < y; + gt = x: y: x > y; + in { + byKey = sortByKey lt; + byPath = sortByPath lt; + reverse = { + byKey = sortByKey gt; + byPath = sortByPath gt; + }; + }; +} diff --git a/static/icon.png b/static/icon.png Binary files differnew file mode 100644 index 0000000..80dc5de --- /dev/null +++ b/static/icon.png |
