Библиотека разбора опций командной строки hflags

Существует много различных библиотек для разбора опций командной строки, таких как optparse-aplicative, options, и прочие. Однако в данном посте я хочу рассмотреть необычную библиотеку для разбора опций командной строки hflags.

Библиотека hflags была написана (экс-?)работниками Google, теперь работающими в Nilcons, на основе библиотеки flags, для проектов на C++. Как хорошо известно в Google придерживаются мнения, что все опции для программы должны быть опциями командной строки, и библиотеки оптимизированы именно под такой случай.

Основной идеей hflags предоставить работать с каждой из опций в полной изолированности от остальных так, чтобы изменение удаление или добавление опции не приводило к изменениям в остальных частях программы. В результате пользователю не нужно иметь централизованный тип данных описывающий все возможные опции, напротив, для каждый из флагов определяется деклараций в том модуле, в котором он используется (или откуда экспортируется). По данным декларациям автоматически формируется парсер командной строки.

Общий принцип работы

Для создания опции используется одна из возможных деклараций флага, являющихся шаблонным методом, с помощью Template Haskell генерируются блоки описывающие флаг, а так же описание экземпляра класса ‘Flag’. Ниже приведен вывод -ddump-splices, вывод кода генерируемого Template Haskell для флага из примеров идущих с библиотекой:

Здесь HFlag_name = HFlagC_name уникальный тип данных соотвествующий каждому создаваемому флагу вида HFlag_<имя_модуля>_<имя_флага>, экземпляр Flag для созданного типа данных и функцию доступа к флагу. HFlags.lookupFlag — это поиск в глобальной изменяемой переменной globalFlags :: IORef (Maybe (Map String String)) завернутое в unsafePerformIO, по этой причине для функции доступа стоит прагма {-# NOINLINE #-}.

Создание экземпляра класса является основной идеей вокруг которой построена реализация данной библиотеки, поскольку все модули рекурсивно экспортируют определенные экземпляры классов типов, то в основном модуле будут находиться все экземпляры для флагов определенных в программе. Далее с помощью Template Haskell в главном модуля на основе всех существующих экземпляров строится парсер.

Таким образом если не обращать внимание на unsafePerformIO и глобальные переменные, а так же Template Haskell, то данная библиотека представляет очень простой и расширяемый подход для определения опций.

Немного примеров

В библиотеке существуют базовый примитив для создания опций defineCustomFlag позволяющий задать:

  1. короткое и полное имя опции в формате @s:[email protected]
  2. значение по умолчанию
  3. строку помощи, определяющую тип аргумента
  4. функцию read, которая будет применяться к строке при чтении аргумента
  5. функцию show, которая будет применяться к строковому представлению аргумента
  6. строку подсказки

И две более простые функции defineFlag использующу методы read и show для обработки значения, и defineEQFlag позволяющую задать еще и тип аргумента.

Несколько примеров:

defineFlag "name" ("Indiana Jones"::String) "Who to greet."

создает флаг name со значением по умолчанию “Indiana Jones”

defineFlag "d:dry_run" False "Don't print anything, just exit."

создает флаг с длинной (--dry_run) и короткой опцией -d

data Color = Red | Yellow | Green deriving (Show, Read)
defineEQFlag "favorite_color" [| Yellow :: Color |] "COLOR" "Your favorite color."

создание опции использующей метод read по умолчанию и определющей тип значения.

defineCustomFlag "percent" [| 100 :: Double |] "PERCENTAGE"
  [| \s -> let p = read s
           in if 0.0 <= p && p <= 100.0
              then p
              else error "Percentage value has to be between 0 and 100."
   |]
  [| show |]

создание опции со своим методом read.

Так же все опции можно задавать и через переменные окружения, используя переменные вида HFLAG_FLAGNAME. В случае если хочется использовать другой формат переменных окружения, то авторы предлагают запускать initFlag после обработки переменных окружения функциями getEnv и setEnv из модуля System.Environment

Поскольку для генерации используется Template Haskell, то нужно помнить об изменившимся в ghc-7.8.2 поведении при обработке шаблонов и в случае использования переменных в главном модуле использовать известный workaround.

Выводы

Несмотря на достаточную ограниченность возможностей и использование небезопасных методов (впрочем не сравнимых с используемыми библиотекой cmdargs) библиотека hflags предлагает очень простой API позволяющий элегантно решить проблемы не покрываемые другими библиотеками. Тем более что hflags можно использовать и вместе с другими библиотеками, для задания дополнительных опций имеющих значение по умолчанию и рантайм конфигурации, например так:

В этом примере все параметры передающиеся как аргументы после символа -- будут обработаны hflags.