def main(): global tmp_file, current_process, child_processes # https://stackoverflow.com/questions/25099895/ signal.signal(signal.SIGTTOU, signal.SIG_IGN) # Ensure that child processes are cleaned up signal.signal(signal.SIGINT, kill_signal_handler) signal.signal(signal.SIGTERM, kill_signal_handler) atexit.register(cleanup_resources) tag_to_paths = load_tags() parser = argparse.ArgumentParser(prog='run', description=cmd_description, usage='run [options] [targets] command', formatter_class=HelpFormatter) parser.add_argument('-p', '--parallel', help='run the command in parallel', action='store_true') parser.add_argument('-e', '--exit-codes', help='display the exit codes', action='store_true') parser.add_argument('arguments', type=str, nargs=argparse.REMAINDER, metavar='[targets]', help='tags or paths to run the command for' ).completer = ChoicesCompleter(tag_to_paths.keys()) autocomplete(parser) parsed = parser.parse_args() # Separate the targets from the command index = 0 targets = OrderedDict() while index < len(parsed.arguments): target = parsed.arguments[index] if target in tag_to_paths: for path in sorted(tag_to_paths[target].keys()): targets[path] = target index += 1 elif target.startswith('@'): parser.error('unknown tag {}'.format(target)) elif os.path.isdir(target): path = expand_path(target) if path not in targets: targets[path] = None index += 1 else: break # beginning of the command # Join the command arguments into a string command_arguments = parsed.arguments[index:] if len(command_arguments) == 0: command = None elif len(command_arguments) == 1: command = ' '.join(command_arguments) else: command = ' '.join("'{}'".format(arg) if ' ' in arg else arg for arg in command_arguments) if not (targets and command): parser.error('too few arguments') if parsed.parallel: # Run the command in parallel for path, tag in targets.items(): tmp_file = tempfile.TemporaryFile(mode='w+t') current_process = subprocess.Popen( '{} -i -c "{}"'.format(shell, command), cwd=path, shell=True, stdout=tmp_file, stderr=tmp_file, preexec_fn=os.setsid, ) child_processes.append((path, tag, current_process, tmp_file)) for path, tag, current_process, tmp_file in child_processes: exit_code = current_process.wait() tail = ' ({}{}{})'.format(PINK, tag, CLEAR) if tag else ':' print('{}>>> {}{}{}{}'.format(YELLOW, CYAN, path, tail, CLEAR)) tmp_file.seek(0) lines = tmp_file.readlines() offset = 0 if len(lines) > 0 and contains_ctrl_error_msg(lines[0]): offset += 1 if len(lines) > 1 and contains_ctrl_error_msg(lines[1]): offset += 1 sys.stdout.write(''.join(lines[offset:])) tmp_file.close() if parsed.exit_codes: print('{}>>> {}exit code: {}{}'.format(YELLOW, RED, exit_code, CLEAR)) sys.stdout.write('\n') else: # Run the command sequentially full_command = [] for path, tag in targets.items(): tag_info = ' ({}{}{})'.format(PINK, tag, CLEAR) if tag else ':' if parsed.exit_codes: tail = 'printf "{}>>> {}exit code: $?{}\n\n"'.format( YELLOW, RED, CLEAR) else: tail = 'printf "\n"' full_command.append( '(printf "{header}"; cd {path} && {cmd};{tail})'.format( header='{}>>> {}{}{}{}\n'.format(YELLOW, CYAN, path, CLEAR, tag_info), path=path, cmd=command, tail=tail)) subprocess.call( [shell, '-i', '-c', '{}'.format(';'.join(full_command))], stderr=sys.stdout.fileno()) # https://stackoverflow.com/questions/25099895/ os.tcsetpgrp(0, os.getpgrp()) sys.exit(0)
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(): global tmp_file, current_process, child_processes # https://stackoverflow.com/questions/25099895/ signal.signal(signal.SIGTTOU, signal.SIG_IGN) # Ensure that child processes are cleaned up signal.signal(signal.SIGINT, kill_signal_handler) signal.signal(signal.SIGTERM, kill_signal_handler) atexit.register(cleanup_resources) tag_to_paths = load_tags() parser = argparse.ArgumentParser( prog='run', description=cmd_description, usage='run [options] [targets] command', formatter_class=HelpFormatter ) parser.add_argument( '-p', '--parallel', help='run the command in parallel', action='store_true' ) parser.add_argument( '-e', '--exit-codes', help='display the exit codes', action='store_true' ) parser.add_argument( 'arguments', type=str, nargs=argparse.REMAINDER, metavar='[targets]', help='tags or paths to run the command for' ).completer = ChoicesCompleter(tag_to_paths.keys()) autocomplete(parser) parsed = parser.parse_args() # Separate the targets from the command index = 0 targets = OrderedDict() while index < len(parsed.arguments): target = parsed.arguments[index] if target in tag_to_paths: for path in sorted(tag_to_paths[target].keys()): targets[path] = target index += 1 elif target.startswith('@'): parser.error('unknown tag {}'.format(target)) elif os.path.isdir(target): path = expand_path(target) if path not in targets: targets[path] = None index += 1 else: break # beginning of the command # Join the command arguments into a string command_arguments = parsed.arguments[index:] if len(command_arguments) == 0: command = None elif len(command_arguments) == 1: command = ' '.join(command_arguments) else: command = ' '.join( "'{}'".format(arg) if ' ' in arg else arg for arg in command_arguments ) if not (targets and command): parser.error('too few arguments') if parsed.parallel: # Run the command in parallel for path, tag in targets.items(): tmp_file = tempfile.TemporaryFile(mode='w+t') current_process = subprocess.Popen( '{} -i -c "{}"'.format(shell, command), cwd=path, shell=True, stdout=tmp_file, stderr=tmp_file, preexec_fn=os.setsid, ) child_processes.append((path, tag, current_process, tmp_file)) for path, tag, current_process, tmp_file in child_processes: exit_code = current_process.wait() tail = ' ({}{}{})'.format(PINK, tag, CLEAR) if tag else ':' print('{}>>> {}{}{}{}'.format( YELLOW, CYAN, path, tail, CLEAR )) tmp_file.seek(0) lines = tmp_file.readlines() offset = 0 if len(lines) > 0 and contains_ctrl_error_msg(lines[0]): offset += 1 if len(lines) > 1 and contains_ctrl_error_msg(lines[1]): offset += 1 sys.stdout.write(''.join(lines[offset:])) tmp_file.close() if parsed.exit_codes: print('{}>>> {}exit code: {}{}'.format( YELLOW, RED, exit_code, CLEAR )) sys.stdout.write('\n') else: # Run the command sequentially full_command = [] for path, tag in targets.items(): tag_info = ' ({}{}{})'.format(PINK, tag, CLEAR) if tag else ':' if parsed.exit_codes: tail = 'printf "{}>>> {}exit code: $?{}\n\n"'.format( YELLOW, RED, CLEAR ) else: tail = 'printf "\n"' full_command.append( '(printf "{header}"; cd {path} && {cmd};{tail})'.format( header='{}>>> {}{}{}{}\n'.format( YELLOW, CYAN, path, CLEAR, tag_info ), path=path, cmd=command, tail=tail ) ) subprocess.call( [shell, '-i', '-c', '{}'.format(';'.join(full_command))], stderr=sys.stdout.fileno() ) # https://stackoverflow.com/questions/25099895/ os.tcsetpgrp(0, os.getpgrp()) sys.exit(0)