def filepull(pull_from='.', autoyes=False): files = list(spinal.walk(pull_from)) cwd = os.getcwd() files = [f for f in files if os.path.split(f.absolute_path)[0] != cwd] if len(files) == 0: print('No files to move') return duplicate_count = {} for f in files: basename = f.basename duplicate_count.setdefault(basename, []) duplicate_count[basename].append(f.absolute_path) duplicates = [ '\n'.join(sorted(copies)) for (basename, copies) in duplicate_count.items() if len(copies) > 1 ] duplicates = sorted(duplicates) if len(duplicates) > 0: raise Exception('duplicate names:\n' + '\n'.join(duplicates)) for f in files: print(f.basename) if autoyes or interactive.getpermission(f'Move {len(files)} files?'): for f in files: local = os.path.join('.', f.basename) os.rename(f.absolute_path, local)
def prune_shortcuts(recurse=False, autoyes=False): if recurse: lnks = [file for file in spinal.walk('.') if file.extension == 'lnk'] else: lnks = pathclass.cwd().glob('*.lnk') stale = [] for lnk in lnks: shortcut = winshell.Shortcut(lnk.absolute_path) # There are some special shortcuts that do not have a path, but instead # trigger some action based on a CLSID that Explorer understands. # I can't find this information in the winshell.Shortcut object, so for # now let's at least not delete these files. if shortcut.path == '': continue if not os.path.exists(shortcut.path): stale.append(lnk) if not stale: return print(f'The following {len(stale)} will be recycled:') for lnk in stale: print(lnk.absolute_path) print() if autoyes or interactive.getpermission('Is that ok?'): for lnk in stale: print(lnk.absolute_path) send2trash.send2trash(lnk.absolute_path)
def rejpg_argparse(args): patterns = pipeable.input_many(args.patterns, skip_blank=True, strip=True) files = spinal.walk(recurse=args.recurse, glob_filenames=patterns) files = [f.absolute_path for f in files] bytes_saved = 0 remaining_size = 0 for filename in files: print(''.join(c for c in filename if c in string.printable)) bytesio = io.BytesIO() i = PIL.Image.open(filename) i = imagetools.rotate_by_exif(i) i.save(bytesio, format='jpeg', quality=args.quality) bytesio.seek(0) new_bytes = bytesio.read() old_size = os.path.getsize(filename) new_size = len(new_bytes) remaining_size += new_size if new_size < old_size: bytes_saved += (old_size - new_size) f = open(filename, 'wb') f.write(new_bytes) f.close() print('Saved', bytestring.bytestring(bytes_saved)) print('Remaining are', bytestring.bytestring(remaining_size))
def contentreplace_argparse(args): if args.recurse: files = spinal.walk('.', yield_files=True, yield_directories=False) files = (f for f in files if winglob.fnmatch(f.basename, args.filename_glob)) else: files = pathclass.cwd().glob(args.filename_glob) files = (f for f in files if f.is_file) if args.clip_prompt: replace_from = input('Ready from') if not replace_from: replace_from = pyperclip.paste() replace_to = input('Ready to') if not replace_to: replace_to = pyperclip.paste() else: replace_from = codecs.decode(args.replace_from, 'unicode_escape') if args.do_regex: replace_to = args.replace_to else: replace_to = codecs.decode(args.replace_to, 'unicode_escape') for file in files: try: contentreplace( file, replace_from, replace_to, autoyes=args.autoyes, do_regex=args.do_regex, ) except UnicodeDecodeError: log.error('%s encountered unicode decode error.', file.absolute_path)
def brename(transformation, autoyes=False, do_naturalsort=False, recurse=False): if recurse: walker = spinal.walk('.', yield_files=True, yield_directories=True) olds = list(walker) else: olds = cwd.listdir() if do_naturalsort: olds.sort(key=lambda x: natural_sorter(x.absolute_path)) else: olds.sort() pairs = [] for (index, old) in enumerate(olds): # These variables are assigned so that you can use them in your # transformation string. x = old.basename parent = old.parent noext = old.replace_extension('').basename ext = old.extension.no_dot dot_ext = old.extension.with_dot index1 = index + 1 new = eval(transformation) new = parent.with_child(new) if new.basename == old.basename: continue pairs.append((old, new)) if not pairs: print('Nothing to replace') return loop(pairs, dry=True) if not (autoyes or interactive.getpermission('Is this correct?')): return # Sort in reverse so that renaming a file inside a directory always # occurs before renaming the directory itself. If you rename the # directory first, then the path to the file is invalid by the time # you want to rename it. pairs = sorted(pairs, reverse=True) loop(pairs, dry=False)
def pathtree_argparse(args): from voussoirkit import safeprint from voussoirkit import spinal paths = spinal.walk() paths = [{'path': path.absolute_path, 'size': path.size} for path in paths] tree = from_paths(paths, '.') recursive_get_size(tree) if args.output_file: output_file = open(args.output_file, 'w', encoding='utf-8') else: output_file = None for line in recursive_print_node(tree, use_html=args.use_html): if output_file: print(line, file=output_file) else: safeprint.safeprint(line)
def inputrename_argparse(args): if args.recurse: files = (file for file in spinal.walk('.') if args.keyword in file.basename) else: files = (file for file in pathclass.cwd().listdir() if args.keyword in file.basename) prev = None for file in files: print(file.relative_path) this = input('> ') if this == '' and prev is not None: this = prev if this: new_name = file.basename.replace(args.keyword, this) new_name = file.parent.with_child(new_name) os.rename(file.absolute_path, new_name.absolute_path) prev = this
def prune_dirs(starting): starting = pathclass.Path(starting) walker = spinal.walk(starting, yield_directories=True, yield_files=False) double_check = set() def pruneme(directory): if directory == starting or directory not in starting: return if len(directory.listdir()) == 0: print(directory.absolute_path) os.rmdir(directory.absolute_path) double_check.add(directory.parent) for directory in walker: pruneme(directory) while double_check: directory = double_check.pop() pruneme(directory)
def sole_lift_argparse(args): starting = pathclass.Path(args.starting) queue = collections.deque() queue.extend(spinal.walk(starting, yield_files=False, yield_directories=True)) while len(queue) > 0: directory = queue.popleft() if not directory.exists: log.debug('%s no longer exists.', directory) continue if directory not in starting: log.debug('%s is outside of starting.', directory) continue children = directory.listdir() child_count = len(children) if child_count != 1: log.debug('%s has %d children.', directory, child_count) continue child = children[0] if not child.is_dir: log.debug('%s contains a file, not a dir.', directory) continue log.info('Lifting contents of %s.', child.absolute_path) # child is renamed to random hex so that the grandchildren we are about # to lift don't have name conflicts with the child dir itself. # Consider .\abc\abc where the grandchild can't be moved. temp_dir = directory.with_child(passwordy.urandom_hex(32)) os.rename(child.absolute_path, temp_dir.absolute_path) for grandchild in temp_dir.listdir(): shutil.move(grandchild.absolute_path, directory.absolute_path) if temp_dir.listdir(): raise Exception() os.rmdir(temp_dir.absolute_path) queue.append(directory.parent)
def search( *, yes_all=None, yes_any=None, not_all=None, not_any=None, case_sensitive=False, content_args=None, do_expression=False, do_glob=False, do_regex=False, do_strip=False, line_numbers=False, local_only=False, only_dirs=False, only_files=False, root_path='.', text=None, ): terms = { 'yes_all': yes_all, 'yes_any': yes_any, 'not_all': not_all, 'not_any': not_any } terms = {k: ([v] if isinstance(v, str) else v or []) for (k, v) in terms.items()} #print(terms, content_args) do_plain = not (do_glob or do_regex) if all(v == [] for v in terms.values()) and not content_args: raise NoTerms('No terms supplied') def term_matches(line, term): if not case_sensitive: line = line.lower() if do_expression: return term.evaluate(line) return ( (do_plain and term in line) or (do_regex and re.search(term, line)) or (do_glob and winglob.fnmatch(line, term)) ) if do_expression: # The value still needs to be a list so the upcoming any() / all() # receives an iterable as it expects. It just happens to be 1 tree. trees = {} for (term_type, term_expression) in terms.items(): if term_expression == []: trees[term_type] = [] continue tree = ' '.join(term_expression) tree = expressionmatch.ExpressionTree.parse(tree) if not case_sensitive: tree.map(str.lower) trees[term_type] = [tree] terms = trees elif not case_sensitive: terms = {k: [x.lower() for x in v] for (k, v) in terms.items()} if text is None: search_objects = spinal.walk( root_path, recurse=not local_only, yield_directories=True, ) elif isinstance(text, (list, tuple)): search_objects = text else: search_objects = text.splitlines() for (index, search_object) in enumerate(search_objects): # if index % 10 == 0: # print(index, end='\r', flush=True) if isinstance(search_object, pathclass.Path): if only_files and not search_object.is_file: continue if only_dirs and not search_object.is_dir: continue search_text = search_object.basename result_text = search_object.absolute_path elif isinstance(search_object, HeaderedText): search_text = search_object.text result_text = search_object.with_header else: search_text = search_object result_text = search_object if not all_terms_match(search_text, terms, term_matches): continue if do_strip: result_text = result_text.strip() if line_numbers: result_text = f'{index+1:>4} | {result_text}' if not content_args: yield result_text continue filepath = pathclass.Path(search_object) if not filepath.is_file: continue if filepath.extension == 'lnk' and winshell: yield from search_contents_windows_lnk(filepath, content_args) else: yield from search_contents_generic(filepath, content_args)
from voussoirkit import spinal basenames = {} walker = spinal.walk('.', yield_directories=True, yield_files=False) for directory in walker: basenames.setdefault(directory.basename, []).append(directory) for (basename, dupes) in basenames.items(): if len(dupes) == 1: continue for dupe in dupes: print(dupe.absolute_path) print()
import collections from voussoirkit import spinal counts = collections.Counter() extensions = {} walker = spinal.walk() for file in walker: extensions.setdefault(file.extension, []).append(file) counts[file.extension] += 1 for (extension, count) in counts.most_common(): files = extensions[extension] print(f'{extension.with_dot}: {len(files)}') if len(files) < 5: for file in files: print(f' {file.absolute_path}')