aboutsummaryrefslogtreecommitdiff
path: root/toys/pending/getopt.c
blob: dcef314b46a646a21a782cce8245904f50d52af6 (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
/* getopt.c - Parse command-line options
 *
 * Copyright 2019 The Android Open Source Project

USE_GETOPT(NEWTOY(getopt, "^a(alternative)n:(name)o:(options)l*(long)(longoptions)Tu", TOYFLAG_USR|TOYFLAG_BIN))

config GETOPT
  bool "getopt"
  default n
  help
    usage: getopt [OPTIONS] [--] ARG...

    Parse command-line options for use in shell scripts.

    -a	Allow long options starting with a single -.
    -l OPTS	Specify long options.
    -n NAME	Command name for error messages.
    -o OPTS	Specify short options.
    -T	Test whether this is a modern getopt.
    -u	Output options unquoted.
*/

#define FOR_getopt
#include "toys.h"

GLOBALS(
  struct arg_list *l;
  char *o, *n;
)

static void out(char *s)
{
  if (FLAG(u)) printf(" %s", s);
  else {
    printf(" '");
    for (; *s; s++) {
      if (*s == '\'') printf("'\\''");
      else putchar(*s);
    }
    printf("'");
  }
}

static char *parse_long_opt(void *data, char *str, int len)
{
  struct option **lopt_ptr = data, *lopt = *lopt_ptr;

  // Trailing : or :: means this option takes a required or optional argument.
  // no_argument = 0, required_argument = 1, optional_argument = 2.
  for (lopt->has_arg = 0; len>0 && str[len-1] == ':'; lopt->has_arg++) len--;
  if (!len || lopt->has_arg>2) return str;

  lopt->name = xstrndup(str, len);

  (*lopt_ptr)++;
  return 0;
}

void getopt_main(void)
{
  int argc = toys.optc+1;
  char **argv = xzalloc(sizeof(char *)*(argc+1));
  struct option *lopts = xzalloc(sizeof(struct option)*argc), *lopt = lopts;
  int i = 0, j = 0, ch;

  if (FLAG(T)) {
    toys.exitval = 4;
    return;
  }

  comma_args(TT.l, &lopt, "bad -l", parse_long_opt);
  argv[j++] = TT.n ? TT.n : "getopt";

  // Legacy mode: don't quote output and take the first argument as OPTSTR.
  if (!FLAG(o)) {
    toys.optflags |= FLAG_u;
    TT.o = toys.optargs[i++];
    if (!TT.o) error_exit("no OPTSTR");
    --argc;
  }

  while (i<toys.optc) argv[j++] = toys.optargs[i++];

  // BSD getopts don't honor argv[0] (for -n), so handle errors ourselves.
  opterr = 0;
  optind = 1;
  while ((ch = (FLAG(a)?getopt_long_only:getopt_long)(argc, argv, TT.o,
          lopts, &i)) != -1) {
    if (ch == '?') {
      fprintf(stderr, "%s: invalid option '%c'\n", argv[0], optopt);
      toys.exitval = 1;
    } else if (!ch) {
      printf(" --%s", lopts[i].name);
      if (lopts[i].has_arg) out(optarg ? optarg : "");
    } else {
      printf(" -%c", ch);
      if (optarg) out(optarg);
    }
  }

  printf(" --");
  for (; optind<argc; optind++) out(argv[optind]);
  printf("\n");
}