aboutsummaryrefslogtreecommitdiff
path: root/dev/_task.py
diff options
context:
space:
mode:
Diffstat (limited to 'dev/_task.py')
-rw-r--r--dev/_task.py163
1 files changed, 163 insertions, 0 deletions
diff --git a/dev/_task.py b/dev/_task.py
new file mode 100644
index 0000000..5cc257a
--- /dev/null
+++ b/dev/_task.py
@@ -0,0 +1,163 @@
+# 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))