1#ifndef CLIOPTS_H_
2#define CLIOPTS_H_
3
4#include <stddef.h> /* size_t */
5
6#ifdef __cplusplus
7extern "C" {
8#endif /* __cplusplus */
9
10#if defined(_WIN32) && defined(CLIOPTS_BUILDING_DLL)
11#define CLIOPTS_API __declspec( dllexport )
12
13#else
14#define CLIOPTS_API
15#endif
16
17
18/**
19 * Various option types
20 */
21typedef enum {
22    /** takes no argument, dest should be anything big enough to hold a boolean*/
23    CLIOPTS_ARGT_NONE,
24
25    /** simple int type, dest should be an 'int' */
26    CLIOPTS_ARGT_INT,
27
28    /** dest should be an unsigned int */
29    CLIOPTS_ARGT_UINT,
30
31    /** dest should be an unsigned int, but command line format is hex */
32    CLIOPTS_ARGT_HEX,
33
34    /** dest should be a char**. Note that the string is allocated, so you should
35     * free() it when done */
36    CLIOPTS_ARGT_STRING,
37
38    /** dest should be a float* */
39    CLIOPTS_ARGT_FLOAT,
40
41    /**
42     * Destination should be cliopts_list. Argument type is assumed to be a
43     * string. You can use this option type to build -Doption=value style
44     * options which can be processed later on.
45     */
46    CLIOPTS_ARGT_LIST
47} cliopts_argtype_t;
48
49typedef struct {
50    /**
51     * Input parameters
52     */
53
54    /** Short option, i.e. -v  (0 for none) */
55    char kshort;
56
57    /** long option, i.e. --verbose, NULL for none */
58    const char *klong;
59
60    /** type of value */
61    cliopts_argtype_t ktype;
62
63    /** destination pointer for value */
64    void *dest;
65
66    /** help string for this option */
67    const char *help;
68
69    /** description of the value, e.g. --file=FILE */
70    const char *vdesc;
71
72
73    /** set this to true if the user must provide this option */
74    int required;
75
76    /** set this to true to disable showing the option in the help text */
77    int hidden;
78
79    /**
80     * Output parameters
81     */
82
83    /** whether this option was encountered on the command line */
84    int found;
85
86} cliopts_entry;
87
88struct cliopts_extra_settings {
89    /** Assume actual arguments start from argv[0], not argv[1] */
90    int argv_noskip;
91    /** Don't exit on error */
92    int error_noexit;
93    /** Don't print help on error */
94    int error_nohelp;
95    /** Don't interpret --help or -? as help flags */
96    int help_noflag;
97    /** Program name (defaults to argv[0]) */
98    const char *progname;
99    /** Usage string (defaults to "[OPTIONS..]") */
100    const char *argstring;
101    /** Short description (empty by default) */
102    const char *shortdesc;
103    /** Print default values as well */
104    int show_defaults;
105    /**
106     * Maximum length of a line when printing help. This may be detected
107     * using the $COLUMNS environment variable
108     */
109    int line_max;
110
111    /** Positional parameters (if found). If this array is non-NULL on input
112     * then parameters which are not recognized will be placed here. Otherwise
113     * the parser will return with an error. This array must be large enough
114     * to contain `argc` count strings.
115     */
116    const char **restargs;
117
118    /** Number of positional parameters (if found) */
119    unsigned nrestargs;
120};
121
122typedef struct {
123    /** Array of string pointers. Allocated via standard malloc functions */
124    char **values;
125    /** Number of valid entries */
126    size_t nvalues;
127    /** Number of entries allocated */
128    size_t nalloc;
129} cliopts_list;
130
131/**
132 * Clear a list of its contents
133 * @param l The list
134 */
135CLIOPTS_API
136void
137cliopts_list_clear(cliopts_list *l);
138
139/**
140 * Parse options.
141 *
142 * @param entries an array of cliopts_entry structures. The list should be
143 * terminated with a structure which has its dest field set to NULL
144 *
145 * @param argc the count of arguments
146 * @param argv the actual list of arguments
147 * @param lastidx populated with the amount of elements from argv actually read
148 * @params setting a structure defining extra settings for the argument parser.
149 * May be NULL
150 *
151 * @return 0 for success, -1 on error.
152 */
153CLIOPTS_API
154int
155cliopts_parse_options(cliopts_entry *entries,
156                      int argc,
157                      char **argv,
158                      int *lastidx,
159                      struct cliopts_extra_settings *settings);
160#ifdef __cplusplus
161}
162
163#ifdef CLIOPTS_ENABLE_CXX
164#include <string>
165#include <vector>
166#include <list>
167#include <cstdlib>
168#include <cstring>
169#include <cstdio>
170
171namespace cliopts {
172class Parser;
173
174/**
175 * This class should typically not be used directly. It is a simple wrapper
176 * around the C-based ::cliopts_entry class for further wrapping by the
177 * cliopts::TOption template class.
178 */
179class Option : protected cliopts_entry {
180public:
181    bool passed() const { return found != 0; }
182    void setPassed(bool val = true) { found = val ? 1 : 0; }
183    int numSpecified() const { return found; }
184    Option() { memset(this, 0, sizeof (cliopts_entry)); }
185private:
186    friend class Parser;
187};
188
189class EmptyPriv {};
190
191/**
192 * Option template class. This class is not meant to be used by applications
193 * directly. Applications should use one of the template instantiations
194 * below (e.g. cliopts::StringOption)
195 *
196 * @param T type returned to the application
197 * @param Targ integer constant indicating the type of the C argument
198 * @param Taccum raw destination type which will store the parsed value
199 * @param Tpriv type of private data to be stored for type-specific processing
200 */
201template <
202    typename T,
203    cliopts_argtype_t Targ,
204    typename Taccum,
205    typename Tpriv = EmptyPriv
206    >
207class TOption : public Option {
208
209private:
210    typedef TOption<T,Targ, Taccum, Tpriv> Ttype;
211    Taccum innerVal; /**< Pointer for cliopts_entry destination */
212    Tpriv priv; /**< Type-specific data */
213public:
214
215    /**
216     * Construct a new option
217     * @param shortname abbreviated short name
218     * @param longname long ("GNU-style" name)
219     * @param deflval default value to be used
220     * @param helpstr Text explaining the option
221     */
222    TOption(char shortname, const char *longname = NULL,
223        T deflval = createDefault(), const char *helpstr = NULL) {
224
225        memset((cliopts_entry *)this, 0, sizeof(cliopts_entry));
226        ktype = Targ;
227        klong = longname;
228        dest = &innerVal;
229
230        abbrev(shortname);
231        description(helpstr);
232        setDefault(deflval);
233    }
234
235    /**
236     * Construct a new option
237     * @param longname the long ("GNU-Style") name.
238     */
239    TOption(const char *longname) {
240        memset((cliopts_entry *)this, 0, sizeof(cliopts_entry));
241        ktype = Targ;
242        klong = longname;
243        innerVal = createDefault();
244        dest = &innerVal;
245    }
246
247    /**
248     * Copy constructor. This mainly exists to allow chaining (See example)
249     * @param other the source option to copy
250     */
251    TOption(TOption& other) {
252        *(cliopts_entry*)this = *(cliopts_entry*) &other;
253        innerVal = other.innerVal;
254        dest = &innerVal;
255        other.dest = NULL;
256        doCopy(other);
257    }
258
259    /**
260     * Set the default value for the option
261     * @param val the default value
262     * @return the option object, for method chaining.
263     */
264    inline Ttype& setDefault(const T& val) {
265        innerVal = val;
266        return *this;
267    }
268
269    /**
270     * Set the single-character switch
271     * @param val the switch character, e.g. '-v'
272     * @return the option object, for method chaining
273     */
274    inline Ttype& abbrev(char val) { kshort = val; return *this; }
275
276    /**
277     * Set the description (or help string) for the option.
278     * @param msg The help string e.g. "Increases verbosity"
279     * @return the obtion object, for method chaining.
280     */
281    inline Ttype& description(const char *msg) { help = msg; return *this; }
282
283    /**
284     * Set whether this option must appear
285     * @param val boolean, set to true if required, false if optional
286     * @return the option object, for method chaining
287     */
288    inline Ttype& mandatory(bool val = true) { required = val; return *this; }
289
290    /**
291     * Set the value description string for the option value.
292     * @param desc The short description string, e.g. "RETRIES"
293     * @return the option object, for method chaining
294     */
295    inline Ttype& argdesc(const char *desc) { vdesc = desc; return *this; }
296
297    /**
298     * Whether to hide this option in the help output
299     * @param val true if the option should be hidden
300     * @return the object object, for method chaining.
301     */
302    inline Ttype& hide(bool val = true) { hidden = val; return *this; }
303
304    /**
305     * Returns the result object
306     * @return a copy of the result object
307     */
308    inline T result() { return (T)innerVal; }
309
310    /**
311     * Returns a reference to the result object
312     * @return a reference to the result object.
313     */
314    inline T& const_result() { return (T)innerVal; }
315
316    operator T() { return result(); }
317
318protected:
319    /** Called from within copy constructor */
320    inline void doCopy(TOption&) {}
321
322    /** Create the default value for the option */
323    static inline Taccum createDefault() { return Taccum(); }
324};
325
326typedef TOption<std::string,
327        CLIOPTS_ARGT_STRING,
328        const char*,
329        std::string> StringOption;
330
331typedef TOption<std::vector<std::string>,
332        CLIOPTS_ARGT_LIST,
333        cliopts_list,
334        std::vector<std::string> > ListOption;
335
336typedef TOption<bool,
337        CLIOPTS_ARGT_NONE,
338        int> BoolOption;
339
340typedef TOption<unsigned,
341        CLIOPTS_ARGT_UINT,
342        unsigned> UIntOption;
343
344typedef TOption<int,
345        CLIOPTS_ARGT_INT,
346        int> IntOption;
347
348typedef TOption<int,
349        CLIOPTS_ARGT_HEX,
350        unsigned> HexOption;
351
352typedef TOption<float,
353        CLIOPTS_ARGT_FLOAT,
354        float> FloatOption;
355
356// STRING ROUTINES
357template<> inline std::string& StringOption::const_result() {
358    if (innerVal && passed()) {
359        priv = innerVal;
360    }
361    return priv;
362}
363template<> inline std::string StringOption::result() {
364    return const_result();
365}
366template<> inline StringOption& StringOption::setDefault(const std::string& s) {
367    priv = s;
368    innerVal = priv.c_str();
369    return *this;
370}
371template<> inline void StringOption::doCopy(StringOption& other) {
372    priv = other.priv;
373    if (other.innerVal == other.priv.c_str()) {
374        innerVal = priv.c_str();
375    }
376}
377template<> inline const char* StringOption::createDefault() { return ""; }
378
379// LIST ROUTINES
380template<> inline std::vector<std::string>& ListOption::const_result() {
381    if (priv.empty()) {
382        for (size_t ii = 0; ii < innerVal.nvalues; ii++) {
383            priv.push_back(innerVal.values[ii]);
384        }
385    }
386    return priv;
387}
388template<> inline std::vector<std::string> ListOption::result() {
389    return const_result();
390}
391
392// BOOL ROUTINES
393template<> inline BoolOption& BoolOption::setDefault(const bool& b) {
394    innerVal = b ? 1 : 0; return *this;
395}
396template<> inline bool BoolOption::result() {
397    return innerVal != 0 ? true : false;
398}
399
400/**
401 * Parser class which contains one or more cliopts::Option objects. Options
402 * should be added via the #addOption() member function.
403 */
404class Parser {
405public:
406    /**
407     * Construct a new parser
408     * @param name the "program name" which is printed at the top of the
409     * help message.
410     */
411    Parser(const char *name = NULL) {
412        memset(&default_settings, 0, sizeof default_settings);
413        default_settings.progname = name;
414    }
415
416    /**
417     * Adds an option to the parser. The option is then checked for presence
418     * on the commandline (in #parse()).
419     * @param opt the option to add. Note that the application is responsible
420     * for keeping the option in valid memory.
421     */
422    void addOption(Option *opt) { options.push_back(opt); }
423
424    void addOption(Option& opt) { options.push_back(&opt); }
425
426    /**
427     * Parses the options from the commandline
428     * @param argc number of arguments
429     * @param argv list of arguments
430     * @param standalone_args whether to accept (and store) positional arguments
431     * (after all named options are processed).
432     * @return true on parse success, false on parse failure
433     */
434    bool parse(int argc, char **argv, bool standalone_args = false) {
435        std::vector<cliopts_entry> ents;
436        cliopts_extra_settings settings = default_settings;
437        int lastix;
438
439        for (unsigned ii = 0; ii < options.size(); ++ii) {
440            ents.push_back(*options[ii]);
441        }
442
443        if (ents.empty()) { return false; }
444        ents.push_back(Option());
445        const char **tmpargs = NULL;
446        if (standalone_args) {
447            tmpargs = new const char*[argc];
448            settings.restargs = tmpargs;
449            settings.nrestargs = 0;
450        }
451        settings.show_defaults = 1;
452
453        int rv = cliopts_parse_options(&ents[0], argc, argv, &lastix, &settings);
454
455        if (tmpargs != NULL) {
456            for (unsigned ii = 0; ii < settings.nrestargs; ii++) {
457                restargs.push_back(tmpargs[ii]);
458            }
459            delete[] tmpargs;
460        }
461
462        // Copy the options back
463        for (unsigned ii = 0; ii < options.size(); ii++) {
464            *(cliopts_entry *)options[ii] = ents[ii];
465        }
466
467        if (rv == 0 && lastix != 0) {
468            for (; lastix < argc; lastix++) {
469                restargs.push_back(argv[lastix]);
470            }
471        }
472
473        return rv == 0;
474    }
475
476    /**
477     * Get the list of any positional arguments found on the commandline
478     * @return A list of positional arguments found.
479     */
480    const std::vector<std::string>& getRestArgs() { return restargs; }
481
482    cliopts_extra_settings default_settings;
483private:
484    std::vector<Option*> options;
485    std::vector<std::string> restargs;
486    Parser(Parser&);
487};
488} // namespace
489#endif /* CLIOPTS_ENABLE_CXX */
490
491#endif /* __cplusplus */
492
493#endif /* CLIOPTS_H_ */
494