treewide: add and partially apply mkTarget function (#1130)
Add and partially apply the mkTarget function to all appropriate
modules, providing a consistent target interface to minimize boilerplate
and automatically safeguard declarations related to disabled options.
The mkTarget function was first discussed in [1] ("extensive mkTarget
function").
[1]: https://github.com/danth/stylix/discussions/1009
Link: https://github.com/danth/stylix/pull/1130
Co-authored-by: Daniel Thwaites <danth@danth.me>
Reviewed-by: awwpotato <awwpotato@voidq.com>
Co-authored-by: Matt Sturgeon <matt@sturgeon.me.uk>
Co-authored-by: NAHO <90870942+trueNAHO@users.noreply.github.com>
Reviewed-by: NAHO <90870942+trueNAHO@users.noreply.github.com>
Reviewed-by: Matt Sturgeon <matt@sturgeon.me.uk>
This commit is contained in:
commit
d3fadda72a
5 changed files with 370 additions and 84 deletions
|
|
@ -41,15 +41,34 @@ folder, using any name which is not on the list above.
|
|||
|
||||
## Module template
|
||||
|
||||
All modules should have an enable option created using `mkEnableTarget`. This is
|
||||
similar to
|
||||
[`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
|
||||
from the standard library, however it integrates with
|
||||
[`stylix.enable`](./options/nixos.md#stylixenable) and
|
||||
[`stylix.autoEnable`](./options/nixos.md#stylixautoenable) and generates more
|
||||
specific documentation.
|
||||
Modules should be created using the `mkTarget` function whenever possible (see
|
||||
the [`/stylix/mk-target.nix`](
|
||||
https://github.com/danth/stylix/blob/-/stylix/mk-target.nix) in-source
|
||||
documentation for more details):
|
||||
|
||||
A general format for modules is shown below.
|
||||
```nix
|
||||
{ config, lib, mkTarget ... }:
|
||||
mkTarget {
|
||||
name = "«name»";
|
||||
humanName = "«human readable name»";
|
||||
|
||||
configElements =
|
||||
{ colors }:
|
||||
{
|
||||
programs.«name».theme.background = colors.base00;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> The `mkTarget` argument is only available to modules imported by Stylix's
|
||||
> [autoload system](https://github.com/danth/stylix/blob/-/stylix/autoload.nix),
|
||||
> e.g., `modules/«target»/«platform».nix` modules.
|
||||
>
|
||||
> I.e., it is not available to normal modules imported via the `imports` list.
|
||||
|
||||
When the `mkTarget` function cannot be used, modules must manually replicate its
|
||||
safeguarding behaviour:
|
||||
|
||||
```nix
|
||||
{ config, lib, ... }:
|
||||
|
|
@ -66,8 +85,8 @@ A general format for modules is shown below.
|
|||
```
|
||||
|
||||
> [!CAUTION]
|
||||
> You **must** check _both_ `config.stylix.enable` _and_ your target's own
|
||||
> `enable` option before defining any config.
|
||||
> If not using `mkTarget`, you **must** check _both_ `config.stylix.enable`
|
||||
> _and_ your target's own`enable` option before defining any config.
|
||||
>
|
||||
> In the above example this is done using
|
||||
> `config = lib.mkIf (config.stylix.enable && config.stylix.targets.«name».enable)`.
|
||||
|
|
|
|||
|
|
@ -1,26 +1,16 @@
|
|||
# Documentation is available at:
|
||||
# - https://alacritty.org/config-alacritty.html
|
||||
# - `man 5 alacritty`
|
||||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
colors = config.lib.stylix.colors.withHashtag;
|
||||
in
|
||||
{
|
||||
options.stylix.targets.alacritty.enable =
|
||||
config.lib.stylix.mkEnableTarget "Alacritty" true;
|
||||
|
||||
config =
|
||||
lib.mkIf (config.stylix.enable && config.stylix.targets.alacritty.enable)
|
||||
{ config, mkTarget, ... }:
|
||||
mkTarget {
|
||||
name = "alacritty";
|
||||
humanName = "Alacritty";
|
||||
configElements = [
|
||||
(
|
||||
{ colors }:
|
||||
with colors.withHashtag;
|
||||
{
|
||||
programs.alacritty.settings = {
|
||||
font = with config.stylix.fonts; {
|
||||
normal = {
|
||||
family = monospace.name;
|
||||
style = "Regular";
|
||||
};
|
||||
size = sizes.terminal;
|
||||
};
|
||||
window.opacity = config.stylix.opacity.terminal;
|
||||
colors = with colors; {
|
||||
primary = {
|
||||
|
|
@ -60,5 +50,25 @@ in
|
|||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
)
|
||||
(
|
||||
{ fonts }:
|
||||
{
|
||||
programs.alacritty.settings.font = {
|
||||
normal = {
|
||||
family = fonts.monospace.name;
|
||||
style = "Regular";
|
||||
};
|
||||
size = fonts.sizes.terminal;
|
||||
};
|
||||
}
|
||||
)
|
||||
(
|
||||
{ opacity }:
|
||||
{
|
||||
programs.alacritty.settings.window.opacity = config.stylix.opacity.terminal;
|
||||
}
|
||||
)
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,59 +1,52 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
{
|
||||
options.stylix.targets.hyprland = {
|
||||
enable = config.lib.stylix.mkEnableTarget "Hyprland" true;
|
||||
hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" (
|
||||
config.stylix.image != null
|
||||
);
|
||||
};
|
||||
|
||||
config =
|
||||
let
|
||||
cfg = config.stylix.targets.hyprland;
|
||||
in
|
||||
lib.mkIf
|
||||
(
|
||||
config.stylix.enable
|
||||
&& cfg.enable
|
||||
&& config.wayland.windowManager.hyprland.enable
|
||||
)
|
||||
(
|
||||
lib.mkMerge [
|
||||
config,
|
||||
lib,
|
||||
mkTarget,
|
||||
...
|
||||
}:
|
||||
mkTarget {
|
||||
name = "hyprland";
|
||||
humanName = "Hyprland";
|
||||
extraOptions.hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" (
|
||||
config.stylix.image != null
|
||||
);
|
||||
configElements = [
|
||||
(
|
||||
{ colors }:
|
||||
{
|
||||
wayland.windowManager.hyprland.settings =
|
||||
let
|
||||
rgb = color: "rgb(${color})";
|
||||
rgba = color: alpha: "rgba(${color}${alpha})";
|
||||
in
|
||||
{
|
||||
wayland.windowManager.hyprland.settings =
|
||||
let
|
||||
inherit (config.lib.stylix) colors;
|
||||
decoration.shadow.color = rgba colors.base00 "99";
|
||||
general = {
|
||||
"col.active_border" = rgb colors.base0D;
|
||||
"col.inactive_border" = rgb colors.base03;
|
||||
};
|
||||
group = {
|
||||
"col.border_inactive" = rgb colors.base03;
|
||||
"col.border_active" = rgb colors.base0D;
|
||||
"col.border_locked_active" = rgb colors.base0C;
|
||||
|
||||
rgb = color: "rgb(${color})";
|
||||
rgba = color: alpha: "rgba(${color}${alpha})";
|
||||
in
|
||||
{
|
||||
decoration.shadow.color = rgba colors.base00 "99";
|
||||
general = {
|
||||
"col.active_border" = rgb colors.base0D;
|
||||
"col.inactive_border" = rgb colors.base03;
|
||||
};
|
||||
group = {
|
||||
"col.border_inactive" = rgb colors.base03;
|
||||
"col.border_active" = rgb colors.base0D;
|
||||
"col.border_locked_active" = rgb colors.base0C;
|
||||
|
||||
groupbar = {
|
||||
text_color = rgb colors.base05;
|
||||
"col.active" = rgb colors.base0D;
|
||||
"col.inactive" = rgb colors.base03;
|
||||
};
|
||||
};
|
||||
misc.background_color = rgb colors.base00;
|
||||
groupbar = {
|
||||
text_color = rgb colors.base05;
|
||||
"col.active" = rgb colors.base0D;
|
||||
"col.inactive" = rgb colors.base03;
|
||||
};
|
||||
}
|
||||
|
||||
(lib.mkIf cfg.hyprpaper.enable {
|
||||
services.hyprpaper.enable = true;
|
||||
stylix.targets.hyprpaper.enable = true;
|
||||
wayland.windowManager.hyprland.settings.misc.disable_hyprland_logo = true;
|
||||
})
|
||||
]
|
||||
);
|
||||
};
|
||||
misc.background_color = rgb colors.base00;
|
||||
};
|
||||
}
|
||||
)
|
||||
(
|
||||
{ cfg }:
|
||||
(lib.mkIf cfg.hyprpaper.enable {
|
||||
services.hyprpaper.enable = true;
|
||||
stylix.targets.hyprpaper.enable = true;
|
||||
wayland.windowManager.hyprland.settings.misc.disable_hyprland_logo = true;
|
||||
})
|
||||
)
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,44 @@ builtins.concatLists (
|
|||
path: kind:
|
||||
let
|
||||
file = "${inputs.self}/modules/${path}/${for}.nix";
|
||||
module = import file;
|
||||
|
||||
# Detect whether the file's value has an argument named `mkTarget`
|
||||
useMkTarget =
|
||||
builtins.isFunction module && (builtins.functionArgs module) ? mkTarget;
|
||||
|
||||
# NOTE: `mkTarget` cannot be distributed normally through the module system
|
||||
# due to issues of infinite recursion.
|
||||
mkTarget = import ./mk-target.nix;
|
||||
in
|
||||
lib.optional (kind == "directory" && builtins.pathExists file) file
|
||||
lib.optional (kind == "directory" && builtins.pathExists file) (
|
||||
if useMkTarget then
|
||||
{ config, ... }@args:
|
||||
let
|
||||
# Based on `lib.modules.applyModuleArgs`
|
||||
#
|
||||
# Apply `mkTarget` as a special arg without actually using `specialArgs`,
|
||||
# which cannot be defined from within a configuration.
|
||||
context =
|
||||
name: ''while evaluating the module argument `${name}' in "${toString file}":'';
|
||||
extraArgs = lib.pipe module [
|
||||
builtins.functionArgs
|
||||
(lib.flip builtins.removeAttrs [ "mkTarget" ])
|
||||
(builtins.mapAttrs (
|
||||
name: _:
|
||||
builtins.addErrorContext (context name) (
|
||||
args.${name} or config._module.args.${name}
|
||||
)
|
||||
))
|
||||
];
|
||||
in
|
||||
{
|
||||
key = file;
|
||||
_file = file;
|
||||
imports = [ (module (args // extraArgs // { inherit mkTarget; })) ];
|
||||
}
|
||||
else
|
||||
file
|
||||
)
|
||||
) (builtins.readDir "${inputs.self}/modules")
|
||||
)
|
||||
|
|
|
|||
227
stylix/mk-target.nix
Normal file
227
stylix/mk-target.nix
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
/**
|
||||
Provides a consistent target interface, minimizing boilerplate and
|
||||
automatically safeguarding declarations related to disabled options.
|
||||
|
||||
# Type
|
||||
|
||||
```
|
||||
mkTarget :: AttrSet -> ModuleBody
|
||||
```
|
||||
|
||||
Where `ModuleBody` is a module that doesn't take any arguments. This allows
|
||||
the caller to use module arguments.
|
||||
|
||||
# Examples
|
||||
|
||||
The `modules/«MODULE»/«PLATFORM».nix` modules should use this function as
|
||||
follows:
|
||||
|
||||
```nix
|
||||
{ mkTarget, lib... }:
|
||||
mkTarget {
|
||||
name = "«name»";
|
||||
humanName = "«human readable name»";
|
||||
|
||||
generalConfig =
|
||||
lib.mkIf complexCondition {
|
||||
home.packages = [ pkgs.hello ];
|
||||
};
|
||||
|
||||
configElements = [
|
||||
{ programs.«name».theme.name = "stylix"; }
|
||||
|
||||
(
|
||||
{ colors }:
|
||||
{
|
||||
programs.«name».theme.background = colors.base00;
|
||||
}
|
||||
)
|
||||
|
||||
(
|
||||
{ fonts }:
|
||||
{
|
||||
programs.«name».font.name = fonts.monospace.name;
|
||||
}
|
||||
)
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
# Inputs
|
||||
|
||||
`config` (Attribute set)
|
||||
|
||||
: `name` (String)
|
||||
: The target name used to generate options in the `stylix.targets.${name}`
|
||||
namespace.
|
||||
|
||||
`humanName` (String)
|
||||
: The descriptive target name passed to the lib.mkEnableOption function
|
||||
when generating the `stylix.targets.${name}.enable` option.
|
||||
|
||||
`autoEnable` (Boolean)
|
||||
: Whether the target should be automatically enabled by default according
|
||||
to the `stylix.autoEnable` option.
|
||||
|
||||
This should be disabled if manual setup is required or if auto-enabling
|
||||
causes issues.
|
||||
|
||||
`extraOptions` (Attribute set)
|
||||
: Additional options to be added in the `stylix.targets.${name}` namespace
|
||||
along the `stylix.targets.${name}.enable` option.
|
||||
|
||||
For example, an extension guard used in the configuration can be declared
|
||||
as follows:
|
||||
```nix
|
||||
{ extension.enable = lib.mkEnableOption "the bloated dependency"; }
|
||||
```
|
||||
|
||||
`configElements` (List or attribute set or function)
|
||||
: Configuration functions that are automatically safeguarded when any of
|
||||
their arguments is disabled. The provided `cfg` argument conveniently
|
||||
aliases to `config.stylix.targets.${name}`.
|
||||
|
||||
For example, the following configuration is not merged if the stylix
|
||||
colors option is null:
|
||||
|
||||
```nix
|
||||
(
|
||||
{ colors }:
|
||||
{
|
||||
programs.«name».theme.background = colors.base00;
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
The `cfg` alias can be accessed as follows:
|
||||
|
||||
```nix
|
||||
(
|
||||
{ cfg }:
|
||||
{
|
||||
programs.«name».extension.enable = cfg.extension.enable;
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
`generalConfig` (Attribute set or function)
|
||||
: This argument mirrors the `configElements` argument but intentionally
|
||||
lacks automatic safeguarding and should only be used for complex
|
||||
configurations where `configElements` is unsuitable.
|
||||
|
||||
# Environment
|
||||
|
||||
The function is provided alongside module arguments in any modules imported
|
||||
through `/stylix/autoload.nix`.
|
||||
*/
|
||||
|
||||
# TODO: Ideally, in the future, this function returns an actual module by better
|
||||
# integrating with the /stylix/autoload.nix logic, allowing the following target
|
||||
# simplification and preventing access to unguarded module arguments by
|
||||
# requiring /modules/<MODULE>/<PLATFORM>.nix files to be attribute sets instead
|
||||
# of modules:
|
||||
#
|
||||
# {
|
||||
# name = "example";
|
||||
# humanName = "Example Target";
|
||||
#
|
||||
# generalConfig =
|
||||
# { lib, pkgs }:
|
||||
# lib.mkIf complexCondition {
|
||||
# home.packages = [ pkgs.hello ];
|
||||
# };
|
||||
#
|
||||
# configElements = [
|
||||
# { programs.example.theme.name = "stylix"; }
|
||||
#
|
||||
# (
|
||||
# { colors }:
|
||||
# {
|
||||
# programs.example.theme.background = colors.base00;
|
||||
# }
|
||||
# )
|
||||
#
|
||||
# (
|
||||
# { fonts }:
|
||||
# {
|
||||
# programs.example.font.name = fonts.monospace.name;
|
||||
# }
|
||||
# )
|
||||
# ];
|
||||
# }
|
||||
{
|
||||
name,
|
||||
humanName,
|
||||
autoEnable ? true,
|
||||
extraOptions ? { },
|
||||
configElements ? [ ],
|
||||
generalConfig ? null,
|
||||
}:
|
||||
let
|
||||
module =
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
cfg = config.stylix.targets.${name};
|
||||
|
||||
# Get the list of function de-structured argument names.
|
||||
functionArgNames =
|
||||
fn:
|
||||
lib.pipe fn [
|
||||
lib.functionArgs
|
||||
builtins.attrNames
|
||||
];
|
||||
|
||||
getStylixAttrs =
|
||||
fn:
|
||||
lib.genAttrs (functionArgNames fn) (
|
||||
arg:
|
||||
if arg == "cfg" then
|
||||
cfg
|
||||
else if arg == "colors" then
|
||||
config.lib.stylix.colors
|
||||
else
|
||||
config.stylix.${arg}
|
||||
or (throw "stylix: mkTarget expected one of `cfg`, `colors`, ${
|
||||
lib.concatMapStringsSep ", " (name: "`${name}`") (
|
||||
builtins.attrNames config.stylix
|
||||
)
|
||||
}, but got: ${arg}")
|
||||
);
|
||||
|
||||
# Call the configuration function with its required Stylix arguments.
|
||||
mkConfig = fn: fn (getStylixAttrs fn);
|
||||
|
||||
# Safeguard configuration functions when any of their arguments is
|
||||
# disabled, while non-function configurations are unguarded.
|
||||
mkConditionalConfig =
|
||||
c:
|
||||
if builtins.isFunction c then
|
||||
let
|
||||
allAttrsNonNull = lib.pipe c [
|
||||
getStylixAttrs
|
||||
builtins.attrValues
|
||||
(builtins.all (attr: attr != null))
|
||||
];
|
||||
in
|
||||
lib.mkIf allAttrsNonNull (mkConfig c)
|
||||
else
|
||||
c;
|
||||
in
|
||||
{
|
||||
options.stylix.targets.${name}.enable =
|
||||
config.lib.stylix.mkEnableTarget humanName autoEnable;
|
||||
|
||||
config = lib.mkIf (config.stylix.enable && cfg.enable) (
|
||||
lib.mkMerge (
|
||||
lib.optional (generalConfig != null) (mkConfig generalConfig)
|
||||
++ map mkConditionalConfig (lib.toList configElements)
|
||||
)
|
||||
);
|
||||
};
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
{ options.stylix.targets.${name} = extraOptions; }
|
||||
module
|
||||
];
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue