Conar: минипарсер параметров командной строки

Как известно, задача распознавания параметров командной строки давно и неоднократно решена. Примеры (список далеко не полон): Boost.Program_options (навороченная, и, увы, требует сборки), TCLAP, ezOptionParser, AnyOption, The Lean Mean C++ Option Parser. Обзора я делать не буду, напишу лишь, что ознакомление с данными библиотеками не отбило у меня желание сделать свою :)

Я хочу библиотеку: header-only (вообще полностью определённую в одном заголовочном файле), C++ (поддержка STL-контейнеров, анонимных функций), никакого (L)GPL (Boost-лицензия лучшая же), использование только Стандартной библиотеки C++ (пусть с элементами ещё не принятого C++17), действительно простая схема использования, не требующая писать каскады if-else или switch-case и явно использовать механизмы type-erasure (void*, any и т.п.). Производительность же не играет особого значения. Особая универсальность также мне не нужна.

Такая библиотека может быть достаточно маленькой для ad hoc программирования (без проектирования). Однако это не самое простое упражнение в программировании. Попробую описать её разработку (начата на момент написания этих строк) «в ходе процесса» в нескольких постах.

Итак, с чего начать? Начнём с названия :) У меня это будет conar.hpp («console arguments»).

#ifndef CONAR_HPP_INCLUDED_N98K2U
#define CONAR_HPP_INCLUDED_N98K2U

namespace conar
{
  // ...
}

#endif//CONAR_HPP_INCLUDED_N98K2U

Далее весь код размещается внутри пространства имён conar. Необходимые #include подразумеваются и в тексте не упоминаются.

Ключ — имя опции (параметра командной строки). Опции может соответствовать несколько ключей. Если ключ состоит из одной буквы, то предполагается, что перед ним ставится один дефис («минус»). Если ключ состоит из нескольких букв (и, возможно, цифр), то перед ним ставится два дефиса (предполагаемое автоматическое дополнение пользовательских ключей). Если хочется иной формулировки, например, на дробь / как принято в Windows, то достаточно будет явно указать этот знак. Предполагается, что ключи чувствительны к регистру (в Windows принято иначе, да…). При поиске соответствия будем пытаться подобрать наиболее длинное возможное совпадение. Навскидку наивный алгоритм даёт в худшем случае время разбора O(N L log K + S), где N — количество параметров, L — длина наиболее длинного ключа, K — общее количество ключей, S — суммарное количество символов во всех параметрах.

Опция вида -abc интерпретируется как последовательность трёх опций -a -b -c, если не заданы явно ключи -abc или -ab. В последнем случае возможна попытка прочитать это как -ab c, успешность которой зависит от того, принимает ли -ab строку или символ.

Итак, базовый класс Option для различных классов опций. Нужно ли здесь вообще наследование с виртуальными функциями? Не уверен, посмотрим.

  /// Basic description of a command line option.
  struct Option
  {
    using Possible_keys = std::vector<std::string>;
    using Info = std::string;

    /// Keys corresponding to this option.
    Possible_keys possible_keys;
    /// Information about this option (for help).
    Info info;

    virtual ~Option() {}
    // This class may be extended later.
  };

Классы опций. Наследники класса Option. Объекты этих классов хранят обработчики (handler) распознанных параметров.

Флаг — опция без параметров, соответственно, обработчик имеет сигнатуру void().

  struct Flag_option: Option
  {
    using Handler = std::function<void()>;
    Handler handler;
  };

Значение — опция с одним параметром заданного типа T. Посему это шаблон. Обработчик имеет сигнатуру void(T) и будет вызываться для каждого удачно распознанного ключа с параметром.

Пример (-t, --time, T = int)

-t1 --time=2 -t 3 6 7 --time 4 8 9 даст четыре вызова обработчика с параметрами 1, 2, 3 и 4.

  template <class T>
  struct Value_option: Option
  {
    using Handler = std::function<void(T)>;
    Handler handler;
  };

Последовательность — опция, предполагающая передачу последовательности значений заданного типа (T) после ключа. Обработчик имеет сигнатуру void(T*, T*) и принимает диапазон массива с очередной успешно считанной последовательностью значений (их может быть несколько — для каждого упоминания ключа).

Пример (-i, --input, T = string)

-i "a b c" d e --input "next time" do this даст два вызова с массивами { «a b c», «d», «e» } и { «next time», «do», «this» }.

  template <class T>
  struct Seq_option: Option
  {
    using Handler = std::function<void(T*, T*)>;
    Handler handler;
  };

Парсер. Главный компонент библиотеки. Опции будем регистрировать, передавая объекты классов-наследников Option с помощью оператора (), который возвращает ссылку на объект парсера. Параметры функции main для распознавания (после регистрации всех опций) также передадим оператором (). Т.е. предполагается использование вроде

Parser{}(опция 1)(опция 2)(опция 3)(argc, argv);

результат вызова — список (вектор) нераспознанных параметров (в текстовой форме «как есть»).

На практике, однако, может быть удобнее всё же определить переменную типа Parser. Ну хотя бы чтобы можно было извлечь автоматически созданное описание зарегистрированных опций для хелпа (функция help).

Итак, заготовка Parser.

  class Parser
  {
  public:
    /// Construct a help message part that describes all the options known to the parser.
    std::string help(int key_column_width = 20, int line_width = 80, const char *line_sep = "\n") const
    {
      std::string result;
      // TODO
      return result;
    }

    /// Add an option description. Use functions flag and value for this.
    template <class Opt>
    Parser& operator()(Opt option)
    {
      static_assert(std::is_base_of<Option, Opt>::value);
      // TODO
      return *this;
    }

    /// A list of unparsed parameters.
    using Unparsed_parameters = std::vector<std::string>;

    /// Finally parse the command-line parameters given by an iterator range (of strings).
    template <class InIt>
    Unparsed_parameters operator()(InIt from, InIt to)
    {
      static_assert(std::is_convertible<
        typename std::iterator_traits<InIt>::iterator_category,
        std::input_iterator_tag>::value);

      Unparsed_parameters unparsed;
      // TODO
      return unparsed;
    }

    /// Parse arguments as they are passed to main, argv[0] is ignored.
    Unparsed_parameters operator()(int argc, char *argv[])
    {
      return (*this)(argv + 1, argv + argc);
    }
  };

Вот так, раз-два и готов файлик в сотню строк. Которые, впрочем, пока абсолютно бесполезны…

Продолжение следует.

Реклама

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s

%d такие блоггеры, как: