]*))/gi ,'<https$1');
+- text = text.replace(/\[\]\(#opt-(\s*([^)]*))/gi ,'$1').replace(/\)/gi,'');
+- //[](#opt-wayland.windowManager.hyprland.plugins)
+- text = text.replace(/\{var\}(\s*([^\n]*))/gi ,'$1').replace(/`/gi,'')
+- text = text.replace(/:::\ \{\.note\}(\s*([^:::]*))/gi ,'$1
').replace(/:::/,'').replace(/\n/g, '
')
+- return text;
+-}
+-
+ var expandOptionMD = function(el){
+
+ modalTitle.innerHTML = currentSet[el].title;
+
+- let dhtml = parseMD(currentSet[el].doc);
+- var elDesc = "Description
" + dhtml + "
";
++ var elDesc = "Description
" + currentSet[el].doc + "
";
+ var elArgs = "Args
" + currentSet[el].args.join(', ') + "
";
+ // var elNote = ( currentSet[el].note == "" ? "": "Note
" + currentSet[el].note + "
");
+ // var elDefault = "Default
" + currentSet[el].default + "
";
+@@ -290,7 +188,7 @@ var expandOption = function(el){
+
+ //console.log(currentSet[el].description.replace(/:::\ \{\.note\}(\s*([^:::]*))/gi ,'$1
').replace(/:::/,''));
+
+- var elDesc = "Description
" + parseDescription(currentSet[el].description) + "
";
++ var elDesc = "Description
" + currentSet[el].description + "
";
+ var elType = "Type
" + currentSet[el].type + "
";
+ //var elNote = ( currentSet[el].note == "" ? "": "Note
" + currentSet[el].note + "
");
+ var elDefault = ( currentSet[el].default == "" ? "" : "Default
" + currentSet[el].default + "
");
+--
+2.47.0
+
diff --git a/modules/nixos/extranix/0002-chore-remove-useless-dependencies.patch b/modules/nixos/extranix/0002-chore-remove-useless-dependencies.patch
new file mode 100644
index 0000000..9d391f2
--- /dev/null
+++ b/modules/nixos/extranix/0002-chore-remove-useless-dependencies.patch
@@ -0,0 +1,24 @@
+From 957189be0a61f954a1bcfb204f982f59ae6435ea Mon Sep 17 00:00:00 2001
+From: catvayor
+Date: Thu, 12 Dec 2024 17:04:45 +0100
+Subject: [PATCH 2/2] chore: remove useless dependencies
+
+---
+ layouts/index.html | 1 -
+ 1 file changed, 1 deletion(-)
+
+diff --git a/layouts/index.html b/layouts/index.html
+index 63a6158..780ea78 100644
+--- a/layouts/index.html
++++ b/layouts/index.html
+@@ -17,7 +17,6 @@
+
+
+
+-
+
+
+
+--
+2.47.0
+
diff --git a/modules/nixos/extranix/0003-feat-separate-HTML-description-of-MD-description.patch b/modules/nixos/extranix/0003-feat-separate-HTML-description-of-MD-description.patch
new file mode 100644
index 0000000..cb8149f
--- /dev/null
+++ b/modules/nixos/extranix/0003-feat-separate-HTML-description-of-MD-description.patch
@@ -0,0 +1,25 @@
+From b0f6c845280bee20bcc28a136436e000bde8a457 Mon Sep 17 00:00:00 2001
+From: catvayor
+Date: Mon, 16 Dec 2024 11:25:38 +0100
+Subject: [PATCH] feat: separate HTML description of MD description
+
+---
+ static/js/script.js | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/static/js/script.js b/static/js/script.js
+index d3ec223..5d9fc6e 100644
+--- a/static/js/script.js
++++ b/static/js/script.js
+@@ -188,7 +188,7 @@ var expandOption = function(el){
+
+ //console.log(currentSet[el].description.replace(/:::\ \{\.note\}(\s*([^:::]*))/gi ,'$1
').replace(/:::/,''));
+
+- var elDesc = "Description
" + currentSet[el].description + "
";
++ var elDesc = "Description
" + currentSet[el].descriptionHTML + "
";
+ var elType = "Type
" + currentSet[el].type + "
";
+ //var elNote = ( currentSet[el].note == "" ? "": "Note
" + currentSet[el].note + "
");
+ var elDefault = ( currentSet[el].default == "" ? "" : "Default
" + currentSet[el].default + "
");
+--
+2.47.0
+
diff --git a/modules/nixos/extranix/0004-fix-indentation-of-ul.patch b/modules/nixos/extranix/0004-fix-indentation-of-ul.patch
new file mode 100644
index 0000000..cabfc09
--- /dev/null
+++ b/modules/nixos/extranix/0004-fix-indentation-of-ul.patch
@@ -0,0 +1,26 @@
+From e31e0330b9b012b6e09f8eb6bc670e4336d1aedc Mon Sep 17 00:00:00 2001
+From: catvayor
+Date: Mon, 16 Dec 2024 12:53:27 +0100
+Subject: [PATCH] fix: indentation of
+
+---
+ static/css/nucleus.css | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/static/css/nucleus.css b/static/css/nucleus.css
+index a4674a8..9ada521 100644
+--- a/static/css/nucleus.css
++++ b/static/css/nucleus.css
+@@ -533,7 +533,8 @@ p {
+
+ ul, ol {
+ margin-top: 1.7rem;
+- margin-bottom: 1.7rem; }
++ margin-bottom: 1.7rem;
++ margin-left: 1rem; }
+ ul ul, ul ol, ol ul, ol ol {
+ margin-top: 0;
+ margin-bottom: 0; }
+--
+2.47.0
+
diff --git a/modules/nixos/extranix/0005-feat-match-all-substring-by-default.patch b/modules/nixos/extranix/0005-feat-match-all-substring-by-default.patch
new file mode 100644
index 0000000..17bbac8
--- /dev/null
+++ b/modules/nixos/extranix/0005-feat-match-all-substring-by-default.patch
@@ -0,0 +1,28 @@
+From 206ce2744cdaa166ee482fba90a879f2688b234a Mon Sep 17 00:00:00 2001
+From: catvayor
+Date: Sat, 21 Dec 2024 10:14:46 +0100
+Subject: [PATCH] feat: match all substring by default
+
+---
+ layouts/index.html | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/layouts/index.html b/layouts/index.html
+index 780ea78..c50ada4 100644
+--- a/layouts/index.html
++++ b/layouts/index.html
+@@ -109,9 +109,9 @@
+ - Strategy
+ -
+
+
+
+--
+2.47.0
+
diff --git a/modules/nixos/extranix/default.nix b/modules/nixos/extranix/default.nix
new file mode 100644
index 0000000..5a8e5b0
--- /dev/null
+++ b/modules/nixos/extranix/default.nix
@@ -0,0 +1,249 @@
+# SPDX-FileCopyrightText: 2024 Lubin Bailly
+#
+# SPDX-License-Identifier: EUPL-1.2
+
+{
+ lib,
+ config,
+ pkgs,
+ ...
+}:
+let
+ inherit (lib)
+ mkOption
+ mkEnableOption
+ mkIf
+ types
+ importJSON
+ mapAttrs'
+ mapAttrsToList
+ removePrefix
+ pathIsDirectory
+ hasSuffix
+ hasPrefix
+ attrNames
+ filter
+ head
+ getExe
+ concatStringsSep
+ ;
+ inherit (lib.strings)
+ sanitizeDerivationName
+ ;
+ yaml = pkgs.formats.yaml { };
+ json = pkgs.formats.json { };
+ cfg = config.services.extranix;
+
+ module-eval =
+ module-name: module:
+ let
+ ignored-eval = lib.evalModules {
+ modules = module.ignored-modules;
+ inherit (module) specialArgs;
+ };
+ ignored-opts-doc = pkgs.nixosOptionsDoc { inherit (ignored-eval) options; };
+ ignored-opts = importJSON "${ignored-opts-doc.optionsJSON}/share/doc/nixos/options.json";
+ eval = lib.evalModules {
+ modules = module.paths ++ module.ignored-modules;
+ inherit (module) specialArgs;
+ };
+ opts-doc = pkgs.nixosOptionsDoc { inherit (eval) options; };
+ opts = importJSON "${opts-doc.optionsJSON}/share/doc/nixos/options.json";
+ filtered-opts = removeAttrs opts (attrNames ignored-opts);
+ path-translation =
+ let
+ translations = map (
+ { base, url }:
+ {
+ url = "${url}${if hasSuffix "/" url then "" else "/"}";
+ base =
+ let
+ base1 = toString base;
+ in
+ base1 + (if hasSuffix "/" base1 then "" else "/");
+ }
+ ) module.path-translations;
+ in
+ path:
+ let
+ fullPath = path + (if pathIsDirectory path then "/default.nix" else "");
+ fitting = filter ({ base, ... }: hasPrefix base fullPath) translations;
+ translate-info = head (
+ fitting
+ ++ [
+ (throw (
+ "${fullPath} is not in any base path of ${module-name}. Base paths are "
+ + concatStringsSep "\n" (map ({ base, ... }: base) translations)
+ ))
+ ]
+ );
+ innerPath = removePrefix translate-info.base fullPath;
+ in
+ {
+ name = "<${innerPath}>";
+ url = "${translate-info.url}${innerPath}";
+ };
+ result' = json.generate "options-extranix-fileDesc.json" {
+ last_update = "-/-";
+ options = mapAttrsToList (title: val: {
+ inherit title;
+ inherit (val)
+ type
+ readOnly
+ loc
+ description
+ ;
+ descriptionHTML = pkgs.runCommand "option-${title}.html" { } ''
+ ${getExe pkgs.pandoc} -f markdown ${pkgs.writeText "option-${title}.md" val.description} > $out
+ '';
+ example = val.example.text or "";
+ default = val.default.text or "";
+ declarations = map path-translation val.declarations;
+ }) filtered-opts;
+ };
+ result =
+ pkgs.runCommand "options-extranix.json"
+ {
+ nativeBuildInputs = [ pkgs.jq ];
+ }
+ ''
+ jq -r '.options[].descriptionHTML | "--rawfile\n" + . + "\n" + .' ${result'} | xargs \
+ jq -c '.options |= map(.descriptionHTML as $desc | .descriptionHTML |= $ARGS.named.[$desc])' ${result'} \
+ > $out
+ '';
+ in
+ result;
+
+ options-files = mapAttrs' (name: value: {
+ name = sanitizeDerivationName name;
+ value = module-eval name value;
+ }) cfg.modules;
+
+ webroot = pkgs.callPackage ./webroot.nix {
+ inherit options-files;
+ inherit (cfg) static-data;
+ settings = yaml.generate "config.yaml" cfg.settings;
+ hugo-theme-extranix-options-search = pkgs.callPackage ./hugo-theme-extranix-options-search.nix { };
+ };
+in
+{
+ options.services.extranix = {
+ enable = mkEnableOption "extranix documentation";
+ modules = mkOption {
+ type =
+ let
+ module-mod.options = {
+ specialArgs = mkOption {
+ type = types.attrs;
+ default = { };
+ description = ''
+ Special arguments to give to evalModules.
+ '';
+ };
+ paths = mkOption {
+ type = types.listOf types.deferredModule;
+ description = ''
+ Modules to from which to document options.
+ '';
+ };
+ ignored-modules = mkOption {
+ type = types.listOf types.deferredModule;
+ default = [ ];
+ description = ''
+ Modules required to make modules of `paths` valid.
+ '';
+ example = ''
+ import "''${infra-modulesPath}/module-list.nix"
+ '';
+ };
+ path-translations = mkOption {
+ type =
+ let
+ path-mod.options = {
+ base = mkOption {
+ type = types.path;
+ description = ''
+ Base path of some module files to be documented.
+ '';
+ };
+ url = mkOption {
+ type = types.str;
+ description = ''
+ Url root to use for files on this path.
+ '';
+ };
+ };
+ in
+ types.listOf (types.submodule path-mod);
+ description = ''
+ Rules to convert file paths to urls in the documentation to indicate where
+ the option is declared.
+ '';
+ };
+ };
+ in
+ types.attrsOf (types.submodule module-mod);
+ description = ''
+ Sets of modules to be documented separately. The identifier to give for
+ `settings.params.release_current_stable` (which is the default module shown) is the key after
+ passing through `sanitizeDerivationName`.
+ '';
+ };
+ settings = mkOption {
+ inherit (yaml) type;
+ description = ''
+ Settings for the `config.yaml` for the hugo instantiation.
+ '';
+ example = {
+ baseUrl = "https://example.org/";
+ title = "Module documentation";
+ languageCode = "en-us";
+ params = {
+ release_current_stable = "Some-Module";
+ logo = "logo.png";
+ footer_credits_line = ''
+ Based on Home Manager Option Search
+ '';
+ footer_copyright_line = ''
+ Made by catvayor for the DGNum.
+ '';
+ main_menu = [
+ {
+ name = ''Sources'';
+ url = "https://git.example.org/";
+ }
+ ];
+ };
+ };
+ };
+ static-data = mkOption {
+ type = types.path;
+ description = ''
+ Static files for the website. Should have `images/favicon.png` for favicon.
+ '';
+ };
+ host = mkOption {
+ type = types.str;
+ description = ''
+ Hostname of the service.
+ '';
+ };
+ };
+ config = mkIf cfg.enable {
+ services = {
+ extranix = {
+ settings = {
+ theme = "extranix-options-search";
+ params.releases = mapAttrsToList (name: _: {
+ inherit name;
+ value = sanitizeDerivationName name;
+ }) cfg.modules;
+ };
+ };
+ nginx = {
+ enable = true;
+ virtualHosts.${cfg.host}.locations."/".alias = "${webroot}/";
+ };
+ };
+ };
+}
diff --git a/modules/nixos/extranix/hugo-theme-extranix-options-search.nix b/modules/nixos/extranix/hugo-theme-extranix-options-search.nix
new file mode 100644
index 0000000..57b3fd7
--- /dev/null
+++ b/modules/nixos/extranix/hugo-theme-extranix-options-search.nix
@@ -0,0 +1,75 @@
+# SPDX-FileCopyrightText: 2024 Lubin Bailly
+#
+# SPDX-License-Identifier: EUPL-1.2
+
+{
+ fetchFromGitHub,
+ stdenv,
+ lib,
+ fetchurl,
+}:
+stdenv.mkDerivation {
+ name = "hugo-theme-extranix-options-search";
+
+ src = fetchFromGitHub {
+ owner = "mipmip";
+ repo = "hugo-theme-extranix-options-search";
+ rev = "3252b5bd98adcbbe629327d72c8416c25014a0d6";
+ hash = "sha256-XV7Js1KaBiWv9qao8iyzQ546nT1KkwCtvyAs++oeXFo=";
+ };
+
+ patches = [
+ ./0001-revert-don-t-parse-md-in-js.patch
+ ./0002-chore-remove-useless-dependencies.patch
+ ./0003-feat-separate-HTML-description-of-MD-description.patch
+ ./0004-fix-indentation-of-ul.patch
+ ./0005-feat-match-all-substring-by-default.patch
+ ];
+
+ installPhase =
+ let
+ js-search = fetchurl {
+ url = "https://unpkg.com/js-search@2.0.1/dist/umd/js-search.min.js";
+ hash = "sha256-LD9UsSATk+xTzAbk8nD2gA2bjHKvetXtCMDAFkM2K5Q=";
+ };
+ jquery = fetchurl {
+ url = "https://code.jquery.com/jquery-3.7.1.slim.min.js";
+ hash = "sha256-kmHvs0B+OpCW5GVHUNjv9rOmY0IvSIRcf7zGUDTDQM8=";
+ };
+ bootstrap.css.main = fetchurl {
+ url = "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css";
+ hash = "sha256-916EbMg70RQy9LHiGkXzG8hSg9EdNy97GazNG/aiY1w=";
+ };
+ bootstrap.css.theme = fetchurl {
+ url = "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css";
+ hash = "sha256-ZT4HPpdCOt2lvDkXokHuhJfdOKSPFLzeAJik5U/Q+l4=";
+ };
+ bootstrap.js = fetchurl {
+ url = "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js";
+ hash = "sha256-U5ZEeKfGNOja007MMD3YBI0A3OSZOQbeG6z2f2Y0hu8=";
+ };
+ in
+ ''
+ mkdir $out
+ rm static/images/favicon.*
+ cp -r * $out
+ cp ${js-search} $out/static/js/js-search.min.js
+ cp ${jquery} $out/static/js/jquery.slim.min.js
+ cp ${bootstrap.css.main} $out/static/css/bootstrap.min.css
+ cp ${bootstrap.css.theme} $out/static/css/bootstrap-theme.min.css
+ cp ${bootstrap.js} $out/static/js/bootstrap.min.js
+ substituteInPlace $out/layouts/index.html \
+ --replace-fail 'https://unpkg.com/js-search@2.0.1/dist/umd/js-search.min.js' js/js-search.min.js \
+ --replace-fail 'https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js' js/jquery.slim.min.js \
+ --replace-fail 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' css/bootstrap.min.css \
+ --replace-fail 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css' css/bootstrap-theme.min.css \
+ --replace-fail 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js' js/bootstrap.min.js \
+ '';
+
+ meta = {
+ description = "Theme which implements a nix modules options search machine.";
+ homepage = "https://github.com/mipmip/hugo-theme-extranix-options-search";
+ license = lib.licenses.asl20;
+ maintainers = [ ];
+ };
+}
diff --git a/modules/nixos/extranix/webroot.nix b/modules/nixos/extranix/webroot.nix
new file mode 100644
index 0000000..aff5c49
--- /dev/null
+++ b/modules/nixos/extranix/webroot.nix
@@ -0,0 +1,25 @@
+# SPDX-FileCopyrightText: 2024 Lubin Bailly
+#
+# SPDX-License-Identifier: EUPL-1.2
+
+{
+ hugo,
+ hugo-theme-extranix-options-search,
+ options-files,
+ settings,
+ static-data,
+ lib,
+ runCommand,
+}:
+runCommand "nix-doc-webroot" { } ''
+ mkdir themes
+ ln -s ${hugo-theme-extranix-options-search} themes/extranix-options-search
+ cp -rs ${static-data} static
+ chmod -R u+w static
+ mkdir static/data
+ ${lib.concatStringsSep "\n" (
+ lib.mapAttrsToList (name: file: "ln -s ${file} static/data/options-${name}.json") options-files
+ )}
+ ln -s ${settings} config.yaml
+ ${lib.getExe hugo} --noBuildLock -d $out
+''