Пример #1
0
def assign():
    # Parse the arguments and resolve what we're assigning.
    arguments = docopt(assign.__doc__)
    which = which_argument(arguments, ('database', 'theme'))

    # Resolve the site path into a site directory and assert we've done so.
    site_path = resolve_path_with_context(arguments['<site>'] or '.',
                                          site=True)
    if not is_site_dir(site_path):
        finish(True, 'Error: no Zoom site at "%s"' % site_path)

    # Load the site configuration.
    site_config = Config(site_path, 'site.ini')

    # Modify based on what assignment is being performed.
    if which == 'database':
        site_config.set('database', 'dbname', arguments['<database>'])
    elif which == 'theme':
        site_config.set('theme', 'name', arguments['<theme>'])
    else:
        raise NotImplementedError()

    # Save the result configuration.
    site_config.write()
    print('Updated "%s"' % site_config.config_pathname)
Пример #2
0
def init():
    # Parse and comprehend arguments.
    arguments = docopt(init.__doc__)
    path = arguments['<path>'] or '.'
    with_creation = not os.path.exists(path)

    # Run initialization with safety.
    try:
        replace_existing = arguments.get('--replace')
        create_site = not arguments.get('--empty')
        create_theme = not arguments.get('--unstyled')

        # Handle the case of the destination directory already existing.
        if not with_creation and is_instance_dir(path):
            if replace_existing:
                # Remove the existing directory.
                shutil.rmtree(path)
                with_creation = True
            else:
                finish(True,
                       ('Failed to initialize in "%s" (already an instance '
                        'directory, use --replace to replace)') % path)
        if with_creation:
            os.makedirs(path, exist_ok=True)

        # Copy instance boilerplate to the new instance directory.
        copy_boilerplate('instances/basic', path)

        if create_site:
            name = arguments.get('--name') or 'default'

            # Initialize the directory.
            default_site_dir = os.path.join(path, 'sites/default')
            os.mkdir(default_site_dir)

            # Perform the creation.
            do_site_creation(default_site_dir, name, arguments)
        if create_theme:
            # Copy theme boilerplate.
            # XXX: (Minimal) duplication from "new theme".
            default_theme_dir = os.path.join(path, 'themes/default')
            os.mkdir(default_theme_dir)
            copy_boilerplate('themes/basic', default_theme_dir)

        # Output a result description.
        op_description = ' (created)' if with_creation else str()
        print('Initialized in "%s"%s' % (path, op_description))
    except BaseException as ex:
        # If an error occurred and we created the directory, remove it.
        if with_creation:
            try:
                shutil.rmtree(path)
            except:
                pass

        if isinstance(ex, SystemExit):
            raise ex

        finish(True, 'Failed to initialize in "%s" (%s)' % (path, str(ex)))
Пример #3
0
def run():
    arguments = docopt(run.__doc__)

    # Configure logging.
    setup_logging(arguments)

    # Resolve the target instance.
    instance_path = resolve_path_with_context(arguments.get('<path>') or '.',
                                              instance=True)
    if not is_instance_dir(instance_path):
        finish(True, 'Error: "%s" is not a Zoom instance' % instance_path)
    instance = Instance(instance_path)

    # Parse and comprehend options.
    repeat = arguments.get('--repeat')
    timeout = arguments.get('--timeout') or -1
    delay = arguments.get('--delay') or 1
    try:
        timeout = int(timeout)
        delay = int(delay)
    except ValueError:
        finish(True, 'Error: invalid timeout or delay')

    indefinite = repeat or timeout == 0
    only_once = timeout == -1

    # State runtime options.
    if indefinite:
        logger.debug('will repeat indefinitely with delay %d second(s)', delay)
    elif only_once:
        logger.debug('will scan once')
    else:
        logger.debug('will repeat for %s second(s) with delay %s seconds(s)',
                     timeout, delay)

    # Mark start.
    start_time = time.time()
    try:
        while True:

            instance.run_background_jobs()

            # Check whether we're done.
            if not indefinite and only_once:
                break
            elapsed_time = time.time() - start_time
            if not indefinite and elapsed_time >= timeout:
                break

            # Sleep the delay in 1 second intervals to allow nice SIGINT
            # pickup.
            sleep_time = 0
            while sleep_time < delay:
                time.sleep(1)
                sleep_time += 1

    except KeyboardInterrupt:
        print('\rstopped')
Пример #4
0
def main():
    """The CLI entry point callable. Handles dispatch to per-command handlers,
    as well as help provision."""
    version_string = ' '.join(('zoom', __version__))
    arguments = docopt(__doc__,
                       version=version_string,
                       options_first=True,
                       help=False)

    show_help = arguments['--help']
    command = arguments['<command>']
    if command:
        if command not in COMMANDS:
            finish(True, 'Invalid command: %s\n' % command, __doc__)
        elif command in DEPRECATED_COMMANDS:
            print('Warning: the %s command is deprecated' % command,
                  file=sys.stderr)
        elif command in EXPERIMENTAL_COMMANDS:
            print('Warning: the %s command is experimental' % command,
                  file=sys.stderr)

        # Resolve the handler and either provide its help or invoke it.
        handler = COMMANDS[command]
        if show_help:
            finish(False, handler.__doc__)
        handler()
    else:
        if show_help:
            finish(False, __doc__)
        else:
            finish(True, 'No command specified (nothing to do)\n', __doc__)
Пример #5
0
def new_filesystem_resource(arguments, command, name):
    path = arguments['<path>'] or '.'

    context_options = COMMAND_PATH_CONTEXTS[command]
    path = os.path.join(resolve_path_with_context(
        path, context_options[0], **context_options[1]
    ), name)

    if os.path.exists(path):
        is_empty_dir = os.path.isdir(path) and not len(os.listdir(path))
        if not is_empty_dir:
            finish(True, (
                'Can\'t create "%s" (already exists as a non-empty directory)'
            )%path)
    else:
        try:
            os.mkdir(path)
        except BaseException as ex:
            finish(True, 'Can\'t create "%s" (%s)'%(path, str(ex)))

    if command == 'app':
        configuration = {
            'title': arguments.get('--title') or name,
            'icon': arguments.get('--icon') or 'cube'
        }
    
        copy_boilerplate('apps/basic', path)

        with open(os.path.join(path, 'config.ini'), 'r') as app_ini_file:
            app_ini = app_ini_file.read()

        for key, value in configuration.items():
            app_ini = app_ini.replace('{{%s}}'%key, value)
        
        with open(os.path.join(path, 'config.ini'), 'w') as app_ini_file:
            app_ini_file.write(app_ini)
    elif command == 'theme':
        copy_boilerplate('themes/basic', path)
    elif command == 'site':
        do_site_creation(path, name, arguments)
    else:
        raise NotImplementedError()

    print('Created "%s"'%path)
Пример #6
0
def serve(_arguments=None):
    arguments = _arguments or docopt(serve.__doc__)

    path_to_try = arguments['<instance>']

    try:
        instance_path = zoom.request.get_instance(path_to_try)

    except zoom.exceptions.NotAnInstanceExecption:
        finish(True, '"%s" is not a Zoom instance' % path_to_try)

    setup_logging(arguments)

    # Comprehend options.
    handlers = None
    if arguments['--noop']:
        handlers = middleware.DEBUGGING_HANDLERS
    user = arguments['--user']
    reloader = arguments['--reloader']
    port = arguments.get('--port') or 80
    try:
        port = int(port)
    except ValueError:
        finish(True, 'Invalid port %s'%port)

    # Create the application.
    if arguments['--verbose']:
        print('Serving Zoom instance at %r' % instance_path)
    app = WSGIApplication(instance=instance_path, handlers=handlers, username=user)
    try:
        # Run.
        run_simple('0.0.0.0', port, app, use_reloader=reloader)
    except (PermissionError, OSError) as err:
        finish(True, (
            '%s: is port %s in use?\n'
            'Use -p or --port to specify another port'
        )%(err.__class__.__name__, port))
Пример #7
0
def do_database_creation(argument_source, collected=dict()):
    """Perform a database creation, including wizarding for required values not
    present in the provided argument source parsed by docopt. The caller can
    supply a set of already-collected applicable options to prevent entry
    duplication."""

    # Decide which arguments need to be collected based on what has already
    # been collected; then collect those and merge the two sets.
    to_collect = list(
        filter(lambda option: option[1] not in collected, DB_CREATION_OPTIONS))
    collected = {**collected, **collect_options(argument_source, to_collect)}

    # Parse and pop some options from the collected set.
    engine = collected['engine']
    db_name = re.sub(r'[^\w_]', '_', collected.pop('database'))
    force = collected.pop('force')
    # Validate and cast port number.
    if 'port' in collected:
        try:
            collected['port'] = int(collected['port'])
        except ValueError:
            finish(True, 'Error: invalid port number')

    # Acquire a connection and maybe create a database based on the engine.
    db = None
    if engine == 'mysql':
        try:
            db = database(**collected)
        except (OperationalError, InternalError) as err:
            if not collected['password']:
                # The user may be attempting to initialize as a root user with
                # no configured password. pymysql doesn't let us authenticate
                # in that case, so we provide a (hopefully) helpful error.
                finish(
                    True,
                    'Error: invalid database credentials (authentication with'
                    ' empty passwords isn\'t supported)')
            else:
                # Otherwise we provide a more generic error.
                finish(True,
                       'Error: invalid database credentials (%s)' % (str(err)))
        # If this database already exists drop it if this operation is forced
        # or die.
        if db_name in db.get_databases():
            if force:
                db('drop database %s' % db_name)
            else:
                finish(True, 'Error: database "%s" already exists' % db_name)
        # Create the database and switch to it.
        db('create database %s;' % db_name)
        db('use %s;' % db_name)
    elif engine == 'sqlite3':
        # TODO(sqlite3 support): We should not collect these options instead.
        # XXX(sqlite3 support): How do we handle --force for sqlite?
        if collected:
            finish(True, 'Error: sqllite3 doesn\'t support extra options')
        db = database('sqlite3', db_name)
    else:
        finish(True, 'Error: engine "%s" isn\'t supported yet' % engine)

    # Create the stock Zoom tables and return the active handle with some
    # collected metadata.
    db.create_site_tables()
    print('Created Zoom database "%s"' % db_name)
    return db, db_name, collected.pop('host')
Пример #8
0
def describe():
    # Parse the provided arguments, resolving which command is running and
    # wizarding any missing options.
    arguments = docopt(doc=describe.__doc__)
    which = which_argument(arguments, ('databases', 'database', 'background'))
    
    def output(result):
        """Output a database query result with formatting."""
        print(str(result).join(('\n=======\n',)*2))

    # Connect to the database.
    collected = db = None
    if which.startswith('database'):
        collected = collect_options(arguments, COMMAND_OPTIONS)
        db = database(**collected)

    if which == 'databases':
        # Describe the set of databases.
        output(db('show databases;'))
    elif which == 'database':
        # Resolve whether an individual table is being referenced.
        db_name = arguments['<db_or_table>']
        table_name = None
        if '.' in db_name:
            db_name, table_name, *rest = db_name.split('.')
            if len(rest):
                finish(True, 'Error: invalid table reference "%s"'%(
                    arguments['<db_or_table>']
                ))
        
        # Switch to the requested database with safety.
        try:
            db('use %s;'%db_name)
        except:
            finish(True, 'Error: invalid database "%s"'%db_name)

        if table_name:
            # Describe an individual table.
            try:
                output(db('describe %s;'%table_name))
            except:
                finish(True, 'Error: invalid table "%s"'%table_name)
        else:
            # Describe the table set for this database.
            output(db('show tables;'))
    elif which == 'background':
        # Resolve the path to the site or instance target.
        target_path = os.path.join(resolve_path_with_context(
            arguments.get('<path>') or '.',
            site=True, instance=True
        ))

        count = None

        def list_site_jobs(site):
            """List background job functions for a site."""
            site.activate()
            for job in site.background_jobs:
                changed = job.has_changed_since_record()
                print('%s%s: %s'%(
                    job.qualified_name, ' [changed]' if changed else str(),
                    job.uow.__doc__
                ))

        def list_site_events(site):
            """List background job events for a site."""
            site.activate()
            for job in site.background_jobs:
                runtimes = job.get_runtimes()
                print('Job: %s'%job.qualified_name)
                for i, runtime in enumerate(runtimes):
                    if i >= count:
                        break
                    print('\t%s'%runtime.describe())

        # Decide which description function to use.
        is_events_query = arguments.get('--events')
        if is_events_query:
            count = arguments.get('--count') or 10
            try:
                count = int(count)
            except ValueError:
                finish(True, 'Error: invalid count')
        
        action_fn = list_site_events if is_events_query else list_site_jobs
        
        if is_site_dir(target_path):
            # Run on the given site only.
            action_fn(Site(target_path))
        elif is_instance_dir(target_path):
            # Run on each site in the instance.
            instance = Instance(target_path)
            for site in instance.get_sites(skip_fails=True).values():
                action_fn(site)
        else:
            finish(True, 'Error: "%s" is not a Zoom site or instance'%(
                target_path
            ))
    else:
        raise NotImplementedError()

    print('Described')