We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[Regression-2.9] Ckan Generate Config 
**CKAN version**

2.9

**Describe the bug**

In ckan 2.9, ckan generate config does not make a config file that can then be immediately edited. 

**Steps to reproduce**
In Ckan <= 2.8.5, 
```
  "$CKAN_HOME"/bin/paster make-config ckan "$CONFIG"

  "$CKAN_HOME"/bin/paster --plugin=ckan config-tool "$CONFIG" \
      "sqlalchemy.url = ${DATABASE_URL}" \
      "solr_url = ${SOLR_URL}" \
      "ckan.site_url = ${SITE_SCHEME}${SITE_HOST}" \
      "ckan.redis.url = ${REDIS_URL}" \
      ...
```
worked, regardless of the database url. 

In ckan 2.9:
```
    "$CKAN_HOME"/bin/ckan generate config "$CONFIG"

    "$CKAN_HOME"/bin/ckan config-tool "$CONFIG" \
      "ckan.storage_path = /var/lib/ckan" \
      "sqlalchemy.url = ${CKAN_DB_URL}"\
```
This fails, because the database is not reachable (and potentially the site url is not defined, IIRC)

**Expected behavior**

Either:
* Ckan generate config should provide the basic parameters required to run config-tool
* config-tool should be able to edit an incomplete ckan.ini file, by not attempting to contact the database, redis, or solr servers. 

```
ckan generate config $CKAN_INI
ckan config-tool $CKAN_INI \ 
    [options] 
```
should work. 

**Additional details**
If possible, please provide the full stack trace of the error raised,  or add screenshots to help explain your problem.

This is my current work-around, which is ugly:
```
  if [ ! -e "$CONFIG" ]; then
      "$CKAN_HOME"/bin/ckan generate config "$CONFIG"
      # site url can't be set with the config tool
      perl -pi -e "s|^(ckan.site_url =)$|\1 ${SITE_SCHEME}${SITE_HOST}|;" $CONFIG
      perl -pi -e "s|^#?(solr_url =).*$|\1 ${SOLR_URL}|;" $CONFIG
      perl -pi -e "s|^#?(ckan.redis.url =).*|\1 ${REDIS_URL}|;" $CONFIG
      # perl has issues with @db in the replacement
      sed -i -e "s|^sqlalchemy.url =.*$|sqlalchemy.url = ${CKAN_DB_URL}|;"  $CONFIG

  fi


  "$CKAN_HOME"/bin/ckan config-tool "$CONFIG" \
      "ckan.storage_path = /var/lib/ckan" \
      "sqlalchemy.url = ${CKAN_DB_URL}"\
      "ckan.site_url = ${SITE_SCHEME}${SITE_HOST}" \
      "solr_url = ${SOLR_URL}" \
```


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `ckan/cli/cli.py`
Content:
```
1 # encoding: utf-8
2 
3 import logging
4 from collections import defaultdict
5 from pkg_resources import iter_entry_points
6 
7 import six
8 import click
9 import sys
10 
11 import ckan.plugins as p
12 import ckan.cli as ckan_cli
13 from ckan.config.middleware import make_app
14 from ckan.exceptions import CkanConfigurationException
15 from ckan.cli import (
16     config_tool,
17     jobs,
18     front_end_build,
19     db, search_index, server,
20     profile,
21     asset,
22     sysadmin,
23     translation,
24     dataset,
25     views,
26     plugin_info,
27     notify,
28     tracking,
29     minify,
30     less,
31     generate,
32     user
33 )
34 
35 from ckan.cli import seed
36 
37 log = logging.getLogger(__name__)
38 
39 
40 class CkanCommand(object):
41 
42     def __init__(self, conf=None):
43         # Don't import `load_config` by itself, rather call it using
44         # module so that it can be patched during tests
45         self.config = ckan_cli.load_config(conf)
46         self.app = make_app(self.config)
47 
48 
49 def _get_commands_from_plugins(plugins):
50     for plugin in plugins:
51         for cmd in plugin.get_commands():
52             cmd._ckan_meta = {
53                 u'name': plugin.name,
54                 u'type': u'plugin'
55             }
56             yield cmd
57 
58 
59 def _get_commands_from_entry_point(entry_point=u'ckan.click_command'):
60     registered_entries = {}
61     for entry in iter_entry_points(entry_point):
62         if entry.name in registered_entries:
63             p.toolkit.error_shout((
64                 u'Attempt to override entry_point `{name}`.\n'
65                 u'First encounter:\n\t{first!r}\n'
66                 u'Second encounter:\n\t{second!r}\n'
67                 u'Either uninstall one of mentioned extensions or update'
68                 u' corresponding `setup.py` and re-install the extension.'
69             ).format(
70                 name=entry.name,
71                 first=registered_entries[entry.name].dist,
72                 second=entry.dist))
73             raise click.Abort()
74         registered_entries[entry.name] = entry
75 
76         cmd = entry.load()
77         cmd._ckan_meta = {
78             u'name': entry.name,
79             u'type': u'entry_point'
80         }
81         yield cmd
82 
83 
84 def _init_ckan_config(ctx, param, value):
85     is_help = u'--help' in sys.argv
86     no_config = len(sys.argv) > 1 and sys.argv[1] in (
87         u'generate', u'config-tool')
88 
89     try:
90         ctx.obj = CkanCommand(value)
91     except CkanConfigurationException as e:
92         # Some commands don't require the config loaded
93         if no_config or is_help:
94             return
95         else:
96             p.toolkit.error_shout(e)
97             raise click.Abort()
98 
99     if six.PY2:
100         ctx.meta["flask_app"] = ctx.obj.app.apps["flask_app"]._wsgi_app
101     else:
102         ctx.meta["flask_app"] = ctx.obj.app._wsgi_app
103 
104     for cmd in _get_commands_from_entry_point():
105         ctx.command.add_command(cmd)
106 
107     plugins = p.PluginImplementations(p.IClick)
108     for cmd in _get_commands_from_plugins(plugins):
109         ctx.command.add_command(cmd)
110 
111 
112 click_config_option = click.option(
113     u'-c',
114     u'--config',
115     default=None,
116     metavar=u'CONFIG',
117     help=u'Config file to use (default: development.ini)',
118     is_eager=True,
119     callback=_init_ckan_config
120 )
121 
122 
123 class CustomGroup(click.Group):
124     _section_titles = {
125         u'plugin': u'Plugins',
126         u'entry_point': u'Entry points',
127     }
128 
129     def format_commands(self, ctx, formatter):
130         # Without any arguments click skips option callbacks.
131         self.parse_args(ctx, [u'help'])
132 
133         commands = []
134         ext_commands = defaultdict(lambda: defaultdict(list))
135 
136         for subcommand in self.list_commands(ctx):
137             cmd = self.get_command(ctx, subcommand)
138             if cmd is None:
139                 continue
140             help = cmd.short_help or u''
141 
142             meta = getattr(cmd, u'_ckan_meta', None)
143             if meta:
144                 ext_commands[meta[u'type']][meta[u'name']].append(
145                     (subcommand, help))
146             else:
147                 commands.append((subcommand, help))
148 
149         if commands:
150             with formatter.section(u'Commands'):
151                 formatter.write_dl(commands)
152 
153         for section, group in ext_commands.items():
154             with formatter.section(self._section_titles.get(section, section)):
155                 for _ext, rows in group.items():
156                     formatter.write_dl(rows)
157 
158 
159 @click.group(cls=CustomGroup)
160 @click.help_option(u'-h', u'--help')
161 @click_config_option
162 def ckan(config, *args, **kwargs):
163     pass
164 
165 
166 ckan.add_command(jobs.jobs)
167 ckan.add_command(config_tool.config_tool)
168 ckan.add_command(front_end_build.front_end_build)
169 ckan.add_command(server.run)
170 ckan.add_command(profile.profile)
171 ckan.add_command(seed.seed)
172 ckan.add_command(db.db)
173 ckan.add_command(search_index.search_index)
174 ckan.add_command(sysadmin.sysadmin)
175 ckan.add_command(asset.asset)
176 ckan.add_command(translation.translation)
177 ckan.add_command(dataset.dataset)
178 ckan.add_command(views.views)
179 ckan.add_command(plugin_info.plugin_info)
180 ckan.add_command(notify.notify)
181 ckan.add_command(tracking.tracking)
182 ckan.add_command(minify.minify)
183 ckan.add_command(less.less)
184 ckan.add_command(generate.generate)
185 ckan.add_command(user.user)
186
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/ckan/cli/cli.py b/ckan/cli/cli.py
--- a/ckan/cli/cli.py
+++ b/ckan/cli/cli.py
@@ -36,6 +36,12 @@
 
 log = logging.getLogger(__name__)
 
+_no_config_commands = [
+    [u'config-tool'],
+    [u'generate', u'config'],
+    [u'generate', u'extension'],
+]
+
 
 class CkanCommand(object):
 
@@ -83,18 +89,20 @@
 
 def _init_ckan_config(ctx, param, value):
     is_help = u'--help' in sys.argv
-    no_config = len(sys.argv) > 1 and sys.argv[1] in (
-        u'generate', u'config-tool')
+    no_config = False
+    if len(sys.argv) > 1:
+        for cmd in _no_config_commands:
+            if sys.argv[1:len(cmd) + 1] == cmd:
+                no_config = True
+                break
+    if no_config or is_help:
+        return
 
     try:
         ctx.obj = CkanCommand(value)
     except CkanConfigurationException as e:
-        # Some commands don't require the config loaded
-        if no_config or is_help:
-            return
-        else:
-            p.toolkit.error_shout(e)
-            raise click.Abort()
+        p.toolkit.error_shout(e)
+        raise click.Abort()
 
     if six.PY2:
         ctx.meta["flask_app"] = ctx.obj.app.apps["flask_app"]._wsgi_app

{"golden_diff": "diff --git a/ckan/cli/cli.py b/ckan/cli/cli.py\n--- a/ckan/cli/cli.py\n+++ b/ckan/cli/cli.py\n@@ -36,6 +36,12 @@\n \n log = logging.getLogger(__name__)\n \n+_no_config_commands = [\n+    [u'config-tool'],\n+    [u'generate', u'config'],\n+    [u'generate', u'extension'],\n+]\n+\n \n class CkanCommand(object):\n \n@@ -83,18 +89,20 @@\n \n def _init_ckan_config(ctx, param, value):\n     is_help = u'--help' in sys.argv\n-    no_config = len(sys.argv) > 1 and sys.argv[1] in (\n-        u'generate', u'config-tool')\n+    no_config = False\n+    if len(sys.argv) > 1:\n+        for cmd in _no_config_commands:\n+            if sys.argv[1:len(cmd) + 1] == cmd:\n+                no_config = True\n+                break\n+    if no_config or is_help:\n+        return\n \n     try:\n         ctx.obj = CkanCommand(value)\n     except CkanConfigurationException as e:\n-        # Some commands don't require the config loaded\n-        if no_config or is_help:\n-            return\n-        else:\n-            p.toolkit.error_shout(e)\n-            raise click.Abort()\n+        p.toolkit.error_shout(e)\n+        raise click.Abort()\n \n     if six.PY2:\n         ctx.meta[\"flask_app\"] = ctx.obj.app.apps[\"flask_app\"]._wsgi_app\n", "issue": "[Regression-2.9] Ckan Generate Config \n**CKAN version**\r\n\r\n2.9\r\n\r\n**Describe the bug**\r\n\r\nIn ckan 2.9, ckan generate config does not make a config file that can then be immediately edited. \r\n\r\n**Steps to reproduce**\r\nIn Ckan <= 2.8.5, \r\n```\r\n  \"$CKAN_HOME\"/bin/paster make-config ckan \"$CONFIG\"\r\n\r\n  \"$CKAN_HOME\"/bin/paster --plugin=ckan config-tool \"$CONFIG\" \\\r\n      \"sqlalchemy.url = ${DATABASE_URL}\" \\\r\n      \"solr_url = ${SOLR_URL}\" \\\r\n      \"ckan.site_url = ${SITE_SCHEME}${SITE_HOST}\" \\\r\n      \"ckan.redis.url = ${REDIS_URL}\" \\\r\n      ...\r\n```\r\nworked, regardless of the database url. \r\n\r\nIn ckan 2.9:\r\n```\r\n    \"$CKAN_HOME\"/bin/ckan generate config \"$CONFIG\"\r\n\r\n    \"$CKAN_HOME\"/bin/ckan config-tool \"$CONFIG\" \\\r\n      \"ckan.storage_path = /var/lib/ckan\" \\\r\n      \"sqlalchemy.url = ${CKAN_DB_URL}\"\\\r\n```\r\nThis fails, because the database is not reachable (and potentially the site url is not defined, IIRC)\r\n\r\n**Expected behavior**\r\n\r\nEither:\r\n* Ckan generate config should provide the basic parameters required to run config-tool\r\n* config-tool should be able to edit an incomplete ckan.ini file, by not attempting to contact the database, redis, or solr servers. \r\n\r\n```\r\nckan generate config $CKAN_INI\r\nckan config-tool $CKAN_INI \\ \r\n    [options] \r\n```\r\nshould work. \r\n\r\n**Additional details**\r\nIf possible, please provide the full stack trace of the error raised,  or add screenshots to help explain your problem.\r\n\r\nThis is my current work-around, which is ugly:\r\n```\r\n  if [ ! -e \"$CONFIG\" ]; then\r\n      \"$CKAN_HOME\"/bin/ckan generate config \"$CONFIG\"\r\n      # site url can't be set with the config tool\r\n      perl -pi -e \"s|^(ckan.site_url =)$|\\1 ${SITE_SCHEME}${SITE_HOST}|;\" $CONFIG\r\n      perl -pi -e \"s|^#?(solr_url =).*$|\\1 ${SOLR_URL}|;\" $CONFIG\r\n      perl -pi -e \"s|^#?(ckan.redis.url =).*|\\1 ${REDIS_URL}|;\" $CONFIG\r\n      # perl has issues with @db in the replacement\r\n      sed -i -e \"s|^sqlalchemy.url =.*$|sqlalchemy.url = ${CKAN_DB_URL}|;\"  $CONFIG\r\n\r\n  fi\r\n\r\n\r\n  \"$CKAN_HOME\"/bin/ckan config-tool \"$CONFIG\" \\\r\n      \"ckan.storage_path = /var/lib/ckan\" \\\r\n      \"sqlalchemy.url = ${CKAN_DB_URL}\"\\\r\n      \"ckan.site_url = ${SITE_SCHEME}${SITE_HOST}\" \\\r\n      \"solr_url = ${SOLR_URL}\" \\\r\n```\r\n\n", "before_files": [{"content": "# encoding: utf-8\n\nimport logging\nfrom collections import defaultdict\nfrom pkg_resources import iter_entry_points\n\nimport six\nimport click\nimport sys\n\nimport ckan.plugins as p\nimport ckan.cli as ckan_cli\nfrom ckan.config.middleware import make_app\nfrom ckan.exceptions import CkanConfigurationException\nfrom ckan.cli import (\n    config_tool,\n    jobs,\n    front_end_build,\n    db, search_index, server,\n    profile,\n    asset,\n    sysadmin,\n    translation,\n    dataset,\n    views,\n    plugin_info,\n    notify,\n    tracking,\n    minify,\n    less,\n    generate,\n    user\n)\n\nfrom ckan.cli import seed\n\nlog = logging.getLogger(__name__)\n\n\nclass CkanCommand(object):\n\n    def __init__(self, conf=None):\n        # Don't import `load_config` by itself, rather call it using\n        # module so that it can be patched during tests\n        self.config = ckan_cli.load_config(conf)\n        self.app = make_app(self.config)\n\n\ndef _get_commands_from_plugins(plugins):\n    for plugin in plugins:\n        for cmd in plugin.get_commands():\n            cmd._ckan_meta = {\n                u'name': plugin.name,\n                u'type': u'plugin'\n            }\n            yield cmd\n\n\ndef _get_commands_from_entry_point(entry_point=u'ckan.click_command'):\n    registered_entries = {}\n    for entry in iter_entry_points(entry_point):\n        if entry.name in registered_entries:\n            p.toolkit.error_shout((\n                u'Attempt to override entry_point `{name}`.\\n'\n                u'First encounter:\\n\\t{first!r}\\n'\n                u'Second encounter:\\n\\t{second!r}\\n'\n                u'Either uninstall one of mentioned extensions or update'\n                u' corresponding `setup.py` and re-install the extension.'\n            ).format(\n                name=entry.name,\n                first=registered_entries[entry.name].dist,\n                second=entry.dist))\n            raise click.Abort()\n        registered_entries[entry.name] = entry\n\n        cmd = entry.load()\n        cmd._ckan_meta = {\n            u'name': entry.name,\n            u'type': u'entry_point'\n        }\n        yield cmd\n\n\ndef _init_ckan_config(ctx, param, value):\n    is_help = u'--help' in sys.argv\n    no_config = len(sys.argv) > 1 and sys.argv[1] in (\n        u'generate', u'config-tool')\n\n    try:\n        ctx.obj = CkanCommand(value)\n    except CkanConfigurationException as e:\n        # Some commands don't require the config loaded\n        if no_config or is_help:\n            return\n        else:\n            p.toolkit.error_shout(e)\n            raise click.Abort()\n\n    if six.PY2:\n        ctx.meta[\"flask_app\"] = ctx.obj.app.apps[\"flask_app\"]._wsgi_app\n    else:\n        ctx.meta[\"flask_app\"] = ctx.obj.app._wsgi_app\n\n    for cmd in _get_commands_from_entry_point():\n        ctx.command.add_command(cmd)\n\n    plugins = p.PluginImplementations(p.IClick)\n    for cmd in _get_commands_from_plugins(plugins):\n        ctx.command.add_command(cmd)\n\n\nclick_config_option = click.option(\n    u'-c',\n    u'--config',\n    default=None,\n    metavar=u'CONFIG',\n    help=u'Config file to use (default: development.ini)',\n    is_eager=True,\n    callback=_init_ckan_config\n)\n\n\nclass CustomGroup(click.Group):\n    _section_titles = {\n        u'plugin': u'Plugins',\n        u'entry_point': u'Entry points',\n    }\n\n    def format_commands(self, ctx, formatter):\n        # Without any arguments click skips option callbacks.\n        self.parse_args(ctx, [u'help'])\n\n        commands = []\n        ext_commands = defaultdict(lambda: defaultdict(list))\n\n        for subcommand in self.list_commands(ctx):\n            cmd = self.get_command(ctx, subcommand)\n            if cmd is None:\n                continue\n            help = cmd.short_help or u''\n\n            meta = getattr(cmd, u'_ckan_meta', None)\n            if meta:\n                ext_commands[meta[u'type']][meta[u'name']].append(\n                    (subcommand, help))\n            else:\n                commands.append((subcommand, help))\n\n        if commands:\n            with formatter.section(u'Commands'):\n                formatter.write_dl(commands)\n\n        for section, group in ext_commands.items():\n            with formatter.section(self._section_titles.get(section, section)):\n                for _ext, rows in group.items():\n                    formatter.write_dl(rows)\n\n\n@click.group(cls=CustomGroup)\n@click.help_option(u'-h', u'--help')\n@click_config_option\ndef ckan(config, *args, **kwargs):\n    pass\n\n\nckan.add_command(jobs.jobs)\nckan.add_command(config_tool.config_tool)\nckan.add_command(front_end_build.front_end_build)\nckan.add_command(server.run)\nckan.add_command(profile.profile)\nckan.add_command(seed.seed)\nckan.add_command(db.db)\nckan.add_command(search_index.search_index)\nckan.add_command(sysadmin.sysadmin)\nckan.add_command(asset.asset)\nckan.add_command(translation.translation)\nckan.add_command(dataset.dataset)\nckan.add_command(views.views)\nckan.add_command(plugin_info.plugin_info)\nckan.add_command(notify.notify)\nckan.add_command(tracking.tracking)\nckan.add_command(minify.minify)\nckan.add_command(less.less)\nckan.add_command(generate.generate)\nckan.add_command(user.user)\n", "path": "ckan/cli/cli.py"}], "after_files": [{"content": "# encoding: utf-8\n\nimport logging\nfrom collections import defaultdict\nfrom pkg_resources import iter_entry_points\n\nimport six\nimport click\nimport sys\n\nimport ckan.plugins as p\nimport ckan.cli as ckan_cli\nfrom ckan.config.middleware import make_app\nfrom ckan.exceptions import CkanConfigurationException\nfrom ckan.cli import (\n    config_tool,\n    jobs,\n    front_end_build,\n    db, search_index, server,\n    profile,\n    asset,\n    sysadmin,\n    translation,\n    dataset,\n    views,\n    plugin_info,\n    notify,\n    tracking,\n    minify,\n    less,\n    generate,\n    user\n)\n\nfrom ckan.cli import seed\n\nlog = logging.getLogger(__name__)\n\n_no_config_commands = [\n    [u'config-tool'],\n    [u'generate', u'config'],\n    [u'generate', u'extension'],\n]\n\n\nclass CkanCommand(object):\n\n    def __init__(self, conf=None):\n        # Don't import `load_config` by itself, rather call it using\n        # module so that it can be patched during tests\n        self.config = ckan_cli.load_config(conf)\n        self.app = make_app(self.config)\n\n\ndef _get_commands_from_plugins(plugins):\n    for plugin in plugins:\n        for cmd in plugin.get_commands():\n            cmd._ckan_meta = {\n                u'name': plugin.name,\n                u'type': u'plugin'\n            }\n            yield cmd\n\n\ndef _get_commands_from_entry_point(entry_point=u'ckan.click_command'):\n    registered_entries = {}\n    for entry in iter_entry_points(entry_point):\n        if entry.name in registered_entries:\n            p.toolkit.error_shout((\n                u'Attempt to override entry_point `{name}`.\\n'\n                u'First encounter:\\n\\t{first!r}\\n'\n                u'Second encounter:\\n\\t{second!r}\\n'\n                u'Either uninstall one of mentioned extensions or update'\n                u' corresponding `setup.py` and re-install the extension.'\n            ).format(\n                name=entry.name,\n                first=registered_entries[entry.name].dist,\n                second=entry.dist))\n            raise click.Abort()\n        registered_entries[entry.name] = entry\n\n        cmd = entry.load()\n        cmd._ckan_meta = {\n            u'name': entry.name,\n            u'type': u'entry_point'\n        }\n        yield cmd\n\n\ndef _init_ckan_config(ctx, param, value):\n    is_help = u'--help' in sys.argv\n    no_config = False\n    if len(sys.argv) > 1:\n        for cmd in _no_config_commands:\n            if sys.argv[1:len(cmd) + 1] == cmd:\n                no_config = True\n                break\n    if no_config or is_help:\n        return\n\n    try:\n        ctx.obj = CkanCommand(value)\n    except CkanConfigurationException as e:\n        p.toolkit.error_shout(e)\n        raise click.Abort()\n\n    if six.PY2:\n        ctx.meta[\"flask_app\"] = ctx.obj.app.apps[\"flask_app\"]._wsgi_app\n    else:\n        ctx.meta[\"flask_app\"] = ctx.obj.app._wsgi_app\n\n    for cmd in _get_commands_from_entry_point():\n        ctx.command.add_command(cmd)\n\n    plugins = p.PluginImplementations(p.IClick)\n    for cmd in _get_commands_from_plugins(plugins):\n        ctx.command.add_command(cmd)\n\n\nclick_config_option = click.option(\n    u'-c',\n    u'--config',\n    default=None,\n    metavar=u'CONFIG',\n    help=u'Config file to use (default: development.ini)',\n    is_eager=True,\n    callback=_init_ckan_config\n)\n\n\nclass CustomGroup(click.Group):\n    _section_titles = {\n        u'plugin': u'Plugins',\n        u'entry_point': u'Entry points',\n    }\n\n    def format_commands(self, ctx, formatter):\n        # Without any arguments click skips option callbacks.\n        self.parse_args(ctx, [u'help'])\n\n        commands = []\n        ext_commands = defaultdict(lambda: defaultdict(list))\n\n        for subcommand in self.list_commands(ctx):\n            cmd = self.get_command(ctx, subcommand)\n            if cmd is None:\n                continue\n            help = cmd.short_help or u''\n\n            meta = getattr(cmd, u'_ckan_meta', None)\n            if meta:\n                ext_commands[meta[u'type']][meta[u'name']].append(\n                    (subcommand, help))\n            else:\n                commands.append((subcommand, help))\n\n        if commands:\n            with formatter.section(u'Commands'):\n                formatter.write_dl(commands)\n\n        for section, group in ext_commands.items():\n            with formatter.section(self._section_titles.get(section, section)):\n                for _ext, rows in group.items():\n                    formatter.write_dl(rows)\n\n\n@click.group(cls=CustomGroup)\n@click.help_option(u'-h', u'--help')\n@click_config_option\ndef ckan(config, *args, **kwargs):\n    pass\n\n\nckan.add_command(jobs.jobs)\nckan.add_command(config_tool.config_tool)\nckan.add_command(front_end_build.front_end_build)\nckan.add_command(server.run)\nckan.add_command(profile.profile)\nckan.add_command(seed.seed)\nckan.add_command(db.db)\nckan.add_command(search_index.search_index)\nckan.add_command(sysadmin.sysadmin)\nckan.add_command(asset.asset)\nckan.add_command(translation.translation)\nckan.add_command(dataset.dataset)\nckan.add_command(views.views)\nckan.add_command(plugin_info.plugin_info)\nckan.add_command(notify.notify)\nckan.add_command(tracking.tracking)\nckan.add_command(minify.minify)\nckan.add_command(less.less)\nckan.add_command(generate.generate)\nckan.add_command(user.user)\n", "path": "ckan/cli/cli.py"}]}