Skip to content

Commit

Permalink
Fix/cli argparse (#14053)
Browse files Browse the repository at this point in the history
* wip

* wip

* wip

* wip

* make sure CLI can be called twice
  • Loading branch information
memsharded authored Jun 9, 2023
1 parent a930709 commit 2202b35
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 41 deletions.
2 changes: 1 addition & 1 deletion conan/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ def run(self, *args):
raise ConanException("Unknown command %s" % str(exc))

try:
command.run(self._conan_api, self._commands[command_argument].parser, args[0][1:])
command.run(self._conan_api, args[0][1:])
except Exception as e:
# must be a local-import to get updated value
if ConanOutput.level_allowed(LEVEL_TRACE):
Expand Down
76 changes: 36 additions & 40 deletions conan/cli/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ def __init__(self, method, formatters=None):
self._formatters = {"text": lambda x: None}
self._method = method
self._name = None
self._parser = None
if formatters:
for kind, action in formatters.items():
if callable(action):
Expand All @@ -44,24 +43,26 @@ def __init__(self, method, formatters=None):
"commands should provide a documentation string explaining "
"its use briefly.".format(self._name))

def _init_log_levels(self):
self._parser.add_argument("-v", default="status", nargs='?',
help="Level of detail of the output. Valid options from less verbose "
"to more verbose: -vquiet, -verror, -vwarning, -vnotice, -vstatus, "
"-v or -vverbose, -vv or -vdebug, -vvv or -vtrace")
@staticmethod
def _init_log_levels(parser):
parser.add_argument("-v", default="status", nargs='?',
help="Level of detail of the output. Valid options from less verbose "
"to more verbose: -vquiet, -verror, -vwarning, -vnotice, -vstatus, "
"-v or -vverbose, -vv or -vdebug, -vvv or -vtrace")

@property
def _help_formatters(self):
"""
Formatters that are shown as available in help, 'text' formatter
should not appear
"""
return [formatter for formatter in list(self._formatters) if formatter != "text"]
return [formatter for formatter in self._formatters if formatter != "text"]

def _init_formatters(self):
if self._help_formatters:
help_message = "Select the output format: {}".format(", ".join(list(self._help_formatters)))
self._parser.add_argument('-f', '--format', action=OnceArgument, help=help_message)
def _init_formatters(self, parser):
formatters = self._help_formatters
if formatters:
help_message = "Select the output format: {}".format(", ".join(formatters))
parser.add_argument('-f', '--format', action=OnceArgument, help=help_message)

@property
def name(self):
Expand All @@ -75,10 +76,6 @@ def method(self):
def doc(self):
return self._doc

@property
def parser(self):
return self._parser

def _format(self, parser, info, *args):
parser_args, _ = parser.parse_known_args(*args)

Expand Down Expand Up @@ -112,35 +109,36 @@ class ConanCommand(BaseConanCommand):
def __init__(self, method, group=None, formatters=None):
super().__init__(method, formatters=formatters)
self._subcommands = {}
self._subcommand_parser = None
self._group = group or "Other"
self._name = method.__name__.replace("_", "-")
self._parser = ConanArgumentParser(description=self._doc,
prog="conan {}".format(self._name),
formatter_class=SmartFormatter)
self._init_formatters()
self._init_log_levels()

def add_subcommand(self, subcommand):
if not self._subcommand_parser:
self._subcommand_parser = self._parser.add_subparsers(dest='subcommand',
help='sub-command help')
self._subcommand_parser.required = True
subcommand.set_name(self.name)
subcommand.set_parser(self._parser, self._subcommand_parser)
self._subcommands[subcommand.name] = subcommand

def run(self, conan_api, parser, *args):
def run(self, conan_api, *args):
parser = ConanArgumentParser(description=self._doc, prog="conan {}".format(self._name),
formatter_class=SmartFormatter)
self._init_log_levels(parser)
self._init_formatters(parser)

info = self._method(conan_api, parser, *args)

if not self._subcommands:
self._format(self._parser, info, *args)
self._format(parser, info, *args)
else:
subcommand = args[0][0] if args[0] else None
if subcommand in self._subcommands:
self._subcommands[subcommand].run(conan_api, *args)
subcommand_parser = parser.add_subparsers(dest='subcommand', help='sub-command help')
subcommand_parser.required = True

try:
sub = self._subcommands[args[0][0]]
except (KeyError, IndexError): # display help
for sub in self._subcommands.values():
sub.set_parser(subcommand_parser)
parser.parse_args(*args)
else:
self._parser.parse_args(*args)
sub.set_parser(subcommand_parser)
sub.run(conan_api, parser, *args)

@property
def group(self):
Expand All @@ -150,24 +148,22 @@ def group(self):
class ConanSubCommand(BaseConanCommand):
def __init__(self, method, formatters=None):
super().__init__(method, formatters=formatters)
self._parent_parser = None
self._parser = None
self._subcommand_name = method.__name__.replace('_', '-')

def run(self, conan_api, *args):
info = self._method(conan_api, self._parent_parser, self._parser, *args)
def run(self, conan_api, parent_parser, *args):
info = self._method(conan_api, parent_parser, self._parser, *args)
# It is necessary to do it after calling the "method" otherwise parser not complete
self._format(self._parent_parser, info, *args)
self._format(parent_parser, info, *args)

def set_name(self, parent_name):
self._name = self._subcommand_name.replace(f'{parent_name}-', '', 1)

def set_parser(self, parent_parser, subcommand_parser):
def set_parser(self, subcommand_parser):
self._parser = subcommand_parser.add_parser(self._name, help=self._doc)
self._parser.description = self._doc
self._parent_parser = parent_parser
self._init_formatters()
self._init_log_levels()
self._init_formatters(self._parser)
self._init_log_levels(self._parser)


def conan_command(group=None, formatters=None):
Expand Down
23 changes: 23 additions & 0 deletions conans/test/integration/conan_api/test_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from conan.api.conan_api import ConanAPI
from conan.cli.cli import Cli
from conans.test.utils.mocks import RedirectedTestOutput
from conans.test.utils.test_files import temp_folder
from conans.test.utils.tools import redirect_output


def test_cli():
""" make sure the CLi can be reused
https://github.com/conan-io/conan/issues/14044
"""
folder = temp_folder()
api = ConanAPI(cache_folder=folder)
cli = Cli(api)
cli2 = Cli(api)

stdout = RedirectedTestOutput()
stderr = RedirectedTestOutput()
with redirect_output(stderr, stdout):
cli.run(["list", "*"])
cli.run(["list", "*"])
cli2.run(["list", "*"])
cli.run(["list", "*"])

0 comments on commit 2202b35

Please sign in to comment.