Repository: conan-io/conan
Pull request: Fix Conan V2 cli broken help
Changelog: Fix: Fixing `--help` for commands in proposal for command line v2.0.
Docs: omit

Closes: #7366

- [X] Refer to the issue that supports this Pull Request.
- [X] If the issue has missing info, explain the purpose/use case/pain/need that covers this Pull Request.
- [X] I've read the [Contributing guide](https://github.com/conan-io/conan/blob/develop/.github/CONTRIBUTING.md).
- [X] I've followed the PEP8 style guides for Python code.
- [ ] I've opened another PR in the Conan docs repo to the ``develop`` branch, documenting this one. 

<sup>**Note:** By default this PR will skip the slower tests and will use a limited set of python versions. Check [here](https://github.com/conan-io/conan/blob/develop/.github/PR_INCREASE_TESTING.md) how to increase the testing level by writing some tags in the current PR body text.</sup>

Problem statement:
[bug] Conan V2 cli broken help
```bash
$ conan --help  # broken
$ conan help # works
$ conan help search # broken
$ conan search --help # works
```

I am fine with removing the ``conan help`` approach (the --help is more standard). 

I would also name the groups like ``Misc`` or ``Consumer``, and let the framework add the `` commands`` part in the output.

Hints from issue discussion:

Commit message: dont test --help

Patch to review:
diff --git a/conans/cli/cli.py b/conans/cli/cli.py
index b9a79b56302..abe850714aa 100644
--- a/conans/cli/cli.py
+++ b/conans/cli/cli.py
@@ -81,13 +81,15 @@ def __init__(self, conan_api):
         self._commands = {}
         conan_commands_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "commands")
         for module in pkgutil.iter_modules([conan_commands_path]):
-            self._add_command("conans.cli.commands.{}".format(module.name), module.name)
+            module_name = module[1]
+            self._add_command("conans.cli.commands.{}".format(module_name), module_name)
         if get_env("CONAN_USER_COMMANDS", default=False):
             user_commands_path = os.path.join(self._conan_api.cache_folder, "commands")
             sys.path.append(user_commands_path)
             for module in pkgutil.iter_modules([user_commands_path]):
-                if module.name.startswith("cmd_"):
-                    self._add_command(module.name, module.name.replace("cmd_", ""))
+                module_name = module[1]
+                if module_name.startswith("cmd_"):
+                    self._add_command(module_name, module_name.replace("cmd_", ""))
 
     def _add_command(self, import_path, method_name):
         try:
@@ -131,7 +133,8 @@ def _print_similar(self, command):
         self._out.writeln("")
 
     def help_message(self):
-        self.commands["help"].method(self.conan_api, self.commands, self.groups)
+        self.commands["help"].method(conan_api=self.conan_api, parser=self.commands["help"].parser,
+                                     commands=self.commands, groups=self.groups)
 
     def run(self, *args):
         """ Entry point for executing commands, dispatcher to class
@@ -164,7 +167,8 @@ def run(self, *args):
             self._print_similar(command_argument)
             raise ConanException("Unknown command %s" % str(exc))
 
-        command.run(self.conan_api, args[0][1:], parser=self.commands[command_argument].parser,
+        command.run(args[0][1:], conan_api=self.conan_api,
+                    parser=self.commands[command_argument].parser,
                     commands=self.commands, groups=self.groups)
 
         return SUCCESS
diff --git a/conans/cli/command.py b/conans/cli/command.py
index b1940087f17..387f96676c2 100644
--- a/conans/cli/command.py
+++ b/conans/cli/command.py
@@ -34,7 +34,7 @@ def __init__(self, method, group, formatters=None):
             self._parser.add_argument('-o', '--output', default=default_output, choices=formatters_list,
                                       action=OnceArgument, help=self._output_help_message)
 
-    def run(self, conan_api, *args, **kwargs):
+    def run(self, *args, conan_api=None, **kwargs):
         try:
             info = self._method(*args, conan_api=conan_api, **kwargs)
             parser_args = self._parser.parse_args(*args)
diff --git a/conans/test/functional/command_v2/__init__.py b/conans/test/functional/command_v2/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/conans/test/functional/command_v2/help_test.py b/conans/test/functional/command_v2/help_test.py
new file mode 100644
index 00000000000..af26fe241ff
--- /dev/null
+++ b/conans/test/functional/command_v2/help_test.py
@@ -0,0 +1,21 @@
+import unittest
+
+from conans.client.tools import environment_append, save, six
+from conans.test.utils.tools import TestClient
+
+
+@unittest.skipIf(six.PY2, "v2.0: Only testing for Python 3")
+class CliHelpTest(unittest.TestCase):
+
+    def run(self, *args, **kwargs):
+        with environment_append({"CONAN_V2_CLI": "1"}):
+            super(CliHelpTest, self).run(*args, **kwargs)
+
+    def help_command_test(self):
+        client = TestClient()
+
+        client.run("help")
+        self.assertIn("Shows help for a specific command", client.out)
+
+        client.run("help search")
+        self.assertIn("Searches for package recipes whose name contain", client.out)
diff --git a/conans/test/utils/tools.py b/conans/test/utils/tools.py
index cfaf582301c..e62aabc44aa 100644
--- a/conans/test/utils/tools.py
+++ b/conans/test/utils/tools.py
@@ -27,7 +27,6 @@
 from conans import load
 from conans.client.cache.cache import ClientCache
 from conans.client.cache.remote_registry import Remotes
-from conans.client.command import Command
 from conans.client.conan_api import Conan
 from conans.client.output import ConanOutput
 from conans.client.rest.file_uploader import IterableToFileAdapter
@@ -830,7 +829,12 @@ def run(self, command_line, user_io=None, assert_error=False):
         """
         conan = self.get_conan_api(user_io)
         self.api = conan
-        command = Command(conan)
+        if os.getenv("CONAN_V2_CLI"):
+            from conans.cli.cli import Cli
+            command = Cli(conan)
+        else:
+            from conans.client.command import Command
+            command = Command(conan)
         args = shlex.split(command_line)
         current_dir = os.getcwd()
         os.chdir(self.current_folder)


Code review comments:
diff_hunk: @@ -34,7 +34,7 @@ def __init__(self, method, group, formatters=None):
             self._parser.add_argument('-o', '--output', default=default_output, choices=formatters_list,
                                       action=OnceArgument, help=self._output_help_message)
 
-    def run(self, conan_api, *args, **kwargs):
+    def run(self, *args, conan_api=None, **kwargs):
path: conans/cli/command.py
text: @user1:
Is there any invocation to this method that will not pass the ``conan_api`` argument?