Compare commits

..

1 Commits

Author SHA1 Message Date
Robert Helgesson
02c8ce9f92 WIP home-manager: use putter for file management 2025-12-16 18:49:58 +01:00
96 changed files with 883 additions and 1674 deletions

View File

@@ -1097,6 +1097,19 @@
name = "Hoang Nguyen";
source = "home-manager";
};
foo-dogsquared = {
email = "foodogsquared@foodogsquared.one";
github = "foo-dogsquared";
githubId = 34962634;
keys = [
{
fingerprint = "DDD7 D0BD 602E 564B AA04 FC35 1431 0D91 4115 2B92";
}
];
matrix = "@foodogsquared:matrix.org";
name = "Gabriel Arazas";
source = "nixpkgs";
};
fpob = {
email = "fpob@proton.me";
github = "fpob";
@@ -2065,7 +2078,7 @@
source = "nixpkgs";
};
shikanime = {
email = "william.phetsinorath@shikanime.studio";
email = "deva.shikanime@protonmail.com";
github = "shikanime";
githubId = 22115108;
name = "William Phetsinorath";

View File

@@ -57,9 +57,3 @@ changes are only active if the `home.stateVersion` option is set to
now default to `true` which is consistent with the default values
for those options used by `i3` and `sway`.
- The [](#opt-programs.swaylock.enable) option now defaults to `false`
and must be explicitly enabled. Previously, it would be implicitly
enabled when `programs.swaylock.settings` was non-empty. Users with
`home.stateVersion` set to earlier versions will continue to get the
old implicit behavior.

View File

@@ -27,8 +27,4 @@ The state version in this release includes the changes below. These
changes are only active if the `home.stateVersion` option is set to
\"25.05\" or later.
- The [](#opt-programs.git.signing.format) option no longer defaults to
`"openpgp"`. Users who use Git signing with GPG should explicitly set
this option to `"openpgp"` to maintain the previous behavior. Users
with `home.stateVersion` set to earlier versions will continue to get
the `"openpgp"` default for backwards compatibility.
- No changes.

View File

@@ -80,10 +80,3 @@ changes are only active if the `home.stateVersion` option is set to
`{ PASSWORD_STORE_DIR = $XDG_DATA_HOME/password-store; }` anymore by its
default value. This will revert to the default behaviour of the program,
namely `$HOME/.password-store` to be used as the store path.
- On macOS, [](#opt-targets.darwin.copyApps.enable) is now enabled by
default instead of [](#opt-targets.darwin.linkApps.enable). This means
applications from `home.packages` will be copied to
`~/Applications/Home Manager Apps` rather than symlinked, making them
work properly with Spotlight. Users with `home.stateVersion` set to
earlier versions will continue to use `linkApps` by default.

View File

@@ -7,19 +7,11 @@ section is therefore not final.
This release has the following notable changes:
- The [](#opt-programs.anki.uiScale) option now expects a value in the
range 1.02.0, previously it erroneously expected values in the
range `0.01.0`.
## State Version Changes {#sec-release-26.05-state-version-changes}
The state version in this release includes the changes below. These
changes are only active if the `home.stateVersion` option is set to
\"26.05\" or later.
- The [](#opt-gtk.gtk4.theme) option does not mirror
[](#opt-gtk.theme) by default anymore.
- The [](#opt-programs.zsh.dotDir) option now defaults to the XDG
configuration directory (usually `~/.config/zsh`) when
[](#opt-xdg.enable) is true.
- The `gtk.gtk4.theme` option does not mirror `gtk.theme` by default
anymore.

232
flake.lock generated
View File

@@ -1,12 +1,112 @@
{
"nodes": {
"crane": {
"inputs": {
"flake-compat": "flake-compat",
"flake-utils": [
"putter",
"flake-utils"
],
"nixpkgs": [
"putter",
"nixpkgs"
],
"rust-overlay": "rust-overlay"
},
"locked": {
"lastModified": 1697588719,
"narHash": "sha256-n9ALgm3S+ygpzjesBkB9qutEtM4dtIkhn8WnstCPOew=",
"owner": "ipetkov",
"repo": "crane",
"rev": "da6b58e270d339a78a6e95728012ec2eea879612",
"type": "github"
},
"original": {
"owner": "ipetkov",
"ref": "v0.14.3",
"repo": "crane",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696267196,
"narHash": "sha256-AAQ/2sD+0D18bb8hKuEEVpHUYD1GmO2Uh/taFamn6XQ=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "4f910c9827911b1ec2bf26b5a062cd09f8d89f85",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-compat_2": {
"flake": false,
"locked": {
"lastModified": 1673956053,
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1694529238,
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"putter",
"pre-commit-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1660459072,
"narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "a20de23b925fd8264fd7fad6454652e142fd7f73",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1766902085,
"narHash": "sha256-coBu0ONtFzlwwVBzmjacUQwj3G+lybcZ1oeNSQkgC0M=",
"lastModified": 1765472234,
"narHash": "sha256-9VvC20PJPsleGMewwcWYKGzDIyjckEz8uWmT0vCDYK0=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "c0b0e0fddf73fd517c3471e546c0df87a42d53f4",
"rev": "2fbfb1d73d239d2402a8fe03963e37aab15abe8b",
"type": "github"
},
"original": {
@@ -16,9 +116,133 @@
"type": "github"
}
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1685801374,
"narHash": "sha256-otaSUoFEMM+LjBI1XL/xGB5ao6IwnZOXc47qhIgJe8U=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "c37ca420157f4abc31e26f436c1145f8951ff373",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-23.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1699094435,
"narHash": "sha256-YLZ5/KKZ1PyLrm2MO8UxRe4H3M0/oaYqNhSlq6FDeeA=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "9d5d25bbfe8c0297ebe85324addcb5020ed1a454",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"pre-commit-hooks": {
"inputs": {
"flake-compat": "flake-compat_2",
"flake-utils": [
"putter",
"flake-utils"
],
"gitignore": "gitignore",
"nixpkgs": [
"putter",
"nixpkgs"
],
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1698852633,
"narHash": "sha256-Hsc/cCHud8ZXLvmm8pxrXpuaPEeNaaUttaCvtdX/Wug=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "dec10399e5b56aa95fcd530e0338be72ad6462a0",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"putter": {
"inputs": {
"crane": "crane",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs_2",
"pre-commit-hooks": "pre-commit-hooks"
},
"locked": {
"lastModified": 1704013409,
"narHash": "sha256-v7CTHSKcD6vnIwXRPav+3XETf+uNJz3G+RUF/SHZ+vE=",
"ref": "refs/heads/master",
"rev": "4d773d3aa9feca3af4578dc62cc6f91ebb16b002",
"revCount": 33,
"type": "git",
"url": "file:///home/rycee/devel/putter"
},
"original": {
"type": "git",
"url": "file:///home/rycee/devel/putter"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
"nixpkgs": "nixpkgs",
"putter": "putter"
}
},
"rust-overlay": {
"inputs": {
"flake-utils": [
"putter",
"crane",
"flake-utils"
],
"nixpkgs": [
"putter",
"crane",
"nixpkgs"
]
},
"locked": {
"lastModified": 1696299134,
"narHash": "sha256-RS77cAa0N+Sfj5EmKbm5IdncNXaBCE1BSSQvUE8exvo=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "611ccdceed92b4d94ae75328148d84ee4a5b462d",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},

View File

@@ -2,11 +2,13 @@
description = "Home Manager for Nix";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
inputs.putter.url = "git+file:///home/rycee/devel/putter";
outputs =
{
self,
nixpkgs,
putter,
...
}:
{

View File

@@ -2,6 +2,7 @@
pkgs,
config,
lib,
putter,
...
}:
@@ -30,6 +31,8 @@ let
name = sourceName;
};
putterStatePath = "${config.xdg.stateHome}/home-manager/putter-state.json";
in
{
@@ -45,6 +48,14 @@ in
internal = true;
description = "Package to contain all home files";
};
home.internal = {
filePutterConfig = lib.mkOption {
type = lib.types.package;
internal = true;
description = "Putter configuration.";
};
};
};
config = {
@@ -91,156 +102,17 @@ in
# This verifies that the links we are about to create will not
# overwrite an existing file.
home.activation.checkLinkTargets = lib.hm.dag.entryBefore [ "writeBoundary" ] (
let
# Paths that should be forcibly overwritten by Home Manager.
# Caveat emptor!
forcedPaths = lib.concatMapStringsSep " " (p: ''"$HOME"/${lib.escapeShellArg p}'') (
lib.mapAttrsToList (n: v: v.target) (lib.filterAttrs (n: v: v.force) cfg)
);
home.activation.checkLinkTargets = lib.hm.dag.entryBefore [ "writeBoundary" ] ''
${lib.getExe putter} check -v \
--state-file "${putterStatePath}" \
${config.home.internal.filePutterConfig}
'';
storeDir = lib.escapeShellArg builtins.storeDir;
check = pkgs.replaceVars ./files/check-link-targets.sh {
inherit (config.lib.bash) initHomeManagerLib;
inherit forcedPaths storeDir;
};
in
''
function checkNewGenCollision() {
local newGenFiles
newGenFiles="$(readlink -e "$newGenPath/home-files")"
find "$newGenFiles" \( -type f -or -type l \) \
-exec bash ${check} "$newGenFiles" {} +
}
checkNewGenCollision || exit 1
''
);
# This activation script will
#
# 1. Remove files from the old generation that are not in the new
# generation.
#
# 2. Symlink files from the new generation into $HOME.
#
# This order is needed to ensure that we always know which links
# belong to which generation. Specifically, if we're moving from
# generation A to generation B having sets of home file links FA
# and FB, respectively then cleaning before linking produces state
# transitions similar to
#
# FA → FA ∩ FB → (FA ∩ FB) FB = FB
#
# and a failure during the intermediate state FA ∩ FB will not
# result in lost links because this set of links are in both the
# source and target generation.
home.activation.linkGeneration = lib.hm.dag.entryAfter [ "writeBoundary" ] (
let
link = pkgs.writeShellScript "link" ''
${config.lib.bash.initHomeManagerLib}
newGenFiles="$1"
shift
for sourcePath in "$@" ; do
relativePath="''${sourcePath#$newGenFiles/}"
targetPath="$HOME/$relativePath"
if [[ -e "$targetPath" && ! -L "$targetPath" ]] ; then
if [[ -n "$HOME_MANAGER_BACKUP_COMMAND" ]] ; then
verboseEcho "Running $HOME_MANAGER_BACKUP_COMMAND $targetPath."
run $HOME_MANAGER_BACKUP_COMMAND "$targetPath" || errorEcho "Running `$HOME_MANAGER_BACKUP_COMMAND` on '$targetPath' failed."
elif [[ -n "$HOME_MANAGER_BACKUP_EXT" ]] ; then
# The target exists, back it up
backup="$targetPath.$HOME_MANAGER_BACKUP_EXT"
if [[ -e "$backup" && -n "$HOME_MANAGER_BACKUP_OVERWRITE" ]]; then
run rm $VERBOSE_ARG "$backup"
fi
run mv $VERBOSE_ARG "$targetPath" "$backup" || errorEcho "Moving '$targetPath' failed!"
fi
fi
if [[ -e "$targetPath" && ! -L "$targetPath" ]] && cmp -s "$sourcePath" "$targetPath" ; then
# The target exists but is identical don't do anything.
verboseEcho "Skipping '$targetPath' as it is identical to '$sourcePath'"
else
# Place that symlink, --force
# This can still fail if the target is a directory, in which case we bail out.
run mkdir -p $VERBOSE_ARG "$(dirname "$targetPath")"
run ln -Tsf $VERBOSE_ARG "$sourcePath" "$targetPath" || exit 1
fi
done
'';
cleanup = pkgs.writeShellScript "cleanup" ''
${config.lib.bash.initHomeManagerLib}
# A symbolic link whose target path matches this pattern will be
# considered part of a Home Manager generation.
homeFilePattern="$(readlink -e ${lib.escapeShellArg builtins.storeDir})/*-home-manager-files/*"
newGenFiles="$1"
shift 1
for relativePath in "$@" ; do
targetPath="$HOME/$relativePath"
if [[ -e "$newGenFiles/$relativePath" ]] ; then
verboseEcho "Checking $targetPath: exists"
elif [[ ! "$(readlink "$targetPath")" == $homeFilePattern ]] ; then
warnEcho "Path '$targetPath' does not link into a Home Manager generation. Skipping delete."
else
verboseEcho "Checking $targetPath: gone (deleting)"
run rm $VERBOSE_ARG "$targetPath"
# Recursively delete empty parent directories.
targetDir="$(dirname "$relativePath")"
if [[ "$targetDir" != "." ]] ; then
pushd "$HOME" > /dev/null
# Call rmdir with a relative path excluding $HOME.
# Otherwise, it might try to delete $HOME and exit
# with a permission error.
run rmdir $VERBOSE_ARG \
-p --ignore-fail-on-non-empty \
"$targetDir"
popd > /dev/null
fi
fi
done
'';
in
''
function linkNewGen() {
_i "Creating home file links in %s" "$HOME"
local newGenFiles
newGenFiles="$(readlink -e "$newGenPath/home-files")"
find "$newGenFiles" \( -type f -or -type l \) \
-exec bash ${link} "$newGenFiles" {} +
}
function cleanOldGen() {
if [[ ! -v oldGenPath || ! -e "$oldGenPath/home-files" ]] ; then
return
fi
_i "Cleaning up orphan links from %s" "$HOME"
local newGenFiles oldGenFiles
newGenFiles="$(readlink -e "$newGenPath/home-files")"
oldGenFiles="$(readlink -e "$oldGenPath/home-files")"
# Apply the cleanup script on each leaf in the old
# generation. The find command below will print the
# relative path of the entry.
find "$oldGenFiles" '(' -type f -or -type l ')' -printf '%P\0' \
| xargs -0 bash ${cleanup} "$newGenFiles"
}
cleanOldGen
linkNewGen
''
);
home.activation.linkGeneration = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
${lib.getExe putter} apply $VERBOSE_ARG -v ''${DRY_RUN:+--dry-run} \
--state-file "${putterStatePath}" \
${config.home.internal.filePutterConfig}
'';
home.activation.checkFilesChanged = lib.hm.dag.entryBefore [ "linkGeneration" ] (
let
@@ -286,6 +158,18 @@ in
'') (lib.filter (v: v.onChange != "") (lib.attrValues cfg))
);
home.internal.filePutterConfig =
let
putter = import ./lib/putter.nix { inherit lib; };
manifest = putter.mkPutterManifest {
inherit putterStatePath;
sourceBaseDirectory = config.home-files;
targetBaseDirectory = config.home.homeDirectory;
fileEntries = attrValues cfg;
};
in
pkgs.writeText "hm-putter.json" manifest;
# Symlink directories and files that have the right execute bit.
# Copy files that need their execute bit changed.
home-files =

View File

@@ -906,6 +906,7 @@ in
--subst-var-by GENERATION_DIR $out
ln -s ${config.home-files} $out/home-files
ln -s ${config.home.internal.filePutterConfig} $out/putter.json
ln -s ${cfg.path} $out/home-path
cp "$extraDependenciesPath" "$out/extra-dependencies"

View File

@@ -270,7 +270,9 @@ in
setupLaunchAgents
# Restore errexit
set -e
if [[ -o errexit ]]; then
set -e
fi
'';
})
];

View File

@@ -21,7 +21,6 @@
isList
mapAttrsToList
replicate
attrNames
;
initialIndent = concatStrings (replicate indentLevel " ");
@@ -29,36 +28,31 @@
toHyprconf' =
indent: attrs:
let
isImportantField =
n: _: foldl (acc: prev: if hasPrefix prev n then true else acc) false importantPrefixes;
importantFields = filterAttrs isImportantField attrs;
withoutImportantFields = fields: removeAttrs fields (attrNames importantFields);
allSections = filterAttrs (n: v: isAttrs v || isList v) attrs;
sections = withoutImportantFields allSections;
sections = filterAttrs (n: v: isAttrs v || (isList v && all isAttrs v)) attrs;
mkSection =
n: attrs:
if isList attrs then
let
separator = if all isAttrs attrs then "\n" else "";
in
(concatMapStringsSep separator (a: mkSection n a) attrs)
else if isAttrs attrs then
if lib.isList attrs then
(concatMapStringsSep "\n" (a: mkSection n a) attrs)
else
''
${indent}${n} {
${toHyprconf' " ${indent}" attrs}${indent}}
''
else
toHyprconf' indent { ${n} = attrs; };
'';
mkFields = generators.toKeyValue {
listsAsDuplicateKeys = true;
inherit indent;
};
allFields = filterAttrs (n: v: !(isAttrs v || isList v)) attrs;
fields = withoutImportantFields allFields;
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)

81
modules/lib/putter.nix Normal file
View File

@@ -0,0 +1,81 @@
# Contains some handy functions for generating Putter file manifests.
{ lib }:
let
inherit (lib)
concatMap
concatLists
mapAttrsToList
hasPrefix
filter
;
in
{
# Converts a Home Manager style list of file specifications into a Putter
# configuration.
#
# Note, the interface of this function is not considered stable, it may change
# as the needs of Home Manager change.
mkPutterManifest =
{
putterStatePath,
sourceBaseDirectory,
targetBaseDirectory,
fileEntries,
}:
let
# Convert a directory to a Putter configuration. Basically, this will
# create a file entry for each file in the directory. Any sub-directories
# will be handled recursively.
mkDirEntry =
f:
concatLists (
mapAttrsToList (
n: v:
let
f' = f // {
source = "${f.source}/${n}";
target = "${f.target}/${n}";
};
in
mkEntriesForType f' v
) (builtins.readDir f.source)
);
mkEntriesForType =
f: t:
if t == "regular" || t == "symlink" then
mkFileEntry f
else if t == "directory" then
mkDirEntry f
else
throw "unexpected file type ${t}";
# Create a file entry for the given file.
mkFileEntry = f: [
{
collision.resolution = if f.force then "force" else "abort";
action.type = "symlink";
source = "${sourceBaseDirectory}/${f.target}";
target = (if hasPrefix "/" f.target then "" else "${targetBaseDirectory}/") + f.target;
}
];
# Given a Home Manager file entry, produce a list of Putter entries. For
# recursive HM file entries, we recursively traverse the source directory
# and generate a Putter entry for each file we encounter.
mkEntries = f: if f.recursive then mkEntriesForType f "directory" else mkFileEntry f;
putterJson = {
version = "1";
state = putterStatePath;
files = concatMap mkEntries (filter (f: f.enable) fileEntries);
};
putterJsonText = builtins.toJSON putterJson;
in
putterJsonText;
}

View File

@@ -1,20 +0,0 @@
{ config, ... }:
{
time = "2025-12-27T19:00:00+00:00";
condition = config.programs.zsh.enable;
message = ''
The default value of `programs.zsh.dotDir` has changed.
When `home.stateVersion` is set to "26.05" or later, and `xdg.enable` is
`true` (the default), `programs.zsh.dotDir` now defaults to
`''${config.xdg.configHome}/zsh`. Previously, it defaulted to the home
directory.
This means your Zsh configuration files (`.zshrc`, `.zshenv`, etc.) will be
moved to `~/.config/zsh` (or your configured XDG config home).
If you prefer the old behavior, you can explicitly set:
`programs.zsh.dotDir = config.home.homeDirectory;`
'';
}

View File

@@ -1,10 +0,0 @@
{ config, ... }:
{
time = "2026-01-02T00:03:48+00:00";
condition = config.services.mpd.enable;
message = ''
`MPD_HOST` and `MPD_PORT` environment variables are now set automatically.
This can be disabled with `services.mpd.enableSessionVariables = false`.
'';
}

View File

@@ -135,7 +135,7 @@ in
home.activation.xfconfSettings = lib.hm.dag.entryAfter [ "installPackages" ] (
let
mkCommand = channel: property: value: ''
run ${pkgs.xfconf}/bin/xfconf-query \
run ${pkgs.xfce.xfconf}/bin/xfconf-query \
${lib.escapeShellArgs (
[
"-c"

View File

@@ -82,7 +82,7 @@ in
};
uiScale = lib.mkOption {
type = with lib.types; nullOr (numbers.between 1.0 2.0);
type = with lib.types; nullOr (numbers.between 0.0 1.0);
default = null;
example = 1.0;
description = "User interface scale.";

View File

@@ -185,7 +185,7 @@ in
extraCss = mkOption {
type = nullOr lines;
default = null;
default = "";
description = ''
Extra CSS lines to add to {file}`~/.config/anyrun/style.css`.
'';

View File

@@ -197,47 +197,6 @@ in
};
};
rules = lib.mkOption {
type = lib.types.attrsOf (lib.types.either lib.types.lines lib.types.path);
default = { };
description = ''
Modular rule files for Claude Code.
The attribute name becomes the rule filename, and the value is either:
- Inline content as a string
- A path to a file containing the rule content
Rules are stored in .claude/rules/ directory.
All markdown files in .claude/rules/ are automatically loaded as project memory.
'';
example = lib.literalExpression ''
{
code-style = '''
# Code Style Guidelines
- Use consistent formatting
- Follow language conventions
''';
testing = '''
# Testing Conventions
- Write tests for all new features
- Maintain test coverage above 80%
''';
security = ./rules/security.md;
}
'';
};
rulesDir = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
description = ''
Path to a directory containing rule files for Claude Code.
Rule files from this directory will be symlinked to .claude/rules/.
All markdown files in this directory are automatically loaded as project memory.
'';
example = lib.literalExpression "./rules";
};
agentsDir = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
@@ -366,10 +325,6 @@ in
assertion = !(cfg.memory.text != null && cfg.memory.source != null);
message = "Cannot specify both `programs.claude-code.memory.text` and `programs.claude-code.memory.source`";
}
{
assertion = !(cfg.rules != { } && cfg.rulesDir != null);
message = "Cannot specify both `programs.claude-code.rules` and `programs.claude-code.rulesDir`";
}
{
assertion = !(cfg.agents != { } && cfg.agentsDir != null);
message = "Cannot specify both `programs.claude-code.agents` and `programs.claude-code.agentsDir`";
@@ -431,11 +386,6 @@ in
if cfg.memory.text != null then { text = cfg.memory.text; } else { source = cfg.memory.source; }
);
".claude/rules" = lib.mkIf (cfg.rulesDir != null) {
source = cfg.rulesDir;
recursive = true;
};
".claude/agents" = lib.mkIf (cfg.agentsDir != null) {
source = cfg.agentsDir;
recursive = true;
@@ -456,12 +406,6 @@ in
recursive = true;
};
}
// lib.mapAttrs' (
name: content:
lib.nameValuePair ".claude/rules/${name}.md" (
if lib.isPath content then { source = content; } else { text = content; }
)
) cfg.rules
// lib.mapAttrs' (
name: content:
lib.nameValuePair ".claude/agents/${name}.md" (

View File

@@ -21,19 +21,14 @@ in
settings = lib.mkOption {
inherit (jsonFormat) type;
default = { };
example = {
ui.theme = "Default";
general = {
vimMode = true;
preferredEditor = "nvim";
previewFeatures = true;
};
ide.enabled = true;
privacy.usageStatisticsEnabled = false;
tools.autoAccept = false;
context.loadMemoryFromIncludeDirectories = true;
security.auth.selectedType = "oauth-personal";
};
example = lib.literalExpression ''
{
"theme": "Default",
"vimMode": true,
"preferredEditor": "nvim",
"autoAccept": true
}
'';
description = "JSON config for gemini-cli";
};
@@ -86,12 +81,12 @@ in
};
defaultModel = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
type = lib.types.str;
default = "gemini-2.5-pro";
example = "gemini-2.5-flash";
description = ''
The default model to use for the CLI.
Will be set as $GEMINI_MODEL when configured.
Will be set as $GEMINI_MODEL.
'';
};
@@ -143,9 +138,7 @@ in
file.".gemini/settings.json" = lib.mkIf (cfg.settings != { }) {
source = jsonFormat.generate "gemini-cli-settings.json" cfg.settings;
};
sessionVariables = lib.mkIf (cfg.defaultModel != null) {
GEMINI_MODEL = cfg.defaultModel;
};
sessionVariables.GEMINI_MODEL = cfg.defaultModel;
};
}
{

View File

@@ -24,8 +24,7 @@ let
meliAccounts = (lib.attrsets.mapAttrs (name: value: (mkMeliAccounts name value)) enabledAccounts);
mkMeliAccounts = (
name: account:
{
name: account: {
root_mailbox = "${config.accounts.email.maildirBasePath}/${account.maildir.path}";
format = "Maildir";
identity = account.address;
@@ -34,14 +33,6 @@ let
send_mail = mkSmtp account;
mailboxes = account.meli.mailboxAliases;
}
// lib.optionalAttrs (account.flavor == "fastmail.com") {
server_username = account.userName;
server_password_command = lib.concatMapStringsSep " " lib.escapeShellArg account.passwordCommand;
format = "jmap";
server_url = "https://api.fastmail.com/jmap/session";
use_token = true;
}
// account.meli.settings
);
mkSmtp = account: {
@@ -162,14 +153,6 @@ in
};
description = "Folder display name";
};
settings = mkOption {
type = types.submodule {
freeformType = tomlFormat.type;
};
default = { };
description = "Account specific meli configuration";
};
};
}
)
@@ -177,15 +160,6 @@ in
};
};
config = mkIf config.programs.meli.enable {
assertions = [
{
assertion = cfg.settings ? accounts == false;
message = ''
programs.meli.settings.accounts override the accounts.email values.
Use per-email accounts.email.<ACCOUNT>.meli.settings instead'';
}
];
home.packages = [ config.programs.meli.package ];
# Generate meli configuration from email accounts

View File

@@ -7,46 +7,121 @@
let
inherit (lib)
concatMapStringsSep
literalExpression
mkEnableOption
mkIf
mkOption
mkPackageOption
optionals
mkRemovedOptionModule
types
;
cfg = config.programs.neovim;
inherit
(
(import ../lib/file-type.nix {
inherit (config.home) homeDirectory;
inherit lib pkgs;
})
)
fileType
;
fileType =
(import ../lib/file-type.nix {
inherit (config.home) homeDirectory;
inherit lib pkgs;
}).fileType;
jsonFormat = pkgs.formats.json { };
pluginWithConfigType = types.submodule {
options = {
config = mkOption {
type = types.nullOr types.lines;
description = "Script to configure this plugin. The scripting language should match type.";
default = null;
};
type = mkOption {
type = types.either (types.enum [
"lua"
"viml"
"teal"
"fennel"
]) types.str;
description = "Language used in config. Configurations are aggregated per-language.";
default = "viml";
};
optional = mkEnableOption "optional" // {
description = "Don't load by default (load with :packadd)";
};
plugin = lib.mkPackageOption pkgs.vimPlugins "plugin" {
default = null;
example = "pkgs.vimPlugins.nvim-treesitter";
pkgsText = "pkgs.vimPlugins";
};
runtime = mkOption {
default = { };
# passing actual "${xdg.configHome}/nvim" as basePath was a bit tricky
# due to how fileType.target is implemented
type = fileType "programs.neovim.plugins._.runtime" "{var}`xdg.configHome/nvim`" "nvim";
example = literalExpression ''
{ "ftplugin/c.vim".text = "setlocal omnifunc=v:lua.vim.lsp.omnifunc"; }
'';
description = ''
Set of files that have to be linked in nvim config folder.
'';
};
};
};
allPlugins =
cfg.plugins
++ lib.optional cfg.coc.enable {
type = "viml";
plugin = cfg.coc.package;
config = cfg.coc.pluginConfig;
optional = false;
};
luaPackages = cfg.finalPackage.unwrapped.lua.pkgs;
resolvedExtraLuaPackages = cfg.extraLuaPackages luaPackages;
extraMakeWrapperArgs = lib.optionalString (
cfg.extraPackages != [ ]
) ''--suffix PATH : "${lib.makeBinPath cfg.extraPackages}"'';
extraMakeWrapperLuaCArgs =
lib.optionalString (resolvedExtraLuaPackages != [ ])
''--suffix LUA_CPATH ";" "${
lib.concatMapStringsSep ";" luaPackages.getLuaCPath resolvedExtraLuaPackages
}"'';
extraMakeWrapperLuaArgs =
lib.optionalString (resolvedExtraLuaPackages != [ ])
''--suffix LUA_PATH ";" "${
lib.concatMapStringsSep ";" luaPackages.getLuaPath resolvedExtraLuaPackages
}"'';
in
{
meta.maintainers = with lib.maintainers; [ khaneliman ];
imports = [
(mkRemovedOptionModule [
"programs"
"neovim"
"withPython"
] "Python2 support has been removed from neovim.")
(mkRemovedOptionModule [
"programs"
"neovim"
"extraPythonPackages"
] "Python2 support has been removed from neovim.")
(mkRemovedOptionModule [ "programs" "neovim" "configure" ] ''
programs.neovim.configure is deprecated.
Other programs.neovim options can override its settings or ignore them.
Please use the other options at your disposal:
configure.packages.*.opt -> programs.neovim.plugins = [ { plugin = ...; optional = true; }]
configure.packages.*.start -> programs.neovim.plugins = [ { plugin = ...; }]
configure.customRC -> programs.neovim.extraConfig
'')
];
options = {
programs.neovim = {
enable = mkEnableOption "Neovim";
package = mkPackageOption pkgs "neovim" { default = "neovim-unwrapped"; };
finalPackage = mkOption {
type = types.package;
readOnly = true;
description = "Resulting customized neovim package.";
};
# Aliases
viAlias = mkOption {
type = types.bool;
default = false;
@@ -71,17 +146,6 @@ in
'';
};
defaultEditor = mkOption {
type = types.bool;
default = false;
description = ''
Whether to configure {command}`nvim` as the default
editor using the {env}`EDITOR` and {env}`VISUAL`
environment variables.
'';
};
# Providers & Runtimes
withNodeJs = mkOption {
type = types.bool;
default = false;
@@ -91,12 +155,11 @@ in
'';
};
withPerl = mkOption {
type = types.bool;
default = false;
withRuby = mkOption {
type = types.nullOr types.bool;
default = true;
description = ''
Enable perl provider. Set to `true` to
use Perl plugins.
Enable ruby provider.
'';
};
@@ -109,16 +172,21 @@ in
'';
};
withRuby = mkOption {
type = types.nullOr types.bool;
default = true;
description = ''
Enable ruby provider.
'';
};
extraPython3Packages = mkOption {
type = types.functionTo (types.listOf types.package);
# In case we get a plain list, we need to turn it into a function,
# as expected by the function in nixpkgs.
# The only way to do so is to call `const`, which will ignore its input.
type =
let
fromType = types.listOf types.package;
in
types.coercedTo fromType (lib.flip lib.warn lib.const ''
Assigning a plain list to extraPython3Packages is deprecated.
Please assign a function taking a package set as argument, so
extraPython3Packages = [ pkgs.python3Packages.xxx ];
should become
extraPython3Packages = ps: [ ps.xxx ];
'') (types.functionTo fromType);
default = _: [ ];
defaultText = literalExpression "ps: [ ]";
example = literalExpression "pyPkgs: with pyPkgs; [ python-language-server ]";
@@ -130,8 +198,21 @@ in
'';
};
# We get the Lua package from the final package and use its
# Lua packageset to evaluate the function that this option was set to.
# This ensures that we always use the same Lua version as the Neovim package.
extraLuaPackages = mkOption {
type = types.functionTo (types.listOf types.package);
type =
let
fromType = types.listOf types.package;
in
types.coercedTo fromType (lib.flip lib.warn lib.const ''
Assigning a plain list to extraLuaPackages is deprecated.
Please assign a function taking a package set as argument, so
extraLuaPackages = [ pkgs.lua51Packages.xxx ];
should become
extraLuaPackages = ps: [ ps.xxx ];
'') (types.functionTo fromType);
default = _: [ ];
defaultText = literalExpression "ps: [ ]";
example = literalExpression "luaPkgs: with luaPkgs; [ luautf8 ]";
@@ -143,34 +224,8 @@ in
'';
};
# Wrapper Configuration
extraName = mkOption {
type = types.str;
default = "";
description = ''
Extra name appended to the wrapper package name.
'';
};
autowrapRuntimeDeps = mkOption {
type = types.bool;
default = true;
description = ''
Whether to automatically wrap the binary with the runtime dependencies of the plugins.
'';
};
waylandSupport = mkOption {
type = types.bool;
default = pkgs.stdenv.isLinux;
defaultText = literalExpression "pkgs.stdenv.isLinux";
description = ''
Whether to enable Wayland clipboard support.
'';
};
extraWrapperArgs = mkOption {
type = types.listOf types.str;
type = with types; listOf str;
default = [ ];
example = literalExpression ''
[
@@ -191,14 +246,54 @@ in
'';
};
extraPackages = mkOption {
type = types.listOf types.package;
default = [ ];
example = literalExpression "[ pkgs.shfmt ]";
description = "Extra packages available to nvim.";
generatedConfigViml = mkOption {
type = types.lines;
visible = true;
readOnly = true;
description = ''
Generated vimscript config.
'';
};
generatedConfigs = mkOption {
type = types.attrsOf types.lines;
visible = true;
readOnly = true;
example = literalExpression ''
{
viml = '''
" Generated by home-manager
map <leader> ,
''';
lua = '''
-- Generated by home-manager
vim.opt.background = "dark"
''';
}'';
description = ''
Generated configurations with as key their language (set via type).
'';
};
package = lib.mkPackageOption pkgs "neovim" { default = "neovim-unwrapped"; };
finalPackage = mkOption {
type = types.package;
readOnly = true;
description = "Resulting customized neovim package.";
};
defaultEditor = mkOption {
type = types.bool;
default = false;
description = ''
Whether to configure {command}`nvim` as the default
editor using the {env}`EDITOR` and {env}`VISUAL`
environment variables.
'';
};
# Configuration & Plugins
extraConfig = mkOption {
type = types.lines;
default = "";
@@ -213,92 +308,45 @@ in
extraLuaConfig = mkOption {
type = types.lines;
default = "";
example = lib.literalExpression ''
let
nvimEarlyInit = lib.mkOrder 500 "set rtp+=vim.opt.rtp:prepend('/home/user/myplugin')";
nvimLateInit = lib.mkAfter "vim.opt.signcolumn = 'auto:1-3'";
in
lib.mkMerge [ nvimEarlyInit nvimLateInit ];
example = ''
vim.opt.nobackup = true
'';
description = ''
Content to be added to {file}`init.lua`.
To specify the order, use `lib.mkOrder`, `lib.mkBefore`, `lib.mkAfter`.
Custom lua lines.
'';
};
plugins =
let
pluginWithConfigType = types.submodule {
options = {
config = mkOption {
type = types.nullOr types.lines;
description = "Script to configure this plugin. The scripting language should match type.";
default = null;
};
extraPackages = mkOption {
type = with types; listOf package;
default = [ ];
example = literalExpression "[ pkgs.shfmt ]";
description = "Extra packages available to nvim.";
};
type = mkOption {
type = types.either (types.enum [
"lua"
"viml"
"teal"
"fennel"
]) types.str;
description = "Language used in config. Configurations are aggregated per-language.";
default = "viml";
};
plugins = mkOption {
type = with types; listOf (either package pluginWithConfigType);
default = [ ];
example = literalExpression ''
with pkgs.vimPlugins; [
yankring
vim-nix
{ plugin = vim-startify;
config = "let g:startify_change_to_vcs_root = 0";
}
]
'';
description = ''
List of vim plugins to install optionally associated with
configuration to be placed in init.vim.
optional = mkEnableOption "optional" // {
description = "Don't load by default (load with :packadd)";
};
plugin = mkPackageOption pkgs.vimPlugins "plugin" {
default = null;
example = "pkgs.vimPlugins.nvim-treesitter";
pkgsText = "pkgs.vimPlugins";
};
runtime = mkOption {
default = { };
# passing actual "${xdg.configHome}/nvim" as basePath was a bit tricky
# due to how fileType.target is implemented
type = fileType "programs.neovim.plugins._.runtime" "{var}`xdg.configHome/nvim`" "nvim";
example = literalExpression ''
{ "ftplugin/c.vim".text = "setlocal omnifunc=v:lua.vim.lsp.omnifunc"; }
'';
description = ''
Set of files that have to be linked in nvim config folder.
'';
};
};
};
in
mkOption {
type = types.listOf (types.either types.package pluginWithConfigType);
default = [ ];
example = literalExpression ''
with pkgs.vimPlugins;
[
yankring
vim-nix
{ plugin = vim-startify;
config = "let g:startify_change_to_vcs_root = 0";
}
]
'';
description = ''
List of vim plugins to install optionally associated with
configuration to be placed in init.vim.
This option is mutually exclusive with {var}`configure`.
'';
};
This option is mutually exclusive with {var}`configure`.
'';
};
coc = {
enable = mkEnableOption "Coc";
package = mkPackageOption pkgs "coc-nvim" {
package = lib.mkPackageOption pkgs "coc-nvim" {
default = [
"vimPlugins"
"coc-nvim"
@@ -328,7 +376,7 @@ in
filetypes = [ "haskell" "lhaskell" ];
};
};
}
};
'';
description = ''
Extra configuration lines to add to
@@ -345,52 +393,11 @@ in
description = "Script to configure CoC. Must be viml.";
};
};
# Generated / Read-Only
generatedConfigViml = mkOption {
type = types.lines;
visible = true;
readOnly = true;
description = ''
Generated vimscript config.
'';
};
generatedConfigs = mkOption {
type = types.attrsOf types.lines;
visible = true;
readOnly = true;
example = literalExpression ''
{
viml = '''
" Generated by home-manager
map <leader> ,
''';
lua = '''
-- Generated by home-manager
vim.opt.background = "dark"
''';
}
'';
description = ''
Generated configurations with as key their language (set via type).
'';
};
};
};
config = mkIf cfg.enable (
config =
let
allPlugins =
cfg.plugins
++ lib.optional cfg.coc.enable {
type = "viml";
plugin = cfg.coc.package;
config = cfg.coc.pluginConfig;
optional = false;
};
defaultPlugin = {
type = "viml";
plugin = null;
@@ -406,100 +413,81 @@ in
suppressNotVimlConfig = p: if p.type != "viml" then p // { config = null; } else p;
# Lua & Python Package Resolution
luaPackages = cfg.finalPackage.unwrapped.lua.pkgs;
resolvedExtraLuaPackages = cfg.extraLuaPackages luaPackages;
# Wrapper Arguments Construction
extraMakeWrapperArgs = optionals (cfg.extraPackages != [ ]) [
"--suffix"
"PATH"
":"
(lib.makeBinPath cfg.extraPackages)
];
extraMakeWrapperLuaCArgs = optionals (resolvedExtraLuaPackages != [ ]) [
"--suffix"
"LUA_CPATH"
";"
(concatMapStringsSep ";" luaPackages.getLuaCPath resolvedExtraLuaPackages)
];
extraMakeWrapperLuaArgs = optionals (resolvedExtraLuaPackages != [ ]) [
"--suffix"
"LUA_PATH"
";"
(concatMapStringsSep ";" luaPackages.getLuaPath resolvedExtraLuaPackages)
];
wrappedNeovim' = pkgs.wrapNeovimUnstable cfg.package {
withNodeJs = cfg.withNodeJs || cfg.coc.enable;
plugins = map suppressNotVimlConfig pluginsNormalized;
neovimConfig = pkgs.neovimUtils.makeNeovimConfig {
inherit (cfg)
extraPython3Packages
withPython3
withRuby
withPerl
viAlias
vimAlias
extraName
autowrapRuntimeDeps
waylandSupport
;
neovimRcContent = cfg.extraConfig;
wrapperArgs =
cfg.extraWrapperArgs ++ extraMakeWrapperArgs ++ extraMakeWrapperLuaCArgs ++ extraMakeWrapperLuaArgs;
wrapRc = false;
};
in
{
programs.neovim = {
generatedConfigViml = cfg.extraConfig;
generatedConfigs =
let
grouped = builtins.groupBy (x: x.type) pluginsNormalized;
configsOnly = lib.foldl (acc: p: if p.config != null then acc ++ [ p.config ] else acc) [ ];
in
lib.mapAttrs (_name: vals: lib.concatStringsSep "\n" (configsOnly vals)) grouped;
finalPackage = wrappedNeovim';
withNodeJs = cfg.withNodeJs || cfg.coc.enable;
plugins = map suppressNotVimlConfig pluginsNormalized;
customRC = cfg.extraConfig;
};
home = {
packages = [ cfg.finalPackage ];
sessionVariables = mkIf cfg.defaultEditor {
EDITOR = "nvim";
VISUAL = "nvim";
};
shellAliases = mkIf cfg.vimdiffAlias { vimdiff = "nvim -d"; };
};
programs.neovim.extraLuaConfig = lib.mkMerge [
(lib.mkIf (wrappedNeovim'.initRc != "") (
lib.mkBefore "vim.cmd [[source ${pkgs.writeText "nvim-init-home-manager.vim" wrappedNeovim'.initRc}]]"
))
(lib.mkIf (lib.hasAttr "lua" cfg.generatedConfigs) (lib.mkAfter cfg.generatedConfigs.lua))
];
xdg.configFile = lib.mkMerge (
# writes runtime
(map (x: x.runtime) pluginsNormalized)
++ [
{
"nvim/init.lua" = mkIf (cfg.extraLuaConfig != "") {
text = cfg.extraLuaConfig;
};
"nvim/coc-settings.json" = mkIf cfg.coc.enable {
source = jsonFormat.generate "coc-settings.json" cfg.coc.settings;
};
}
]
wrappedNeovim' = pkgs.wrapNeovimUnstable cfg.package (
neovimConfig
// {
wrapperArgs =
(lib.escapeShellArgs (neovimConfig.wrapperArgs ++ cfg.extraWrapperArgs))
+ " "
+ extraMakeWrapperArgs
+ " "
+ extraMakeWrapperLuaCArgs
+ " "
+ extraMakeWrapperLuaArgs;
wrapRc = false;
}
);
}
);
in
mkIf cfg.enable {
programs.neovim.generatedConfigViml = neovimConfig.neovimRcContent;
programs.neovim.generatedConfigs =
let
grouped = lib.lists.groupBy (x: x.type) pluginsNormalized;
configsOnly = lib.foldl (acc: p: if p.config != null then acc ++ [ p.config ] else acc) [ ];
in
lib.mapAttrs (name: vals: lib.concatStringsSep "\n" (configsOnly vals)) grouped;
home.packages = [ cfg.finalPackage ];
home.sessionVariables = mkIf cfg.defaultEditor {
EDITOR = "nvim";
VISUAL = "nvim";
};
home.shellAliases = mkIf cfg.vimdiffAlias { vimdiff = "nvim -d"; };
xdg.configFile =
let
hasLuaConfig = lib.hasAttr "lua" config.programs.neovim.generatedConfigs;
in
lib.mkMerge (
# writes runtime
(map (x: x.runtime) pluginsNormalized)
++ [
{
"nvim/init.lua" =
let
luaRcContent =
lib.optionalString (
wrappedNeovim'.initRc != ""
) "vim.cmd [[source ${pkgs.writeText "nvim-init-home-manager.vim" wrappedNeovim'.initRc}]]\n"
+ config.programs.neovim.extraLuaConfig
+ lib.optionalString hasLuaConfig config.programs.neovim.generatedConfigs.lua;
in
mkIf (luaRcContent != "") { text = luaRcContent; };
"nvim/coc-settings.json" = mkIf cfg.coc.enable {
source = jsonFormat.generate "coc-settings.json" cfg.coc.settings;
};
}
]
);
programs.neovim.finalPackage = wrappedNeovim';
};
}

View File

@@ -66,7 +66,10 @@ in
config = lib.mkIf cfg.enable {
assertions = [
(lib.hm.assertions.assertPlatform "programs.niriswitcher" pkgs lib.platforms.linux)
{
assertion = pkgs.stdenv.hostPlatform.isLinux;
message = "niriswitcher is only available on Linux.";
}
];
home.packages = lib.mkIf (cfg.package != null) [ cfg.package ];

View File

@@ -133,7 +133,7 @@ in
The attribute name becomes the command filename, and the value is either:
- Inline content as a string
- A path to a file containing the command content
Commands are stored in {file}`$XDG_CONFIG_HOME/opencode/command/` directory.
Commands are stored in {file}`$XDG_CONFIG_HOME/.config/opencode/command/` directory.
'';
example = lib.literalExpression ''
{
@@ -162,7 +162,7 @@ in
The attribute name becomes the agent filename, and the value is either:
- Inline content as a string
- A path to a file containing the agent content
Agents are stored in {file}`$XDG_CONFIG_HOME/opencode/agent/` directory.
Agents are stored in {file}`$XDG_CONFIG_HOME/.config/opencode/agent/` directory.
'';
example = lib.literalExpression ''
{
@@ -183,49 +183,6 @@ in
'';
};
skills = lib.mkOption {
type = lib.types.either (lib.types.attrsOf (lib.types.either lib.types.lines lib.types.path)) lib.types.path;
default = { };
description = ''
Custom agent skills for opencode.
This option can either be:
- An attribute set defining skills
- A path to a directory containing multiple skill folders
If an attribute set is used, the attribute name becomes the skill directory name,
and the value is either:
- Inline content as a string (creates `opencode/skill/<name>/SKILL.md`)
- A path to a file (creates `opencode/skill/<name>/SKILL.md`)
- A path to a directory (creates `opencode/skill/<name>/` with all files)
If a path is used, it is expected to contain one folder per skill name, each
containing a {file}`SKILL.md`. The directory is symlinked to
{file}`$XDG_CONFIG_HOME/opencode/skill/`.
See <https://opencode.ai/docs/skills/> for the documentation.
'';
example = lib.literalExpression ''
{
git-release = '''
---
name: git-release
description: Create consistent releases and changelogs
---
## What I do
- Draft release notes from merged PRs
- Propose a version bump
- Provide a copy-pasteable `gh release create` command
''';
# A skill can also be a directory containing SKILL.md and other files.
data-analysis = ./skills/data-analysis;
}
'';
};
themes = mkOption {
type = lib.types.attrsOf (lib.types.either jsonFormat.type lib.types.path);
default = { };
@@ -242,13 +199,6 @@ in
};
config = mkIf cfg.enable {
assertions = [
{
assertion = !lib.isPath cfg.skills || lib.pathIsDirectory cfg.skills;
message = "`programs.opencode.skills` must be a directory when set to a path";
}
];
home.packages = mkIf (cfg.package != null) [ cfg.package ];
xdg.configFile = {
@@ -277,11 +227,6 @@ in
text = cfg.rules;
})
);
"opencode/skill" = mkIf (lib.isPath cfg.skills) {
source = cfg.skills;
recursive = true;
};
}
// lib.mapAttrs' (
name: content:
@@ -295,18 +240,6 @@ in
if lib.isPath content then { source = content; } else { text = content; }
)
) cfg.agents
// lib.mapAttrs' (
name: content:
if lib.isPath content && lib.pathIsDirectory content then
lib.nameValuePair "opencode/skill/${name}" {
source = content;
recursive = true;
}
else
lib.nameValuePair "opencode/skill/${name}/SKILL.md" (
if lib.isPath content then { source = content; } else { text = content; }
)
) (if builtins.isAttrs cfg.skills then cfg.skills else { })
// lib.mapAttrs' (
name: content:
lib.nameValuePair "opencode/themes/${name}.json" (

View File

@@ -30,42 +30,44 @@ let
mapAttrsToList (name: value: ''${name}="${lib.escape [ ''"'' "\\" ] (toString value)}"'') envStr
);
mkAddressPortModule =
{
actionType,
nullableAddress ? actionType == "forward",
}:
types.submodule {
options = {
bindOptions = {
address = mkOption {
type = types.str;
default = "localhost";
example = "example.org";
description = "The address where to bind the port.";
};
port = mkOption {
type = types.nullOr types.port;
default = null;
example = 8080;
description = "Specifies port number to bind on bind address.";
};
};
dynamicForwardModule = types.submodule { options = bindOptions; };
forwardModule = types.submodule {
options = {
bind = bindOptions;
host = {
address = mkOption {
type = if nullableAddress then types.nullOr types.str else types.str;
default = if nullableAddress then null else "localhost";
type = types.nullOr types.str;
default = null;
example = "example.org";
description = "The address to ${actionType} to.";
description = "The address where to forward the traffic to.";
};
port = mkOption {
type = types.nullOr types.port;
default = null;
example = 8080;
description = "Specifies port number to ${actionType} to.";
example = 80;
description = "Specifies port number to forward the traffic to.";
};
};
};
dynamicForwardModule = mkAddressPortModule { actionType = "bind"; };
forwardModule = types.submodule {
options = {
bind = mkOption {
type = mkAddressPortModule { actionType = "bind"; };
description = "Local port binding options";
};
host = mkOption {
type = mkAddressPortModule { actionType = "forward"; };
description = "Host port binding options";
};
};
};
matchBlockModule = types.submodule {
@@ -381,18 +383,6 @@ let
example = "10m";
description = "Whether control socket should remain open in the background.";
};
kexAlgorithms = mkOption {
type = types.nullOr (types.listOf types.str);
default = null;
example = [
"curve25519-sha256@libssh.org"
"diffie-hellman-group-exchange-sha256"
];
description = ''
Specifies the available KEX (Key Exchange) algorithms.
'';
};
};
# config.host = mkDefault dagName;
@@ -440,9 +430,6 @@ let
++ map (f: " LocalForward" + addressPort f.bind + addressPort f.host) cf.localForwards
++ map (f: " RemoteForward" + addressPort f.bind + addressPort f.host) cf.remoteForwards
++ map (f: " DynamicForward" + addressPort f) cf.dynamicForwards
++ optional (
cf.kexAlgorithms != null
) " KexAlgorithms ${builtins.concatStringsSep "," cf.kexAlgorithms}"
++ [
(lib.generators.toKeyValue {
mkKeyValue = lib.generators.mkKeyValueDefault { } " ";

View File

@@ -106,13 +106,13 @@ in
];
programs.bash.initExtra = lib.mkIf cfg.enableBashIntegration ''
source ${cfg.package}/share/television/completion.bash
eval "$(${lib.getExe cfg.package} init bash)"
'';
programs.zsh.initContent = lib.mkIf cfg.enableZshIntegration ''
source ${cfg.package}/share/television/completion.zsh
eval "$(${lib.getExe cfg.package} init zsh)"
'';
programs.fish.interactiveShellInit = lib.mkIf cfg.enableFishIntegration ''
source ${cfg.package}/share/television/completion.fish
${lib.getExe cfg.package} init fish | source
'';
};
}

View File

@@ -12,7 +12,6 @@ let
packageVersion = if cfg.package != null then lib.getVersion cfg.package else null;
themeIsToml = lib.versionAtLeast packageVersion "0.15.0";
versionPost0_17 = lib.versionAtLeast packageVersion "0.17.0";
in
{
meta.maintainers = [ lib.maintainers.leiserfg ];
@@ -44,12 +43,7 @@ in
useLayerShell = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
Whether vicinae should use the layer shell.
If you are using version 0.17 or newer, you should use
{option}.programs.vicinae.settings.launcher_window.layer_shell.enabled = false
instead.
'';
description = "If vicinae should use the layer shell";
};
extensions = lib.mkOption {
@@ -133,22 +127,27 @@ in
settings = lib.mkOption {
inherit (jsonFormat) type;
default = { };
description = "Settings written as JSON to `~/.config/vicinae/vicinae.json.";
example = lib.literalExpression ''
{
favicon_service = "twenty";
font.normal.size = 10;
pop_to_root_on_close=false;
search_files_in_root= false;
faviconService = "twenty";
font = {
size = 10;
};
popToRootOnClose = false;
rootSearch = {
searchFiles = false;
};
theme = {
dark.name = "vicinae-dark";
light.name = "vicinae-light";
name = "vicinae-dark";
};
window = {
csd = true;
opacity = 0.95;
rounding = 10;
};
}
'';
description = ''
Settings written as JSON to {file}`~/.config/vicinae/settings.json`.
See {command}`vicinae config default`.
'';
};
};
@@ -159,10 +158,6 @@ in
assertion = cfg.systemd.enable -> cfg.package != null;
message = "{option}programs.vicinae.systemd.enable requires non null {option}programs.vicinae.package";
}
{
assertion = !cfg.useLayerShell -> !versionPost0_17;
message = ''After version 0.17, if you want to explicitly disable the use of layer shell, you need to set {option}.programs.vicinae.settings.launcher_window.layer_shell.enabled = false.'';
}
];
lib.vicinae.mkExtension = (
{
@@ -228,11 +223,10 @@ in
source = themeFormat.generate "vicinae-${name}-theme" theme;
}
) cfg.themes;
settingsPath = if versionPost0_17 then "vicinae/settings.json" else "vicinae/vicinae.json";
in
{
configFile = {
"${settingsPath}" = lib.mkIf (cfg.settings != { }) {
"vicinae/vicinae.json" = lib.mkIf (cfg.settings != { }) {
source = jsonFormat.generate "vicinae-settings" cfg.settings;
};
}
@@ -256,16 +250,14 @@ in
PartOf = [ cfg.systemd.target ];
};
Service = {
EnvironmentFile = pkgs.writeText "vicinae-env" ''
USE_LAYER_SHELL=${if cfg.useLayerShell then builtins.toString 1 else builtins.toString 0}
'';
Type = "simple";
ExecStart = "${lib.getExe' cfg.package "vicinae"} server";
Restart = "always";
RestartSec = 5;
KillMode = "process";
EnvironmentFile = lib.mkIf (!versionPost0_17) (
pkgs.writeText "vicinae-env" ''
USE_LAYER_SHELL=${if cfg.useLayerShell then builtins.toString 1 else builtins.toString 0}
''
);
};
Install = lib.mkIf cfg.systemd.autoStart {
WantedBy = [ cfg.systemd.target ];

View File

@@ -9,27 +9,12 @@ let
cfg = config.programs.yt-dlp;
configAtom =
with types;
oneOf [
bool
int
str
];
renderSingleOption =
renderSettings = lib.mapAttrsToList (
name: value:
if lib.isBool value then
if value then "--${name}" else "--no-${name}"
else
"--${name} ${toString value}";
renderSettings = lib.mapAttrsToList (
name: value:
if lib.isList value then
lib.concatStringsSep "\n" (map (renderSingleOption name) value)
else
renderSingleOption name value
"--${name} ${toString value}"
);
in
@@ -42,7 +27,13 @@ in
package = lib.mkPackageOption pkgs "yt-dlp" { };
settings = mkOption {
type = with types; attrsOf (either configAtom (listOf configAtom));
type =
with types;
attrsOf (oneOf [
bool
int
str
]);
default = { };
example = lib.literalExpression ''
{
@@ -51,10 +42,6 @@ in
sub-langs = "all";
downloader = "aria2c";
downloader-args = "aria2c:'-c -x8 -s8 -k1M'";
color = [
"stdout:no_color"
"stderr:always"
];
}
'';
description = ''

View File

@@ -2,7 +2,6 @@
config,
lib,
pkgs,
options,
...
}:
let
@@ -101,18 +100,9 @@ in
};
dotDir = mkOption {
default =
if config.xdg.enable && lib.versionAtLeast config.home.stateVersion "26.05" then
"${config.xdg.configHome}/zsh"
else
homeDir;
defaultText = lib.literalExpression ''
if config.xdg.enable && lib.versionAtLeast config.home.stateVersion "26.05" then
"''${config.xdg.configHome}/zsh"
else
config.home.homeDirectory
'';
example = literalExpression ''"''${config.xdg.configHome}/zsh"'';
default = homeDir;
defaultText = "`config.home.homeDirectory`";
example = "`\${config.xdg.configHome}/zsh`";
description = ''
Directory where the zsh configuration and more should be located,
relative to the users home directory. The default is the home
@@ -401,24 +391,7 @@ in
- config.xdg.dataHome (XDG data directory)
- config.xdg.cacheHome (XDG cache directory)
''
]
++
lib.optionals
(
config.xdg.enable
&& !lib.versionAtLeast config.home.stateVersion "26.05"
&& options.programs.zsh.dotDir.highestPrio >= 1500
)
[
''
The default value of `programs.zsh.dotDir` will change in future versions.
You are currently using the legacy default (home directory) because `home.stateVersion` is less than "26.05".
To silence this warning and lock in the current behavior, set:
programs.zsh.dotDir = config.home.homeDirectory;
To adopt the new behavior (XDG config directory), set:
programs.zsh.dotDir = "''${config.xdg.configHome}/zsh";
''
];
];
}
(mkIf (cfg.envExtra != "") {
@@ -439,7 +412,7 @@ in
(mkIf (dotDirAbs != homeDir) {
home.file."${dotDirRel}/.zshenv".text = ''
${config.lib.zsh.export "ZDOTDIR" dotDirAbs}
export ZDOTDIR=${dotDirAbs}
'';
# When dotDir is set, only use ~/.zshenv to source ZDOTDIR/.zshenv,
@@ -447,7 +420,7 @@ in
# already set correctly (by e.g. spawning a zsh inside a zsh), all env
# vars still get exported
home.file.".zshenv".text = ''
source ${lib.escapeShellArg "${dotDirAbs}/.zshenv"}
source ${dotDirAbs}/.zshenv
'';
})

View File

@@ -1,12 +1,9 @@
{ config, lib, ... }:
let
cfg = config.programs.zsh;
stripSlash = lib.removeSuffix "/";
in
rec {
# Raw home directory, no trailing slash.
homeDir = stripSlash config.home.homeDirectory;
homeDir = config.home.homeDirectory;
/*
Escape a path string for shell usage and remove trailing slashes.
@@ -21,11 +18,10 @@ rec {
cleanPathStr "/path/to/dir/" => "'/path/to/dir'"
cleanPathStr "path with spaces" => "'path with spaces'"
*/
cleanPathStr = pathStr: lib.escapeShellArg (stripSlash pathStr);
cleanPathStr = pathStr: lib.escapeShellArg (lib.removeSuffix "/" pathStr);
/*
Convert an absolute path to a relative path by stripping the home directory prefix.
Returns the raw path (unescaped) for use in home.file keys.
This function converts absolute paths within the home directory to relative paths
by removing the home directory prefix. Paths already relative are returned as-is.
@@ -34,22 +30,19 @@ rec {
Type: String -> String
Example:
mkRelPathStr "/home/user/config" => "config"
mkRelPathStr "config" => "config"
mkRelPathStr "/home/user" => "."
mkRelPathStr "/home/user/config" => "'config'"
mkRelPathStr "config" => "'config'"
mkRelPathStr "/etc/config" => <error>
*/
mkRelPathStr =
pathStr:
let
normPath = stripSlash pathStr;
in
if (!lib.hasPrefix "/" normPath) then
normPath
else if normPath == homeDir then
"."
else if (lib.hasPrefix "${homeDir}/" normPath) then
lib.removePrefix "${homeDir}/" normPath
# is already a relative path
if (!lib.hasPrefix "/" pathStr) then
cleanPathStr pathStr
# is an absolute path within home dir
else if (lib.hasPrefix homeDir pathStr) then
cleanPathStr (lib.removePrefix "${homeDir}/" pathStr)
# is an absolute path not in home dir
else
throw ''
Attempted to convert an absolute path not within home directory to a
@@ -61,47 +54,47 @@ rec {
'';
/*
Convert a relative path to an absolute path.
Returns RAW path (unescaped).
Convert a relative path to an absolute path by prepending the home directory.
This function ensures paths are absolute by prepending the home directory
to relative paths. Already absolute paths are returned unchanged (after cleaning).
This function does NOT support shell variables.
Type: String -> String
Example:
mkAbsPathStr "config" => "/home/user/config"
mkAbsPathStr "/absolute/path" => "/absolute/path"
mkAbsPathStr "config" => "'/home/user/config'"
mkAbsPathStr "/absolute/path" => "'/absolute/path'"
*/
mkAbsPathStr =
pathStr:
let
normPath = stripSlash pathStr;
in
if lib.hasPrefix "/" normPath then normPath else "${homeDir}/${normPath}";
pathStr: cleanPathStr ((lib.optionalString (!lib.hasPrefix "/" pathStr) "${homeDir}/") + pathStr);
/*
Convert a path to absolute form while preserving shell variables.
Returns RAW path (unescaped) unless vars are present (then preserves vars).
Convert a path to absolute form while preserving shell variables for runtime expansion.
This function handles both literal paths and shell variable expressions.
Shell variables (containing '$') are preserved unescaped to allow runtime expansion.
Literal paths are made absolute.
Literal paths are made absolute and properly escaped for shell usage.
Type: String -> String
Example:
mkShellVarPathStr "config" => "/home/user/config"
mkShellVarPathStr "config" => "'/home/user/config'"
mkShellVarPathStr "$HOME/config" => "$HOME/config"
mkShellVarPathStr "\${XDG_CONFIG_HOME:-$HOME/.config}/app" => "\${XDG_CONFIG_HOME:-$HOME/.config}/app"
*/
mkShellVarPathStr =
pathStr:
let
normPath = stripSlash pathStr;
hasShellVars = lib.hasInfix "$" normPath;
cleanPath = lib.removeSuffix "/" pathStr;
hasShellVars = lib.hasInfix "$" cleanPath;
in
if hasShellVars then normPath else mkAbsPathStr normPath;
if hasShellVars then
# Does not escape shell variables, allowing them to be expanded at runtime
cleanPath
else
# For literal paths, make them absolute if needed and escape them
cleanPathStr ((lib.optionalString (!lib.hasPrefix "/" cleanPath) "${homeDir}/") + cleanPath);
dotDirAbs = mkAbsPathStr cfg.dotDir;
dotDirRel = mkRelPathStr cfg.dotDir;
@@ -114,5 +107,5 @@ rec {
Type: String
*/
pluginsDir = dotDirAbs + (lib.optionalString (mkRelPathStr cfg.dotDir == ".") "/.zsh") + "/plugins";
pluginsDir = dotDirAbs + (lib.optionalString (homeDir == dotDirAbs) "/.zsh") + "/plugins";
}

View File

@@ -136,7 +136,7 @@ let
);
in
{
meta.maintainers = [ ];
meta.maintainers = with lib.maintainers; [ foo-dogsquared ];
options.services.activitywatch = {
enable = lib.mkEnableOption "ActivityWatch, an automated time tracker";

View File

@@ -117,8 +117,6 @@ in
systemd.user.services.easyeffects = {
Unit = {
Description = "Easyeffects daemon";
After = [ "graphical-session.target" ];
PartOf = [ "graphical-session.target" ];
};
Install.WantedBy = [ "graphical-session.target" ];

View File

@@ -107,10 +107,10 @@ in
'';
};
defaultEditor = mkOption {
defaultEditor = mkOption rec {
type = types.bool;
default = false;
example = true;
example = !default;
description = ''
Whether to configure {command}`emacsclient` as the default
editor using the {env}`EDITOR` and {env}`VISUAL`

View File

@@ -29,7 +29,7 @@ in
default = { };
description = ''
Configuration settings for hyprshell. All the avaiblable
options can be found here: <https://github.com/H3rmt/hyprshell/blob/hyprshell-release/docs/CONFIGURE.md#config-options>
options can be found here: <https://github.com/H3rmt/hyprshell/blob/hyprshell-release/CONFIGURE.md#config-options>
'';
};
@@ -38,7 +38,7 @@ in
default = "";
description = ''
CSS file for customizing hyprshell. All the available
options can be found here: <https://github.com/H3rmt/hyprshell/blob/hyprshell-release/docs/CONFIGURE.md#css>
options can be found here: <https://github.com/H3rmt/hyprshell/blob/hyprshell-release/CONFIGURE.md#css>
'';
};

View File

@@ -8,6 +8,7 @@ let
inherit (lib) mkOption types;
cfg = config.services.linux-wallpaperengine;
in
{
meta.maintainers = [ lib.hm.maintainers.ckgxrg ];
@@ -104,10 +105,10 @@ in
config = lib.mkIf cfg.enable {
assertions = [
(lib.hm.assertions.assertPlatform "services.linux-wallpaperengine" pkgs lib.platforms.linux)
{
({
assertion = cfg.wallpapers != null;
message = "linux-wallpaperengine: You must set at least one wallpaper";
}
})
];
home.packages = [ cfg.package ];
@@ -120,7 +121,7 @@ in
lib.cli.toGNUCommandLine { } {
screen-root = each.monitor;
inherit (each) scaling fps;
inherit (each.audio) silent;
silent = each.audio.silent;
noautomute = !each.audio.automute;
no-audio-processing = !each.audio.processing;
}
@@ -137,12 +138,11 @@ in
PartOf = [ "graphical-session.target" ];
};
Service = {
ExecStart = lib.concatStringsSep " " (
[ (lib.getExe cfg.package) ]
++ lib.optional (cfg.assetsPath != null) "--assets-dir ${cfg.assetsPath}"
++ lib.optional (cfg.clamping != null) "--clamping ${cfg.clamping}"
++ args
);
ExecStart =
lib.getExe cfg.package
+ (lib.optionalString (cfg.assetsPath != null) " --assets-dir ${cfg.assetsPath} ")
+ (lib.optionalString (cfg.clamping != null) "--clamping ${cfg.clamping} ")
+ (lib.strings.concatStringsSep " " args);
Restart = "on-failure";
};
Install = {

View File

@@ -75,7 +75,7 @@ let
in
{
meta.maintainers = [ ];
meta.maintainers = [ lib.maintainers.foo-dogsquared ];
options.services.mopidy = {
enable = lib.mkEnableOption "Mopidy music player daemon";

View File

@@ -22,15 +22,6 @@ in
package = lib.mkPackageOption pkgs "mpd" { };
enableSessionVariables = mkOption {
type = types.bool;
default = true;
description = ''
Whether to set {env}`MPD_HOST` {env}`MPD_PORT` environment variables
according to {option}`services.mpd.network`.
'';
};
musicDirectory = mkOption {
type = with types; either path str;
defaultText = lib.literalExpression ''
@@ -164,13 +155,7 @@ in
);
in
mkIf cfg.enable {
home = {
packages = [ cfg.package ];
sessionVariables = mkIf cfg.enableSessionVariables {
MPD_HOST = mkIf (cfg.network.listenAddress != "any") cfg.network.listenAddress;
MPD_PORT = builtins.toString cfg.network.port;
};
};
home.packages = [ cfg.package ];
services.mpd = lib.mkMerge [
(mkIf (lib.versionAtLeast config.home.stateVersion "22.11" && config.xdg.userDirs.enable) {

View File

@@ -103,7 +103,7 @@ let
settingsFormat = recollConfFormat { };
in
{
meta.maintainers = [ ];
meta.maintainers = [ lib.maintainers.foo-dogsquared ];
options.services.recoll = {
enable = lib.mkEnableOption "Recoll file index service";

View File

@@ -84,11 +84,13 @@ in
'';
in
{
# $SSH_AUTH_SOCK has to be set early since other tools rely on it
bash.profileExtra = lib.mkIf cfg.enableBashIntegration (lib.mkOrder 900 bashIntegration);
fish.shellInit = lib.mkIf cfg.enableFishIntegration (lib.mkOrder 900 fishIntegration);
nushell.extraConfig = lib.mkIf cfg.enableNushellIntegration (lib.mkOrder 900 nushellIntegration);
zsh.envExtra = lib.mkIf cfg.enableZshIntegration (lib.mkOrder 900 bashIntegration);
bash.initExtra = lib.mkIf cfg.enableBashIntegration bashIntegration;
zsh.initContent = lib.mkIf cfg.enableZshIntegration bashIntegration;
fish.interactiveShellInit = lib.mkIf cfg.enableFishIntegration fishIntegration;
nushell.extraConfig = lib.mkIf cfg.enableNushellIntegration nushellIntegration;
};
}

View File

@@ -430,7 +430,10 @@ in
# Do not install any user services if username is root.
config = mkIf (cfg.enable && config.home.username != "root") {
assertions = [
(lib.hm.assertions.assertPlatform "systemd" pkgs lib.platforms.linux)
{
assertion = pkgs.stdenv.isLinux;
message = "This module is only available on Linux.";
}
];
xdg.configFile = mkMerge [

View File

@@ -274,26 +274,6 @@ in
sed "s|${pkg.out}|$out|g" "$src" > "$dsk"
done
# Patch systemd user services
for svc in "$out/share/systemd/user"/*.service ; do
if ! grep -q "${pkg.out}" "$svc"; then
continue
fi
src="$(readlink "$svc")"
rm "$svc"
sed "s|${pkg.out}|$out|g" "$src" > "$svc"
done
# Patch DBus services
for svc in "$out/share/dbus-1/services"/*.service ; do
if ! grep -q "${pkg.out}" "$svc"; then
continue
fi
src="$(readlink "$svc")"
rm "$svc"
sed "s|${pkg.out}|$out|g" "$src" > "$svc"
done
shopt -u nullglob # Revert nullglob back to its normal default state
'';
}))

View File

@@ -130,13 +130,6 @@ let
stateVersion = lib.mkDefault "18.09";
};
# NOTE: Added 2025-12-27
# Avoid option change deprecation warning
# Remove after deprecation period
programs.zsh.dotDir = lib.mkIf (config.home.stateVersion == "18.09") (
lib.mkDefault "/home/hm-user"
);
# Avoid including documentation since this will cause
# unnecessary rebuilds of the tests.
manual.manpages.enable = lib.mkDefault false;

View File

@@ -1,5 +1,4 @@
{
generators-hyprconf = ./tohyprconf.nix;
generators-tokdl = ./tokdl.nix;
generators-toscfg-empty = ./toscfg-empty.nix;
generators-toscfg-example = ./toscfg-example.nix;

View File

@@ -1,93 +0,0 @@
$important=123
attrs-section {
bool=true
float=0.800000
int=5
null=null
string=abc
}
combined-attrs-nested-section {
a {
a=123
}
a {
b=123
}
a {
c=123
}
b {
a=123
}
b {
b=123
}
b {
c=123
}
}
combined-list-nested-section {
b {
c {
abc=123
}
}
}
combined-list-nested-section {
bar {
baz {
aaa=111
}
}
}
list-section=foo
list-section=bar
list-section=baz
list-with-strings-and-attrs=abc
list-with-strings-and-attrs {
a=123
}
list-with-strings-and-attrs=foo
list-with-strings-and-attrs {
b=321
}
nested-attrs-section {
a {
b {
c {
abc=123
}
}
}
foo {
bar {
baz {
aaa=111
}
}
}
}
nested-list-section {
a=123
}
nested-list-section {
b=123
}
nested-list-section {
c=123
}

View File

@@ -1,70 +0,0 @@
{ lib, ... }:
{
home.file."tohyprconf-result.txt".text = lib.hm.generators.toHyprconf {
attrs = rec {
"$important" = 123;
list-section = [
"foo"
"bar"
"baz"
];
attrs-section = {
string = "abc";
int = 5;
float = 0.8;
bool = true;
null = null;
};
nested-attrs-section = {
a = {
b = {
c = {
abc = 123;
};
};
};
foo = {
bar = {
baz = {
aaa = 111;
};
};
};
};
nested-list-section = [
{ a = 123; }
{ b = 123; }
{ c = 123; }
];
combined-list-nested-section = [
nested-attrs-section.a
nested-attrs-section.foo
];
combined-attrs-nested-section = {
a = nested-list-section;
b = nested-list-section;
};
list-with-strings-and-attrs = [
"abc"
{ a = 123; }
"foo"
{ b = 321; }
];
};
};
nmt.script = ''
assertFileContent \
home-files/tohyprconf-result.txt \
${./tohyprconf-result.txt}
'';
}

View File

@@ -2,5 +2,4 @@
lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux {
anyrun = ./basic-config.nix;
anyrun-empty-css = ./empty-css.nix;
}

View File

@@ -1,10 +0,0 @@
{
programs.anyrun = {
enable = true;
config.plugins = [ ];
};
nmt.script = ''
assertPathNotExists home-files/.config/anyrun/style.css
'';
}

View File

@@ -5,8 +5,6 @@
claude-code-assertion = ./assertion.nix;
claude-code-memory-management = ./memory-management.nix;
claude-code-memory-from-source = ./memory-from-source.nix;
claude-code-rules-dir = ./rules-dir.nix;
claude-code-rules-path = ./rules-path.nix;
claude-code-agents-dir = ./agents-dir.nix;
claude-code-commands-dir = ./commands-dir.nix;
claude-code-hooks-dir = ./hooks-dir.nix;

View File

@@ -1,14 +0,0 @@
{
programs.claude-code = {
enable = true;
rulesDir = ./rules;
};
nmt.script = ''
assertFileExists home-files/.claude/rules/test-rule.md
assertLinkExists home-files/.claude/rules/test-rule.md
assertFileContent \
home-files/.claude/rules/test-rule.md \
${./rules/test-rule.md}
'';
}

View File

@@ -1,20 +0,0 @@
{
programs.claude-code = {
enable = true;
rules = {
test-rule = ./test-rule.md;
inline-rule = ''
# Inline Rule
This is an inline rule for testing.
'';
};
};
nmt.script = ''
assertFileExists home-files/.claude/rules/test-rule.md
assertFileContent home-files/.claude/rules/test-rule.md \
${./test-rule.md}
assertFileExists home-files/.claude/rules/inline-rule.md
'';
}

View File

@@ -1,9 +0,0 @@
# Test Rule from Directory
This is a test rule loaded from a directory.
Used to verify rulesDir support functionality.
## Best Practices
- Write clean code
- Test thoroughly

View File

@@ -1,9 +0,0 @@
# Test Rule
This is a test rule loaded from a file path.
Used to verify path support functionality for rules.
## Guidelines
- Follow test conventions
- Maintain code quality

View File

@@ -41,9 +41,5 @@
assertFileExists home-files/.gemini/CONTEXT.md
assertFileContent home-files/.gemini/CONTEXT.md \
${./context-additional.md}
assertFileExists home-path/etc/profile.d/hm-session-vars.sh
assertFileNotRegex home-path/etc/profile.d/hm-session-vars.sh \
"GEMINI_MODEL"
'';
}

View File

@@ -1,7 +1,6 @@
{
programs.gemini-cli = {
enable = true;
defaultModel = "gemini-2.5-flash";
settings = {
theme = "Default";
vimMode = true;
@@ -29,9 +28,5 @@
${./changelog.toml}
assertFileContent home-files/.gemini/commands/git/fix.toml \
${./fix.toml}
assertFileExists home-path/etc/profile.d/hm-session-vars.sh
assertFileContains home-path/etc/profile.d/hm-session-vars.sh \
'export GEMINI_MODEL="gemini-2.5-flash"'
'';
}

View File

@@ -5,9 +5,6 @@ identity = "hm@example.com"
root_mailbox = "/home/hm-user/Mail/hm@example.com"
subscribed_mailboxes = ["Inbox", "Sent", "Trash", "Drafts"]
[accounts."hm@example.com".listing]
index_style = "compact"
[accounts."hm@example.com".mailboxes]
[accounts."hm@example.com".send_mail]

View File

@@ -16,12 +16,7 @@
};
accounts.email.accounts = {
"hm@example.com" = {
meli = {
enable = true;
settings = {
listing.index_style = "compact";
};
};
meli.enable = true;
smtp.port = 1848;
};
};

View File

@@ -2,7 +2,6 @@
neovim-plugin-config = ./plugin-config.nix;
neovim-coc-config = ./coc-config.nix;
neovim-runtime = ./runtime.nix;
neovim-wrapper-args = ./wrapper-args.nix;
# waiting for a nixpkgs patch
neovim-no-init = ./no-init.nix;

View File

@@ -34,25 +34,15 @@ lib.mkIf config.test.enableBig {
_module.args.pkgs = lib.mkForce realPkgs;
nmt.script =
let
# Force evaluation of generatedConfigs.
luaConfig = config.programs.neovim.generatedConfigs.lua;
vimlConfig = config.programs.neovim.generatedConfigs.viml;
in
''
vimout=$(mktemp)
echo "redir >> /dev/stdout | echo g:hmExtraConfig | echo g:hmPlugins | redir END" \
| ${pkgs.neovim}/bin/nvim -es -u "$TESTED/home-files/.config/nvim/init.lua" \
> "$vimout" || true
assertFileContains "$vimout" "HM_EXTRA_CONFIG"
assertFileContains "$vimout" "HM_PLUGINS_CONFIG"
nmt.script = ''
vimout=$(mktemp)
echo "redir >> /dev/stdout | echo g:hmExtraConfig | echo g:hmPlugins | redir END" \
| ${pkgs.neovim}/bin/nvim -es -u "$TESTED/home-files/.config/nvim/init.lua" \
> "$vimout" || true
assertFileContains "$vimout" "HM_EXTRA_CONFIG"
assertFileContains "$vimout" "HM_PLUGINS_CONFIG"
initLua="$TESTED/home-files/.config/nvim/init.lua"
assertFileContent $(normalizeStorePaths "$initLua") ${./plugin-config.expected}
# Verify generatedConfigs evaluated properly (issue #8371)
echo "Lua config length: ${toString (builtins.stringLength luaConfig)}"
echo "Viml config length: ${toString (builtins.stringLength vimlConfig)}"
'';
initLua="$TESTED/home-files/.config/nvim/init.lua"
assertFileContent $(normalizeStorePaths "$initLua") ${./plugin-config.expected}
'';
}

View File

@@ -1,83 +0,0 @@
{
lib,
pkgs,
...
}:
let
inherit (pkgs.stdenv.hostPlatform) isLinux;
dummyDep = pkgs.runCommand "dummy-dep" { } ''
mkdir -p $out/bin
echo "echo dummy" > $out/bin/dummy-dep-bin
chmod +x $out/bin/dummy-dep-bin
'';
dummyPlugin = pkgs.vimUtils.buildVimPlugin {
pname = "dummy-plugin";
version = "1.0";
src = pkgs.writeTextDir "plugin/dummy.vim" "\" dummy";
runtimeDeps = [ dummyDep ];
};
in
{
imports = [ ./stubs.nix ];
tests.stubs.wl-clipboard = { };
programs.neovim = {
enable = true;
extraName = "-my-suffix";
withPerl = true;
withPython3 = true;
withRuby = true;
withNodeJs = true;
autowrapRuntimeDeps = true;
waylandSupport = isLinux;
plugins = [ dummyPlugin ];
};
nmt.script = ''
nvimBin="home-path/bin/nvim"
assertBinaryContains() {
local file="$TESTED/$1"
if [[ $1 == /* ]]; then file="$1"; fi
if ! grep -a -qF -- "$2" "$file"; then
fail "Expected binary file '$1' to contain '$2' but it did not."
fi
}
# Ensure the main binary exists
assertFileExists "$nvimBin"
# 1. extraName: Check if the suffix is in the rplugin manifest path within the wrapper
assertBinaryContains "$nvimBin" "-my-suffix/rplugin.vim"
# 2. withPerl: Check if nvim-perl binary exists and host prog is set
assertFileExists "home-path/bin/nvim-perl"
assertBinaryContains "$nvimBin" "perl_host_prog="
# 3. withPython3: Check if nvim-python3 binary exists and host prog is set
assertFileExists "home-path/bin/nvim-python3"
assertBinaryContains "$nvimBin" "python3_host_prog="
# 4. withRuby: Check if nvim-ruby binary exists, GEM_HOME and host prog are set
assertFileExists "home-path/bin/nvim-ruby"
assertBinaryContains "$nvimBin" "GEM_HOME="
assertBinaryContains "$nvimBin" "ruby_host_prog="
# 5. withNodeJs: Check if nvim-node binary exists and host prog is set
assertFileExists "home-path/bin/nvim-node"
assertBinaryContains "$nvimBin" "node_host_prog="
# 6. waylandSupport: Check for wl-clipboard path in wrapper's PATH modification
# We check for the store path of wl-clipboard in the current pkgs
${lib.optionalString isLinux ''
assertBinaryContains "$nvimBin" "wl-clipboard-"
''}
# 7. autowrapRuntimeDeps: Check for dummyDep path in wrapper's PATH modification
assertBinaryContains "$nvimBin" "${dummyDep}/bin"
'';
}

View File

@@ -9,10 +9,6 @@
opencode-agents-path = ./agents-path.nix;
opencode-commands-path = ./commands-path.nix;
opencode-mixed-content = ./mixed-content.nix;
opencode-skills-inline = ./skills-inline.nix;
opencode-skills-path = ./skills-path.nix;
opencode-skills-directory = ./skills-directory.nix;
opencode-skills-bulk-directory = ./skills-bulk-directory.nix;
opencode-themes-inline = ./themes-inline.nix;
opencode-themes-path = ./themes-path.nix;
opencode-mcp-integration = ./mcp-integration.nix;

View File

@@ -1,10 +0,0 @@
---
name: git-release
description: Create consistent releases and changelogs
---
## What I do
- Draft release notes from merged PRs
- Propose a version bump
- Provide a copy-pasteable `gh release create` command

View File

@@ -1,9 +0,0 @@
---
name: pdf-processing
description: Extract text and tables from PDF files
---
## What I do
- Extract text from PDFs
- Identify tables and structured data

View File

@@ -1,9 +0,0 @@
---
name: data-analysis
description: Help analyze datasets and results
---
## What I do
- Summarize datasets
- Suggest charts and metrics

View File

@@ -1 +0,0 @@
extra fixture file

View File

@@ -1,15 +0,0 @@
{
programs.opencode = {
enable = true;
skills = ./skills-bulk;
};
nmt.script = ''
assertFileExists home-files/.config/opencode/skill/git-release/SKILL.md
assertFileExists home-files/.config/opencode/skill/pdf-processing/SKILL.md
assertFileContent home-files/.config/opencode/skill/git-release/SKILL.md \
${./skills-bulk/git-release/SKILL.md}
assertFileContent home-files/.config/opencode/skill/pdf-processing/SKILL.md \
${./skills-bulk/pdf-processing/SKILL.md}
'';
}

View File

@@ -1,6 +0,0 @@
---
name: git-release
description: Create consistent releases and changelogs
---
This is the bulk skillsDir fixture for git-release.

View File

@@ -1,6 +0,0 @@
---
name: pdf-processing
description: Extract text and tables from PDF files
---
This is the bulk skillsDir fixture for pdf-processing.

View File

@@ -1,15 +0,0 @@
{
programs.opencode = {
enable = true;
skills = {
data-analysis = ./skill-dir/data-analysis;
};
};
nmt.script = ''
assertFileExists home-files/.config/opencode/skill/data-analysis/SKILL.md
assertFileExists home-files/.config/opencode/skill/data-analysis/notes.txt
assertFileContent home-files/.config/opencode/skill/data-analysis/SKILL.md \
${./skill-dir/data-analysis/SKILL.md}
'';
}

View File

@@ -1,25 +0,0 @@
{
programs.opencode = {
enable = true;
skills = {
git-release = ''
---
name: git-release
description: Create consistent releases and changelogs
---
## What I do
- Draft release notes from merged PRs
- Propose a version bump
- Provide a copy-pasteable `gh release create` command
'';
};
};
nmt.script = ''
assertFileExists home-files/.config/opencode/skill/git-release/SKILL.md
assertFileContent home-files/.config/opencode/skill/git-release/SKILL.md \
${./git-release-SKILL.md}
'';
}

View File

@@ -1,14 +0,0 @@
{
programs.opencode = {
enable = true;
skills = {
pdf-processing = ./pdf-processing-SKILL.md;
};
};
nmt.script = ''
assertFileExists home-files/.config/opencode/skill/pdf-processing/SKILL.md
assertFileContent home-files/.config/opencode/skill/pdf-processing/SKILL.md \
${./pdf-processing-SKILL.md}
'';
}

View File

@@ -1,19 +1,9 @@
{ config, pkgs, ... }:
{
config = {
programs.radicle.enable = true;
test.stubs.radicle-node = {
buildScript = ''
mkdir -p "$out/bin"
cat > "$out/bin/rad" << 'EOF'
#!/bin/sh
# Stub rad command that does nothing
exit 0
EOF
chmod +x "$out/bin/rad"
'';
};
nmt.script = ''
assertFileContent \
home-files/.radicle/config.json \

View File

@@ -15,7 +15,6 @@ Host xyz
RemoteForward [localhost]:8081 [10.0.0.2]:80
RemoteForward /run/user/1000/gnupg/S.gpg-agent.extra /run/user/1000/gnupg/S.gpg-agent
DynamicForward [localhost]:2839
KexAlgorithms sntrup761x25519-sha512,sntrup761x25519-sha512@openssh.com,mlkem768x25519-sha256
Host ordered
Port 1

View File

@@ -34,11 +34,6 @@
host.address = "/run/user/1000/gnupg/S.gpg-agent";
}
];
kexAlgorithms = [
"sntrup761x25519-sha512"
"sntrup761x25519-sha512@openssh.com"
"mlkem768x25519-sha256"
];
dynamicForwards = [ { port = 2839; } ];
setEnv = {
FOO = "foo12";

View File

@@ -1,5 +1,4 @@
{ lib, pkgs, ... }:
lib.optionalAttrs (pkgs.stdenv.hostPlatform.isLinux) {
vicinae-pre17-settings = ./pre17-settings.nix;
vicinae-example-settings = ./example-settings.nix;
}

View File

@@ -8,7 +8,7 @@
programs.vicinae = {
enable = true;
systemd.enable = true;
useLayerShell = false;
settings = {
faviconService = "twenty";
font = {
@@ -80,16 +80,11 @@
];
};
test.asserts.assertions.expected = [
''After version 0.17, if you want to explicitly disable the use of layer shell, you need to set {option}.programs.vicinae.settings.launcher_window.layer_shell.enabled = false.''
];
nmt.script = ''
assertFileExists "home-files/.config/vicinae/settings.json"
assertFileExists "home-files/.config/vicinae/vicinae.json"
assertFileExists "home-files/.config/systemd/user/vicinae.service"
assertFileExists "home-files/.local/share/vicinae/themes/catppuccin-mocha.toml"
assertFileExists "home-files/.local/share/vicinae/extensions/gif-search/package.json"
assertFileExists "home-files/.local/share/vicinae/extensions/test-extension/package.json"
assertFileContent "home-files/.config/systemd/user/vicinae.service" ${./service.service}
'';
}

View File

@@ -1,46 +0,0 @@
{
pkgs,
config,
...
}:
{
programs.vicinae = {
enable = true;
systemd.enable = true;
package = pkgs.stdenv.mkDerivation {
pname = "fake-vicinae";
version = "0.10.0";
src = pkgs.emptyFile;
buildCommand = "mkdir -p $out";
meta = {
mainProgram = "vicinae";
};
};
settings = {
faviconService = "twenty";
font = {
size = 10;
};
popToRootOnClose = false;
rootSearch = {
searchFiles = false;
};
theme = {
name = "vicinae-dark";
};
window = {
csd = true;
opacity = 0.95;
rounding = 10;
};
};
};
nmt.script = ''
assertFileExists "home-files/.config/vicinae/vicinae.json"
assertFileExists "home-files/.config/systemd/user/vicinae.service"
assertFileContains "home-files/.config/systemd/user/vicinae.service" "EnvironmentFile"
'';
}

View File

@@ -1,15 +0,0 @@
[Install]
WantedBy=graphical-session.target
[Service]
ExecStart=@vicinae@/bin/vicinae server
KillMode=process
Restart=always
RestartSec=5
Type=simple
[Unit]
After=graphical-session.target
Description=Vicinae server daemon
Documentation=https://docs.vicinae.com
PartOf=graphical-session.target

View File

@@ -1,4 +1,5 @@
{ lib, pkgs, ... }:
lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux {
yt-dlp-config = ./yt-dlp-config.nix;
yt-dlp-simple-config = ./yt-dlp-simple-config.nix;
yt-dlp-extraConfig = ./yt-dlp-extraConfig.nix;
}

View File

@@ -0,0 +1,2 @@
--config-locations /home/user/.yt-dlp.conf

View File

@@ -0,0 +1,13 @@
{
programs.yt-dlp = {
enable = true;
extraConfig = ''
--config-locations /home/user/.yt-dlp.conf
'';
};
nmt.script = ''
assertFileExists home-files/.config/yt-dlp/config
assertFileContent home-files/.config/yt-dlp/config ${./yt-dlp-extraConfig-expected}
'';
}

View File

@@ -1,5 +1,3 @@
--color stdout:no_color
--color stderr:always
--downloader aria2c
--downloader-args aria2c:'-c -x8 -s8 -k1M'
--no-embed-subs

View File

@@ -8,10 +8,6 @@
downloader = "aria2c";
downloader-args = "aria2c:'-c -x8 -s8 -k1M'";
trim-filenames = 30;
color = [
"stdout:no_color"
"stderr:always"
];
};
extraConfig = ''
--config-locations /home/user/.yt-dlp.conf
@@ -20,6 +16,6 @@
nmt.script = ''
assertFileExists home-files/.config/yt-dlp/config
assertFileContent home-files/.config/yt-dlp/config ${./yt-dlp-config-expected}
assertFileContent home-files/.config/yt-dlp/config ${./yt-dlp-simple-config-expected}
'';
}

View File

@@ -3,13 +3,6 @@
zsh-aliases = ./aliases.nix;
zsh-dotdir-absolute = import ./dotdir.nix "absolute";
zsh-dotdir-default = import ./dotdir.nix "default";
zsh-dotdir-path-normalization-abs-no-slash = import ./dotdir.nix "abs-no-slash";
zsh-dotdir-path-normalization-abs-slash = import ./dotdir.nix "abs-slash";
zsh-dotdir-path-normalization-rel-no-slash = import ./dotdir.nix "rel-no-slash";
zsh-dotdir-path-normalization-rel-slash = import ./dotdir.nix "rel-slash";
zsh-dotdir-path-normalization-root-no-slash = import ./dotdir.nix "root-no-slash";
zsh-dotdir-path-normalization-root-slash = import ./dotdir.nix "root-slash";
zsh-dotdir-path-normalization-abs-space = import ./dotdir.nix "abs-space";
zsh-dotdir-relative = import ./dotdir.nix "relative";
zsh-dotdir-shell-variable = import ./dotdir.nix "shell-variable";
zsh-history-ignore-pattern = ./history-ignore-pattern.nix;
@@ -19,15 +12,12 @@
zsh-history-path-xdg-variable = import ./history-path.nix "xdg-variable";
zsh-history-path-zdotdir-variable = import ./history-path.nix "zdotdir-variable";
zsh-history-substring-search = ./history-substring-search.nix;
zsh-legacy-warning = ./legacy-warning.nix;
zsh-siteFunctions-mkcd = ./siteFunctions-mkcd.nix;
zsh-plugins = ./plugins.nix;
zsh-prezto = ./prezto.nix;
zsh-session-variables = ./session-variables.nix;
zsh-smart-formatting = ./smart-formatting.nix;
zsh-syntax-highlighting = ./syntax-highlighting.nix;
zsh-xdg-default = ./xdg-default.nix;
zsh-xdg-disabled = ./xdg-disabled.nix;
zsh-zprof = ./zprof.nix;
zshrc-contents-priorities = ./zshrc-content-priorities.nix;
}

View File

@@ -7,46 +7,24 @@ case:
}:
let
home = config.home.homeDirectory;
subDir = "subdir/subdir2";
dotDirCases = {
absolute = "${home}/${subDir}";
relative = subDir;
inherit (options.programs.zsh.dotDir) default;
shell-variable = "\${XDG_CONFIG_HOME:-$HOME/.config}/zsh";
# Path normalization cases
abs-no-slash = "${home}/subdir";
abs-slash = "${home}/subdir/";
rel-no-slash = "subdir";
rel-slash = "subdir/";
root-no-slash = "${home}";
root-slash = "${home}/";
abs-space = "${home}/subdir with space";
};
dotDir = dotDirCases.${case} or (abort "Unknown case: ${case}");
# Normalize absolute path to match module behavior (no trailing slash)
absDotDir =
dotDir =
let
fullPath = if lib.hasPrefix "/" dotDir then dotDir else "${home}/${dotDir}";
subDir = "subdir/subdir2";
in
lib.removeSuffix "/" fullPath;
if case == "absolute" then
"${home}/${subDir}"
else if case == "relative" then
subDir
else if case == "default" then
options.programs.zsh.dotDir.default
else if case == "shell-variable" then
"\${XDG_CONFIG_HOME:-\$HOME/.config}/zsh"
else
abort "Test condition not provided.";
# Calculate relative path for file location assertions
relDotDir =
let
# Use the normalized absDotDir to determine relative location
rawRel = lib.removePrefix home absDotDir;
in
if lib.hasPrefix "/" rawRel then lib.removePrefix "/" rawRel else rawRel;
isRelative = lib.elem case [
"relative"
"rel-no-slash"
"rel-slash"
];
absDotDir = lib.optionalString (!lib.hasPrefix home dotDir) "${home}/" + dotDir;
relDotDir = lib.removePrefix home dotDir;
in
{
config = {
@@ -55,37 +33,33 @@ in
inherit dotDir;
};
test = {
stubs.zsh = { };
test.stubs.zsh = { };
asserts = {
assertions.expected = lib.optionals (case == "shell-variable") [
''
programs.zsh.dotDir cannot contain shell variables as it is used for file creation at build time.
Current dotDir: ''${XDG_CONFIG_HOME:-''$HOME/.config}/zsh
Consider using an absolute path or home-manager config options instead.
You can replace shell variables with options like:
- config.home.homeDirectory (user's home directory)
- config.xdg.configHome (XDG config directory)
- config.xdg.dataHome (XDG data directory)
- config.xdg.cacheHome (XDG cache directory)
''
];
test.asserts.warnings.expected = lib.optionals (case == "relative") [
''
Using relative paths in programs.zsh.dotDir is deprecated and will be removed in a future release.
Current dotDir: subdir/subdir2
Consider using absolute paths or home-manager config options instead.
You can replace relative paths or environment variables with options like:
- config.home.homeDirectory (user's home directory)
- config.xdg.configHome (XDG config directory)
- config.xdg.dataHome (XDG data directory)
- config.xdg.cacheHome (XDG cache directory)
''
];
warnings.expected = lib.optionals isRelative [
''
Using relative paths in programs.zsh.dotDir is deprecated and will be removed in a future release.
Current dotDir: ${dotDir}
Consider using absolute paths or home-manager config options instead.
You can replace relative paths or environment variables with options like:
- config.home.homeDirectory (user's home directory)
- config.xdg.configHome (XDG config directory)
- config.xdg.dataHome (XDG data directory)
- config.xdg.cacheHome (XDG cache directory)
''
];
};
};
test.asserts.assertions.expected = lib.optionals (case == "shell-variable") [
''
programs.zsh.dotDir cannot contain shell variables as it is used for file creation at build time.
Current dotDir: ''${XDG_CONFIG_HOME:-''$HOME/.config}/zsh
Consider using an absolute path or home-manager config options instead.
You can replace shell variables with options like:
- config.home.homeDirectory (user's home directory)
- config.xdg.configHome (XDG config directory)
- config.xdg.dataHome (XDG data directory)
- config.xdg.cacheHome (XDG cache directory)
''
];
nmt.script =
if case == "shell-variable" then
@@ -96,15 +70,17 @@ in
else
lib.concatStringsSep "\n" [
# check dotDir entrypoint exists
"assertFileExists 'home-files/${if relDotDir == "" then "" else "${relDotDir}/"}.zshenv'"
"assertFileExists home-files/${relDotDir}/.zshenv"
# for non-default dotDir only:
(lib.optionalString (absDotDir != home) ''
(lib.optionalString (case != "default") ''
# check .zshenv in homeDirectory sources .zshenv in dotDir
assertFileRegex home-files/.zshenv "source ${lib.escapeShellArg "${absDotDir}/.zshenv"}"
assertFileRegex home-files/.zshenv \
"source [\"']\?${absDotDir}/.zshenv[\"']\?"
# check that .zshenv in dotDir exports ZDOTDIR
assertFileRegex 'home-files/${relDotDir}/.zshenv' "export ZDOTDIR=\"${absDotDir}\""
assertFileRegex home-files/${relDotDir}/.zshenv \
"export ZDOTDIR=[\"']\?${absDotDir}[\"']\?"
'')
];
};

View File

@@ -68,7 +68,7 @@ in
else if case == "zdotdir-variable" then
''
assertFileContains home-files/.config/zsh/.zshrc 'HISTFILE="$ZDOTDIR/.zsh_history"'
assertFileContains home-files/.config/zsh/.zshenv "export ZDOTDIR=\"${homeDir}/.config/zsh\""
assertFileContains home-files/.config/zsh/.zshenv "export ZDOTDIR=${homeDir}/.config/zsh"
''
else
''

View File

@@ -1,29 +0,0 @@
{
config = {
programs.zsh.enable = true;
xdg.enable = true;
# We use 25.05 to trigger the legacy warning (since < 26.05)
# AND to bypass the global fix in tests/default.nix (which checks for 18.09)
home.stateVersion = "25.05";
nmt.script = ''
assertFileExists home-files/.zshrc
# Verify that the warning is generated
# We check the evaluation output for the warning message
assertPathNotExists home-files/.config/zsh
'';
test.asserts.warnings.expected = [
''
The default value of `programs.zsh.dotDir` will change in future versions.
You are currently using the legacy default (home directory) because `home.stateVersion` is less than "26.05".
To silence this warning and lock in the current behavior, set:
programs.zsh.dotDir = config.home.homeDirectory;
To adopt the new behavior (XDG config directory), set:
programs.zsh.dotDir = "''${config.xdg.configHome}/zsh";
''
];
};
}

View File

@@ -1,21 +0,0 @@
{
config = {
programs.zsh.enable = true;
home.stateVersion = "26.05";
xdg.enable = true;
# With xdg.enable = true and new state version, dotDir should default to XDG config home
nmt.script = ''
assertFileExists home-files/.config/zsh/.zshenv
assertFileExists home-files/.config/zsh/.zshrc
# Verify global .zshenv points to the XDG location
assertFileExists home-files/.zshenv
assertFileRegex home-files/.zshenv "source /home/hm-user/.config/zsh/.zshenv"
# Verify ZDOTDIR is exported in the inner .zshenv
assertFileRegex home-files/.config/zsh/.zshenv "export ZDOTDIR=\"/home/hm-user/.config/zsh\""
'';
};
}

View File

@@ -1,20 +0,0 @@
{
config = {
programs.zsh.enable = true;
xdg.enable = false;
home.stateVersion = "26.05";
# With xdg.enable = false, dotDir should default to home directory regardless of state version
nmt.script = ''
assertFileExists home-files/.zshenv
assertFileExists home-files/.zshrc
# Should NOT exist in XDG location
assertPathNotExists home-files/.config/zsh
# Verify ZDOTDIR is NOT exported (or points to home if it is, but usually it isn't if dotDir is home)
assertFileNotRegex home-files/.zshenv "export ZDOTDIR="
'';
};
}

View File

@@ -13,10 +13,6 @@ animations {
enabled=true
}
bindm=$mod, mouse:272, movewindow
bindm=$mod, mouse:273, resizewindow
bindm=$mod ALT, mouse:272, resizewindow
decoration {
col.shadow=rgba(00000099)
shadow_offset=0 5
@@ -49,6 +45,9 @@ plugin {
dummy=plugin setting
}
}
bindm=$mod, mouse:272, movewindow
bindm=$mod, mouse:273, resizewindow
bindm=$mod ALT, mouse:272, resizewindow
# window resize
bind = $mod, S, submap, resize

View File

@@ -13,10 +13,6 @@ animations {
enabled=true
}
bindm=$mod, mouse:272, movewindow
bindm=$mod, mouse:273, resizewindow
bindm=$mod ALT, mouse:272, resizewindow
decoration {
col.shadow=rgba(00000099)
shadow_offset=0 5
@@ -52,6 +48,9 @@ plugin {
dummy=plugin setting
}
}
bindm=$mod, mouse:272, movewindow
bindm=$mod, mouse:273, resizewindow
bindm=$mod ALT, mouse:272, resizewindow
# window resize
bind = $mod, S, submap, resize

View File

@@ -10,5 +10,4 @@ input {
follow_mouse=1
kb_layout=ro
}
source=sourced.conf

View File

@@ -17,7 +17,6 @@ submap = reset
submap = resize
bind=, escape, submap, reset
bind=, return, submap, reset
binde=, right, resizeactive, 10 0
binde=, left, resizeactive, -10 0
binde=, up, resizeactive, 0 -10

View File

@@ -1,8 +1,7 @@
ipc=on
preload=/share/wallpapers/buttons.png
preload=/share/wallpapers/cat_pacman.png
wallpaper=DP-3,/share/wallpapers/buttons.png
wallpaper=DP-1,/share/wallpapers/cat_pacman.png
ipc=on
splash=false
splash_offset=2.000000
wallpaper=DP-3,/share/wallpapers/buttons.png
wallpaper=DP-1,/share/wallpapers/cat_pacman.png

View File

@@ -3,5 +3,4 @@
lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux {
linux-wallpaperengine-basic-configuration = ./basic-configuration.nix;
linux-wallpaperengine-null-options = ./null-options.nix;
linux-wallpaperengine-missing-spaces = ./missing-spaces.nix;
}

View File

@@ -1,11 +0,0 @@
[Install]
WantedBy=graphical-session.target
[Service]
ExecStart=@linux-wallpaperengine@/bin/linux-wallpaperengine --screen-root HDMI-A-1 --bg 2902931482
Restart=on-failure
[Unit]
After=graphical-session.target
Description=Implementation of Wallpaper Engine on Linux
PartOf=graphical-session.target

View File

@@ -1,17 +0,0 @@
{
services.linux-wallpaperengine = {
enable = true;
wallpapers = [
{
monitor = "HDMI-A-1";
wallpaperId = "2902931482";
}
];
};
nmt.script = ''
assertFileContent \
home-files/.config/systemd/user/linux-wallpaperengine.service \
${./missing-spaces-expected.service}
'';
}

View File

@@ -8,7 +8,7 @@
nmt.script = ''
assertFileContains \
home-files/.profile \
home-files/.bashrc \
'export SSH_AUTH_SOCK=$(@getconf-system_cmds@/bin/getconf DARWIN_USER_TEMP_DIR)/ssh-agent'
'';
}