def set_flow_opts(self, flowopts: Union[str, Iterable, bool]) -> NoReturn: """Sets `self.flowopts` with FlowItem objects. Handles passing different types of flowopts (single string, tuple of strings, or boolean), and returns a FlowItem tuple.""" darkprint( f'{self.__class__.__qualname__}.set_flow_opts(flowopts={repr(flowopts)})' ) if flowopts is True: flowitems = tuple(map(FlowItem, Flow.__members__.values())) elif isinstance(flowopts, str): # 'quit' # flowopts = (FlowItem.from_full_name(flowopts),) flowitems = (FlowItem(flowopts), ) else: # flowopts = tuple(map(FlowItem.from_full_name, flowopts)) flowitems = tuple(map(FlowItem, flowopts)) if has_duplicates(flowitems): raise ValueError( f"{repr(self)}\nset_flow_opts(flowopts) | duplicate flowitems: {repr(flowitems)}" ) for flowitem in flowitems: self.items.store(flowitem) # if flowopt.value in self.items: # raise ValueError(f'{repr(self)}\nset_special_options() | flowopt.value ({repr(flowopt.value)}) already exists in self.\nflowopt: {repr(flowopt)}.\nflowopts: {repr(flowopts)}') # self.items[flowopt.value] = flowopt.name return None
def main(ignore_paths: List[ExPathOrStr], exclude_paths_tuple: Tuple[str, ...], confirm: bool = False, dry_run: bool = False, backup: bool = True): # TODO: support i.e. "e/h/package" for "efficient-frontier/home-task/package-lock.json" darkprint( f'ignore.py main() | ignore_paths: {ignore_paths}, exclude_paths_tuple: {exclude_paths_tuple}, confirm: {confirm}, dry_run: {dry_run}, backup: {backup}' ) if exclude_paths_tuple: for exclude_paths_str in exclude_paths_tuple: ignore(confirm, dry_run, backup, ignore_paths, exclude_paths_str) else: ignore(confirm, dry_run, backup, ignore_paths)
def search(self, keyword: Union[str, ExPath], *, noprompt=True) -> ExPath: """Tries to return an ExPath in status. First assumes `keyword` is an exact file (str or ExPath), and if fails, uses `search` module. @param noprompt: specify False to allow using search_and_prompt(keyword, file) in case nothing matched earlier. """ darkprint(f'Status.search({repr(keyword)}) |') path = ExPath(keyword) for file in self.files: if file == path: return file has_suffix = path.has_file_suffix() has_slash = '/' in keyword has_regex = regex.has_regex(keyword) darkprint(f'\t{has_suffix = }, {has_slash = }, {has_regex = }') if has_suffix: files = self.files else: files = [f.with_suffix('') for f in self.files] if has_regex: for expath in files: if re.search(keyword, str(expath)): return expath if has_slash: darkprint( f"looking for the nearest match among status paths for: '{keyword}'" ) return ExPath(search.nearest(keyword, files)) darkprint( f"looking for a matching part among status paths ({'with' if has_suffix else 'without'} suffixes...) for: '{keyword}'" ) for f in files: # TODO: integrate into git.search somehow for i, part in enumerate(f.parts): if part == keyword: ret = ExPath(os.path.join(*f.parts[0:i + 1])) return ret if noprompt: return None darkprint( f"didn't find a matching part, calling search_and_prompt()...") choice = search_and_prompt(keyword, [str(f) for f in files], criterion='substring') if choice: return ExPath(choice) prompt.generic(colors.red(f"'{keyword}' didn't match anything"), flowopts=True)
def compare(a, b): darkprint(f'compare({repr(a)}, {repr(b)})') if a == b: sys.exit(colors.red(f'trying to compare a branch to itself: {a}')) if a not in btree: yellowprint(f'"{a}" not in branches, searching...') a = btree.search(a) if b not in btree: yellowprint(f'"{b}" not in branches, searching...') b = btree.search(b) repo = Repo() if repo.host == 'bitbucket': url = f"https://{repo.weburl}/branches/compare/{a}%0D{b}#diff" else: url = f"https://{repo.weburl}/compare/{a}..{b}" webbrowser.open(url)
def main(a, b): darkprint(f'main(a: {a}, b: {b})') if a and b: darkprint(f'comparing a to b') return compare(a, b) if a: darkprint(f'comparing btree.current to a') return compare(btree.current, a) # not a and not b darkprint(f'comparing btree.current to master') return compare(btree.current, 'master')
def mutate_identifier(identifier: str): upper = identifier.upper() identifier = upper darkprint( f'mutate_identifier({repr(identifier)}) yielding upper: {repr(upper)}') yield upper words = self.val.split(' ') if len(words) == 1: raise NotImplementedError( f"no word separators, and both lowercase and uppercase identifier is taken ('{upper.lower()}')" ) words_identifiers = ''.join(map(lambda s: s[0], words)) identifier = words_identifiers darkprint( f'mutate_identifier() yielding words_identifiers: {repr(words_identifiers)}' ) yield words_identifiers for i in range(len(words_identifiers)): new_identifiers = words_identifiers[:i] + words_identifiers[i].upper( ) + words_identifiers[i + 1:] identifier = new_identifiers darkprint( f'mutate_identifier() yielding new_identifiers (#{i}): {repr(new_identifiers)}' ) yield new_identifiers raise StopIteration( f'mutate_identifier() exhausted all options: {repr(self)}')
def set_kw_options(self, **kw_opts: Union[str, tuple, bool]) -> None: """foo='bar', baz='continue'""" darkprint( f'{self.__class__.__qualname__}.set_kw_options(kw_opts={repr(kw_opts)})' ) if not kw_opts: return if has_duplicates(kw_opts.values()): raise ValueError( f"{repr(self)}\nset_kw_options() duplicate kw_opts: {repr(kw_opts)}" ) if 'free_input' in kw_opts: raise DeveloperError( f"{repr(self)}\nset_kw_options() | 'free_input' found in kw_opts, should have popped it out earlier.\nkw_opts: {repr(kw_opts)}" ) non_flow_kw_opts = dict() for kw in kw_opts: opt = kw_opts[kw] if kw in self.items: raise ValueError( f"{repr(self)}\nset_kw_options() | '{kw}' in kw_opts but was already in self.items.\nkw_opts: {repr(kw_opts)}" ) try: # flowitem = FlowItem.from_full_name(opt) flowitem = FlowItem(opt) except ValueError: non_flow_kw_opts[kw] = opt else: # not using self.items.store(flowitem) because kw isn't flowitem.identifier self.items[kw] = flowitem if not non_flow_kw_opts: return kw_items = KeywordItems(non_flow_kw_opts) self.items.update(**kw_items)
def __init__(self, question: str, **kwargs): # noinspection PyTypeChecker self.answer: Answer = None if 'flowopts' in kwargs: self.options.set_flow_opts(kwargs.pop('flowopts')) try: free_input = kwargs.pop('free_input') except KeyError: free_input = False # * keyword-choices self.options.set_kw_options(**kwargs) # * Complex Prompt dialog_string = self.dialog_string(question, free_input=free_input) # question = self.dialog_string(question, options, free_input=free_input) key, answer = self.get_answer(dialog_string, free_input=free_input) darkprint(f'{repr(self)} | key: {repr(key)}, answer: {repr(answer)}') # * FlowItem Answer if isinstance(answer, FlowItem): # flow_answer: FlowItem = FlowItem(answer) answer.execute() if answer.DEBUG: # debugger had already started and had finished in answer.execute() (user 'continue'd here) self.answer = self.get_answer(dialog_string) elif answer.CONTINUE: self.answer: Tuple[str, FlowItem] = key, answer else: raise NotImplementedError else: # * DIDN'T answer any flow if isinstance(answer, MutableItem) and answer.is_yes_or_no: darkprint(f'{repr(self)} no flow chosen, answer is yes / no. key: {repr(key)}, answer: {repr(answer)}, options: {self.options}') self.answer: bool = key.lower() in ('y', 'yes') else: darkprint(f'{repr(self)} no flow chosen, answer is not yes / no. key: {repr(key)}, answer: {repr(answer)}, options: {self.options}') self.answer: Tuple[str, MutableItem] = key, answer
def main(items: Tuple[str], exclude): # TODO: option to view in web like comparebranch exclude_exts = [] if exclude: for ex in map(misc.clean, exclude.split(' ')): exclude_exts.append(misc.quote(f':!{ex}')) cmd = 'git diff --color-moved=zebra --find-copies-harder --ignore-blank-lines --ignore-cr-at-eol --ignore-space-at-eol --ignore-space-change --ignore-all-space ' darkprint(f'diff.py main(items): {items}') if not items: # TODO: exclude shell.run(cmd, stdout=sys.stdout) btree = Branches() ctree = Commits() formatted = [] # if exclude_exts: gitignore = None status = Status() any_sha = False diff_files = set() for i, item in enumerate(map(misc.clean, items)): if regex.SHA_RE.fullmatch(item): # e.g. 'f5905f1' any_sha = True if i > 1: redprint( f'SHA1 items, if any, must be placed at the most in the first 2 arg slots' ) return if item in ctree: if i == 1 and formatted[0] not in ctree: redprint( f'When specifying two SHA1 args, both must belong to the same tree. 0th arg doesnt belong to ctree, 1st does' ) return formatted.append(item) continue if item in btree: if i == 1 and formatted[0] not in btree: redprint( f'When specifying two SHA1 args, both must belong to the same tree. 0th arg doesnt belong to btree, 1st does' ) return formatted.append(item) continue redprint( f'item is SHA1 but not in commits nor branches. item: {repr(item)}' ) return else: if any_sha: # all SHA items have already been appended, and current item is not a SHA numstat_cmd = f'git diff --numstat {" ".join(items[:i])}' else: # no SHA items at all, this is the first item numstat_cmd = f'git diff --numstat' filestats = shell.runquiet(numstat_cmd).splitlines() diff_files = set(line.rpartition(' ')[2] for line in filestats) # TODO (continue here): # gpdy SHA SHA REGEX -e REGEX # make -e filter out results if item in diff_files: with_quotes = misc.quote(item) formatted.append(with_quotes) darkprint(f'appended: {with_quotes}') else: brightwhiteprint( f'{item} is not in diff_files, searching within diff_files...') choice = search_and_prompt(item, diff_files) if choice: with_quotes = misc.quote(choice) formatted.append(with_quotes) darkprint(f'appended: {with_quotes}') # file = status.search(item, quiet=False) # if not file: # brightyellowprint(f'{item} is not in status, skipping') # continue # stripped = misc.unquote(file.strip()) # formatted.append(misc.quote(stripped)) # darkprint(f'appended: {repr(stripped)}') joined = " ".join(formatted) cmd += f'{joined} ' if exclude_exts: cmd += " ".join(exclude_exts) shell.run(cmd, stdout=sys.stdout)
def is_yes_or_no(self): ret = bool(YES_OR_NO.fullmatch(self.identifier)) darkprint(f'{repr(self)}.is_yes_or_no() → {ret}') return ret