# coding: utf-8 from __future__ import unicode_literals, division, absolute_import, print_function import ast import _ast import os import sys from . import package_root, task_keyword_args from ._import import _import_from if sys.version_info < (3,): byte_cls = str else: byte_cls = bytes def _list_tasks(): """ Fetches a list of all valid tasks that may be run, and the args they accept. Does not actually import the task module to prevent errors if a user does not have the dependencies installed for every task. :return: A list of 2-element tuples: 0: a unicode string of the task name 1: a list of dicts containing the parameter definitions """ out = [] dev_path = os.path.join(package_root, 'dev') for fname in sorted(os.listdir(dev_path)): if fname.startswith('.') or fname.startswith('_'): continue if not fname.endswith('.py'): continue name = fname[:-3] args = () full_path = os.path.join(package_root, 'dev', fname) with open(full_path, 'rb') as f: full_code = f.read() if sys.version_info >= (3,): full_code = full_code.decode('utf-8') task_node = ast.parse(full_code, filename=full_path) for node in ast.iter_child_nodes(task_node): if isinstance(node, _ast.Assign): if len(node.targets) == 1 \ and isinstance(node.targets[0], _ast.Name) \ and node.targets[0].id == 'run_args': args = ast.literal_eval(node.value) break out.append((name, args)) return out def show_usage(): """ Prints to stderr the valid options for invoking tasks """ valid_tasks = [] for task in _list_tasks(): usage = task[0] for run_arg in task[1]: usage += ' ' name = run_arg.get('name', '') if run_arg.get('required', False): usage += '{%s}' % name else: usage += '[%s]' % name valid_tasks.append(usage) out = 'Usage: run.py' for karg in task_keyword_args: out += ' [%s=%s]' % (karg['name'], karg['placeholder']) out += ' (%s)' % ' | '.join(valid_tasks) print(out, file=sys.stderr) sys.exit(1) def _get_arg(num): """ :return: A unicode string of the requested command line arg """ if len(sys.argv) < num + 1: return None arg = sys.argv[num] if isinstance(arg, byte_cls): arg = arg.decode('utf-8') return arg def run_task(): """ Parses the command line args, invoking the requested task """ arg_num = 1 task = None args = [] kwargs = {} # We look for the task name, processing any global task keyword args # by setting the appropriate env var while True: val = _get_arg(arg_num) if val is None: break next_arg = False for karg in task_keyword_args: if val.startswith(karg['name'] + '='): os.environ[karg['env_var']] = val[len(karg['name']) + 1:] next_arg = True break if next_arg: arg_num += 1 continue task = val break if task is None: show_usage() task_mod = _import_from('dev.%s' % task, package_root, allow_error=True) if task_mod is None: show_usage() run_args = task_mod.__dict__.get('run_args', []) max_args = arg_num + 1 + len(run_args) if len(sys.argv) > max_args: show_usage() for i, run_arg in enumerate(run_args): val = _get_arg(arg_num + 1 + i) if val is None: if run_arg.get('required', False): show_usage() break if run_arg.get('cast') == 'int' and val.isdigit(): val = int(val) kwarg = run_arg.get('kwarg') if kwarg: kwargs[kwarg] = val else: args.append(val) run = task_mod.__dict__.get('run') result = run(*args, **kwargs) sys.exit(int(not result))