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
|
@ -23,6 +23,7 @@ pkgs.stdenv.mkDerivation {
|
|||
substituteInPlace $out/bin/home-manager \
|
||||
--subst-var-by bash "${pkgs.bash}" \
|
||||
--subst-var-by coreutils "${pkgs.coreutils}" \
|
||||
--subst-var-by less "${pkgs.less}" \
|
||||
--subst-var-by MODULES_PATH '${modulesPathStr}' \
|
||||
--subst-var-by HOME_MANAGER_EXPR_PATH "${./home-manager.nix}"
|
||||
'';
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
# This code explicitly requires GNU Core Utilities and we therefore
|
||||
# need to ensure they are prioritized over any other similarly named
|
||||
# tools on the system.
|
||||
PATH=@coreutils@/bin:$PATH
|
||||
PATH=@coreutils@/bin:@less@/bin${PATH:+:}$PATH
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
function errorEcho() {
|
||||
>&2 echo "$*"
|
||||
echo $* >&2
|
||||
}
|
||||
|
||||
# Attempts to set the HOME_MANAGER_CONFIG global variable.
|
||||
|
@ -52,12 +52,11 @@ function setHomeManagerModulesPath() {
|
|||
done
|
||||
}
|
||||
|
||||
function doBuild() {
|
||||
function doBuildAttr() {
|
||||
setConfigFile
|
||||
setHomeManagerModulesPath
|
||||
|
||||
local extraArgs
|
||||
extraArgs="$1"
|
||||
local extraArgs="$*"
|
||||
|
||||
for p in "${EXTRA_NIX_PATH[@]}"; do
|
||||
extraArgs="$extraArgs -I $p"
|
||||
|
@ -67,11 +66,53 @@ function doBuild() {
|
|||
extraArgs="$extraArgs --show-trace"
|
||||
fi
|
||||
|
||||
nix-build $extraArgs \
|
||||
"@HOME_MANAGER_EXPR_PATH@" \
|
||||
--argstr confPath "$HOME_MANAGER_CONFIG" \
|
||||
--argstr confAttr "$HOME_MANAGER_CONFIG_ATTRIBUTE" \
|
||||
-A activationPackage
|
||||
nix-build \
|
||||
"@HOME_MANAGER_EXPR_PATH@" \
|
||||
$extraArgs \
|
||||
--argstr confPath "$HOME_MANAGER_CONFIG" \
|
||||
--argstr confAttr "$HOME_MANAGER_CONFIG_ATTRIBUTE"
|
||||
}
|
||||
|
||||
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() {
|
||||
|
@ -84,12 +125,17 @@ function doSwitch() {
|
|||
# prevents an unfortunately timed GC from removing the generation
|
||||
# before activation completes.
|
||||
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
|
||||
# running and $wrkdir is always removed.
|
||||
rm -r "$wrkdir"
|
||||
|
||||
if [[ $exitCode -eq 0 ]]; then
|
||||
presentNews
|
||||
fi
|
||||
|
||||
return $exitCode
|
||||
}
|
||||
|
||||
|
@ -110,6 +156,53 @@ function doListPackages() {
|
|||
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() {
|
||||
echo "Usage: $0 [OPTION] COMMAND"
|
||||
echo
|
||||
|
@ -130,6 +223,7 @@ function doHelp() {
|
|||
echo " switch Build and activate configuration"
|
||||
echo " generations List all home environment generations"
|
||||
echo " packages List all packages installed in home-manager-path"
|
||||
echo " news Show news entries in a pager"
|
||||
}
|
||||
|
||||
EXTRA_NIX_PATH=()
|
||||
|
@ -171,7 +265,7 @@ cmd="$*"
|
|||
|
||||
case "$cmd" in
|
||||
build)
|
||||
doBuild ""
|
||||
doBuild
|
||||
;;
|
||||
switch)
|
||||
doSwitch
|
||||
|
@ -182,6 +276,9 @@ case "$cmd" in
|
|||
packages)
|
||||
doListPackages
|
||||
;;
|
||||
news)
|
||||
doShowNews --all
|
||||
;;
|
||||
help|--help)
|
||||
doHelp
|
||||
;;
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
{ pkgs ? import <nixpkgs> {}, confPath, confAttr }:
|
||||
{ pkgs ? import <nixpkgs> {}
|
||||
, confPath
|
||||
, confAttr
|
||||
, check ? true
|
||||
, newsReadIdsFile ? null
|
||||
}:
|
||||
|
||||
with pkgs.lib;
|
||||
|
||||
let
|
||||
|
||||
env = import <home-manager> {
|
||||
configuration =
|
||||
let
|
||||
|
@ -8,8 +16,74 @@ let
|
|||
in
|
||||
if confAttr == "" then conf else conf.${confAttr};
|
||||
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
|
||||
{
|
||||
inherit (env) activationPackage;
|
||||
inherit newsInfo;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
{ configuration
|
||||
, pkgs
|
||||
, lib ? pkgs.stdenv.lib
|
||||
|
||||
# Whether to check that each option has a matching declaration.
|
||||
, check ? true
|
||||
}:
|
||||
|
||||
with lib;
|
||||
|
@ -64,6 +67,7 @@ let
|
|||
pkgsModule = {
|
||||
config._module.args.pkgs = lib.mkForce pkgs;
|
||||
config._module.args.baseModules = modules;
|
||||
config._module.check = check;
|
||||
};
|
||||
|
||||
module = showWarnings (
|
||||
|
@ -90,4 +94,10 @@ in
|
|||
|
||||
# For backwards compatibility. Please use activationPackage instead.
|
||||
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