Доводилось ли Вам писать код, который разбирает аргументы, переданные программе в командной строке? Думаю, ответ - да. Одни программисты предпочитают использовать специализированные инструменты, такие как Boost.Program_options в С++, или Commons CLI в Java. Другие же разработчики неустанно пишут свои собственные "парсеры" аргументов командной строки.

Что мы имеем в итоге? Если в компании программисты трудятся на разных языках, то скорее всего они будут использовать разные подходы к решению данной задачи. Хорошо если выработано общее соглашение о том, в каком виде программы должны принимать аргументы. Например, как выглядит короткая (-h) и полная (--help) форма для настройки,  какие ключи за что отвечают, и так далее. Такие требования не всегда существуют, как и документация по допустимым параметрам запуска. Реализации "парсеров" ведут себя по-разному, и не всегда хорошо написаны. Беспорядок и неразбериха...

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

Что такое docopt? Во-первых, это общепринятое соглашение о том, как должен выглядеть command-line интерфейс программы.

На этой странице Вы можете немного поэкспериментировать. На ней описан интерфейс к вымышленной программе (а-ля "морской бой") на естественном языке, и Вы можете легко изменить его под себя. При  этом нужно следовать простым правилам описания, которые приведены на странице проекта. Затем, в строке argv введите аргументы и нажмите run. В итоге, Вы получите значения всех аргументов в удобном виде (json). Точно также, Вы сможете получить их впоследствии в своей программе. Таким образом, docopt - это соглашение о том, как должен выглядеть интерфейс командной строки в общем виде. То самое соглашение, которое так часто отсутствует между программистами. При этом docopt ничего не придумывал нового. Это то самое соглашение, которое принято в сообществе для описания аргументов приложения в man-страницах и help-справке. Этому соглашению люди следуют много лет. Этому опыту можно доверять.


Во-вторых, docopt - это микро-библиотека, написанная для большого числа языков, которая способна по строке с описанием интерфейса, реализовать необходимый разбор аргументов!

В результате, всё что Вам нужно - это создать простой текстовый файл с описанием интерфейса командной строки, по правилам docopt. Приятно, что описывается интерфейс полностью на естественном языке, что позволяет перенести это описание как есть в документацию для пользователя. Если бы изначальным форматом был json, xml или даже yaml - то его бы пришлось адаптировать, прежде чем копировать в документацию.

Далее, полученный файл нужно передать тем программистам, которые будут реализовывать описанный интерфейс. Они, предварительно подключив реализацию docopt на любимом языке, должны просто направить содержимое файла "парсеру", вместе с переданными аргументами. Всё остальное сделает docopt.

Он проверит: корректные были переданы аргументы или нет, допустимы ли такие комбинации аргументов или нет, и так далее. Если разбор оказался успешным, то мы получим все настройки в удобной, для дальнейшей обработки, форме.


А теперь короткий пример всего вышеописанного сразу для трёх языков.

(я не буду рассказывать как подключить docopt в свою программу, потому что, это точно тоже самое, что и подключить любую другую библиотеку на соответствующем языке)

Итак, пример описания интерфейса возьмём с официального сайта, и поместим его в файл cli.docopt (имя может быть любым). Содержимое файла cli.docopt:

Naval Fate.

Usage:
  naval_fate.py ship new <name>...
  naval_fate.py ship <name> move <x> <y> [--speed=<kn>]
  naval_fate.py ship shoot <x> <y>
  naval_fate.py mine (set|remove) <x> <y> [--moored|--drifting]
  naval_fate.py -h | --help
  naval_fate.py --version

Options:
  -h --help    Show this screen.
  --version    Show version.
  --speed=<kn>  Speed in knots [default: 10].
  --moored     Moored (anchored) mine.
  --drifting    Drifting mine


Теперь этот файл можно использовать в программе, написанной на Python, для разбора аргументов командной строки. Предварительно нужно установить в системе модуль docopt. Вот как может выглядеть простой скрипт на Python, способный "разобраться" в переданных аргументах. 

Примеры запуска:

$ python docopt_test.py --version

Naval Fate 3.0.1

$ python docopt_test.py ship shoot 100 200

{ '--drifting': False, '--help': False, '--moored': False, '--speed': '10', '--version': False, '<name>': [], 
 '<x>': '100', '<y>': '200', 'mine': False, 'move': False, 'new': False, 'remove': False, 'set': False,
 'ship': True, 'shoot': True }

$ python docopt_test.py

Usage:
  naval_fate.py ship new <name>...
  naval_fate.py ship <name> move <x> <y> [--speed=<kn>]
  naval_fate.py ship shoot <x> <y>
  naval_fate.py mine (set|remove) <x> <y> [--moored|--drifting]
  naval_fate.py -h | --help
  naval_fate.py --version


Вся работа с docopt в программе заняла 2 строки кода! Поэтому мы можем сосредоточиться на описании интерфейса, а не на его реализации. И пусть интерфейс будет сложным, нам всё равно не придется его реализовывать вручную. К тому же мы всегда будем иметь понятное и полное описание. Это описание подходит и "парсеру", и пользователю - это удобно!

Вот как можно работать с docopt на C++ (файл cli.docopt тот же самый):

Ну что тут сказать... чтение из файла получилось чуть длиннее, чем в Python. В остальном всё осталось также очень просто. Следует иметь ввиду, что реализация docopt для C++ использует возможности из нового стандарта С++11. Поэтому Ваш компилятор должен поддерживать этот стандарт. Библиотека состоит всего из одного файла docopt.cpp и нескольких заголовочных файлов. Таким образом, docopt можно компилировать сразу вместе с Вашим проектом. Например, так:

$ clang++ --std=c++11 --stdlib=libc++ docopt.cpp main.cpp -o docopt_test

Разбор аргументов на С++ работает точно также, как и в Python. Если Вы внесёте изменения в файл cli.docopt, то Вы фактически перенастроите работу "парсера" как в С++, так и в Python, при этом просто изменив человекочитаемое описание интерфейса. Сам код менять не придется. Это удобно!

С++ программисты конечно могут возмутиться, и сказать, что используя Boost.Program_options, можно реализовать всё тоже самое. Однако, нужно иметь ввиду, что кода в этому случае придётся написать намного больше. Особенно сложно будет сделать его универсальным и гибким, способным быстро адаптироваться к новым настройкам. С docopt писать код парсера вообще не нужно. Какой бы сложности интерфейс командной строки не был. Это касается не только С++, но и других языков, например, Java.

Пример кода на Java, который разбирает аргументы с помощью docopt.

Пример очень похож на два предыдущих (почти такой же, как и для Python). Пакет Docopt легко подключить и использовать. Из особенностей то, что он был протестирован с Java 6 и 7. Но с Java 8 также не должно быть проблем. Обратите внимание, файл cli.docopt не менялся ни в одном из примеров. Определив один раз интерфейс командной строки, мы следуем ему в независимости от языка программирования. cli.docopt легко изменять по необходимости, а код парсера и вовсе не нуждается в изменениях. Имеется прекрасная возможность протестировать интерфейс на этой странице try.docopt.org.

Можно и дальше приводить примеры для языков Go, C#, Ruby, и многих других. С полным списком реализаций Вы сможете ознакомиться странице github.com/docopt. Если Ваш любимый язык отсутствует в списке, то возможно вскоре он появится. Проект активно развивается, и это очень хорошо. Все ошибки быстро исправляются.


Вместо заключения, хотелось бы сказать, унификация инструментов в программировании - очень полезный процесс. Возможно Вы знакомы с проектом Protobuf или Thrift. Эти инструменты позволяют сериализовать данные на одном языке так, что десериализовать их обратно можно на любом другом языке. В результате, программа на C++ легко может передать объект в программу на Go или Node.js. Фактически, становится не так важно на чем Вы пишите. Выработанные соглашения соблюдаются в любой реализации. В какой-то степени - всё это напоминает паттерны проектирования, которые помогают решать архитектурные задачи, возникающие снова и снова. Паттерны абстрагированы от конкретного языка программирования (обычно), и если Вы перейдёте на использование другого языка, они снова окажутся Вам полезны. Их не придется выучивать повторно. То же можно сказать и про Docopt. Этот проект заслуживает внимания, и он будет полезен на каком бы языке Вы не писали.


Краткий обзор docopt завершен. Спасибо за внимание.