summaryrefslogtreecommitdiff
path: root/nix/lib.nix
blob: 54d234401cc2cfdeee2c86f77c77d53e6924ff05 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
lib:
let
  inherit (lib)
    attrNames
    filterAttrs
    foldl
    generators
    partition
    ;

  inherit (lib.strings)
    concatMapStrings
    hasPrefix
    ;

  /**
    Convert a structured Nix attribute set into Hyprland's configuration format.

    This function takes a nested attribute set and converts it into Hyprland-compatible
    configuration syntax, supporting top, bottom, and regular command sections.

    Commands are flattened using the `flattenAttrs` function, and attributes are formatted as
    `key = value` pairs. Lists are expanded as duplicate keys to match Hyprland's expected format.

    Configuration:

    * `topCommandsPrefixes` - A list of prefixes to define **top** commands (default: `["$"]`).
    * `bottomCommandsPrefixes` - A list of prefixes to define **bottom** commands (default: `[]`).

    Attention:

    - The function ensures top commands appear **first** and bottom commands **last**.
    - The generated configuration is a **single string**, suitable for writing to a config file.
    - Lists are converted into multiple entries, ensuring compatibility with Hyprland.

    # Inputs

    Structured function argument:

    : topCommandsPrefixes (optional, default: `["$"]`)
      : A list of prefixes that define **top** commands. Any key starting with one of these
        prefixes will be placed at the beginning of the configuration.
    : bottomCommandsPrefixes (optional, default: `[]`)
      : A list of prefixes that define **bottom** commands. Any key starting with one of these
        prefixes will be placed at the end of the configuration.

    Value:

    : The attribute set to be converted to Hyprland configuration format.

    # Type

    ```
    toHyprlang :: AttrSet -> AttrSet -> String
    ```

    # Examples
    :::{.example}

    ```nix
    let
      config = {
        "$mod" = "SUPER";
        monitor = {
          "HDMI-A-1" = "1920x1080@60,0x0,1";
        };
        exec = [
          "waybar"
          "dunst"
        ];
      };
    in lib.toHyprlang {} config
    ```

    **Output:**
    ```nix
    "$mod = SUPER"
    "monitor:HDMI-A-1 = 1920x1080@60,0x0,1"
    "exec = waybar"
    "exec = dunst"
    ```

    :::
  */
  toHyprlang =
    {
      topCommandsPrefixes ? [
        "$"
        "bezier"
      ],
      bottomCommandsPrefixes ? [ ],
    }:
    attrs:
    let
      toHyprlang' =
        attrs:
        let
          # Specially configured `toKeyValue` generator with support for duplicate keys
          # and a legible key-value separator.
          mkCommands = generators.toKeyValue {
            mkKeyValue = generators.mkKeyValueDefault { } " = ";
            listsAsDuplicateKeys = true;
            indent = ""; # No indent, since we don't have nesting
          };

          # Flatten the attrset, combining keys in a "path" like `"a:b:c" = "x"`.
          # Uses `flattenAttrs` with a colon separator.
          commands = flattenAttrs (p: k: "${p}:${k}") attrs;

          # General filtering function to check if a key starts with any prefix in a given list.
          filterCommands = list: n: foldl (acc: prefix: acc || hasPrefix prefix n) false list;

          # Partition keys into top commands and the rest
          result = partition (filterCommands topCommandsPrefixes) (attrNames commands);
          topCommands = filterAttrs (n: _: builtins.elem n result.right) commands;
          remainingCommands = removeAttrs commands result.right;

          # Partition remaining commands into bottom commands and regular commands
          result2 = partition (filterCommands bottomCommandsPrefixes) result.wrong;
          bottomCommands = filterAttrs (n: _: builtins.elem n result2.right) remainingCommands;
          regularCommands = removeAttrs remainingCommands result2.right;
        in
        # Concatenate strings from mapping `mkCommands` over top, regular, and bottom commands.
        concatMapStrings mkCommands [
          topCommands
          regularCommands
          bottomCommands
        ];
    in
    toHyprlang' attrs;

  /**
    Flatten a nested attribute set into a flat attribute set, using a custom key separator function.

    This function recursively traverses a nested attribute set and produces a flat attribute set
    where keys are joined using a user-defined function (`pred`). It allows transforming deeply
    nested structures into a single-level attribute set while preserving key-value relationships.

    Configuration:

    * `pred` - A function `(string -> string -> string)` defining how keys should be concatenated.

    # Inputs

    Structured function argument:

    : pred (required)
      : A function that determines how parent and child keys should be combined into a single key.
        It takes a `prefix` (parent key) and `key` (current key) and returns the joined key.

    Value:

    : The nested attribute set to be flattened.

    # Type

    ```
    flattenAttrs :: (String -> String -> String) -> AttrSet -> AttrSet
    ```

    # Examples
    :::{.example}

    ```nix
    let
      nested = {
        a = "3";
        b = { c = "4"; d = "5"; };
      };

      separator = (prefix: key: "${prefix}.${key}");  # Use dot notation
    in lib.flattenAttrs separator nested
    ```

    **Output:**
    ```nix
    {
      "a" = "3";
      "b.c" = "4";
      "b.d" = "5";
    }
    ```

    :::
  */
  flattenAttrs =
    pred: attrs:
    let
      flattenAttrs' =
        prefix: attrs:
        builtins.foldl' (
          acc: key:
          let
            value = attrs.${key};
            newKey = if prefix == "" then key else pred prefix key;
          in
          acc // (if builtins.isAttrs value then flattenAttrs' newKey value else { "${newKey}" = value; })
        ) { } (builtins.attrNames attrs);
    in
    flattenAttrs' "" attrs;
in
{
  inherit flattenAttrs toHyprlang;
}