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)
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)))
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')
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__)
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)
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))
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')
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')