From aea82df683bc22b965734776911e80dc15482d72 Mon Sep 17 00:00:00 2001 From: Michal Stolarczyk Date: Tue, 5 May 2020 13:02:47 -0400 Subject: [PATCH] multiple changes: - implement looper mod command; closes #64 - look for ditfile in dir parents; #253 --- looper/__init__.py | 13 ++++++++----- looper/looper.py | 8 +++++--- looper/utils.py | 46 ++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 51 insertions(+), 16 deletions(-) diff --git a/looper/__init__.py b/looper/__init__.py index 50f77304d..c7142aa9f 100644 --- a/looper/__init__.py +++ b/looper/__init__.py @@ -103,7 +103,9 @@ def build_parser(): "check": "Check flag status of current runs.", "clean": "Run clean scripts of already processed jobs.", "inspect": "Print information about a project.", - "init": "Initialize looper dotfile."} + "init": "Initialize looper dotfile.", + "mod": "Modify looper dotfile." + } subparsers = parser.add_subparsers(dest="command") @@ -122,8 +124,9 @@ def add_subparser(cmd): clean_subparser = add_subparser("clean") inspect_subparser = add_subparser("inspect") init_subparser = add_subparser("init") + mod_subparser = add_subparser("mod") for subparser in [run_subparser, rerun_subparser, collate_subparser, - init_subparser]: + init_subparser, mod_subparser]: subparser.add_argument( "--ignore-flags", dest="ignore_flags", default=False, action=_StoreBoolActionType, type=html_checkbox(checked=False), @@ -160,7 +163,7 @@ def add_subparser(cmd): help="String to append to every command, " "overriding values in PEP.") - for subparser in [run_subparser, rerun_subparser, init_subparser]: + for subparser in [run_subparser, rerun_subparser, init_subparser, mod_subparser]: # Note that defaults for otherwise numeric lump parameters are set to # null by default so that the logic that parses their values may # distinguish between explicit 0 and lack of specification. @@ -188,7 +191,7 @@ def add_subparser(cmd): type=html_select(choices=FLAGS), help="Check on only these flags/status values.") - for subparser in [destroy_subparser, clean_subparser, init_subparser]: + for subparser in [destroy_subparser, clean_subparser, init_subparser, mod_subparser]: subparser.add_argument( "--force-yes", action=_StoreBoolActionType, default=False, type=html_checkbox(checked=False), @@ -199,7 +202,7 @@ def add_subparser(cmd): for subparser in [run_subparser, rerun_subparser, table_subparser, report_subparser, destroy_subparser, check_subparser, clean_subparser, collate_subparser, inspect_subparser, - init_subparser]: + init_subparser, mod_subparser]: subparser.add_argument("config_file", nargs="?", help="Project configuration file (YAML).") subparser.add_argument( diff --git a/looper/looper.py b/looper/looper.py index b21699bff..cfebc259d 100755 --- a/looper/looper.py +++ b/looper/looper.py @@ -702,10 +702,12 @@ def main(): global _LOGGER parser = build_parser() args, remaining_args = parser.parse_known_args() - dotfile_path = os.path.join(os.getcwd(), LOOPER_DOTFILE_NAME) if args.command == "init": - sys.exit(int(not init_dotfile(parser, dotfile_path, args))) - args = enrich_args_via_dotfile(args, dotfile_path) + sys.exit(int(not write_dotfile(parser, dotfile_path(), args))) + if args.command == "mod": + sys.exit(int(not write_dotfile(parser, dotfile_path(must_exist=True), + args, update=True))) + args = enrich_args_via_dotfile(args, dotfile_path()) if args.command is None: parser.print_help(sys.stderr) sys.exit(1) diff --git a/looper/utils.py b/looper/utils.py index a9890ada1..31900ce2a 100644 --- a/looper/utils.py +++ b/looper/utils.py @@ -296,24 +296,54 @@ def enrich_args_via_dotfile(parser_args, dotfile_path): return result -def init_dotfile(parser, path, arg_values=None): +def write_dotfile(parser, path, arg_values=None, update=False): """ Print out available dests and respective defaults in the provided subparser :param argparse.ArgumentParser parser: parser to examine :param argparse.Namespace arg_values: argument values + :param bool update: whether the file should be read, updated and written to :return bool: whether the initialization happened successfully """ - if os.path.exists(path): + if not update and os.path.exists(path): print("Can't initialize, file exists: {}".format(path)) return False arg_values = vars(arg_values) if arg_values is not None else dict() - defaults = merge_dicts(parser.arg_defaults(top_level=True), - parser.arg_defaults(unique=True)) - for k, v in arg_values.items(): - if k in defaults: - defaults[k] = arg_values[k] + if update: + with open(path, 'r') as dotfile: + defaults = yaml.safe_load(dotfile) + else: + defaults = merge_dicts(parser.arg_defaults(top_level=True), + parser.arg_defaults(unique=True)) + defaults.update(arg_values) with open(path, 'w') as dotfile: yaml.dump({k: str(v) for k, v in defaults.items()}, dotfile) - print("Initialized looper dotfile in: {}".format(path)) + print("{} looper dotfile: {}". + format("Modified" if update else "Initialized", path)) return True + + +def dotfile_path(directory=os.getcwd(), must_exist=False): + """ + Get the path to the looper dotfile + + If file existence is forced this function will look for it in + the directory parents + + :param str directory: directory path to start the search in + :param bool must_exist: whether the file must exist + :return str: path to the dotfile + :raise OSError: if the file does not exist + """ + cur_dir = directory + if not must_exist: + return os.path.join(cur_dir, LOOPER_DOTFILE_NAME) + while True: + parent_dir = os.path.dirname(cur_dir) + if LOOPER_DOTFILE_NAME in os.listdir(cur_dir): + return os.path.join(cur_dir, LOOPER_DOTFILE_NAME) + if cur_dir == parent_dir: + # root, file does not exist + raise OSError("Looper dotfile ({}) not found in '{}' and all " + "its parents".format(LOOPER_DOTFILE_NAME, directory)) + cur_dir = parent_dir