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:
Robert Helgesson 2017-08-26 22:24:40 +02:00
parent ab0338f6ae
commit 9c1b3735b4
No known key found for this signature in database
GPG key ID: C3DB11069E65DC86
4 changed files with 195 additions and 13 deletions

View file

@ -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}"
'';

View file

@ -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
;;

View file

@ -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;
}

View file

@ -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
);
}