Как мне реализовать "вложенные" подкоманды в Python?

Реализация "вложенных" подкоманд в Python с помощью cmdln.

Я не уверен, что я использую правильную терминологию здесь. Я пытаюсь реализовать инструмент командной строки, используя cmdln, который позволяет "вложенные" подкоманды. Вот пример из реального мира:

git svn rebase

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

За исключением недокументированной функции, которая делает это автоматически, я сначала хотел, чтобы предыдущий обработчик подкоманды определил, что есть другая подкоманда, и снова отправил диспетчер команд. Я посмотрел внутреннюю часть cmdln, и диспетчер является частным методом _dispatch_cmd. Моя следующая мысль - создать моего собственного диспетчера под-подкоманд, но это кажется не совсем идеальным и грязным.

Любая помощь будет оценена.

3 ответа

Решение

argparse делает подкоманды очень легкими.

Опоздал на вечеринку здесь, но я должен был сделать это совсем немного и нашел 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.

Если у кого-то есть больше информации по этому вопросу, пожалуйста, дайте мне знать.

Обновление на 2020 год!
Нажмите библиотеку легче использования
Огня является крутой библиотекой для создания вашей командной строки приложения признаков!

Другие вопросы по тегам