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], 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 build_tree(paths, depth: int = None): aggregator = defaultdict(list) for path in paths: try: head, *tail = PurePath(path).parts except ValueError: return dict() if tail: head += '/' aggregator[head].append(PurePath(*tail)) return {k: build_tree(v, depth=depth) for k, v in aggregator.items()}
def get( self, patterns: Iterable[PurePath], unless: Iterable[PurePath] = None, order: bool = None, descendants: bool = False, active: bool = False, since: datetime = None, last: timedelta = None, ) -> List[RunEntry]: if descendants: patterns = list(map(str, patterns)) patterns += [f'{str(pattern).rstrip("/%")}/%' for pattern in patterns] condition = DataBase.pattern_match(*patterns) if since or last: if since: time = since if last: time = datetime.now() - last if since and last: time = max(datetime.now() - last, since) condition = condition & GreaterThan("datetime", time) if active: condition = condition & In("path", *TMUXSession.active_runs(self.logger)) if unless: unless = DataBase.pattern_match(*unless) return [ RunEntry(PurePath(p), *e) for (p, *e) in self.select( condition=condition, unless=unless, order=order ).fetchall() ]
def paths(runs: List[RunEntry], pprint: bool = True, depth: int = None) -> List[str]: _paths = [PurePath(*e.path.parts[:depth]) for e in runs] if depth is not None: _paths = sorted(set(_paths), key=lambda p: natural_order(str(p))) return tree_strings(build_tree(_paths)) if pprint else _paths
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 move( query_args: QueryArgs, dest_path: str, kill_tmux: bool, transaction: Transaction, db: DataBase, ): dest_path_is_dir = any( [dest_path == ".", f"{dest_path}/%" in db, dest_path.endswith("/")]) for src_pattern in query_args.patterns: dest_to_src = defaultdict(list) src_entries = db.get(**query_args._replace( patterns=[src_pattern])._asdict()) for entry in src_entries: # parent, grandparent, great-grandparent, etc. parents = [str(entry.path) ] + [str(p) + "/" for p in entry.path.parents] matches = [ p for p in reversed(parents) if like(str(p), str(src_pattern) + "%") ] head = next(iter(matches)) # a/b/% -> a/b tail = PurePath(*[PurePath(m).name for m in matches]) # a/b/% -> c/d if dest_path_is_dir: dest = PurePath(dest_path, tail) else: dest = str(entry.path).replace(head.rstrip("/"), dest_path, 1) dest_to_src[dest] += [entry.path] for dest, srcs in dest_to_src.items(): for i, src in enumerate(srcs): if len(srcs) > 1: dest = PurePath(dest, str(i)) else: dest = PurePath(dest) transaction.move(src=src, dest=dest, kill_tmux=kill_tmux) if dest in db: transaction.remove(dest)
def get_value(path: PurePath) -> Optional[float]: path = Path( str(value_path).replace("<path>", str(path)).replace("\\", "")) try: with path.open() as f: return float(f.read()) except (ValueError, FileNotFoundError): print(f"{path} not found") return
def tree_strings(tree, prefix='', root_prefix='', root='.'): yield prefix + root_prefix + root if root_prefix == '├── ': prefix += '│ ' if root_prefix == '└── ': prefix += ' ' if tree: items = _, *tail = tree.items() for (root, tree), _next in zip_longest(items, tail): for s in tree_strings(tree=tree, prefix=prefix, root_prefix='├── ' if _next else '└── ', root=root): yield PurePath(s)
def strings(runs: List[RunEntry], flags: List[str], prefix: str, db: DataBase, path: Optional[PurePath], description: Optional[str]): entry_dict = defaultdict(list) return_strings = [highlight('To reproduce:')] for entry in runs: entry_dict[entry.commit].append(entry) for commit, entries in entry_dict.items(): return_strings.append(f'git checkout {commit}') command_string = 'runs new' for i, entry in enumerate(entries): if path is None: new_path = entry.path elif len(entries) > 1: new_path = PurePath(path, str(i)) else: new_path = path subcommand = get_command_string( path=PurePath(new_path), prefix=prefix, command=entry.command, flags=flags) new_path, subcommand, _description = map(json.dumps, [ str(new_path), subcommand, description or entry.description.strip('"').strip("'") ]) if len(entries) == 1: command_string += f' {new_path} {subcommand} --description={_description}' else: command_string = ' \\\n '.join([ command_string, f'--path={new_path}', f'--command={subcommand}', f'--description={_description}', ]) return_strings.append(command_string) return return_strings
def tree_strings(tree, prefix="", root_prefix="", root="."): yield prefix + root_prefix + root if root_prefix == "├── ": prefix += "│ " if root_prefix == "└── ": prefix += " " if tree: items = _, *tail = tree.items() for (root, tree), _next in zip_longest(items, tail): for s in tree_strings( tree=tree, prefix=prefix, root_prefix="├── " if _next else "└── ", root=root, ): yield PurePath(s)
def strings( runs: List[RunEntry], args: List[str], prefix: str, db: DataBase, description: Optional[str], path: Optional[PurePath], porcelain: bool, ): entry_dict = defaultdict(list) return_strings = [] if porcelain else [highlight("To reproduce:")] for entry in runs: entry_dict[entry.commit].append(entry) for commit, entries in entry_dict.items(): return_strings.append(f"git checkout {commit}") string = "runs new" for i, entry in enumerate(entries): if path is None: new_path = entry.path elif len(entries) > 1: new_path = PurePath(path, str(i)) else: new_path = path command = Command(entry.command, path=entry.path) command = command.exclude(prefix, *args) new_path, command, _description = map( json.dumps, [ str(new_path), str(command), description or entry.description.strip('"').strip("'"), ], ) join_string = " " if len(entries) == 1 else " \\\n" string = join_string.join( [ string, f"--path={new_path}", f"--command={command}", f"--description={_description}", ] ) return_strings.append(string) return return_strings
def new(path: PurePath, prefix: str, command: str, description: str, flags: List[List[str]], transaction: Transaction): bash = transaction.bash if description is None: description = '' if description == 'commit-message': description = bash.cmd('git log -1 --pretty=%B'.split()) flag_sets = list(itertools.product(*flags)) for i, flag_set in enumerate(flag_sets): new_path = path if len(flag_sets) == 1 else PurePath(path, str(i)) if new_path in transaction.db: transaction.remove(new_path) full_command = build_command(command=command, path=new_path, prefix=prefix, flags=flag_set) transaction.add_run(path=new_path, command=full_command, commit=bash.last_commit(), datetime=datetime.now().isoformat(), description=description)
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, )