mirror of
https://github.com/nix-community/home-manager
synced 2024-11-23 11:39:46 +01:00
home-manager: add news sub-command
This command allows the user to examine the news items generated by the news module. See #52. Many thanks to @nonsequitur and @uvNikita for suggestions and improvements.
This commit is contained in:
parent
ab0338f6ae
commit
9c1b3735b4
4 changed files with 195 additions and 13 deletions
|
@ -23,6 +23,7 @@ pkgs.stdenv.mkDerivation {
|
||||||
substituteInPlace $out/bin/home-manager \
|
substituteInPlace $out/bin/home-manager \
|
||||||
--subst-var-by bash "${pkgs.bash}" \
|
--subst-var-by bash "${pkgs.bash}" \
|
||||||
--subst-var-by coreutils "${pkgs.coreutils}" \
|
--subst-var-by coreutils "${pkgs.coreutils}" \
|
||||||
|
--subst-var-by less "${pkgs.less}" \
|
||||||
--subst-var-by MODULES_PATH '${modulesPathStr}' \
|
--subst-var-by MODULES_PATH '${modulesPathStr}' \
|
||||||
--subst-var-by HOME_MANAGER_EXPR_PATH "${./home-manager.nix}"
|
--subst-var-by HOME_MANAGER_EXPR_PATH "${./home-manager.nix}"
|
||||||
'';
|
'';
|
||||||
|
|
|
@ -3,12 +3,12 @@
|
||||||
# This code explicitly requires GNU Core Utilities and we therefore
|
# This code explicitly requires GNU Core Utilities and we therefore
|
||||||
# need to ensure they are prioritized over any other similarly named
|
# need to ensure they are prioritized over any other similarly named
|
||||||
# tools on the system.
|
# tools on the system.
|
||||||
PATH=@coreutils@/bin:$PATH
|
PATH=@coreutils@/bin:@less@/bin${PATH:+:}$PATH
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
function errorEcho() {
|
function errorEcho() {
|
||||||
>&2 echo "$*"
|
echo $* >&2
|
||||||
}
|
}
|
||||||
|
|
||||||
# Attempts to set the HOME_MANAGER_CONFIG global variable.
|
# Attempts to set the HOME_MANAGER_CONFIG global variable.
|
||||||
|
@ -52,12 +52,11 @@ function setHomeManagerModulesPath() {
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
function doBuild() {
|
function doBuildAttr() {
|
||||||
setConfigFile
|
setConfigFile
|
||||||
setHomeManagerModulesPath
|
setHomeManagerModulesPath
|
||||||
|
|
||||||
local extraArgs
|
local extraArgs="$*"
|
||||||
extraArgs="$1"
|
|
||||||
|
|
||||||
for p in "${EXTRA_NIX_PATH[@]}"; do
|
for p in "${EXTRA_NIX_PATH[@]}"; do
|
||||||
extraArgs="$extraArgs -I $p"
|
extraArgs="$extraArgs -I $p"
|
||||||
|
@ -67,11 +66,53 @@ function doBuild() {
|
||||||
extraArgs="$extraArgs --show-trace"
|
extraArgs="$extraArgs --show-trace"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
nix-build $extraArgs \
|
nix-build \
|
||||||
"@HOME_MANAGER_EXPR_PATH@" \
|
"@HOME_MANAGER_EXPR_PATH@" \
|
||||||
|
$extraArgs \
|
||||||
--argstr confPath "$HOME_MANAGER_CONFIG" \
|
--argstr confPath "$HOME_MANAGER_CONFIG" \
|
||||||
--argstr confAttr "$HOME_MANAGER_CONFIG_ATTRIBUTE" \
|
--argstr confAttr "$HOME_MANAGER_CONFIG_ATTRIBUTE"
|
||||||
-A activationPackage
|
}
|
||||||
|
|
||||||
|
function presentNews() {
|
||||||
|
local infoFile
|
||||||
|
infoFile=$(doBuildNews -A newsInfo) || return 1
|
||||||
|
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "$infoFile"
|
||||||
|
|
||||||
|
if [[ $newsNumUnread -eq 0 ]]; then
|
||||||
|
return
|
||||||
|
elif [[ "$newsDisplay" == "silent" ]]; then
|
||||||
|
return
|
||||||
|
elif [[ "$newsDisplay" == "notify" ]]; then
|
||||||
|
local msg
|
||||||
|
if [[ $newsNumUnread -eq 1 ]]; then
|
||||||
|
msg="There is an unread and relevant news item.\n"
|
||||||
|
msg+="Read it by running the command '$(basename "$0") news'."
|
||||||
|
else
|
||||||
|
msg="There are $newsNumUnread unread and relevant news items.\n"
|
||||||
|
msg+="Read them by running the command '$(basename "$0") news'."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Not actually an error but here stdout is reserved for
|
||||||
|
# nix-build output.
|
||||||
|
errorEcho
|
||||||
|
errorEcho -e "$msg"
|
||||||
|
errorEcho
|
||||||
|
|
||||||
|
if [[ -v DISPLAY ]] && type -P notify-send > /dev/null; then
|
||||||
|
notify-send "Home Manager" "$msg"
|
||||||
|
fi
|
||||||
|
elif [[ "$newsDisplay" == "show" ]]; then
|
||||||
|
doShowNews --unread
|
||||||
|
else
|
||||||
|
errorEcho "Unknown 'news.display' setting '$newsDisplay'."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function doBuild() {
|
||||||
|
doBuildAttr -A activationPackage
|
||||||
|
presentNews
|
||||||
}
|
}
|
||||||
|
|
||||||
function doSwitch() {
|
function doSwitch() {
|
||||||
|
@ -84,12 +125,17 @@ function doSwitch() {
|
||||||
# prevents an unfortunately timed GC from removing the generation
|
# prevents an unfortunately timed GC from removing the generation
|
||||||
# before activation completes.
|
# before activation completes.
|
||||||
wrkdir="$(mktemp -d)"
|
wrkdir="$(mktemp -d)"
|
||||||
generation=$(doBuild "-o $wrkdir/result") && $generation/activate || exitCode=1
|
generation=$(doBuildAttr -o "$wrkdir/result" -A activationPackage) \
|
||||||
|
&& $generation/activate || exitCode=1
|
||||||
|
|
||||||
# Because the previous command never fails, the script keeps
|
# Because the previous command never fails, the script keeps
|
||||||
# running and $wrkdir is always removed.
|
# running and $wrkdir is always removed.
|
||||||
rm -r "$wrkdir"
|
rm -r "$wrkdir"
|
||||||
|
|
||||||
|
if [[ $exitCode -eq 0 ]]; then
|
||||||
|
presentNews
|
||||||
|
fi
|
||||||
|
|
||||||
return $exitCode
|
return $exitCode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,6 +156,53 @@ function doListPackages() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function newsReadIdsFile() {
|
||||||
|
local dataDir="${XDG_DATA_HOME:-$HOME/.local/share}/home-manager"
|
||||||
|
local path="$dataDir/news-read-ids"
|
||||||
|
|
||||||
|
# If the path doesn't exist then we should create it, otherwise
|
||||||
|
# Nix will error out when we attempt to use builtins.readFile.
|
||||||
|
if [[ ! -f "$path" ]]; then
|
||||||
|
mkdir -p "$dataDir"
|
||||||
|
touch "$path"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$path"
|
||||||
|
}
|
||||||
|
|
||||||
|
function doBuildNews() {
|
||||||
|
doBuildAttr "$*" \
|
||||||
|
--no-out-link \
|
||||||
|
--arg check false \
|
||||||
|
--argstr newsReadIdsFile "$(newsReadIdsFile)"
|
||||||
|
}
|
||||||
|
|
||||||
|
function doShowNews() {
|
||||||
|
local infoFile
|
||||||
|
infoFile=$(doBuildNews -A newsInfo) || return 1
|
||||||
|
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "$infoFile"
|
||||||
|
|
||||||
|
case $1 in
|
||||||
|
--all)
|
||||||
|
${PAGER:-less} "$newsFileAll"
|
||||||
|
;;
|
||||||
|
--unread)
|
||||||
|
${PAGER:-less} "$newsFileUnread"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
errorEcho "Unknown argument $1"
|
||||||
|
return 1
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [[ -s "$newsUnreadIdsFile" ]]; then
|
||||||
|
local newsReadIdsFile
|
||||||
|
newsReadIdsFile="$(newsReadIdsFile)"
|
||||||
|
cat "$newsUnreadIdsFile" >> "$newsReadIdsFile"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
function doHelp() {
|
function doHelp() {
|
||||||
echo "Usage: $0 [OPTION] COMMAND"
|
echo "Usage: $0 [OPTION] COMMAND"
|
||||||
echo
|
echo
|
||||||
|
@ -130,6 +223,7 @@ function doHelp() {
|
||||||
echo " switch Build and activate configuration"
|
echo " switch Build and activate configuration"
|
||||||
echo " generations List all home environment generations"
|
echo " generations List all home environment generations"
|
||||||
echo " packages List all packages installed in home-manager-path"
|
echo " packages List all packages installed in home-manager-path"
|
||||||
|
echo " news Show news entries in a pager"
|
||||||
}
|
}
|
||||||
|
|
||||||
EXTRA_NIX_PATH=()
|
EXTRA_NIX_PATH=()
|
||||||
|
@ -171,7 +265,7 @@ cmd="$*"
|
||||||
|
|
||||||
case "$cmd" in
|
case "$cmd" in
|
||||||
build)
|
build)
|
||||||
doBuild ""
|
doBuild
|
||||||
;;
|
;;
|
||||||
switch)
|
switch)
|
||||||
doSwitch
|
doSwitch
|
||||||
|
@ -182,6 +276,9 @@ case "$cmd" in
|
||||||
packages)
|
packages)
|
||||||
doListPackages
|
doListPackages
|
||||||
;;
|
;;
|
||||||
|
news)
|
||||||
|
doShowNews --all
|
||||||
|
;;
|
||||||
help|--help)
|
help|--help)
|
||||||
doHelp
|
doHelp
|
||||||
;;
|
;;
|
||||||
|
|
|
@ -1,6 +1,14 @@
|
||||||
{ pkgs ? import <nixpkgs> {}, confPath, confAttr }:
|
{ pkgs ? import <nixpkgs> {}
|
||||||
|
, confPath
|
||||||
|
, confAttr
|
||||||
|
, check ? true
|
||||||
|
, newsReadIdsFile ? null
|
||||||
|
}:
|
||||||
|
|
||||||
|
with pkgs.lib;
|
||||||
|
|
||||||
let
|
let
|
||||||
|
|
||||||
env = import <home-manager> {
|
env = import <home-manager> {
|
||||||
configuration =
|
configuration =
|
||||||
let
|
let
|
||||||
|
@ -8,8 +16,74 @@ let
|
||||||
in
|
in
|
||||||
if confAttr == "" then conf else conf.${confAttr};
|
if confAttr == "" then conf else conf.${confAttr};
|
||||||
pkgs = pkgs;
|
pkgs = pkgs;
|
||||||
|
check = check;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
newsReadIds =
|
||||||
|
if newsReadIdsFile == null
|
||||||
|
then {}
|
||||||
|
else
|
||||||
|
let
|
||||||
|
ids = splitString "\n" (fileContents newsReadIdsFile);
|
||||||
|
in
|
||||||
|
builtins.listToAttrs (map (id: { name = id; value = null; }) ids);
|
||||||
|
|
||||||
|
newsIsRead = entry: builtins.hasAttr entry.id newsReadIds;
|
||||||
|
|
||||||
|
newsFiltered =
|
||||||
|
let
|
||||||
|
pred = entry: entry.condition && ! newsIsRead entry;
|
||||||
|
in
|
||||||
|
filter pred env.newsEntries;
|
||||||
|
|
||||||
|
newsNumUnread = length newsFiltered;
|
||||||
|
|
||||||
|
newsFileUnread = pkgs.writeText "news-unread.txt" (
|
||||||
|
concatMapStringsSep "\n\n" (entry:
|
||||||
|
let
|
||||||
|
time = replaceStrings ["T"] [" "] (removeSuffix "+00:00" entry.time);
|
||||||
|
in
|
||||||
|
''
|
||||||
|
* ${time}
|
||||||
|
|
||||||
|
${replaceStrings ["\n"] ["\n "] entry.message}
|
||||||
|
''
|
||||||
|
) newsFiltered
|
||||||
|
);
|
||||||
|
|
||||||
|
newsFileAll = pkgs.writeText "news-all.txt" (
|
||||||
|
concatMapStringsSep "\n\n" (entry:
|
||||||
|
let
|
||||||
|
flag = if newsIsRead entry then "read" else "unread";
|
||||||
|
time = replaceStrings ["T"] [" "] (removeSuffix "+00:00" entry.time);
|
||||||
|
in
|
||||||
|
''
|
||||||
|
* ${time} [${flag}]
|
||||||
|
|
||||||
|
${replaceStrings ["\n"] ["\n "] entry.message}
|
||||||
|
''
|
||||||
|
) env.newsEntries
|
||||||
|
);
|
||||||
|
|
||||||
|
# File where each line corresponds to an unread news entry
|
||||||
|
# identifier. If non-empty then the file ends in "\n".
|
||||||
|
newsUnreadIdsFile = pkgs.writeText "news-unread-ids" (
|
||||||
|
let
|
||||||
|
text = concatMapStringsSep "\n" (entry: entry.id) newsFiltered;
|
||||||
|
in
|
||||||
|
text + optionalString (text != "") "\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
newsInfo = pkgs.writeText "news-info.sh" ''
|
||||||
|
local newsNumUnread=${toString newsNumUnread}
|
||||||
|
local newsDisplay="${env.newsDisplay}"
|
||||||
|
local newsFileAll="${newsFileAll}"
|
||||||
|
local newsFileUnread="${newsFileUnread}"
|
||||||
|
local newsUnreadIdsFile="${newsUnreadIdsFile}"
|
||||||
|
'';
|
||||||
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
inherit (env) activationPackage;
|
inherit (env) activationPackage;
|
||||||
|
inherit newsInfo;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
{ configuration
|
{ configuration
|
||||||
, pkgs
|
, pkgs
|
||||||
, lib ? pkgs.stdenv.lib
|
, lib ? pkgs.stdenv.lib
|
||||||
|
|
||||||
|
# Whether to check that each option has a matching declaration.
|
||||||
|
, check ? true
|
||||||
}:
|
}:
|
||||||
|
|
||||||
with lib;
|
with lib;
|
||||||
|
@ -64,6 +67,7 @@ let
|
||||||
pkgsModule = {
|
pkgsModule = {
|
||||||
config._module.args.pkgs = lib.mkForce pkgs;
|
config._module.args.pkgs = lib.mkForce pkgs;
|
||||||
config._module.args.baseModules = modules;
|
config._module.args.baseModules = modules;
|
||||||
|
config._module.check = check;
|
||||||
};
|
};
|
||||||
|
|
||||||
module = showWarnings (
|
module = showWarnings (
|
||||||
|
@ -90,4 +94,10 @@ in
|
||||||
|
|
||||||
# For backwards compatibility. Please use activationPackage instead.
|
# For backwards compatibility. Please use activationPackage instead.
|
||||||
activation-script = module.config.home.activationPackage;
|
activation-script = module.config.home.activationPackage;
|
||||||
|
|
||||||
|
newsDisplay = module.config.news.display;
|
||||||
|
newsEntries =
|
||||||
|
sort (a: b: a.time > b.time) (
|
||||||
|
filter (a: a.condition) module.config.news.entries
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue