Как мне реализовать "вложенные" подкоманды в Python?
Реализация "вложенных" подкоманд в Python с помощью cmdln.
Я не уверен, что я использую правильную терминологию здесь. Я пытаюсь реализовать инструмент командной строки, используя cmdln, который позволяет "вложенные" подкоманды. Вот пример из реального мира:
git svn rebase
Каков наилучший способ реализации этого? Я искал дополнительную информацию по этому вопросу в документе, здесь и в Интернете, но он оказался пустым. (Возможно, я искал с неправильными терминами.)
За исключением недокументированной функции, которая делает это автоматически, я сначала хотел, чтобы предыдущий обработчик подкоманды определил, что есть другая подкоманда, и снова отправил диспетчер команд. Я посмотрел внутреннюю часть cmdln, и диспетчер является частным методом _dispatch_cmd. Моя следующая мысль - создать моего собственного диспетчера под-подкоманд, но это кажется не совсем идеальным и грязным.
Любая помощь будет оценена.
3 ответа
Опоздал на вечеринку здесь, но я должен был сделать это совсем немного и нашел argparse
довольно неуклюже, чтобы сделать это с. Это побудило меня написать расширение argparse
называется arghandler, который явно поддерживает это - возможно выполнение подкоманд с практически нулевыми строками кода.
Вот пример:
from arghandler import *
@subcmd
def push(context,args):
print 'command: push'
@subcmd
def pull(context,args):
print 'command: pull'
# run the command - which will gather up all the subcommands
handler = ArgumentHandler()
handler.run()
Мне кажется, что в argparse есть небольшое ограничение для sub_parsers, если, скажем, у вас есть набор инструментов, которые могут иметь похожие параметры, которые могут распространяться на разные уровни. Такое может случиться редко, но если вы пишете подключаемый / модульный код, это может произойти.
У меня есть следующий пример. Это надуманно и не очень хорошо объяснено в настоящее время, потому что уже довольно поздно, но здесь это идет:
Usage: tool [-y] {a, b}
a [-x] {create, delete}
create [-x]
delete [-y]
b [-y] {push, pull}
push [-x]
pull [-x]
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument('-x', action = 'store_true')
parser.add_argument('-y', action = 'store_true')
subparsers = parser.add_subparsers(dest = 'command')
parser_a = subparsers.add_parser('a')
parser_a.add_argument('-x', action = 'store_true')
subparsers_a = parser_a.add_subparsers(dest = 'sub_command')
parser_a_create = subparsers_a.add_parser('create')
parser_a_create.add_argument('-x', action = 'store_true')
parser_a_delete = subparsers_a.add_parser('delete')
parser_a_delete.add_argument('-y', action = 'store_true')
parser_b = subparsers.add_parser('b')
parser_b.add_argument('-y', action = 'store_true')
subparsers_b = parser_b.add_subparsers(dest = 'sub_command')
parser_b_create = subparsers_b.add_parser('push')
parser_b_create.add_argument('-x', action = 'store_true')
parser_b_delete = subparsers_b.add_parser('pull')
parser_b_delete.add_argument('-y', action = 'store_true')
print parser.parse_args(['-x', 'a', 'create'])
print parser.parse_args(['a', 'create', '-x'])
print parser.parse_args(['b', '-y', 'pull', '-y'])
print parser.parse_args(['-x', 'b', '-y', 'push', '-x'])
Выход
Namespace(command='a', sub_command='create', x=True, y=False)
Namespace(command='a', sub_command='create', x=True, y=False)
Namespace(command='b', sub_command='pull', x=False, y=True)
Namespace(command='b', sub_command='push', x=True, y=True)
Как видите, трудно определить, где в цепочке был задан каждый аргумент. Вы можете решить эту проблему, изменив имя для каждой переменной. Например, вы можете установить для dest значение "x", "a_x", "a_create_x", "b_push_x" и т. Д., Но это будет болезненно и трудно отделить.
Альтернативой может быть остановка ArgumentParser при достижении подкоманды и передача оставшихся аргументов другому, независимому парсеру, чтобы он мог генерировать отдельные объекты. Вы можете попытаться добиться этого, используя parse_known_args() и не определяя аргументы для каждой подкоманды. Однако это было бы нехорошо, потому что любые непроверенные аргументы, которые были до этого, все равно будут присутствовать и могут запутать программу.
Я чувствую себя немного дешевым, но полезным обходным путем, чтобы argparse интерпретировал следующие аргументы как строки в списке. Это можно сделать, установив для префикса нулевой терминатор "\0" (или какой-либо другой "сложный в использовании" символ) - если префикс пуст, код выдаст ошибку, по крайней мере, в Python 2.7.3.
Пример:
parser = ArgumentParser()
parser.add_argument('-x', action = 'store_true')
parser.add_argument('-y', action = 'store_true')
subparsers = parser.add_subparsers(dest = 'command')
parser_a = subparsers.add_parser('a' prefix_chars = '\0')
parser_a.add_argument('args', type = str, nargs = '*')
print parser.parse_args(['-xy', 'a', '-y', '12'])
Выход:
Namespace(args=['-y', '12'], command='a', x=True, y=True)
Обратите внимание, что он не потребляет второй -y
вариант. Затем вы можете передать результат args другому ArgumentParser.
Недостатки:
- Помощь не может быть обработана хорошо. Придется сделать еще несколько обходных путей с этим
- Обнаружение ошибок может быть трудно отследить и потребовать дополнительных усилий, чтобы убедиться, что сообщения об ошибках правильно связаны.
- Немного больше накладных расходов, связанных с несколькими ArgumentParsers.
Если у кого-то есть больше информации по этому вопросу, пожалуйста, дайте мне знать.