def cli(prefix: str, paths: List[PurePath], commands: List[str], args: List[str], logger: UI, descriptions: List[str], transaction: Transaction, *_, **__): n = len(commands) if not len(paths) in [1, n]: logger.exit("There must either be 1 or n paths " "where n is the number of subcommands.") if not (descriptions is None or len(descriptions) in [0, 1, n]): logger.exit("There must either be 1 or n descriptions " "where n is the number of subcommands.") descriptions = descriptions or [] iterator = enumerate(itertools.zip_longest(paths, commands, descriptions)) for i, (path, command, description) in iterator: if path is None: if n == 1: path = PurePath(paths[0]) else: path = PurePath(paths[0], str(i)) if description is None: if descriptions: description = descriptions[0] new( command=Command(prefix, command, *args, path=path), description=description, path=path, transaction=transaction, )
def cli(prefix: str, path: PurePath, spec: Path, flags: List[str], logger: UI, description: str, transaction: Transaction, *args, **kwargs): # spec: Path with spec.open() as f: obj = json.load(f, object_pairs_hook=lambda pairs: pairs) try: try: array = [SpecObj(**dict(obj))] except TypeError: array = [SpecObj(**dict(o)) for o in obj] except TypeError: logger.exit(f'Each object in {spec} must have a ' f'"command" field and a "flags" field.') for i, obj in enumerate(array): new_path = path if len(array) == 1 else PurePath(path, str(i)) def parse_flag(flag: Flag): values = flag.values if isinstance(flag.values, list) else [flag.values] null_keys = ['null', '', 'none', 'None'] return [ f'--{v}' if flag.key in null_keys else f'--{flag.key}="{v}"' for v in values ] flags = [[f] for f in flags] flags += list(map(parse_flag, obj.flags)) new(path=new_path, prefix=prefix, command=obj.command, description=description, flags=flags, transaction=transaction)
def cli(prefix: str, paths: List[PurePath], commands: List[str], flags: List[str], logger: UI, descriptions: List[str], transaction: Transaction, *args, **kwargs): flags = list(map(parse_flag, flags)) n = len(commands) if not len(paths) in [1, n]: logger.exit('There must either be 1 or n paths ' 'where n is the number of commands.') if not len(descriptions) in [0, 1, n]: logger.exit('There must either be 1 or n descriptions ' 'where n is the number of commands.') iterator = enumerate(itertools.zip_longest(paths, commands, descriptions)) for i, (path, command, description) in iterator: if path is None: if n == 1: path = PurePath(paths[0]) else: path = PurePath(paths[0], str(i)) if len(descriptions) == 0: description = 'Description not given.' if len(descriptions) == 1: description = descriptions[0] new(path=path, prefix=prefix, command=command, description=description, flags=flags, transaction=transaction)
def cli(prefix: str, paths: List[PurePath], commands: List[str], flags: List[str], logger: UI, descriptions: List[str], transaction: Transaction, *args, **kwargs): paths = [p for p in paths if p] commands = [c for c in commands if c] if len(paths) == 0: logger.exit('Must provide at least one path.') if len(commands) == 0: logger.exit('Must provide at least one command.') if descriptions is None: descriptions = [''] * len(commands) elif len(descriptions) == 1: descriptions *= len(paths) if not len(paths) == len(commands): logger.exit('Number of paths must be the same as the number of commands') elif not len(paths) == len(commands) == len(descriptions): logger.exit( f'Got {len(paths)} paths, {len(commands)} commands, and {len(descriptions)} descriptions.' f'These numbers should all be the same so that they can be collated.') runs = defaultdict(list) for path, command, description in zip(paths, commands, descriptions): for parsed_flags in generate_runs(flags): runs[path].append((command, parsed_flags, description)) for path in runs: for i, (command, flags, description) in enumerate(runs[path]): new_path = path if len(runs[path]) > 1: new_path = PurePath(path, str(i)) new(path=new_path, prefix=prefix, command=command, description=description, flags=flags, transaction=transaction)
def _wrapper(db_path, quiet, assume_yes, root, dir_names, *args, **kwargs): ui = UI(assume_yes=assume_yes, quiet=quiet) with DataBase(path=db_path, logger=ui) as db: transaction = Transaction(ui=ui, db=db, root=root, dir_names=dir_names) with transaction as open_transaction: return func( db=db, logger=ui, bash=open_transaction.bash, transaction=open_transaction, *args, **kwargs )
def main(argv=sys.argv[1:]): parser = argparse.ArgumentParser( epilog= "The script will ask permission before running, deleting, moving, or " "permanently changing anything.") parser.add_argument("--quiet", "-q", action="store_true", help="Suppress print output") parser.add_argument( "--db-path", help="path to sqlite file storing run database information.", type=Path, ) parser.add_argument( "--root", help="Custom path to directory where config directories (if any) are " "automatically " "created", type=Path, ) parser.add_argument( "--dir-names", type=pure_path_list, help="directories to create and sync automatically with each run", ) parser.add_argument( "--assume-yes", "-y", action="store_true", help="Don't ask permission before performing operations.", ) subparsers = parser.add_subparsers(dest="dest") config = ConfigParser( delimiters=[":"], allow_no_value=True, interpolation=ExtendedInterpolation(), converters=dict( _path=Path, _pure_path=PurePath, _pure_path_list=pure_path_list, _arg_list=arg_list, ), ) config_filename = Path(".runsrc") config_path = find_up(config_filename) missing_config_keys = [] default_values = dict( root=str(Path(".runs").absolute()), db_path=str(Path("runs.db").absolute()), dir_names="", args="", ) if config_path: config.read(str(config_path)) if MAIN not in config: config[MAIN] = {} for k, v in default_values.items(): if k not in config[MAIN]: missing_config_keys.append(k) config[MAIN][k] = v main_config = dict(config[MAIN]).copy() main_config.update( root=config[MAIN].get_path("root"), db_path=config[MAIN].get_path("db_path"), dir_names=config[MAIN].get_pure_path_list("dir_names"), args=config[MAIN].get_arg_list(ARGS), ) for subparser in [parser] + [ adder(subparsers) for adder in [ new.add_subparser, from_json.add_subparser, rm.add_subparser, mv.add_subparser, ls.add_subparser, lookup.add_subparser, change_description.add_subparser, reproduce.add_subparser, correlate.add_subparser, kill.add_subparser, diff.add_subparser, to_json.add_subparser, ] ]: assert isinstance(subparser, argparse.ArgumentParser) config_section = subparser.prog.split()[-1] assert isinstance(config_section, str) subparser.set_defaults(**config["DEFAULT"]) subparser.set_defaults(**main_config) if config_section in config: subparser.set_defaults(**config[config_section]) args = parser.parse_args(args=argv) ui = UI(assume_yes=args.assume_yes, quiet=args.quiet) def write_config(): if ui.get_permission( f"Write new config to {config_filename.absolute()}?"): with config_filename.open("w") as f: config.write(f) else: ui.exit() if not config_path: ui.print( "Config not found. Using default config:", pprint.pformat(dict(config[MAIN])), sep="\n", ) write_config() elif missing_config_keys: for key in missing_config_keys: ui.print(f"Using default value for {key}: {config[MAIN][key]}") write_config() module = import_module("runs.subcommands." + args.dest.replace("-", "_")) kwargs = {k: v for k, v in vars(args).items()} try: # pluralize args kwargs[ARGS] = list(set(args.arg) | set(main_config[ARGS])) except AttributeError: pass module.cli(**kwargs)
import argparse parser = argparse.ArgumentParser() parser.add_argument('--option', default=0) print(vars(parser.parse_args()))\ """ COMMAND = "python3 test.py" WORK_DIR = "/tmp/test-run-manager" DB_PATH = Path(WORK_DIR, "runs.db") ROOT = WORK_DIR + "/.runs" DESCRIPTION = "test 'new' command" SEP = "/" SUBDIR = "subdir" TEST_RUN = "test_run" LOGGER = UI(quiet=True, assume_yes=True, raise_on_exit=True) BASH = Bash(logger=LOGGER) DB = DataBase(DB_PATH, LOGGER) def sessions(): try: output = BASH.cmd( 'tmux list-session -F "#{session_name}"'.split(), fail_ok=True ) assert isinstance(output, str) return output.split("\n") except subprocess.CalledProcessError: return []
def cli( prefix: str, path: PurePath, spec: Path, args: List[str], logger: UI, description: str, transaction: Transaction, max_runs: int, *_, **__, ): # spec: Path if not spec.exists(): logger.exit(f"{spec.absolute()} does not exist.") with spec.open() as f: obj = json.load(f, object_pairs_hook=lambda pairs: pairs) try: try: spec_objs = [SpecObj(**dict(obj))] except ValueError: spec_objs = [SpecObj(**dict(o)) for o in obj] except TypeError: logger.exit(f"Each object in {spec} must have a " '"command" field and a "args" field.') def listify(x): if isinstance(x, list): return x return [x] def prepend(arg: str): if not arg or arg.startswith("-"): return arg return f"--{arg}" def arg_alternatives(key, values): for v in listify(values): if isinstance(v, list): value = " ".join([f'"{_v}"' for _v in v]) yield [prepend(f"{key} {value}")] else: yield [prepend(f'{key}="{value}"') for value in listify(v)] def flag_alternatives(values): if values: for v in values: yield list(map(prepend, v)) else: yield [None] def group_args(spec): for k, v in spec.args or []: yield list(arg_alternatives(k, v)) yield list(flag_alternatives(spec.flags)) def arg_assignments(): for spec in spec_objs: for arg_set in itertools.product(*group_args(spec)): yield spec.command, [a for s in arg_set for a in s if a] assignments = list(arg_assignments()) if max_runs is not None and len(assignments) > max_runs: random.shuffle(assignments) assignments = assignments[:max_runs] for i, (command, arg_set) in enumerate(assignments): new_path = path if len(assignments) == 1 else PurePath(path, str(i)) command = Command(prefix, command, *arg_set, *args, path=new_path) new( path=new_path, command=command, description=description, transaction=transaction, )
import argparse parser = argparse.ArgumentParser() parser.add_argument('--option', default=0) print(vars(parser.parse_args()))\ """ COMMAND = 'python3 test.py' WORK_DIR = '/tmp/test-run-manager' DB_PATH = Path(WORK_DIR, 'runs.db') ROOT = WORK_DIR + '/.runs' DESCRIPTION = "test 'new' command" SEP = '/' SUBDIR = 'subdir' TEST_RUN = 'test_run' LOGGER = UI(quiet=True, assume_yes=True) BASH = Bash(logger=LOGGER) DB = DataBase(DB_PATH, LOGGER) def sessions(): try: output = BASH.cmd('tmux list-session -F "#{session_name}"'.split(), fail_ok=True) assert isinstance(output, str) return output.split('\n') except subprocess.CalledProcessError: return [] def quote(string): return '"' + string + '"'