From b316a65c997692ae4ff6eee387f20511928a2dcf Mon Sep 17 00:00:00 2001 From: Patrick Widmer Date: Sat, 11 Nov 2023 10:19:45 +0100 Subject: [PATCH] senpai: switch to scfg format --- modules/lib/generators.nix | 93 +++++++++++++++++++ modules/programs/senpai.nix | 68 ++++++++++---- tests/default.nix | 1 + tests/lib/generators/default.nix | 6 +- tests/lib/generators/tokdl.nix | 4 +- tests/lib/generators/toscfg-empty-result.txt | 0 tests/lib/generators/toscfg-empty.nix | 11 +++ .../generators/toscfg-err-dir-empty-name.nix | 12 +++ .../lib/generators/toscfg-example-result.txt | 10 ++ tests/lib/generators/toscfg-example.nix | 24 +++++ tests/modules/programs/senpai/default.nix | 4 + .../senpai/empty-settings-expected.conf | 2 + .../programs/senpai/empty-settings.nix | 20 ++++ .../senpai/example-settings-expected.conf | 13 +++ .../programs/senpai/example-settings.nix | 27 ++++++ 15 files changed, 275 insertions(+), 20 deletions(-) create mode 100644 tests/lib/generators/toscfg-empty-result.txt create mode 100644 tests/lib/generators/toscfg-empty.nix create mode 100644 tests/lib/generators/toscfg-err-dir-empty-name.nix create mode 100644 tests/lib/generators/toscfg-example-result.txt create mode 100644 tests/lib/generators/toscfg-example.nix create mode 100644 tests/modules/programs/senpai/default.nix create mode 100644 tests/modules/programs/senpai/empty-settings-expected.conf create mode 100644 tests/modules/programs/senpai/empty-settings.nix create mode 100644 tests/modules/programs/senpai/example-settings-expected.conf create mode 100644 tests/modules/programs/senpai/example-settings.nix diff --git a/modules/lib/generators.nix b/modules/lib/generators.nix index 98a03ba21..9cb0e1cf9 100644 --- a/modules/lib/generators.nix +++ b/modules/lib/generators.nix @@ -99,4 +99,97 @@ 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 (reserved ++ [ " " "'" "{" "}" ])) + (lib.stringToCharacters s); + + # String -> String + sanitizeString = + replaceStrings reserved [ ''\"'' "\\\\" "\\r" "\\n" "\\t" ]; + + reserved = [ ''"'' "\\" "\r" "\n" " " ]; + + # 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); + + # Bool -> ListOf (OneOf [Int Float String Bool]) -> String + toOptParamsString = cond: list: + lib.optionalString (cond) (lib.pipe list [ + (map literalValueToString) + (concatStringsSep " ") + (s: " " + s) + ]); + + # Attrset Conversion + # String -> AttrsOf Anything -> String + convertAttrsToSCFG = name: attrs: + let + optParamsString = toOptParamsString (attrs ? "_params") attrs._params; + in '' + ${name}${optParamsString} { + ${indentStrings (convertToAttrsSCFG' attrs)} + }''; + + # Attrset Conversion + # AttrsOf Anything -> ListOf String + convertToAttrsSCFG' = attrs: + mapAttrsToList convertAttributeToSCFG + (lib.filterAttrs (name: val: !isNull val && name != "_params") attrs); + + # List Conversion + # String -> ListOf (OneOf [Int Float String Bool]) -> String + convertListOfFlatAttrsToSCFG = name: list: + let optParamsString = toOptParamsString (list != [ ]) list; + in "${name}${optParamsString}"; + + # 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} + ''); + in attrs: + lib.optionalString (attrs != { }) '' + ${concatStringsSep "\n" (convertToAttrsSCFG' attrs)} + ''; } diff --git a/modules/programs/senpai.nix b/modules/programs/senpai.nix index 24256e764..1aaac393e 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,23 +14,30 @@ 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. + + UR`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 spaces or colons (:). ''; }; + password = mkOption { type = types.nullOr types.str; default = null; @@ -41,17 +46,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 +79,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 6ba912295..567b75d5a 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -136,6 +136,7 @@ in import nmtSrc { ./modules/programs/sapling ./modules/programs/sbt ./modules/programs/scmpuff + ./modules/programs/senpai ./modules/programs/sftpman ./modules/programs/sioyek ./modules/programs/sm64ex diff --git a/tests/lib/generators/default.nix b/tests/lib/generators/default.nix index 37bb20047..da52640d1 100644 --- a/tests/lib/generators/default.nix +++ b/tests/lib/generators/default.nix @@ -1 +1,5 @@ -{ generators-tokdl = ./tokdl.nix; } +{ + generators-tokdl = ./tokdl.nix; + generators-toscfg-empty = ./toscfg-empty.nix; + generators-toscfg-example = ./toscfg-example.nix; +} diff --git a/tests/lib/generators/tokdl.nix b/tests/lib/generators/tokdl.nix index 88c5752b8..6e4a4047b 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-empty-result.txt b/tests/lib/generators/toscfg-empty-result.txt new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lib/generators/toscfg-empty.nix b/tests/lib/generators/toscfg-empty.nix new file mode 100644 index 000000000..e6800481f --- /dev/null +++ b/tests/lib/generators/toscfg-empty.nix @@ -0,0 +1,11 @@ +{ config, lib, ... }: + +{ + home.file."toscfg-empty-result.txt".text = lib.hm.generators.toSCFG { } { }; + + nmt.script = '' + assertFileContent \ + home-files/toscfg-empty-result.txt \ + ${./toscfg-empty-result.txt} + ''; +} diff --git a/tests/lib/generators/toscfg-err-dir-empty-name.nix b/tests/lib/generators/toscfg-err-dir-empty-name.nix new file mode 100644 index 000000000..f9b8dcdec --- /dev/null +++ b/tests/lib/generators/toscfg-err-dir-empty-name.nix @@ -0,0 +1,12 @@ +{ config, lib, ... }: + +{ + home.file."toscfg-err-dir-empty-name-result.txt".text = + lib.hm.generators.toSCFG { } { "" = [ ]; }; + + nmt.script = '' + assertFileContent \ + home-files/toscfg-err-dir-empty-name-result.txt \ + ${./toscfg-err-dir-empty-name-result.txt} + ''; +} diff --git a/tests/lib/generators/toscfg-example-result.txt b/tests/lib/generators/toscfg-example-result.txt new file mode 100644 index 000000000..7a538d5a1 --- /dev/null +++ b/tests/lib/generators/toscfg-example-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-example.nix b/tests/lib/generators/toscfg-example.nix new file mode 100644 index 000000000..d38568dcd --- /dev/null +++ b/tests/lib/generators/toscfg-example.nix @@ -0,0 +1,24 @@ +{ config, lib, ... }: + +{ + home.file."toscfg-example-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-example-result.txt \ + ${./toscfg-example-result.txt} + ''; +} diff --git a/tests/modules/programs/senpai/default.nix b/tests/modules/programs/senpai/default.nix new file mode 100644 index 000000000..d1a2ba467 --- /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 000000000..9e4164549 --- /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 000000000..eeed8a39c --- /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 000000000..39d5b11f2 --- /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 000000000..5b0a5aa26 --- /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} + ''; + }; +}