diff --git a/modules/lib/generators.nix b/modules/lib/generators.nix
index 98a03ba21077..018f89fd70bd 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" "\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);
+
+ # 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 -> 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 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..da52640d1298 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 88c5752b8769..6e4a4047b73f 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 000000000000..e69de29bb2d1
diff --git a/tests/lib/generators/toscfg-empty.nix b/tests/lib/generators/toscfg-empty.nix
new file mode 100644
index 000000000000..e6800481f1ac
--- /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 000000000000..f9b8dcdec933
--- /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 000000000000..7a538d5a10ce
--- /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 000000000000..d38568dcd72e
--- /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 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}
+ '';
+ };
+}