示例#1
0
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,
        )
示例#2
0
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)
示例#3
0
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)
示例#4
0
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)
示例#5
0
 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
             )
示例#6
0
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)
示例#7
0
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 []

示例#8
0
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,
        )
示例#9
0
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 + '"'