diff --git a/modules/lib/generators.nix b/modules/lib/generators.nix index 98a03ba21077..41abaff142cf 100644 --- a/modules/lib/generators.nix +++ b/modules/lib/generators.nix @@ -99,4 +99,102 @@ in attrs: '' ${concatStringsSep "\n" (mapAttrsToList convertAttributeToKDL attrs)} ''; + + toSCFG = { }: + let + inherit (lib) concatStringsSep mapAttrsToList any; + inherit (builtins) typeOf replaceStrings elem; + + # ListOf String -> String + indentStrings = let + # Although the input of this function is a list of strings, + # the strings themselves *will* contain newlines, so you need + # to normalize the list by joining and resplitting them. + unlines = lib.splitString "\n"; + lines = lib.concatStringsSep "\n"; + indentAll = lines: concatStringsSep "\n" (map (x: " " + x) lines); + in stringsWithNewlines: indentAll (unlines (lines stringsWithNewlines)); + + # String -> Bool + specialChars = s: + any (char: elem char [ ''"'' "\\" "\r" "\n" "'" "{" "}" " " " " ]) + (lib.stringToCharacters s); + + # String -> String + sanitizeString = replaceStrings [ ''"'' "\\" "\r" "\n" " " ] [ + ''\"'' + "\\\\" + "\\r" + "\\n" + "\\t" + ]; + + # OneOf [Int Float String Bool] -> String + literalValueToString = element: + lib.throwIfNot (elem (typeOf element) [ "int" "float" "string" "bool" ]) + "Cannot convert value of type ${typeOf element} to SCFG literal." + (if element == false then + "false" + else if element == true then + "true" + else if typeOf element == "string" then + if element == "" || specialChars element then + ''"${sanitizeString element}"'' + else + element + else + toString element); + + # Attrset Conversion + # String -> AttrsOf Anything -> String + convertAttrsToSCFG = name: attrs: + let + optParamsString = lib.optionalString (attrs ? "_params") + (lib.pipe attrs._params [ + (map literalValueToString) + (lib.concatStringsSep " ") + (s: s + " ") + ]); + in '' + ${name} ${optParamsString}{ + ${indentStrings + (mapAttrsToList convertAttributeToSCFG (filterAttrsSCFG attrs))} + }''; + + # List Conversion + # String -> ListOf (OneOf [Int Float String Bool]) -> String + convertListOfFlatAttrsToSCFG = name: list: + if list == [ ] then + name + else + let flatElements = map literalValueToString list; + in "${name} ${concatStringsSep " " flatElements}"; + + # Combined Conversion + # String -> Anything -> String + convertAttributeToSCFG = name: value: + lib.throwIf (name == "") "Directive must not be empty" + (let vType = typeOf value; + in if elem vType [ "int" "float" "bool" "string" ] then + "${name} ${literalValueToString value}" + else if vType == "set" then + convertAttrsToSCFG name value + else if vType == "list" then + convertListOfFlatAttrsToSCFG name value + else + throw '' + Cannot convert type `(${typeOf value})` to SCFG: + ${name} = ${toString value} + ''); + + # AttrsOf Anything -> AttrsOf Anything + filterAttrsSCFG = + lib.filterAttrs (name: val: !isNull val && name != "_params"); + in attrs: + if attrs == { } then + ''""'' + else '' + ${concatStringsSep "\n" + (mapAttrsToList convertAttributeToSCFG (filterAttrsSCFG attrs))} + ''; } diff --git a/modules/programs/senpai.nix b/modules/programs/senpai.nix index 24256e764915..7c2f3d2d5c40 100644 --- a/modules/programs/senpai.nix +++ b/modules/programs/senpai.nix @@ -2,9 +2,7 @@ with lib; -let - cfg = config.programs.senpai; - cfgFmt = pkgs.formats.yaml { }; +let cfg = config.programs.senpai; in { options.programs.senpai = { enable = mkEnableOption "senpai"; @@ -16,17 +14,23 @@ in { }; config = mkOption { type = types.submodule { - freeformType = cfgFmt.type; + freeformType = types.attrsOf types.anything; options = { - addr = mkOption { + address = mkOption { type = types.str; description = '' - The address (host[:port]) of the IRC server. senpai uses TLS - connections by default unless you specify no-tls option. TLS - connections default to port 6697, plain-text use port 6667. + The address (host[:port]) of the IRC server. senpai uses TLS + connections by default unless you specify tls option to be false. + TLS connections default to port 6697, plain-text use port 6667. + + ircs://, irc://, and + irc+insecure:// URLs are supported, in which + case only the hostname and port parts will be used. If the scheme + is ircs/irc+insecure, tls will be overriden and set to true/false + accordingly. ''; }; - nick = mkOption { + nickname = mkOption { type = types.str; description = '' Your nickname, sent with a NICK IRC message. It mustn't contain @@ -41,17 +45,28 @@ in { reside world-readable in the Nix store. ''; }; - no-tls = mkOption { - type = types.bool; - default = false; - description = "Disables TLS encryption."; + password-cmd = mkOption { + type = types.nullOr (types.listOf types.str); + default = null; + example = [ "gopass" "show" "irc/guest" ]; + description = '' + Alternatively to providing your SASL authentication password + directly in plaintext, you can specify a command to be run to + fetch the password at runtime. This is useful if you store your + passwords in a separate (probably encrypted) file using `gpg` or + a command line password manager such as pass + or gopass. If a password-cmd is provided, the + value of password will be ignored and the first line of the output + of password-cmd will be used + for login. + ''; }; }; }; example = literalExpression '' { - addr = "libera.chat:6697"; - nick = "nicholas"; + address = "libera.chat:6697"; + nickname = "nicholas"; password = "verysecurepassword"; } ''; @@ -63,9 +78,27 @@ in { }; config = mkIf cfg.enable { + assertions = with cfg.config; [ + { + assertion = !isNull password-cmd -> isNull password; + message = "senpai: password-cmd overrides password!"; + } + { + assertion = !cfg.config ? addr; + message = "senpai: addr is deprecated, use address instead"; + } + { + assertion = !cfg.config ? nick; + message = "senpai: nick is deprecated, use nickname instead"; + } + { + assertion = !cfg.config ? no-tls; + message = "senpai: no-tls is deprecated, use tls instead"; + } + ]; home.packages = [ cfg.package ]; - xdg.configFile."senpai/senpai.yaml".source = - cfgFmt.generate "senpai.yaml" cfg.config; + xdg.configFile."senpai/senpai.scfg".text = + lib.hm.generators.toSCFG { } cfg.config; }; meta.maintainers = [ hm.maintainers.malvo ]; diff --git a/tests/default.nix b/tests/default.nix index db4e1d05f788..d3bb444307e4 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -135,6 +135,7 @@ import nmt { ./modules/programs/sagemath ./modules/programs/sbt ./modules/programs/scmpuff + ./modules/programs/senpai ./modules/programs/sioyek ./modules/programs/sm64ex ./modules/programs/ssh diff --git a/tests/lib/generators/default.nix b/tests/lib/generators/default.nix index 37bb2004787b..975c83ea3c90 100644 --- a/tests/lib/generators/default.nix +++ b/tests/lib/generators/default.nix @@ -1 +1,4 @@ -{ generators-tokdl = ./tokdl.nix; } +{ + generators-tokdl = ./tokdl.nix; + generators-toscfg = ./toscfg.nix; +} diff --git a/tests/lib/generators/tokdl.nix b/tests/lib/generators/tokdl.nix index 88c5752b8769..0055501c738d 100644 --- a/tests/lib/generators/tokdl.nix +++ b/tests/lib/generators/tokdl.nix @@ -1,7 +1,7 @@ { config, lib, ... }: { - home.file."result.txt".text = lib.hm.generators.toKDL { } { + home.file."tokdl/result.txt".text = lib.hm.generators.toKDL { } { a = 1; b = "string"; c = '' @@ -47,7 +47,7 @@ nmt.script = '' assertFileContent \ - home-files/result.txt \ + home-files/tokdl/result.txt \ ${./tokdl-result.txt} ''; } diff --git a/tests/lib/generators/toscfg-result.txt b/tests/lib/generators/toscfg-result.txt new file mode 100644 index 000000000000..7a538d5a10ce --- /dev/null +++ b/tests/lib/generators/toscfg-result.txt @@ -0,0 +1,10 @@ +dir { + blk1 p1 "\"p2\"" { + sub1 arg11 arg12 + sub2 arg21 arg22 + sub3 arg31 arg32 { + sub-sub1 + sub-sub2 arg321 arg322 + } + } +} diff --git a/tests/lib/generators/toscfg.nix b/tests/lib/generators/toscfg.nix new file mode 100644 index 000000000000..793ddb8472dc --- /dev/null +++ b/tests/lib/generators/toscfg.nix @@ -0,0 +1,24 @@ +{ config, lib, ... }: + +{ + home.file."toscfg/result.txt".text = lib.hm.generators.toSCFG { } { + dir = { + blk1 = { + _params = [ "p1" ''"p2"'' ]; + sub1 = [ "arg11" "arg12" ]; + sub2 = [ "arg21" "arg22" ]; + sub3 = { + _params = [ "arg31" "arg32" ]; + sub-sub1 = [ ]; + sub-sub2 = [ "arg321" "arg322" ]; + }; + }; + }; + }; + + nmt.script = '' + assertFileContent \ + home-files/toscfg/result.txt \ + ${./toscfg-result.txt} + ''; +} diff --git a/tests/modules/programs/senpai/default.nix b/tests/modules/programs/senpai/default.nix new file mode 100644 index 000000000000..d1a2ba467a77 --- /dev/null +++ b/tests/modules/programs/senpai/default.nix @@ -0,0 +1,4 @@ +{ + senpai-example-settings = ./example-settings.nix; + senpai-empty-settings = ./empty-settings.nix; +} diff --git a/tests/modules/programs/senpai/empty-settings-expected.conf b/tests/modules/programs/senpai/empty-settings-expected.conf new file mode 100644 index 000000000000..9e41645499c9 --- /dev/null +++ b/tests/modules/programs/senpai/empty-settings-expected.conf @@ -0,0 +1,2 @@ +address irc.libera.chat +nickname Guest123456 diff --git a/tests/modules/programs/senpai/empty-settings.nix b/tests/modules/programs/senpai/empty-settings.nix new file mode 100644 index 000000000000..eeed8a39c8b2 --- /dev/null +++ b/tests/modules/programs/senpai/empty-settings.nix @@ -0,0 +1,20 @@ +{ config, ... }: + +{ + config = { + programs.senpai = { + enable = true; + package = config.lib.test.mkStubPackage { }; + config = { + address = "irc.libera.chat"; + nickname = "Guest123456"; + }; + }; + + nmt.script = '' + assertFileContent \ + home-files/.config/senpai/senpai.scfg \ + ${./empty-settings-expected.conf} + ''; + }; +} diff --git a/tests/modules/programs/senpai/example-settings-expected.conf b/tests/modules/programs/senpai/example-settings-expected.conf new file mode 100644 index 000000000000..39d5b11f2c2e --- /dev/null +++ b/tests/modules/programs/senpai/example-settings-expected.conf @@ -0,0 +1,13 @@ +address irc.libera.chat +channel #rahxephon +colors { + prompt 2 +} +highlight guest senpai lenon +nickname Guest123456 +pane-widths { + nicknames 16 +} +password-cmd gopass show irc/guest +realname "Guest von Lenon" +username senpai diff --git a/tests/modules/programs/senpai/example-settings.nix b/tests/modules/programs/senpai/example-settings.nix new file mode 100644 index 000000000000..5b0a5aa267b6 --- /dev/null +++ b/tests/modules/programs/senpai/example-settings.nix @@ -0,0 +1,27 @@ +{ config, ... }: + +{ + config = { + programs.senpai = { + enable = true; + package = config.lib.test.mkStubPackage { }; + config = { + address = "irc.libera.chat"; + nickname = "Guest123456"; + password-cmd = [ "gopass" "show" "irc/guest" ]; + username = "senpai"; + realname = "Guest von Lenon"; + channel = [ "#rahxephon" ]; + highlight = [ "guest" "senpai" "lenon" ]; + pane-widths = { nicknames = 16; }; + colors = { prompt = 2; }; + }; + }; + + nmt.script = '' + assertFileContent \ + home-files/.config/senpai/senpai.scfg \ + ${./example-settings-expected.conf} + ''; + }; +}