def main(): tag_to_paths = load_tags() parser = argparse.ArgumentParser( prog="untag", usage="untag [options] [[paths] [tags]...]", description=cmd_description, formatter_class=HelpFormatter) parser.add_argument('-a', '--all', help='untag all tags or paths', action='store_true') parser.add_argument('arguments', type=str, nargs=argparse.REMAINDER, metavar='[paths] [tags]', help='directory paths and tag names') parsed = parser.parse_args() if parsed.all: # Collect all the paths and tags to remove messages = set() tags_to_remove = set() paths_to_remove = set() for arg in parsed.arguments: if arg in tag_to_paths: tags_to_remove.add(arg) elif os.path.isdir(arg): paths_to_remove.add(expand_path(arg)) # Untag all the paths specified for tag, paths in tag_to_paths.items(): for path in [p for p in paths if p in paths_to_remove]: paths.pop(path) messages.add(msg.format(tag, path)) if len(paths) == 0: tags_to_remove.add(tag) # Remove all the tags specified for tag in tags_to_remove: paths = tag_to_paths.pop(tag) for path in paths: messages.add(msg.format(tag, expand_path(path))) # Save the updated tags and print messages save_tags(tag_to_paths) if messages: print('\n'.join(messages)) else: # Initialize tracking variables and collectors updates = [] arg_index = 0 parsing_paths = True tags_collected = set() paths_collected = set() # Iterate through the arguments and pair up tags with paths while arg_index < len(parsed.arguments): arg = parsed.arguments[arg_index] if parsing_paths and arg.startswith('@'): if len(paths_collected) == 0: parser.error('excepting paths before {}'.format(arg)) parsing_paths = False elif parsing_paths and not arg.startswith('@'): paths_collected.add(arg) arg_index += 1 elif not parsing_paths and arg.startswith('@'): tags_collected.add(arg) arg_index += 1 else: updates.append((tags_collected, paths_collected)) tags_collected, paths_collected = set(), set() parsing_paths = True if parsing_paths: parser.error('expecting a tag name') updates.append((tags_collected, paths_collected)) # Apply updates and collect messages to print messages = set() for tags, paths in updates: for tag in tags: if tag not in tag_to_paths: continue for path in paths: if not os.path.isdir(path): continue full_path = expand_path(path) if full_path in tag_to_paths[tag]: tag_to_paths[tag].pop(full_path) messages.add(msg.format(tag, full_path)) if len(tag_to_paths[tag]) == 0: # Remove the tag completely if it has no paths tag_to_paths.pop(tag) # Save the updated tags and print messages save_tags(tag_to_paths) if messages: print('\n'.join(messages))
def main(): tag_to_paths = load_tags() parser = ArgumentParser( prog="tag", usage="tag [[paths] [tags]...]", description=cmd_description, formatter_class=HelpFormatter ) parser.add_argument( "arguments", type=str, nargs="+", metavar="[paths] [tags]", help="directory paths and tag names" ) parsed = parser.parse_args() # Tracking variables and collectors updates = [] arg_index = 0 parsing_paths = True tags_collected = set() paths_collected = set() # Iterate through the arguments and pair up tags with paths while arg_index < len(parsed.arguments): arg = parsed.arguments[arg_index] if parsing_paths and arg.startswith("@"): if len(paths_collected) == 0: parser.error("expecting paths before {}".format(arg)) parsing_paths = False elif parsing_paths and not arg.startswith("@"): paths_collected.add(arg) arg_index += 1 elif not parsing_paths and arg.startswith("@"): tag_name_has_alphabet = False for ch in arg[1:]: if ch not in TAG_NAME_CHARS: parser.error("bad char {} in tag name {}".format(ch, arg)) tag_name_has_alphabet |= ch.isalpha() if not tag_name_has_alphabet: parser.error("no alphabets in tag name {}".format(arg)) tags_collected.add(arg) arg_index += 1 else: updates.append((tags_collected, paths_collected)) tags_collected, paths_collected = set(), set() parsing_paths = True if parsing_paths: parser.error("expecting a tag name") updates.append((tags_collected, paths_collected)) # Apply updates and message messages = set() for tags, paths in updates: valid_paths = [p for p in paths if os.path.isdir(p)] if len(valid_paths) == 0: continue for tag in tags: if tag not in tag_to_paths: tag_to_paths[tag] = {} for path in valid_paths: full_path = expand_path(path) short_path = shrink_path(path) tag_to_paths[tag][full_path] = short_path messages.add(msg.format(tag, full_path)) save_tags(tag_to_paths) if messages: print("\n".join(messages))
def main(): tag_to_paths = load_tags() path_to_tags = defaultdict(set) for tag, paths in tag_to_paths.items(): for path in paths.keys(): path_to_tags[path].add(tag) parser = ArgumentParser( prog='tags', description=cmd_description, usage='tags [options] [paths] [tags]', formatter_class=HelpFormatter ) parser.add_argument( '-e', '--edit', action='store_true', help='Edit the tags directly using an editor' ) parser.add_argument( '-x', '--expand', action='store_true', help='Expand the directory paths' ) parser.add_argument( '-r', '--reverse', help='display the reverse mapping', action='store_true' ) parser.add_argument( '-j', '--json', help='display the raw JSON', action='store_true' ) parser.add_argument( 'search_terms', type=str, nargs='*', metavar='[paths] [tags]', help='tag and directory paths to filter by' ).completer = ChoicesCompleter(tag_to_paths.keys()) autocomplete(parser) parsed = parser.parse_args() if parsed.edit: if parsed.search_terms: raise parser.error('no arguments allowed with option -e/--edit') edit_file_path = TAGS_FILE_PATH + '.edit' shutil.copy2(TAGS_FILE_PATH, edit_file_path) subprocess.call([os.environ.get('EDITOR', 'vi'), edit_file_path]) new_tag_to_paths = save_error = None with open(edit_file_path, 'r') as edit_file: try: new_tag_to_paths = json.load(edit_file) except ValueError as err: save_error = 'Failed to save tags: {}'.format(msgify(err)) else: try: check_tags(new_tag_to_paths) except ValueError as err: save_error = 'Failed to save tags: {}'.format(err) safe_remove(edit_file_path) if save_error is not None: halt(save_error) save_tags({ tag: {expand_path(p): shrink_path(p) for p in paths} for tag, paths in new_tag_to_paths.items() }) print('New tags saved successfully') sys.exit(0) if len(tag_to_paths) == 0: print('No tags defined') sys.exit(0) # Filter by any given tags and paths # TODO optimize here if possible if not parsed.search_terms: if parsed.expand: filtered = {t: ps.keys() for t, ps in tag_to_paths.items()} else: filtered = {t: ps.values() for t, ps in tag_to_paths.items()} else: filtered = {} for term in parsed.search_terms: if term in tag_to_paths: if parsed.expand: filtered[term] = tag_to_paths[term].keys() else: filtered[term] = tag_to_paths[term].values() elif os.path.isdir(term): term = expand_path(term) if term in path_to_tags: for tag in path_to_tags[term]: if parsed.expand: filtered[tag] = tag_to_paths[tag].keys() else: filtered[tag] = tag_to_paths[tag].values() if parsed.json: formatted = {tag: sorted(paths) for tag, paths in filtered.items()} print(json.dumps(formatted, sort_keys=True, indent=4)) elif parsed.reverse: reverse = defaultdict(set) for tag, paths in filtered.items(): for path in paths: reverse[path].add(tag) for path, tags in reverse.items(): print('{}{}{}'.format(CYAN, path, CLEAR)) print('{}{}{}\n'.format(PINK, ' '.join(sorted(tags)), CLEAR)) else: for tag, paths in sorted(filtered.items()): print('{}{}{}'.format(PINK, tag, CLEAR)) print('{}{}{}\n'.format(CYAN, '\n'.join(sorted(paths)), CLEAR))
def main(): tag_to_paths = load_tags() path_to_tags = defaultdict(set) for tag, paths in tag_to_paths.items(): for path in paths.keys(): path_to_tags[path].add(tag) parser = ArgumentParser(prog='tags', description=cmd_description, usage='tags [options] [paths] [tags]', formatter_class=HelpFormatter) parser.add_argument('-e', '--edit', action='store_true', help='Edit the tags directly using an editor') parser.add_argument('-x', '--expand', action='store_true', help='Expand the directory paths') parser.add_argument('-r', '--reverse', help='display the reverse mapping', action='store_true') parser.add_argument('-j', '--json', help='display the raw JSON', action='store_true') parser.add_argument('search_terms', type=str, nargs='*', metavar='[paths] [tags]', help='tag and directory paths to filter by' ).completer = ChoicesCompleter(tag_to_paths.keys()) autocomplete(parser) parsed = parser.parse_args() if parsed.edit: if parsed.search_terms: raise parser.error('no arguments allowed with option -e/--edit') edit_file_path = TAGS_FILE_PATH + '.edit' shutil.copy2(TAGS_FILE_PATH, edit_file_path) subprocess.call([os.environ.get('EDITOR', 'vi'), edit_file_path]) new_tag_to_paths = save_error = None with open(edit_file_path, 'r') as edit_file: try: new_tag_to_paths = json.load(edit_file) except ValueError as err: save_error = 'Failed to save tags: {}'.format(msgify(err)) else: try: check_tags(new_tag_to_paths) except ValueError as err: save_error = 'Failed to save tags: {}'.format(err) safe_remove(edit_file_path) if save_error is not None: halt(save_error) save_tags({ tag: {expand_path(p): shrink_path(p) for p in paths} for tag, paths in new_tag_to_paths.items() }) print('New tags saved successfully') sys.exit(0) if len(tag_to_paths) == 0: print('No tags defined') sys.exit(0) # Filter by any given tags and paths # TODO optimize here if possible if not parsed.search_terms: if parsed.expand: filtered = {t: ps.keys() for t, ps in tag_to_paths.items()} else: filtered = {t: ps.values() for t, ps in tag_to_paths.items()} else: filtered = {} for term in parsed.search_terms: if term in tag_to_paths: if parsed.expand: filtered[term] = tag_to_paths[term].keys() else: filtered[term] = tag_to_paths[term].values() elif os.path.isdir(term): term = expand_path(term) if term in path_to_tags: for tag in path_to_tags[term]: if parsed.expand: filtered[tag] = tag_to_paths[tag].keys() else: filtered[tag] = tag_to_paths[tag].values() if parsed.json: formatted = {tag: sorted(paths) for tag, paths in filtered.items()} print(json.dumps(formatted, sort_keys=True, indent=4)) elif parsed.reverse: reverse = defaultdict(set) for tag, paths in filtered.items(): for path in paths: reverse[path].add(tag) for path, tags in reverse.items(): print('{}{}{}'.format(CYAN, path, CLEAR)) print('{}{}{}\n'.format(PINK, ' '.join(sorted(tags)), CLEAR)) else: for tag, paths in sorted(filtered.items()): print('{}{}{}'.format(PINK, tag, CLEAR)) print('{}{}{}\n'.format(CYAN, '\n'.join(sorted(paths)), CLEAR))
def main(): tag_to_paths = load_tags() parser = argparse.ArgumentParser( prog="untag", usage="untag [options] [[paths] [tags]...]", description=cmd_description, formatter_class=HelpFormatter ) parser.add_argument( '-a', '--all', help='untag all tags or paths', action='store_true' ) parser.add_argument( 'arguments', type=str, nargs=argparse.REMAINDER, metavar='[paths] [tags]', help='directory paths and tag names' ) parsed = parser.parse_args() if parsed.all: # Collect all the paths and tags to remove messages = set() tags_to_remove = set() paths_to_remove = set() for arg in parsed.arguments: if arg in tag_to_paths: tags_to_remove.add(arg) elif os.path.isdir(arg): paths_to_remove.add(expand_path(arg)) # Untag all the paths specified for tag, paths in tag_to_paths.items(): for path in [p for p in paths if p in paths_to_remove]: paths.pop(path) messages.add(msg.format(tag, path)) if len(paths) == 0: tags_to_remove.add(tag) # Remove all the tags specified for tag in tags_to_remove: paths = tag_to_paths.pop(tag) for path in paths: messages.add(msg.format(tag, expand_path(path))) # Save the updated tags and print messages save_tags(tag_to_paths) if messages: print('\n'.join(messages)) else: # Initialize tracking variables and collectors updates = [] arg_index = 0 parsing_paths = True tags_collected = set() paths_collected = set() # Iterate through the arguments and pair up tags with paths while arg_index < len(parsed.arguments): arg = parsed.arguments[arg_index] if parsing_paths and arg.startswith('@'): if len(paths_collected) == 0: parser.error('excepting paths before {}'.format(arg)) parsing_paths = False elif parsing_paths and not arg.startswith('@'): paths_collected.add(arg) arg_index += 1 elif not parsing_paths and arg.startswith('@'): tags_collected.add(arg) arg_index += 1 else: updates.append((tags_collected, paths_collected)) tags_collected, paths_collected = set(), set() parsing_paths = True if parsing_paths: parser.error('expecting a tag name') updates.append((tags_collected, paths_collected)) # Apply updates and collect messages to print messages = set() for tags, paths in updates: for tag in tags: if tag not in tag_to_paths: continue for path in paths: if not os.path.isdir(path): continue full_path = expand_path(path) if full_path in tag_to_paths[tag]: tag_to_paths[tag].pop(full_path) messages.add(msg.format(tag, full_path)) if len(tag_to_paths[tag]) == 0: # Remove the tag completely if it has no paths tag_to_paths.pop(tag) # Save the updated tags and print messages save_tags(tag_to_paths) if messages: print('\n'.join(messages))
def main(): tag_to_paths = load_tags() parser = ArgumentParser( prog='tag', usage='tag [[paths] [tags]...]', description=cmd_description, formatter_class=HelpFormatter ) parser.add_argument( 'arguments', type=str, nargs='+', metavar='[paths] [tags]', help='directory paths and tag names' ) parsed = parser.parse_args() # Tracking variables and collectors updates = [] arg_index = 0 parsing_paths = True tags_collected = set() paths_collected = set() # Iterate through the arguments and pair up tags with paths while arg_index < len(parsed.arguments): arg = parsed.arguments[arg_index] if parsing_paths and arg.startswith('@'): if len(paths_collected) == 0: parser.error('expecting paths before {}'.format(arg)) parsing_paths = False elif parsing_paths and not arg.startswith('@'): paths_collected.add(arg) arg_index += 1 elif not parsing_paths and arg.startswith('@'): tag_name_has_alphabet = False for ch in arg[1:]: if ch not in TAG_NAME_CHARS: parser.error('bad char {} in tag name {}'.format(ch, arg)) tag_name_has_alphabet |= ch.isalpha() if not tag_name_has_alphabet: parser.error('no alphabets in tag name {}'.format(arg)) tags_collected.add(arg) arg_index += 1 else: updates.append((tags_collected, paths_collected)) tags_collected, paths_collected = set(), set() parsing_paths = True if parsing_paths: parser.error('expecting a tag name') updates.append((tags_collected, paths_collected)) # Apply updates and message messages = set() for tags, paths in updates: valid_paths = [p for p in paths if os.path.isdir(p)] if len(valid_paths) == 0: continue for tag in tags: if tag not in tag_to_paths: tag_to_paths[tag] = {} for path in valid_paths: full_path = expand_path(path) short_path = shrink_path(path) tag_to_paths[tag][full_path] = short_path messages.add(msg.format(tag, full_path)) save_tags(tag_to_paths) if messages: print('\n'.join(messages))