solovyov.net

Finaloption

UPD. Библиотека переименована в Opster.

Я тут выпустил библиотеку для парсинга коммандлайновых аргументов. Зачем еще одна, когда уже в питоне есть getopt и optparse, когда не так давно появились argparse и optfunc?

Ну… как обычно, потому что они все неправильные и делают не то и не так, как хочется. ;)

Всë началось тогда, когда Зед Шоу (Zed Shaw) написал здоровенную статью про проблемы в питоне и упомянул, что он в Lamson’e использует систему парсинга аргументов значительно более приятную, чем argparse/optparse. Меня в тот момент как раз беспокоила эта тема и я пошëл посмотреть. Сказать, что она мне понравилась, я не могу. Даже наоборот, мне она показалась не меньшей ересью, чем optparse, но с другого боку.

Я в твиттере написал, что это смешно, и удостоился некоторого количества сарказма и понимания того, что он не видит ничего плохого в том, что:

  • у функций команд должно быть определëнное имя
  • функции команд должны быть определены в одном модуле
  • дефолтные значения определяются вызовом левой функции в функции-команде
  • подача опции с ошибкой в написании не приведëт к ошибке
  • форматировать хелп по опциям нужно ручками самому

В общем, я подумал, что миру нужна система команд из меркуриала. ;) И выдрал - большую часть, конечно, переписал, потому как оно было под меркуриал заточено, но сама идея осталась как есть.

Вот небольшой пример кода:

from finaloption import command

@command(usage='[-l HOST] DIR')
def main(dirname,
         listen=('l', 'localhost', 'ip to listen on'),
         port=('p', 8000, 'port to listen on'),
         daemonize=('d', False, 'daemonize process'),
         pid_file=('', '', 'name of file to write process ID to')):
    '''Command with option declaration as keyword arguments

    Otherwise it's the same as previous command
    '''
    print locals()

if __name__ == '__main__':
    main()

Думаю, что примерно понятно, к чему это всë. Например, опция обязательно имеет длинное имя (имя keyword argument’а), возможно короткое имя ('' - убивает короткое имя), какой-то дефолт и помощь. Дефолтное значение определяет, что делать с пришедшими данными:

  • строка - ничего не происходит, так и остаëтся строкой
  • int - на пришедшей строке вызывается int()
  • список - пришедший аргумент просто прибавляется к списку
  • функция исполняется с пришедшим аргументом
  • True/False/None - не требует аргумента, просто переключает дефолт в противоположное значение

Что еще неочевидно - main() может не принимать аргументов (будет парсить sys.argv), принимать список строк (аналогичный sys.argv) или те же самые аргументы, которые принимает обëрнутая функция.

Помощь генерируется такого вида:

piranha@gto ~/dev/misc/finaloption>./test_opts.py --help
test_opts.py [-l HOST] DIR

Command with option declaration as keyword arguments

    Otherwise it's the same as previous command

options:

 -l --listen     ip to listen on (default: localhost)
 -p --port       port to listen on (default: 8000)
 -d --daemonize  daemonize process
    --pid-file   name of file to write process ID to
 -h --help       show help

Кроме всего прочего, подчëркивания в именах аргументов превращаются в дефисы, чтоб не нарушать конвенций, принятых для коммандлайна. ;)

Что еще хорошего? :) Ну, например то, что имена опций (и сабкомманд, если такие используются), можно сокращать: вместо --pid-file использовать --pi, например.

Самый близкий аналог этой библиотеки - это optfunc Саймона Виллисона. Из отличий - синтаксис объявления аргументов и поддержка сабкоманд. Точнее у него поддержка тоже есть, но на уровне хака.

Плюс optfunc - это фактически просто надстройка над optparse, небольшое облегчение его синтаксиса, а finaloption использует только getopt - поэтому внутренний апи на самом деле начинался как внешний, а @command - это просто обëртка для finaloption.parse. ;-) И, кстати, @command вполне понимает внутреннее представление опций:

>>> opts = [('l', 'listen', 'localhost', 'ip to listen on'),
...         ('p', 'port', 8000, 'port to listen on')]
>>> main = command(options=opts)(main)

Благодаря этому генерация кучи команд с потенциально похожими опциями упрощается. Я вот подумываю, что может при наличии кваргсов у функции и переданных опций одновременно их просто объединять?.. Тогда можно будет выделять общие опции в такие списки, а уникальные писать кваргсами у функции.

В общем, пользуйтесь, пожалуйста. ;) Отзывы, вопросы, предложения и код крайне приветствуются. :)