1 /// Author: Aziz Köksal
2 /// License: GPL3
3 /// $(Maturity average)
4 module util.OptParser;
5 
6 import common;
7 
8 /// A command line option parser.
9 class OptParser
10 {
11   cstring[] argv; /// The argument vector.
12   bool delegate()[] parseDgs; /// Option parsing delegates.
13   cstring error; /// Holds the error message if an error occurred.
14   /// Missing argument error message.
15   cstring missingArgMessage = "missing argument for option ‘{}’";
16   /// Usage error message.
17   cstring usageErrorMessage = "Usage error:\n  {}";
18 
19   /// Constructs an OptParser object.
20   this(cstring[] argv)
21   {
22     this.argv = argv;
23   }
24   /// ditto
25   this(string[] argv)
26   {
27     this.argv = cast(cstring[])argv;
28   }
29 
30   /// Parses all arguments.
31   bool parseArgs()
32   {
33     cstring[] remArgs; // Remaining arguments.
34     while (hasArgs)
35     {
36       auto n = argv.length; // Remember number of args.
37       foreach (parseOption; parseDgs)
38         if (!hasArgs || parseOption())
39           break;
40         else if (error !is null)
41           goto Lerror;
42       if (argv.length == n) // No arguments consumed?
43         remArgs ~= popArg(); // Append to remaining args.
44     }
45     argv = remArgs;
46     return true;
47   Lerror:
48     argv = remArgs ~ argv;
49     return false;
50   }
51 
52   /// Adds a parser delegate.
53   void add()(bool delegate() parseDg)
54   {
55     parseDgs ~= parseDg;
56   }
57 
58   /// A dummy variable used to force the compiler to create a closure.
59   private bool delegate() closureDelegate;
60 
61   /// Adds a delegate for parsing an option.
62   void add(T)(cstring param, ref T out_arg, void delegate() cb = null)
63   { // Have to assign to outer variable first to create a closure.
64     add(closureDelegate =
65       { return parse(param, out_arg) && (cb && cb(), true); });
66   }
67 
68   /// Adds a delegate accepting any option.
69   void addDefault(void delegate() defaultDg)
70   {
71     add(closureDelegate = { defaultDg(); return true; });
72   }
73 
74   /// Parses a parameter.
75   bool parse(cstring param, ref cstring out_arg)
76   {
77     if (!hasArgs) return false;
78     auto arg0 = argv[0];
79     auto n = param.length;
80     if (arg0.startsWith(param))
81     {
82       if (arg0.length == n) // arg0 == param
83       { // Eg: -I /include/path
84         if (argv.length <= 1)
85           goto Lerror;
86         out_arg = argv[1];
87         n = 2;
88       }
89       else
90       { // Eg: -I/include/path
91         auto skipEqualSign = arg0[n] == '=';
92         out_arg = arg0[n + skipEqualSign .. $];
93         n = 1;
94       }
95       consume(n); // Consume n arguments.
96       return true;
97     }
98     return false;
99   Lerror:
100     error = Format(missingArgMessage, param);
101     return false;
102   }
103 
104   /// Parses a flag.
105   bool parse(cstring flag, ref bool out_arg)
106   {
107     if (hasArgs && argv[0] == flag) {
108       out_arg = true;
109       consume(1);
110       return true;
111     }
112     return false;
113   }
114 
115   /// Slices off n elements from argv.
116   void consume(size_t n)
117   {
118     argv = argv[n..$];
119   }
120 
121   /// Returns true if arguments are available.
122   bool hasArgs() @property
123   {
124     return argv.length != 0;
125   }
126 
127   /// Returns the first argument and removes it from the list.
128   cstring popArg()
129   {
130     auto arg = argv[0];
131     consume(1);
132     return arg;
133   }
134 
135   /// Prints the usage error message.
136   void printUsageError()
137   {
138     Printfln(usageErrorMessage, error);
139   }
140 }
141 
142 /// Returns true if str starts with s.
143 bool startsWith(cstring str, cstring s)
144 {
145   return str.length >= s.length && str[0 .. s.length] == s;
146 }