Browse Source

Started working on a FlaskBB CLI

Pretty much a WIP
sh4nks 8 years ago
parent
commit
209af8c9ce
4 changed files with 406 additions and 1 deletions
  1. 203 0
      docs/cli.rst
  2. 2 1
      docs/contents.rst.inc
  3. 197 0
      flaskbb/cli.py
  4. 4 0
      setup.py

+ 203 - 0
docs/cli.rst

@@ -0,0 +1,203 @@
+.. _commandline:
+
+Command Line Interface
+======================
+
+Here you find the documentation about FlaskBB's Command Line Interface.
+The command line is provided by click.
+
+To get help for a commands, just type ``flaskbb COMMAND --help``.
+If no command options or arguments are used it will print out all
+available commands
+
+.. sourcecode:: text
+
+    Usage: flaskbb [OPTIONS] COMMAND [ARGS]...
+
+      This is the commandline interface for flaskbb.
+
+    Options:
+      --help  Show this message and exit.
+
+    Commands:
+      db            Perform database migrations.
+      install       Interactively setup flaskbb.
+      plugins       Plugins command sub group.
+      populate      Creates the database with some test data.
+      run           Runs a development server.
+      setup         Runs a preconfigured setup for FlaskBB.
+      shell         Runs a shell in the app context.
+      start         Starts a production ready wsgi server.
+      themes        Themes command sub group.
+      translations  Translations command sub group.
+      upgrade       Updates the migrations and fixtures.
+      users         Create, update or delete users.
+
+
+
+Commands
+--------
+
+You can find a complete overview, including the ones from the sub groups,
+below.
+
+
+.. program:: flaskbb
+
+
+.. option:: install
+
+        Installs flaskbb. If no arguments are used, an interactive setup
+        will be run.
+
+
+.. option:: upgrade
+
+    Updates the migrations and fixtures to the latest version.
+
+    .. option:: --all, -a
+
+        Upgrades migrations AND fixtures to the latest version.
+
+    .. option:: --fixture FIXTURE, -f FIXTURE
+
+        The fixture which should be upgraded or installed.
+        All fixtures have to be places inside flaskbb/fixtures/
+
+    .. option:: --force-fixture, -ff
+
+        Forcefully upgrades the fixtures. WARNING: This will also overwrite
+        any settings.
+
+
+.. option:: populate
+
+        Creates the necessary tables and groups for FlaskBB.
+
+        .. option:: --test, -t
+
+            Adds some test data.
+
+        .. option:: --posts
+
+            Number of posts to create in each topic (default: 100).
+
+        .. option:: --topics
+
+            Number of topics to create (default: 100).
+
+        .. option:: --force, -f
+
+            Will delete the database before populating it.
+
+
+.. option:: runserver
+
+        Starts the development server
+
+
+.. option:: start
+
+    Starts a production ready wsgi server.
+    TODO: Unsure about this command, would 'serve' or 'server' be better?
+
+    .. option:: --server SERVER
+
+        Defaults to gunicorn. The following WSGI Servers are supported:
+            - gunicorn (default)
+            - TODO
+
+
+.. option:: shell
+
+    Creates a python shell with an app context.
+
+
+.. option:: translations
+
+    Translations command sub group.
+
+    .. option:: add LANGUAGE_CODE
+
+        Adds a new language to FlaskBB's translations.
+        The ``LANGUAGE_CODE`` is the short identifier for the language i.e.
+        '``en``', '``de``', '``de_AT``', etc.
+
+        .. option:: -p, --plugin PLUGIN_NAME
+
+            Adds a new language to a plugin.
+
+    .. option:: update
+
+        Updates all translations, including the ones from the plugins.
+        Use -p <PLUGIN_NAME> to only update the translation of a given
+        plugin.
+
+        .. option:: -p, --plugin PLUGIN_NAME
+
+            Update the language of the given plugin.
+
+    .. option:: compile
+
+        Compiles all translations, including the ones from the plugins.
+
+        .. option:: -p, --plugin PLUGIN_NAME
+
+            Compiles only the given plugin translation.
+
+
+.. option:: plugins
+
+    Plugins command sub group.
+
+    .. option:: create PLUGIN_NAME
+
+        Creates a basic starter template for a new plugin.
+
+    .. option:: add PLUGIN_NAME
+
+        Adds a new plugin.
+
+    .. option:: remove PLUGIN_NAME
+
+        Removes a plugin.
+
+
+.. option:: themes
+
+    Themes command sub group.
+
+    .. option:: create THEME_NAME
+
+        Creates a basic starter template for a new theme.
+
+    .. option:: add THEME_NAME
+
+        Adds a new theme.
+
+    .. option:: remove THEME_NAME
+
+        Removes a theme.
+
+
+.. option:: users
+
+    Creates a new user. Pass any arguments to omit the interactive mode.
+
+    .. option:: -g, --group GROUP
+
+        Uses ``GROUP`` as the primary group.
+
+    .. option:: -u, --username USERNAME
+
+        Uses ``USERNAME`` as the name of the new user.
+
+    .. option:: -p, --password PASSWORD
+
+        Uses ``PASSWORD`` as password for the new user. But you have to ḱnow,
+        that when choosing this option, the password is most likely stored
+        in a history file (i.e. ``.bash_history``).
+
+    .. option:: -e, --email EMAIL
+
+        Uses ``EMAIL`` as the email address for the new user.

+ 2 - 1
docs/contents.rst.inc

@@ -5,10 +5,11 @@ Contents
    :maxdepth: 2
 
    installation
+   theming
+   cli
    plugins
    plugin_tutorial/index
    events
-   theming
    settings
    permissions
    models

+ 197 - 0
flaskbb/cli.py

@@ -0,0 +1,197 @@
+# -*- coding: utf-8 -*-
+"""
+    flaskbb.cli
+    ~~~~~~~~~~~
+
+    FlaskBB's Command Line Interface.
+    To make it work, you have to install FlaskBB via ``pip install -e .``.
+
+    :copyright: (c) 2016 by the FlaskBB Team.
+    :license: BSD, see LICENSE for more details.
+"""
+import sys
+import os
+
+from flask.cli import FlaskGroup, with_appcontext
+import click
+
+from flaskbb import create_app
+
+
+@click.group(cls=FlaskGroup, create_app=create_app)
+def cli():
+    """This is the commandline interface for flaskbb."""
+    pass
+
+
+@cli.command()
+@click.option('--emojis', default=True, help='Downloads some emojis')
+@click.option('--initdb', default=True, help='Creates an empty database')
+@click.option('--dropdb', default=True, help='Deletes the database')
+def install():
+    """Installs flaskbb. If no arguments are used, an interactive setup
+    will be run.
+    """
+    click.echo('FlaskBB has been successfully installed.')
+
+
+@cli.command()
+@click.option('--all', '-a', default=True, is_flag=True,
+              help='Upgrades migrations AND fixtures to the latest version.')
+@click.option('--fixture/', '-f', default=None,
+              help='The fixture which should be upgraded or installed.')
+@click.option('--force-fixture', '-ff', default=False, is_flag=True,
+              help='Forcefully upgrades the fixtures.')
+def upgrade(all, fixture, force_fixture):
+    """Updates the migrations and fixtures."""
+    click.echo('FlaskBB has been successfully installed.')
+
+
+@cli.command()
+@click.option('--test', '-t', default=False, is_flag=True,
+              help='Adds some test data.')
+@click.option('--posts', default=100,
+              help='Number of posts to create in each topic (default: 100).')
+@click.option('--topics', default=100,
+              help='Number of topics to create (default: 100).')
+@click.option('--force', '-f', default=100,
+              help='Will delete the database before populating it.')
+def populate(test, posts, topics):
+    """Creates the necessary tables and groups for FlaskBB."""
+    click.echo('populate()')
+
+
+@cli.command()
+@click.option("--server", "-s", type=click.Choice(["gunicorn"]))
+def start(server):
+    """Starts a production ready wsgi server.
+    TODO: Unsure about this command, would 'serve' or 'server' be better?
+    """
+    click.echo('start()')
+
+
+@cli.group()
+def translations():
+    """Translations command sub group."""
+    click.echo('translations()')
+
+
+@cli.group()
+def plugins():
+    """Plugins command sub group."""
+    click.echo('plugins()')
+
+
+@cli.group()
+def themes():
+    """Themes command sub group."""
+    click.echo('themes()')
+
+
+@cli.group()
+def users():
+    """Create, update or delete users."""
+    click.echo("users()")
+
+
+@users.command("new")
+@click.option('--username', prompt=True,
+              default=lambda: os.environ.get('USER', ''),
+              help="The username of the new user.")
+@click.option('--email', prompt=True,
+              help="The email address of the new user.")
+@click.option('--password', prompt=True, hide_input=True,
+              confirmation_prompt=True,
+              help="The password of the new user.")
+def new_user(username, email, password):
+    """Creates a new user. Omit any options to use the interactive mode."""
+    click.echo('user: %s, %s, %s' % (username, email, password))
+
+
+@cli.command('shell', short_help='Runs a shell in the app context.')
+@with_appcontext
+def shell_command():
+    """Runs an interactive Python shell in the context of a given
+    Flask application.  The application will populate the default
+    namespace of this shell according to it's configuration.
+    This is useful for executing small snippets of management code
+    without having to manually configuring the application.
+
+    This code snippet is taken from Flask's cli module and modified to
+    run IPython and falls back to the normal shell if IPython is not
+    available.
+    """
+    import code
+    from flask import _app_ctx_stack
+    app = _app_ctx_stack.top.app
+    banner = 'Python %s on %s\nApp: %s%s\nInstance: %s' % (
+        sys.version,
+        sys.platform,
+        app.import_name,
+        app.debug and ' [debug]' or '',
+        app.instance_path,
+    )
+    ctx = {}
+
+    # Support the regular Python interpreter startup script if someone
+    # is using it.
+    startup = os.environ.get('PYTHONSTARTUP')
+    if startup and os.path.isfile(startup):
+        with open(startup, 'r') as f:
+            eval(compile(f.read(), startup, 'exec'), ctx)
+
+    ctx.update(app.make_shell_context())
+
+    try:
+        import IPython
+        IPython.embed(banner1=banner, user_ns=ctx)
+    except ImportError:
+        code.interact(banner=banner, local=ctx)
+
+
+@cli.command("urls")
+@click.option("--order", default="rule", help="Property on Rule to order by.")
+def list_urls(order):
+    """Lists all available routes.
+    Taken from Flask-Script: https://goo.gl/K6NCAz"""
+    from flask import current_app
+
+    rows = []
+    column_length = 0
+    column_headers = ('Rule', 'Endpoint', 'Arguments')
+
+    rules = sorted(
+        current_app.url_map.iter_rules(),
+        key=lambda rule: getattr(rule, order)
+    )
+    for rule in rules:
+        rows.append((rule.rule, rule.endpoint, None))
+    column_length = 2
+
+    str_template = ''
+    table_width = 0
+
+    if column_length >= 1:
+        max_rule_length = max(len(r[0]) for r in rows)
+        max_rule_length = max_rule_length if max_rule_length > 4 else 4
+        str_template += '%-' + str(max_rule_length) + 's'
+        table_width += max_rule_length
+
+    if column_length >= 2:
+        max_endpoint_len = max(len(str(r[1])) for r in rows)
+        # max_endpoint_len = max(rows, key=len)
+        max_endpoint_len = max_endpoint_len if max_endpoint_len > 8 else 8
+        str_template += '  %-' + str(max_endpoint_len) + 's'
+        table_width += 2 + max_endpoint_len
+
+    if column_length >= 3:
+        max_args_len = max(len(str(r[2])) for r in rows)
+        max_args_len = max_args_len if max_args_len > 9 else 9
+        str_template += '  %-' + str(max_args_len) + 's'
+        table_width += 2 + max_args_len
+
+    print(str_template % (column_headers[:column_length]))
+    print('-' * table_width)
+
+    for row in rows:
+        print(str_template % row[:column_length])

+ 4 - 0
setup.py

@@ -109,6 +109,10 @@ setup(
         'Whoosh',
         'WTForms'
     ],
+    entry_points='''
+        [console_scripts]
+        flaskbb=flaskbb.cli:cli
+    ''',
     test_suite='tests',
     tests_require=[
         'py',