Commit f2225c90 authored by Profpatsch's avatar Profpatsch

containers/helpers: add script.withArguments

Gone are the days where bash scripts have to do their own argument parsing
logic, this helper generates an argparser for your scripts, with nice usage
strings and optional checks that can be performed on the value.
parent 4955848e
......@@ -26,6 +26,8 @@ let
debugDockerImage = ./helpers/debug-docker-image.nix;
createStandaloneDockerImage = ./helpers/create-standalone-docker-image.nix;
fetchgitUpdater = ./helpers/fetchgit-updater;
# TODO: give better name
script = ./helpers/write-script-argparse;
}) // {
inherit (self.callPackage ./helpers/nix-json-trans.nix {})
json2json json2string;
......
{ stdenv, lib, writeText, writeScript }:
{
name,
description,
# { description, checks }
options,
script
}:
let
usage =
let
checks = lib.concatMapStringsSep ", " (c: c.name);
usageAttr = n: v: "--${n} (${checks v.checks}): ${v.description}";
in
writeText "${name}-usage.txt" ''
${name}: ${description}
${name}
${builtins.concatStringsSep "\n "
(lib.mapAttrsToList usageAttr options)}
'';
usageFn = ''
function USAGE__ {
cat 1>&2 <<EOF
ERROR: $(echo -e $1)
$(cat ${usage})
EOF
exit 1
}
'';
# all checks we are using in this script
ourChecks = builtins.concatLists
(lib.mapAttrsToList (_: opt: opt.checks) options);
checkFns =
let
checkFn = c: ''
function ${c.fnName} {
${c.code}
} '';
in lib.concatMapStringsSep "\n" checkFn ourChecks;
nameMapOptsSep = sep: f: lib.concatMapStringsSep sep f
(builtins.attrNames options);
getopt =
let
opts = nameMapOptsSep "," (o: "${o}:");
in ''
PARSED__=$(getopt --name="${name}" \
--options= \
--longoptions=${opts} \
-- "$@")
# read getopt’s output this way to handle the quoting right:
eval set -- "$PARSED__"
'';
parseArguments =
let
# this is probably not very efficient …
# a small embedding for indentation inside
# lists of lists of strings
rep = n: ch: builtins.foldl' (str: _: str + ch) ""
(builtins.genList lib.id n);
indent = n: list: (map (s: (rep n " ") + s) list);
i4 = indent 4;
i2 = indent 2;
# pure
i0 = s: [s];
# join
embed = builtins.concatLists;
applyIndent = builtins.concatStringsSep "\n";
runCheck = argName: c: embed [
(i0 ''${c.fnName} "$2" \'')
(i2 (i0 ''|| ERRS__+="--${argName}: file '$2' does not exist\n"''))
];
argHandler = name: opt: embed [
(i0 ''--${name})'')
(i2 (embed (map (runCheck name) opt.checks)))
(i2 [
''${name}="$2"''
''shift 2''
'';;''
])
];
in ''
ERRS_=
while true; do
case "$1" in
${applyIndent
(i4 (embed (lib.mapAttrsToList argHandler options)))}
--)
shift
# no further arguments
[[ $# -ne 0 ]] \
&& ERRS__+="too many arguments: $@"
break
;;
*)
ERRS__+="unknown argument: $1\n"
shift 1
;;
esac
done
[[ "$ERRS__" != "" ]] \
&& USAGE__ "Argument errors:\n$ERRS__"
'';
checkAllOptionsGiven = ''
# check whether all options have been given
ERRS__=
for opt in ${nameMapOptsSep " " lib.id}; do
test -v $opt \
|| ERRS__+=" --$opt"
done
[[ "$ERRS__" != "" ]] \
&& USAGE__ "options$ERRS__ are required"
'';
argParser = writeText "${name}-argparser.sh" ''
# This is an automatically generated argparser.
# It sets the following bash variables:
# ${nameMapOptsSep ", " lib.id}
# Inspired by:
# https://stackoverflow.com/a/29754866/1382925
${usageFn}
[[ $# -eq 0 ]] && USAGE__ "no arguments given"
${checkFns}
${getopt}
${parseArguments}
${checkAllOptionsGiven}
# unset all variables, as to not lead to strange
# effects in the following script
unset -v PARSED__ ERRS__
unset -f USAGE__
unset -f ${lib.concatMapStringsSep " " (c: c.fnName) ourChecks}
'';
# TODO: maybe invert it, that you call the argparser yourself?
finalScript = writeScript name ''
#!${stdenv.shell}
# call the argparser, which sets the following variables:
# ${nameMapOptsSep ", " lib.id}
source ${argParser}
${script}
'';
in
finalScript
{ pkgs }:
let
writeScriptArgparse = pkgs.callPackage ./build-script.nix {};
checks = {
fileExists = {
fnName = "FILE_EXISTS__";
name = "FILE";
code = ''test -a "$1"'';
};
};
# TODO: nice tests
tests = {
foo = writeScriptArgparse {
name = "myname";
description = "dis is description";
options = {
args = {
description = "argument description";
checks = [ checks.fileExists ];
};
json = {
description = "some json!";
checks = [];
};
};
script = ''
echo $args
echo $json
'';
};
};
in {
argumentChecks = checks;
withArguments = writeScriptArgparse;
# tests = tests;
}
......@@ -26,8 +26,6 @@ PARSED__=$(getopt --name=myname --options= --longoptions=args:,script:,json: --
# read getopt’s output this way to handle the quoting right:
eval set -- "$PARSED__"
echo "$PARSED__"
# parse arguments, run checks, accumulate errors
ERRS__=
while true; do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment