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)
Благодаря этому генерация кучи команд с потенциально похожими опциями упрощается. Я вот подумываю, что может при наличии кваргсов у функции и переданных опций одновременно их просто объединять?.. Тогда можно будет выделять общие опции в такие списки, а уникальные писать кваргсами у функции.
В общем, пользуйтесь, пожалуйста. ;) Отзывы, вопросы, предложения и код крайне приветствуются. :)