summaryrefslogtreecommitdiff
path: root/media/libjxl/src/tools/cmdline.h
blob: ed5d84705085627d8dcf6b6bbcef497ac736239f (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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

#ifndef TOOLS_CMDLINE_H_
#define TOOLS_CMDLINE_H_

#include <stdio.h>
#include <string.h>

#include <memory>
#include <string>
#include <vector>

#include "lib/jxl/base/status.h"

namespace jpegxl {
namespace tools {

class CommandLineParser {
 public:
  typedef size_t OptionId;

  // An abstract class for defining command line options.
  class CmdOptionInterface {
   public:
    CmdOptionInterface() = default;
    virtual ~CmdOptionInterface() = default;

    // Return a string with the option name or available flags.
    virtual std::string help_flags() const = 0;

    // Return the help string if any, or nullptr if no help string.
    virtual const char* help_text() const = 0;

    // Return the verbosity level for this option
    virtual int verbosity_level() const = 0;

    // Return whether the option was passed.
    virtual bool matched() const = 0;

    // Returns whether this option matches the passed command line argument.
    virtual bool Match(const char* arg, bool parse_options) const = 0;

    // Parses the option. The passed i points to the argument with the flag
    // that matches either the short or the long name.
    virtual bool Parse(int argc, const char* argv[], int* i) = 0;

    // Returns whether the option is positional, and therefore will be shown
    // in the first command line representation of the help output.
    virtual bool positional() const = 0;

    // Returns whether the option should be displayed as required in the help
    // output. No effect on validation.
    virtual bool required() const = 0;
  };

  // Add a positional argument. Returns the id of the added option or
  // kOptionError on error.
  // The "required" flag indicates whether the parameter is mandatory or
  // optional, but is only used for how it is displayed in the command line
  // help.
  OptionId AddPositionalOption(const char* name, bool required,
                               const char* help_text, const char** storage,
                               int verbosity_level = 0) {
    options_.emplace_back(new CmdOptionPositional(name, help_text, storage,
                                                  verbosity_level, required));
    return options_.size() - 1;
  }

  // Add an option with a value of type T. The option can be passed as
  // '-s <value>' or '--long value' or '--long=value'. The CommandLineParser
  // parser will call the function parser with the string pointing to '<value>'
  // in either case. Returns the id of the added option or kOptionError on
  // error.
  template <typename T>
  OptionId AddOptionValue(char short_name, const char* long_name,
                          const char* metavar, const char* help_text,
                          T* storage, bool(parser)(const char*, T*),
                          int verbosity_level = 0) {
    options_.emplace_back(new CmdOptionFlag<T>(short_name, long_name, metavar,
                                               help_text, storage, parser,
                                               verbosity_level));
    return options_.size() - 1;
  }

  // Add a flag without a value. Returns the id of the added option or
  // kOptionError on error.
  template <typename T>
  OptionId AddOptionFlag(char short_name, const char* long_name,
                         const char* help_text, T* storage, bool(parser)(T*),
                         int verbosity_level = 0) {
    options_.emplace_back(new CmdOptionFlag<T>(
        short_name, long_name, help_text, storage, parser, verbosity_level));
    return options_.size() - 1;
  }

  const CmdOptionInterface* GetOption(OptionId id) const {
    JXL_ASSERT(id < options_.size());
    return options_[id].get();
  }

  // Print the help message to stdout.
  void PrintHelp() const;

  // Whether a help flag was specified
  bool HelpFlagPassed() const { return help_; }

  int verbosity = 0;

  // Parse the command line.
  bool Parse(int argc, const char* argv[]);

  // Return the remaining positional args
  std::vector<const char*> PositionalArgs() const;

 private:
  // A positional argument.
  class CmdOptionPositional : public CmdOptionInterface {
   public:
    CmdOptionPositional(const char* name, const char* help_text,
                        const char** storage, int verbosity_level,
                        bool required)
        : name_(name),
          help_text_(help_text),
          storage_(storage),
          verbosity_level_(verbosity_level),
          required_(required) {}

    std::string help_flags() const override { return name_; }
    const char* help_text() const override { return help_text_; }
    int verbosity_level() const override { return verbosity_level_; }
    bool matched() const override { return matched_; }

    // Only match non-flag values. This means that you can't pass '-foo' as a
    // positional argument, but it helps with detecting when passed a flag with
    // a typo. After '--', option matching is disabled so positional arguments
    // starting with '-' can be used.
    bool Match(const char* arg, bool parse_options) const override {
      return !matched_ && (!parse_options || arg[0] != '-');
    }

    bool Parse(const int argc, const char* argv[], int* i) override {
      *storage_ = argv[*i];
      (*i)++;
      matched_ = true;
      return true;
    }

    bool positional() const override { return true; }

    bool required() const override { return required_; }

   private:
    const char* name_;
    const char* help_text_;
    const char** storage_;
    const int verbosity_level_;
    const bool required_;

    bool matched_{false};
  };

  // A class for handling an option flag like '-v' or '--foo=bar'.
  template <typename T>
  class CmdOptionFlag : public CmdOptionInterface {
   public:
    // Construct a flag that doesn't take any value, for example '-v' or
    // '--long'. Passing a value to it raises an error.
    CmdOptionFlag(char short_name, const char* long_name, const char* help_text,
                  T* storage, bool(parser)(T*), int verbosity_level)
        : short_name_(short_name),
          long_name_(long_name),
          long_name_len_(long_name ? strlen(long_name) : 0),
          metavar_(nullptr),
          help_text_(help_text),
          storage_(storage),
          verbosity_level_(verbosity_level) {
      parser_.parser_no_value_ = parser;
    }

    // Construct a flag that expects a value to be passed.
    CmdOptionFlag(char short_name, const char* long_name, const char* metavar,
                  const char* help_text, T* storage,
                  bool(parser)(const char* arg, T*), int verbosity_level)
        : short_name_(short_name),
          long_name_(long_name),
          long_name_len_(long_name ? strlen(long_name) : 0),
          metavar_(metavar ? metavar : ""),
          help_text_(help_text),
          storage_(storage),
          verbosity_level_(verbosity_level) {
      parser_.parser_with_arg_ = parser;
    }

    std::string help_flags() const override {
      std::string ret;
      if (short_name_) {
        ret += std::string("-") + short_name_;
        if (metavar_) ret += std::string(" ") + metavar_;
        if (long_name_) ret += ", ";
      }
      if (long_name_) {
        ret += std::string("--") + long_name_;
        if (metavar_) ret += std::string("=") + metavar_;
      }
      return ret;
    }
    const char* help_text() const override { return help_text_; }
    int verbosity_level() const override { return verbosity_level_; }
    bool matched() const override { return matched_; }

    bool Match(const char* arg, bool parse_options) const override {
      return parse_options && (MatchShort(arg) || MatchLong(arg));
    }

    bool Parse(const int argc, const char* argv[], int* i) override {
      matched_ = true;
      if (MatchLong(argv[*i])) {
        const char* arg = argv[*i] + 2 + long_name_len_;
        if (arg[0] == '=') {
          if (metavar_) {
            // Passed '--long_name=...'.
            (*i)++;
            // Skip over the '=' on the LongMatch.
            arg += 1;
            return (*parser_.parser_with_arg_)(arg, storage_);
          } else {
            fprintf(stderr, "--%s didn't expect any argument passed to it.\n",
                    argv[*i]);
            return false;
          }
        }
      }
      // In any other case, it passed a -s or --long_name
      (*i)++;
      if (metavar_) {
        if (argc <= *i) {
          fprintf(stderr, "--%s expected an argument but none passed.\n",
                  argv[*i - 1]);
          return false;
        }
        return (*parser_.parser_with_arg_)(argv[(*i)++], storage_);
      } else {
        return (*parser_.parser_no_value_)(storage_);
      }
    }

    bool positional() const override { return false; }

    bool required() const override {
      // Only used for help display of positional arguments.
      return false;
    }

   private:
    // Returns whether arg matches the short_name flag of this option.
    bool MatchShort(const char* arg) const {
      if (!short_name_ || arg[0] != '-') return false;
      return arg[1] == short_name_ && arg[2] == 0;
    }

    // Returns whether arg matches the long_name flag of this option,
    // potentially with an argument passed to it.
    bool MatchLong(const char* arg) const {
      if (!long_name_ || arg[0] != '-' || arg[1] != '-') return false;
      arg += 2;  // Skips the '--'
      if (strncmp(long_name_, arg, long_name_len_) != 0) return false;
      arg += long_name_len_;
      // Allow "--long_name=foo" and "--long_name" as long matches.
      return arg[0] == 0 || arg[0] == '=';
    }

    // A short option passed as '-X' where X is the char. A value of 0 means
    // no short option.
    const char short_name_;

    // A long option name passed as '--long' where 'long' is the name of the
    // option.
    const char* long_name_;
    size_t long_name_len_;

    // The text to display when referring to the value passed to this flag, for
    // example "N" in the flag '--value N'. If null, this flag accepts no value
    // and therefore no value must be passed.
    const char* metavar_;

    // The help string for this flag.
    const char* help_text_;

    // The pointer to the storage of this flag used when parsing.
    T* storage_;

    // At which verbosity level do we show this option?
    int verbosity_level_;

    // The function to use to parse the value when matched. The function used is
    // parser_with_arg_ when metavar_ is not null (and the value string will be
    // used) or parser_no_value_ when metavar_ is null.
    union {
      bool (*parser_with_arg_)(const char*, T*);
      bool (*parser_no_value_)(T*);
    } parser_;

    // Whether this flag was matched.
    bool matched_{false};
  };

  const char* program_name_{nullptr};

  std::vector<std::unique_ptr<CmdOptionInterface>> options_;

  // If true, help argument was given, so print help to stdout rather than
  // stderr.
  bool help_ = false;
};

}  // namespace tools
}  // namespace jpegxl

#endif  // TOOLS_CMDLINE_H_