mirror of
https://github.com/hyprwm/Hyprland
synced 2025-04-10 23:52:22 +02:00
nix/module: toHyprconf -> toHyprlang (#9221)
* nix/module: toHyprconf -> toHyprlang Updated generator that will end up living in Nixpkgs' `lib/generators`. * nix/module: use xdph package directly The downstream module already applies hyprland's finalPackage to the portalPackage. * new lib * lib: add flattenAttrs, remove category processing Flattening attributes means we no longer need to process categories separately. For all intents and purposes, they do not exist. Simplify the codebase once again, while introducing an easy to grasp recursive function. Add a bit of documentation for toHyprlang, though I doubt it's clear enough even now. Still needs proper NixDoc. * lib: add proper NixDoc * nix/lib: inherit from lib
This commit is contained in:
parent
3a21dd84b3
commit
905ca39bc9
2 changed files with 238 additions and 97 deletions
201
nix/lib.nix
Normal file
201
nix/lib.nix
Normal file
|
@ -0,0 +1,201 @@
|
|||
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 ? ["$"],
|
||||
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;
|
||||
}
|
134
nix/module.nix
134
nix/module.nix
|
@ -5,72 +5,8 @@ inputs: {
|
|||
...
|
||||
}: let
|
||||
inherit (pkgs.stdenv.hostPlatform) system;
|
||||
selflib = import ./lib.nix lib;
|
||||
cfg = config.programs.hyprland;
|
||||
|
||||
# basically 1:1 taken from https://github.com/nix-community/home-manager/blob/master/modules/services/window-managers/hyprland.nix
|
||||
toHyprconf = {
|
||||
attrs,
|
||||
indentLevel ? 0,
|
||||
importantPrefixes ? ["$"],
|
||||
}: let
|
||||
inherit
|
||||
(lib)
|
||||
all
|
||||
concatMapStringsSep
|
||||
concatStrings
|
||||
concatStringsSep
|
||||
filterAttrs
|
||||
foldl
|
||||
generators
|
||||
hasPrefix
|
||||
isAttrs
|
||||
isList
|
||||
mapAttrsToList
|
||||
replicate
|
||||
;
|
||||
|
||||
initialIndent = concatStrings (replicate indentLevel " ");
|
||||
|
||||
toHyprconf' = indent: attrs: let
|
||||
sections =
|
||||
filterAttrs (n: v: isAttrs v || (isList v && all isAttrs v)) attrs;
|
||||
|
||||
mkSection = n: attrs:
|
||||
if lib.isList attrs
|
||||
then (concatMapStringsSep "\n" (a: mkSection n a) attrs)
|
||||
else ''
|
||||
${indent}${n} {
|
||||
${toHyprconf' " ${indent}" attrs}${indent}}
|
||||
'';
|
||||
|
||||
mkFields = generators.toKeyValue {
|
||||
listsAsDuplicateKeys = true;
|
||||
inherit indent;
|
||||
};
|
||||
|
||||
allFields =
|
||||
filterAttrs (n: v: !(isAttrs v || (isList v && all isAttrs v)))
|
||||
attrs;
|
||||
|
||||
isImportantField = n: _:
|
||||
foldl (acc: prev:
|
||||
if hasPrefix prev n
|
||||
then true
|
||||
else acc)
|
||||
false
|
||||
importantPrefixes;
|
||||
|
||||
importantFields = filterAttrs isImportantField allFields;
|
||||
|
||||
fields =
|
||||
builtins.removeAttrs allFields
|
||||
(mapAttrsToList (n: _: n) importantFields);
|
||||
in
|
||||
mkFields importantFields
|
||||
+ concatStringsSep "\n" (mapAttrsToList mkSection sections)
|
||||
+ mkFields fields;
|
||||
in
|
||||
toHyprconf' initialIndent attrs;
|
||||
in {
|
||||
options = {
|
||||
programs.hyprland = {
|
||||
|
@ -106,6 +42,9 @@ in {
|
|||
should be written as lists. Variables' and colors' names should be
|
||||
quoted. See <https://wiki.hyprland.org> for more examples.
|
||||
|
||||
Special categories (e.g `devices`) should be written as
|
||||
`"devices[device-name]"`.
|
||||
|
||||
::: {.note}
|
||||
Use the [](#programs.hyprland.plugins) option to
|
||||
declare plugins.
|
||||
|
@ -151,20 +90,21 @@ in {
|
|||
'';
|
||||
};
|
||||
|
||||
sourceFirst =
|
||||
lib.mkEnableOption ''
|
||||
putting source entries at the top of the configuration
|
||||
''
|
||||
// {
|
||||
default = true;
|
||||
};
|
||||
|
||||
importantPrefixes = lib.mkOption {
|
||||
topPrefixes = lib.mkOption {
|
||||
type = with lib.types; listOf str;
|
||||
default = ["$" "bezier" "name"] ++ lib.optionals cfg.sourceFirst ["source"];
|
||||
example = ["$" "bezier"];
|
||||
default = ["$" "bezier"];
|
||||
example = ["$" "bezier" "source"];
|
||||
description = ''
|
||||
List of prefix of attributes to source at the top of the config.
|
||||
List of prefix of attributes to put at the top of the config.
|
||||
'';
|
||||
};
|
||||
|
||||
bottomPrefixes = lib.mkOption {
|
||||
type = with lib.types; listOf str;
|
||||
default = [];
|
||||
example = ["source"];
|
||||
description = ''
|
||||
List of prefix of attributes to put at the bottom of the config.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
@ -173,38 +113,38 @@ in {
|
|||
{
|
||||
programs.hyprland = {
|
||||
package = lib.mkDefault inputs.self.packages.${system}.hyprland;
|
||||
portalPackage = lib.mkDefault (inputs.self.packages.${system}.xdg-desktop-portal-hyprland.override {
|
||||
hyprland = cfg.finalPackage;
|
||||
});
|
||||
portalPackage = lib.mkDefault inputs.self.packages.${system}.xdg-desktop-portal-hyprland;
|
||||
};
|
||||
}
|
||||
(lib.mkIf cfg.enable {
|
||||
environment.etc."xdg/hypr/hyprland.conf" = let
|
||||
shouldGenerate = cfg.extraConfig != "" || cfg.settings != {} || cfg.plugins != [];
|
||||
|
||||
pluginsToHyprconf = plugins:
|
||||
toHyprconf {
|
||||
attrs = {
|
||||
plugin = let
|
||||
mkEntry = entry:
|
||||
if lib.types.package.check entry
|
||||
then "${entry}/lib/lib${entry.pname}.so"
|
||||
else entry;
|
||||
in
|
||||
map mkEntry cfg.plugins;
|
||||
};
|
||||
inherit (cfg) importantPrefixes;
|
||||
pluginsToHyprlang = plugins:
|
||||
selflib.toHyprlang {
|
||||
topCommandsPrefixes = cfg.topPrefixes;
|
||||
bottomCommandsPrefixes = cfg.bottomPrefixes;
|
||||
}
|
||||
{
|
||||
plugin = let
|
||||
mkEntry = entry:
|
||||
if lib.types.package.check entry
|
||||
then "${entry}/lib/lib${entry.pname}.so"
|
||||
else entry;
|
||||
in
|
||||
map mkEntry cfg.plugins;
|
||||
};
|
||||
in
|
||||
lib.mkIf shouldGenerate {
|
||||
text =
|
||||
lib.optionalString (cfg.plugins != [])
|
||||
(pluginsToHyprconf cfg.plugins)
|
||||
(pluginsToHyprlang cfg.plugins)
|
||||
+ lib.optionalString (cfg.settings != {})
|
||||
(toHyprconf {
|
||||
attrs = cfg.settings;
|
||||
inherit (cfg) importantPrefixes;
|
||||
})
|
||||
(selflib.toHyprlang {
|
||||
topCommandsPrefixes = cfg.topPrefixes;
|
||||
bottomCommandsPrefixes = cfg.bottomPrefixes;
|
||||
}
|
||||
cfg.settings)
|
||||
+ lib.optionalString (cfg.extraConfig != "") cfg.extraConfig;
|
||||
};
|
||||
})
|
||||
|
|
Loading…
Add table
Reference in a new issue