diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d92482dab..ef8717a74 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -413,6 +413,9 @@ Makefile @thiagokokada /modules/services/random-background.nix @rycee +/modules/services/recoll.nix @foo-dogsquared +/tests/modules/recoll @foo-dogsquared + /modules/services/redshift-gammastep @rycee @petabyteboy @thiagokokada /tests/modules/redshift-gammastep @thiagokokada diff --git a/modules/misc/news.nix b/modules/misc/news.nix index 9f6bd72f1..fadecda83 100644 --- a/modules/misc/news.nix +++ b/modules/misc/news.nix @@ -624,6 +624,14 @@ in A new module is available: 'xsession.windowManager.spectrwm'. ''; } + + { + time = "2022-07-27T12:22:37+00:00"; + condition = hostPlatform.isLinux; + message = '' + A new module is available: 'services.recoll'. + ''; + } ]; }; } diff --git a/modules/modules.nix b/modules/modules.nix index 3be6a1867..d9e90f7f2 100644 --- a/modules/modules.nix +++ b/modules/modules.nix @@ -242,6 +242,7 @@ let ./services/poweralertd.nix ./services/pulseeffects.nix ./services/random-background.nix + ./services/recoll.nix ./services/redshift-gammastep/gammastep.nix ./services/redshift-gammastep/redshift.nix ./services/rsibreak.nix diff --git a/modules/services/recoll.nix b/modules/services/recoll.nix new file mode 100644 index 000000000..644b01184 --- /dev/null +++ b/modules/services/recoll.nix @@ -0,0 +1,192 @@ +{ config, options, lib, pkgs, ... }: + +with lib; + +# TODO: Fix the formatting of the resulting config. +let + cfg = config.services.recoll; + + # The key-value generator for Recoll config format. For future references, + # see the example configuration from the package (i.e., + # `$out/share/recoll/examples/recoll.conf`). + mkRecollConfKeyValue = generators.mkKeyValueDefault { + mkValueString = v: + if v == true then + "1" + else if v == false then + "0" + else if isList v then + concatStringsSep " " v + else + generators.mkValueStringDefault { } v; + } " = "; + + # A modified version of 'lib.generators.toINI' that also accepts top-level + # attributes as non-attrsets. + toRecollConf = { listsAsDuplicateKeys ? false }: + attr: + let + toKeyValue = generators.toKeyValue { + inherit listsAsDuplicateKeys; + mkKeyValue = mkRecollConfKeyValue; + }; + mkSectionName = name: strings.escape [ "[" "]" ] name; + convert = k: v: + if isAttrs v then + '' + [${mkSectionName k}] + '' + toKeyValue v + else + toKeyValue { "${k}" = v; }; + + # TODO: Improve this chunk of code, pls. + # There's a possibility of attributes with attrsets overriding other + # top-level attributes with non-attrsets so we're forcing the attrsets to + # come last. + _config = mapAttrsToList convert (filterAttrs (k: v: !isAttrs v) attr); + _config' = mapAttrsToList convert (filterAttrs (k: v: isAttrs v) attr); + config = _config ++ _config'; + in concatStringsSep "\n" config; + + # A specific type for Recoll config format. Taken from `pkgs.formats` + # implementation from nixpkgs. See the 'Nix-representable formats' from the + # NixOS manual for more information. + recollConfFormat = { }: { + type = with types; + let + valueType = nullOr (oneOf [ + bool + float + int + path + str + (attrsOf valueType) + (listOf valueType) + ]) // { + description = "Recoll config value"; + }; + in attrsOf valueType; + + generate = name: value: pkgs.writeText name (toRecollConf { } value); + }; + + # The actual object we're going to use for this module. This is for the sake + # of consistency (and dogfooding the settings format implementation). + settingsFormat = recollConfFormat { }; +in { + meta.maintainers = [ maintainers.foo-dogsquared ]; + + options.services.recoll = { + enable = mkEnableOption "Recoll file index service"; + + package = mkOption { + type = types.package; + default = pkgs.recoll; + defaultText = literalExpression "pkgs.recoll"; + description = '' + Package providing the recoll binary. + ''; + example = literalExpression "(pkgs.recoll.override { withGui = false; })"; + }; + + startAt = mkOption { + type = types.str; + default = "hourly"; + example = "00/2:00"; + description = '' + When or how often the periodic update should run. Must be the format + described from + + systemd.time + 7 + . + ''; + }; + + settings = mkOption { + type = settingsFormat.type; + default = { }; + description = '' + The configuration to be written at + ''${config.services.recoll.configDir}/recoll.conf. + + See + + recoll + 5 + for more details about the configuration. + ''; + example = literalExpression '' + { + nocjk = true; + loglevel = 5; + topdirs = [ "~/Downloads" "~/Documents" "~/projects" ]; + + "~/Downloads" = { + "skippedNames+" = [ "*.iso" ]; + }; + + "~/projects" = { + "skippedNames+" = [ "node_modules" "target" "result" ]; + }; + } + ''; + }; + + configDir = mkOption { + type = types.str; + default = "${config.home.homeDirectory}/.recoll"; + defaultText = literalExpression "\${config.home.homeDirectory}/.recoll"; + example = literalExpression "\${config.xdg.configHome}/recoll"; + description = '' + The directory to contain Recoll configuration files. This will be set + as RECOLL_CONFDIR. + ''; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + (lib.hm.assertions.assertPlatform "services.recoll" pkgs + lib.platforms.linux) + ]; + + home.packages = [ cfg.package ]; + + home.sessionVariables = { RECOLL_CONFDIR = cfg.configDir; }; + + home.file."${cfg.configDir}/recoll.conf".source = + settingsFormat.generate "recoll-conf-${config.home.username}" + cfg.settings; + + systemd.user.services.recollindex = { + Unit = { + Description = "Recoll index update"; + Documentation = [ + "man:recoll" + "man:recollindex" + "https://www.lesbonscomptes.com/recoll/usermanual/" + ]; + }; + + Service = { + ExecStart = "${cfg.package}/bin/recollindex"; + Environment = [ "RECOLL_CONFDIR=${escapeShellArg cfg.configDir}" ]; + }; + }; + + systemd.user.timers.recollindex = { + Unit = { + Description = "Recoll index update"; + PartOf = [ "default.target" ]; + }; + + Timer = { + Persistent = true; + OnCalendar = cfg.startAt; + }; + + Install.WantedBy = [ "timers.target" ]; + }; + }; +} diff --git a/tests/default.nix b/tests/default.nix index 017e31176..4ab7ef9e3 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -171,6 +171,7 @@ import nmt { ./modules/services/picom ./modules/services/playerctld ./modules/services/polybar + ./modules/services/recoll ./modules/services/redshift-gammastep ./modules/services/screen-locker ./modules/services/swayidle diff --git a/tests/modules/services/recoll/basic-configuration.conf b/tests/modules/services/recoll/basic-configuration.conf new file mode 100644 index 000000000..185768a8e --- /dev/null +++ b/tests/modules/services/recoll/basic-configuration.conf @@ -0,0 +1,16 @@ +nocjk = 0 + +skippedNames+ = node_modules + +topdirs = ~/Downloads ~/Documents ~/library + +underscoresasletter = 1 + +[~/library/projects] +skippedNames+ = .editorconfig .gitignore result flake.lock go.sum + +[~/library/projects/software] +skippedNames+ = target result + +[~/what-is-this-project] +skippedNames+ = whoa-there diff --git a/tests/modules/services/recoll/basic-configuration.nix b/tests/modules/services/recoll/basic-configuration.nix new file mode 100644 index 000000000..9775c170c --- /dev/null +++ b/tests/modules/services/recoll/basic-configuration.nix @@ -0,0 +1,35 @@ +{ config, ... }: + +{ + services.recoll = { + enable = true; + package = config.lib.test.mkStubPackage { }; + configDir = "${config.xdg.configHome}/recoll"; + settings = { + topdirs = [ "~/Downloads" "~/Documents" "~/library" ]; + "skippedNames+" = [ "node_modules" ]; + underscoresasletter = true; + nocjk = false; + + "~/library/projects" = { + "skippedNames+" = + [ ".editorconfig" ".gitignore" "result" "flake.lock" "go.sum" ]; + }; + + "~/library/projects/software" = { + "skippedNames+" = [ "target" "result" ]; + }; + + "~/what-is-this-project" = { "skippedNames+" = [ "whoa-there" ]; }; + }; + }; + + nmt.script = '' + assertFileExists home-files/.config/systemd/user/recollindex.service + assertFileExists home-files/.config/systemd/user/recollindex.timer + + assertFileExists home-files/.config/recoll/recoll.conf + assertFileContent home-files/.config/recoll/recoll.conf \ + ${./basic-configuration.conf} + ''; +} diff --git a/tests/modules/services/recoll/config-format-order.conf b/tests/modules/services/recoll/config-format-order.conf new file mode 100644 index 000000000..f1e251ff3 --- /dev/null +++ b/tests/modules/services/recoll/config-format-order.conf @@ -0,0 +1,25 @@ +b = 10 + +d = 0 + +e = This should be the second to the last non-attrset value in the config. + +g = This is coming from a list + +[a] +foo = bar + +[c] +a = This should appear as the second section. +aa = 1 +b = 53 + +[f] +a = This should be second to the last for the attribute names with an attrset. +b = 3193 +c = 0 +d = Hello there + +[foo] +bar = This should be the last attribute with an attrset. +baz = 42 diff --git a/tests/modules/services/recoll/config-format-order.nix b/tests/modules/services/recoll/config-format-order.nix new file mode 100644 index 000000000..846627d96 --- /dev/null +++ b/tests/modules/services/recoll/config-format-order.nix @@ -0,0 +1,45 @@ +# This is a test primarily concerned with the order of the configuration. The +# configuration is dynamically generated in alphabetical order of the top-level +# attribute names. Because of this, it is possible to override top-level +# attributes that are supposed to be configured in the top-level configuration. +{ config, ... }: + +{ + services.recoll = { + enable = true; + package = config.lib.test.mkStubPackage { }; + settings = { + a = { foo = "bar"; }; + b = 10; + c = { + a = "This should appear as the second section."; + b = 53; + aa = true; + }; + d = false; + e = + "This should be the second to the last non-attrset value in the config."; + f = { + a = + "This should be second to the last for the attribute names with an attrset."; + b = 3193; + c = false; + d = [ "Hello" "there" ]; + }; + foo = { + bar = "This should be the last attribute with an attrset."; + baz = 42; + }; + g = [ "This" "is" "coming" "from" "a" "list" ]; + }; + }; + + nmt.script = '' + assertFileExists home-files/.config/systemd/user/recollindex.service + assertFileExists home-files/.config/systemd/user/recollindex.timer + + assertFileExists home-files/.recoll/recoll.conf + assertFileContent home-files/.recoll/recoll.conf \ + ${./config-format-order.conf} + ''; +} diff --git a/tests/modules/services/recoll/default.nix b/tests/modules/services/recoll/default.nix new file mode 100644 index 000000000..3b0e560f1 --- /dev/null +++ b/tests/modules/services/recoll/default.nix @@ -0,0 +1,4 @@ +{ + recoll-basic-configuration = ./basic-configuration.nix; + recoll-config-format-order = ./config-format-order.nix; +}