summaryrefslogtreecommitdiffstats
path: root/krebs/5pkgs/simple/withGetopt.nix
blob: 179051bdfb1039da759601c72628198a2e15dd85 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
with import <stockholm/lib>;
{ coreutils, quote, utillinux, writeDash }:

opt-spec: cmd-spec: let

  cmd = cmd-spec opts;

  cmd-script =
    if typeOf cmd == "set"
      then "exec ${cmd}"
      else cmd;

  opts = mapAttrs (name: value: value // rec {
    long = value.long or (replaceStrings ["_"] ["-"] name);
    ref = value.ref or "\"\$${varname}\"";
    short = value.short or null;
    switch = value.switch or false;
    varname = value.varname or (replaceStrings ["-"] ["_"] name);
  }) opt-spec;

  # true if b requires a to define its default value
  opts-before = a: b:
    test ".*[$]${stringAsChars (c: "[${c}]") a.varname}\\>.*" (b.default or "");

  opts-list = let
    sort-out = toposort opts-before (attrValues opts);
  in
    if sort-out ? result
      then sort-out.result
      else throw "toposort output: ${toJSON sort-out}";

  wrapper-name =
    if typeOf cmd == "set" && cmd ? name
      then "${cmd.name}-getopt"
      else "getopt-wrapper";

in writeDash wrapper-name ''
  set -efu

  wrapper_name=${shell.escape wrapper-name}

  ${concatStringsSep "\n" (mapAttrsToList (name: opt: /* sh */ ''
    unset ${opt.varname}
  '') opts)}

  WITHGETOPT_ORIG_ARGS=$(${quote}/bin/quote "$@")
  export WITHGETOPT_ORIG_ARGS

  args=$(${utillinux}/bin/getopt \
      -l ${shell.escape
            (concatMapStringsSep ","
              (opt: opt.long + optionalString (!opt.switch) ":")
              (filter (opt: opt.long != null)
                      (attrValues opts)))} \
      -n "$wrapper_name" \
      -o ${shell.escape
            (concatMapStringsSep ""
              (opt: opt.short + optionalString (!opt.switch) ":")
              (filter (opt: opt.short != null)
                      (attrValues opts)))} \
      -s sh \
      -- "$@")
  if \test $? != 0; then exit 1; fi
  eval set -- "$args"

  while :; do
    case $1 in
    ${concatStringsSep "\n" (mapAttrsToList (name: opt: /* sh */ ''
      (${concatMapStringsSep "|" shell.escape (filter (x: x != "") [
        (optionalString (opt.long != null) "--${opt.long}")
        (optionalString (opt.short != null) "-${opt.short}")
      ])})
        ${if opt.switch then /* sh */ ''
          ${opt.varname}=true
          shift
        '' else /* sh */ ''
          ${opt.varname}=$2
          shift 2
        ''}
      ;;
    '') (filterAttrs
          (_: opt: opt.long != null || opt.short != null)
          opts))}
    --)
      shift
      break
    esac
  done

  ${concatMapStringsSep "\n"
    (opt: /* sh */ ''
      if \test "''${${opt.varname}+1}" != 1; then
        printf '%s: missing mandatory option '--%s'\n' \
            "$wrapper_name" \
            ${shell.escape opt.long}
        error=1
      fi
    '')
    (filter
      (x: ! hasAttr "default" x)
      (attrValues opts))}
  if test "''${error+1}" = 1; then
    exit 1
  fi

  ${concatMapStringsSep "\n"
    (opt: /* sh */ ''
      if \test "''${${opt.varname}+1}" != 1; then
        ${opt.varname}=${opt.default}
      fi
    '')
    (filter
      (hasAttr "default")
      opts-list)}

  ${concatStringsSep "\n" (mapAttrsToList (name: opt: /* sh */ ''
    export ${opt.varname}
  '') opts)}

  ${cmd-script} "$@"
''