From 4a9051e6176b621edb7eaa9563b3d8f884ba6e86 Mon Sep 17 00:00:00 2001 From: Ethan Dalool Date: Thu, 23 Sep 2021 23:42:34 -0700 Subject: [PATCH] Big migrations and linting. With pathclass.glob_many, we can clean up and feel more confident about many programs that use pipeable to take glob patterns. Added return 0 to all programs that didn't have it, so we have consistent and explicit command line return values. Other linting and whitespace. --- adb_install.py | 21 +++------- allexecutables.py | 5 ++- bitwise_or.py | 6 +-- breplace.py | 2 + clipboard.py | 2 + contentreplace.py | 3 +- crc32.py | 26 +++++++----- crlf.py | 36 ++++++++++++----- crop.py | 26 ++++++------ delete.py | 23 ++++++----- directory_discrepancy.py | 2 + drawn_quartered.py | 2 + empty_directories.py | 6 +-- eval.py | 2 + fdroidapk.py | 1 - ffstreams.py | 2 + filenameorderedrandomness.pyw | 15 ++++--- filenamescramble.py | 15 +++---- filenamescrambleint.py | 15 +++---- filepull.py | 1 + grayscale.py | 17 +++++--- groups_of.py | 1 + hash_hardlink.py | 2 + heresmyclipboard.py | 6 ++- hexpng.py | 2 +- icoconvert.py | 75 +++++++++++++++++++++-------------- inodes.py | 20 ++++++++-- inputrename.py | 4 +- internetcheck.py | 1 + linenumbers.py | 4 +- lint_main_returns.py | 1 - lowercase.py | 1 - move_all.py | 16 ++++---- mp3slice.py | 7 ++-- nonempty_directories.py | 6 +-- pickn.py | 15 ++++--- pip_download.py | 3 +- prune_dirs.py | 2 - randomfile.py | 1 - recycle.py | 17 ++++---- recycle_files.py | 4 +- rejpg.py | 11 +++-- repeat.py | 49 +++++++++++++---------- replace.py | 1 - repr.py | 1 - resize.py | 10 +++-- reverse.py | 8 +++- reversed.py | 4 +- rotate.py | 26 +++++++----- search.py | 1 + shuffle.py | 2 + size.py | 3 +- sole_subdir_lift.py | 2 + sorted.py | 2 + stderr.py | 2 + stdout.py | 2 + stitch.py | 7 ++-- sum.py | 3 +- svgrender.py | 2 + threaded_dl.py | 15 +++++-- touch.py | 2 + unique.py | 1 + watchforlinks.py | 1 + zerofile.py | 2 - 64 files changed, 345 insertions(+), 228 deletions(-) diff --git a/adb_install.py b/adb_install.py index d488bf0..e8227ce 100644 --- a/adb_install.py +++ b/adb_install.py @@ -1,37 +1,24 @@ import argparse import os -import re import sys from voussoirkit import interactive from voussoirkit import pathclass from voussoirkit import pipeable +from voussoirkit import stringtools from voussoirkit import vlogging -from voussoirkit import winglob log = vlogging.getLogger(__name__, 'adbinstall') -def natural_sorter(x): - ''' - Used for sorting files in 'natural' order instead of lexicographic order, - so that you get 1 2 3 4 5 6 7 8 9 10 11 12 13 ... - instead of 1 10 11 12 13 2 3 4 5 ... - Thank you Mark Byers - http://stackoverflow.com/a/11150413 - ''' - convert = lambda text: int(text) if text.isdigit() else text.lower() - alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)] - return alphanum_key(x) - def adbinstall_argparse(args): patterns = pipeable.input_many(args.apks, skip_blank=True, strip=True) - apks = [file for pattern in patterns for file in winglob.glob(pattern)] + apks = pathclass.glob_many(patterns, files=True) installs = [] for apk in apks: apk = pathclass.Path(apk) if apk.is_dir: files = apk.glob('*.apk') - files.sort(key=lambda x: natural_sorter(x.basename.lower())) + files.sort(key=lambda x: stringtools.natural_sorter(x.basename.lower())) apk = files[-1] installs.append(apk) @@ -46,6 +33,8 @@ def adbinstall_argparse(args): log.info(command) os.system(command) + return 0 + @vlogging.main_decorator def main(argv): parser = argparse.ArgumentParser(description=__doc__) diff --git a/allexecutables.py b/allexecutables.py index 89ef482..7827a4a 100644 --- a/allexecutables.py +++ b/allexecutables.py @@ -2,6 +2,7 @@ import os import sys from voussoirkit import pathclass +from voussoirkit import pipeable def windows(): paths = os.getenv('PATH').strip(' ;').split(';') @@ -30,7 +31,9 @@ def main(argv): executables = linux() for executable in executables: - print(executable.absolute_path) + pipeable.stdout(executable.absolute_path) + + return 0 if __name__ == '__main__': raise SystemExit(main(sys.argv[1:])) diff --git a/bitwise_or.py b/bitwise_or.py index e456ce4..1614d5a 100644 --- a/bitwise_or.py +++ b/bitwise_or.py @@ -7,16 +7,13 @@ Merge two or more files by performing bitwise or on their bits. > bitwise_or file1 file2 --output file3 ''' import argparse -import os import sys from voussoirkit import betterhelp from voussoirkit import interactive -from voussoirkit import operatornotify from voussoirkit import pathclass from voussoirkit import pipeable from voussoirkit import vlogging -from voussoirkit import winglob log = vlogging.getLogger(__name__, 'bitwise_or') @@ -24,8 +21,7 @@ CHUNK_SIZE = 2**20 def bitwise_or_argparse(args): patterns = pipeable.input_many(args.files, skip_blank=True, strip=True) - files = [file for pattern in patterns for file in winglob.glob(pattern)] - files = [pathclass.Path(file) for file in files] + files = pathclass.glob_many(patterns, files=True) if len(files) < 2: log.fatal('Need at least two input files.') diff --git a/breplace.py b/breplace.py index b213546..4312c25 100644 --- a/breplace.py +++ b/breplace.py @@ -23,6 +23,8 @@ def breplace_argparse(args): command = f'x.replace("{replace_from}", "{replace_to}")' brename.brename(command, autoyes=args.autoyes, recurse=args.recurse) + return 0 + def main(argv): parser = argparse.ArgumentParser(__doc__) diff --git a/clipboard.py b/clipboard.py index 62cada2..8d33483 100644 --- a/clipboard.py +++ b/clipboard.py @@ -13,6 +13,8 @@ def clipboard_argparse(args): text = text.replace('\r', '') print(text) + return 0 + def main(argv): parser = argparse.ArgumentParser(description=__doc__) diff --git a/contentreplace.py b/contentreplace.py index c4d1856..9ddf558 100644 --- a/contentreplace.py +++ b/contentreplace.py @@ -1,6 +1,5 @@ import argparse import codecs -import os import pyperclip import re import sys @@ -71,6 +70,8 @@ def contentreplace_argparse(args): except UnicodeDecodeError: log.error('%s encountered unicode decode error.', file.absolute_path) + return 0 + @vlogging.main_decorator def main(argv): parser = argparse.ArgumentParser(description=__doc__) diff --git a/crc32.py b/crc32.py index d469409..343be76 100644 --- a/crc32.py +++ b/crc32.py @@ -1,26 +1,32 @@ import argparse -import os import sys import zlib +from voussoirkit import pathclass from voussoirkit import pipeable -from voussoirkit import winglob +from voussoirkit import vlogging + +log = vlogging.getLogger(__name__, 'crc32') def crc32_argparse(args): - files = ( - file - for pattern in pipeable.input_many(args.patterns) - for file in winglob.glob(pattern) - if os.path.isfile(file) - ) + return_status = 0 + + patterns = pipeable.input_many(args.patterns, skip_blank=True, strip=True) + files = pathclass.glob_many(patterns, files=True) + for file in files: try: with open(file, 'rb') as handle: crc = zlib.crc32(handle.read()) - print(hex(crc)[2:].rjust(8, '0'), file) + crc = hex(crc)[2:].rjust(8, '0') + pipeable.stdout(f'{crc} {file}') except Exception as e: - print(file, e) + log.error('%s %s', file, e) + return_status = 1 + return return_status + +@vlogging.main_decorator def main(argv): parser = argparse.ArgumentParser(description=__doc__) diff --git a/crlf.py b/crlf.py index 6e1113a..211d02e 100644 --- a/crlf.py +++ b/crlf.py @@ -1,28 +1,46 @@ ''' Convert LF line endings to CRLF. ''' +import argparse import sys +from voussoirkit import pathclass from voussoirkit import pipeable -from voussoirkit import winglob CR = b'\x0D' LF = b'\x0A' CRLF = CR + LF -def crlf(filename): - with open(filename, 'rb') as handle: +def crlf(file): + with file.open('rb') as handle: content = handle.read() + + original = content content = content.replace(CRLF, LF) content = content.replace(LF, CRLF) - with open(filename, 'wb') as handle: + if content == original: + return + + with file.open('wb') as handle: handle.write(content) -def main(args): - for line in pipeable.go(args, strip=True, skip_blank=True): - for filename in winglob.glob(line): - pipeable.stdout(filename) - crlf(filename) +def crlf_argparse(args): + patterns = pipeable.input_many(args.patterns, skip_blank=True, strip=True) + files = pathclass.glob_many(patterns) + for file in files: + crlf(file) + pipeable.stdout(file) + + return 0 + +def main(argv): + parser = argparse.ArgumentParser(description=__doc__) + + parser.add_argument('patterns') + parser.set_defaults(func=crlf_argparse) + + args = parser.parse_args(argv) + return args.func(args) if __name__ == '__main__': raise SystemExit(main(sys.argv[1:])) diff --git a/crop.py b/crop.py index ff3ed80..93e77f3 100644 --- a/crop.py +++ b/crop.py @@ -1,13 +1,12 @@ import argparse -import os import PIL.Image import sys +from voussoirkit import pathclass from voussoirkit import pipeable -from voussoirkit import winglob -def crop(filename, crops, *, inplace=False): - image = PIL.Image.open(filename) +def crop(file, crops, *, inplace=False): + image = PIL.Image.open(file.absolute_path) if len(crops) == 2: crops.extend(image.size) @@ -20,24 +19,27 @@ def crop(filename, crops, *, inplace=False): image = image.crop(crops) if inplace: - newname = filename + newname = file else: suffix = '_'.join(str(x) for x in crops) suffix = f'_{suffix}' - (base, extension) = os.path.splitext(filename) - newname = base + suffix + extension + base = file.replace_extension('').basename + newname = file.parent.with_child(base + suffix).add_extension(file.extension) - pipeable.stdout(newname) - image.save(newname, exif=image.info.get('exif', b''), quality=100) + pipeable.stdout(newname.absolute_path) + image.save(newname.absolute_path, exif=image.info.get('exif', b''), quality=100) def crop_argparse(args): - filenames = winglob.glob(args.pattern) - for filename in filenames: + patterns = pipeable.input(args.pattern, skip_blank=True, strip=True) + files = pathclass.glob_many(patterns, files=True) + + for file in files: crop( - filename, + file, crops=args.crops, inplace=args.inplace, ) + return 0 def main(argv): parser = argparse.ArgumentParser(description=__doc__) diff --git a/delete.py b/delete.py index 6c65edb..add5c2d 100644 --- a/delete.py +++ b/delete.py @@ -1,14 +1,19 @@ import os import shutil +import sys +from voussoirkit import pathclass from voussoirkit import pipeable -from voussoirkit import winglob -for pattern in pipeable.go(skip_blank=True): - for name in winglob.glob(pattern): - if os.path.isfile(name): - pipeable.stdout(name) - os.remove(name) - elif os.path.isdir(name): - pipeable.stdout(name) - shutil.rmtree(name) +def main(argv): + for path in pathclass.glob_many(pipeable.go(argv, skip_blank=True)): + if path.is_file: + pipeable.stdout(path.absolute_path) + os.remove(path.absolute_path) + elif path.is_dir: + pipeable.stdout(path.absolute_path) + shutil.rmtree(path.absolute_path) + return 0 + +if __name__ == '__main__': + raise SystemExit(main(sys.argv[1:])) diff --git a/directory_discrepancy.py b/directory_discrepancy.py index b265e6e..9f81343 100644 --- a/directory_discrepancy.py +++ b/directory_discrepancy.py @@ -45,6 +45,8 @@ def directory_discrepancy_argparse(args): for discrepancy in sorted(files2.difference(files1)): print(discrepancy) + return 0 + @vlogging.main_decorator def main(argv): parser = argparse.ArgumentParser(description=__doc__) diff --git a/drawn_quartered.py b/drawn_quartered.py index 45c7f20..ad6ac1c 100644 --- a/drawn_quartered.py +++ b/drawn_quartered.py @@ -55,6 +55,8 @@ def drawquarter_argparse(args): print(output_filename.relative_path) piece.save(output_filename.absolute_path) + return 0 + def main(argv): parser = argparse.ArgumentParser() diff --git a/empty_directories.py b/empty_directories.py index 7098d2b..53e8b54 100644 --- a/empty_directories.py +++ b/empty_directories.py @@ -3,17 +3,17 @@ import sys from voussoirkit import pathclass from voussoirkit import pipeable -from voussoirkit import winglob def empty_directories_argparse(args): patterns = pipeable.input_many(args.patterns, skip_blank=True, strip=True) - directories = (pathclass.Path(d) for pattern in patterns for d in winglob.glob(pattern)) - directories = (d for d in directories if d.is_dir) + directories = pathclass.glob_many(patterns, directories=True) for directory in directories: if len(directory.listdir()) == 0: pipeable.stdout(directory.absolute_path) + return 0 + def main(argv): parser = argparse.ArgumentParser(description=__doc__) diff --git a/eval.py b/eval.py index 00a6ee7..80fa280 100644 --- a/eval.py +++ b/eval.py @@ -25,6 +25,8 @@ def eval_argparse(args): x = line pipeable.stdout(eval(args.eval_string)) + return 0 + def main(argv): parser = argparse.ArgumentParser(description=__doc__) diff --git a/fdroidapk.py b/fdroidapk.py index 52d100f..3e5bc59 100644 --- a/fdroidapk.py +++ b/fdroidapk.py @@ -29,7 +29,6 @@ import bs4 import requests import sys import tenacity -import time from voussoirkit import betterhelp from voussoirkit import downloady diff --git a/ffstreams.py b/ffstreams.py index ff9e134..e32ba37 100644 --- a/ffstreams.py +++ b/ffstreams.py @@ -138,6 +138,8 @@ def ffstreams_argparse(args): moveto=args.moveto, ) + return 0 + @vlogging.main_decorator def main(argv): parser = argparse.ArgumentParser(description=__doc__) diff --git a/filenameorderedrandomness.pyw b/filenameorderedrandomness.pyw index f5c3d2c..b6e1c08 100644 --- a/filenameorderedrandomness.pyw +++ b/filenameorderedrandomness.pyw @@ -16,11 +16,10 @@ argv = sys.argv[1:] randname = [random.choice(string.digits) for x in range(12)] randname = int(''.join(randname)) -for pattern in argv: - for path in winglob.glob(pattern): - path = pathclass.Path(path) - newname = str(randname).rjust(12, '0') + path.dot_extension - randname += 1 - newname = path.parent.with_child(newname) - os.rename(path.absolute_path, newname.absolute_path) - print('%s -> %s' % (path.absolute_path, newname.basename)) + +for path in pathclass.glob_many(argv): + newname = str(randname).rjust(12, '0') + path.dot_extension + randname += 1 + newname = path.parent.with_child(newname) + os.rename(path.absolute_path, newname.absolute_path) + print('%s -> %s' % (path.absolute_path, newname.basename)) diff --git a/filenamescramble.py b/filenamescramble.py index 5f3f4b6..9002615 100644 --- a/filenamescramble.py +++ b/filenamescramble.py @@ -9,15 +9,12 @@ import string import sys from voussoirkit import pathclass -from voussoirkit import winglob argv = sys.argv[1:] -for pattern in argv: - for path in winglob.glob(pattern): - path = pathclass.Path(path) - newname = [random.choice(string.ascii_lowercase) for x in range(9)] - newname = ''.join(newname) + path.dot_extension - newname = path.parent.with_child(newname) - os.rename(path.absolute_path, newname.absolute_path) - print('%s -> %s' % (path.absolute_path, newname.basename)) +for path in pathclass.glob_many(argv): + newname = [random.choice(string.ascii_lowercase) for x in range(9)] + newname = ''.join(newname) + path.dot_extension + newname = path.parent.with_child(newname) + os.rename(path.absolute_path, newname.absolute_path) + print('%s -> %s' % (path.absolute_path, newname.basename)) diff --git a/filenamescrambleint.py b/filenamescrambleint.py index 0347e83..f241d38 100644 --- a/filenamescrambleint.py +++ b/filenamescrambleint.py @@ -9,15 +9,12 @@ import string import sys from voussoirkit import pathclass -from voussoirkit import winglob argv = sys.argv[1:] -for pattern in argv: - for path in winglob.glob(pattern): - path = pathclass.Path(path) - newname = [random.choice(string.digits) for x in range(12)] - newname = ''.join(newname) + path.dot_extension - newname = path.parent.with_child(newname) - os.rename(path.absolute_path, newname.absolute_path) - print('%s -> %s' % (path.absolute_path, newname.basename)) +for path in pathclass.glob_many(argv): + newname = [random.choice(string.digits) for x in range(12)] + newname = ''.join(newname) + path.dot_extension + newname = path.parent.with_child(newname) + os.rename(path.absolute_path, newname.absolute_path) + print('%s -> %s' % (path.absolute_path, newname.basename)) diff --git a/filepull.py b/filepull.py index ee518e8..2292e49 100644 --- a/filepull.py +++ b/filepull.py @@ -42,6 +42,7 @@ def filepull(pull_from='.', autoyes=False): def filepull_argparse(args): filepull(pull_from=args.pull_from, autoyes=args.autoyes) + return 0 def main(argv): parser = argparse.ArgumentParser() diff --git a/grayscale.py b/grayscale.py index 0f32514..dd8b63a 100644 --- a/grayscale.py +++ b/grayscale.py @@ -3,7 +3,7 @@ import PIL.Image import sys from voussoirkit import pathclass -from voussoirkit import winglob +from voussoirkit import pipeable def grayscale(filename, *, inplace=False): filename = pathclass.Path(filename) @@ -20,18 +20,23 @@ def grayscale(filename, *, inplace=False): image = PIL.Image.open(filename.absolute_path) image = image.convert('LA').convert(image.mode) - print(f'{new_filename.absolute_path}') image.save(new_filename.absolute_path, exif=image.info.get('exif', b'')) + return new_filename def grayscale_argparse(args): - filenames = winglob.glob(args.pattern) - for filename in filenames: - grayscale(filename, inplace=args.inplace) + patterns = pipeable.input_many(args.patterns, skip_blank=True, strip=True) + files = pathclass.glob_many(patterns, files=True) + for file in files: + new_filename = grayscale(file, inplace=args.inplace) + if new_filename: + pipeable.stdout(new_filename.absolute_path) + + return 0 def main(argv): parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('pattern') + parser.add_argument('patterns', nargs='+') parser.add_argument('--inplace', action='store_true') parser.set_defaults(func=grayscale_argparse) diff --git a/groups_of.py b/groups_of.py index e5e39f3..49a7f6c 100644 --- a/groups_of.py +++ b/groups_of.py @@ -11,6 +11,7 @@ def groupsof_argparse(args): for chunk in chunks: chunk = args.separator.join(chunk) pipeable.stdout(chunk) + return 0 def main(argv): parser = argparse.ArgumentParser(description=__doc__) diff --git a/hash_hardlink.py b/hash_hardlink.py index e3d7863..c7d7024 100644 --- a/hash_hardlink.py +++ b/hash_hardlink.py @@ -65,6 +65,8 @@ def hash_hardlink_argparse(args): send2trash.send2trash(follower.absolute_path) os.link(leader.absolute_path, follower.absolute_path) + return 0 + @vlogging.main_decorator def main(argv): parser = argparse.ArgumentParser(description=__doc__) diff --git a/heresmyclipboard.py b/heresmyclipboard.py index 2f5796a..0923790 100644 --- a/heresmyclipboard.py +++ b/heresmyclipboard.py @@ -27,7 +27,11 @@ def root(): def heresmyclipboard_argparse(args): log.info(f'Starting server on port {args.port}, pid={os.getpid()}') - site.run(host='0.0.0.0', port=args.port) + try: + site.run(host='0.0.0.0', port=args.port) + except KeyboardInterrupt: + pass + return 0 @vlogging.main_decorator def main(argv): diff --git a/hexpng.py b/hexpng.py index ce4f097..e98ba70 100644 --- a/hexpng.py +++ b/hexpng.py @@ -5,7 +5,6 @@ import argparse import PIL.Image import sys - def full_hex(h): h = h.replace('#', '') if len(h) in [3, 4]: @@ -28,6 +27,7 @@ def make_hexpng(h, width=1, height=1): def hexpng_argparse(args): make_hexpng(args.hex_value, width=args.width, height=args.height) + return 0 def main(argv): parser = argparse.ArgumentParser(description=__doc__) diff --git a/icoconvert.py b/icoconvert.py index ee51c21..a926cba 100644 --- a/icoconvert.py +++ b/icoconvert.py @@ -131,43 +131,59 @@ def load_image(filename): return image def build_ico_header_blob(image_count): - datablob = (b'' - + little(0, 2) # reserved - + little(1, 2) # 1 = ico type - + little(image_count, 2) - ) + datablob = b''.join([ + # reserved + little(0, 2), + # 1 = ico type + little(1, 2), + little(image_count, 2), + ]) return datablob def build_icon_directory_blob(image, offset_from_start): (width, height) = image.size - datablob = (b'' - + little(width if width < 256 else 0, 1) - + little(height if height < 256 else 0, 1) - + little(0, 1) # colors in palette - + little(0, 1) # reserved - + little(1, 2) # color planes - + little(32, 2) # bit depth - + little((width * height * 4) + BMP_HEADER_LENGTH, 4) # image bytes length - + little(offset_from_start, 4) - ) + datablob = b''.join([ + little(width if width < 256 else 0, 1), + little(height if height < 256 else 0, 1), + # colors in palette + little(0, 1), + # reserved + little(0, 1), + # color planes + little(1, 2), + # bit depth + little(32, 2), + # image bytes length + little((width * height * 4) + BMP_HEADER_LENGTH, 4), + little(offset_from_start, 4), + ]) return datablob def build_image_data_blob(image): - datablob = (b'' - + little(40, 4) # header size - + little(image.size[0], 4) + datablob = b''.join([ + # header size + little(40, 4), + little(image.size[0], 4), # "Even if the AND mask is not supplied, if the image is in Windows BMP # format, the BMP header must still specify a doubled height." - wikipedia - + little(image.size[1] * 2, 4) - + little(1, 2) # color planes - + little(32, 2) # bit depth - + little(0, 4) # no compression - + little(0, 4) # bytes length, inferred - + little(0, 4) # hor print - + little(0, 4) # ver print - + little(0, 4) # palette - + little(0, 4) # important palette - ) + little(image.size[1] * 2, 4), + # color planes + little(1, 2), + # bit depth + little(32, 2), + # no compression + little(0, 4), + # bytes length, inferred + little(0, 4), + # hor print + little(0, 4), + # ver print + little(0, 4), + # palette + little(0, 4), + # important palette + little(0, 4), + ]) pixeldata = [] # Image.getdata() is a list of (r, g, b, a) channels # But the BMP are written (b, g, r, a) @@ -214,11 +230,10 @@ def images_to_ico(images): final_data = b''.join(datablobs) return final_data - if __name__ == '__main__': try: inputfiles = sys.argv[1:] - except: + except Exception: print('Please provide an image file') raise SystemExit print('Iconifying', inputfiles) diff --git a/inodes.py b/inodes.py index b8d46f4..7ae58d2 100644 --- a/inodes.py +++ b/inodes.py @@ -1,12 +1,24 @@ +import argparse import sys +from voussoirkit import betterhelp from voussoirkit import pathclass +from voussoirkit import pipeable + +def inodes_argparse(args): + patterns = pipeable.input_many(args.patterns, skip_blank=True, strip=True) + files = pathclass.glob_many(patterns, files=True) + for file in files: + pipeable.stdout(f'{file.stat.st_dev} {file.stat.st_ino} {file.relative_path}') + return 0 def main(argv): - for file in pathclass.cwd().listdir(): - if not file.is_file: - continue - print(file.stat.st_dev, file.stat.st_ino, file.relative_path) + parser = argparse.ArgumentParser(description=__doc__) + + parser.add_argument('patterns', nargs='+') + parser.set_defaults(func=inodes_argparse) + + return betterhelp.single_main(argv, parser, __doc__) if __name__ == '__main__': raise SystemExit(main(sys.argv[1:])) diff --git a/inputrename.py b/inputrename.py index e97f705..bdb992d 100644 --- a/inputrename.py +++ b/inputrename.py @@ -17,7 +17,7 @@ def inputrename_argparse(args): files = (file for file in pathclass.cwd().listdir() if args.keyword in file.basename) prev = None for file in files: - print(file.relative_path) + pipeable.stderr(file.relative_path) this = input('> ') if this == '' and prev is not None: this = prev @@ -27,6 +27,8 @@ def inputrename_argparse(args): os.rename(file.absolute_path, new_name.absolute_path) prev = this + return 0 + def main(argv): parser = argparse.ArgumentParser(description=__doc__) diff --git a/internetcheck.py b/internetcheck.py index 42de385..5c96c0c 100644 --- a/internetcheck.py +++ b/internetcheck.py @@ -154,6 +154,7 @@ def main(argv): check_forever() except KeyboardInterrupt: pass + return 0 if __name__ == '__main__': raise SystemExit(main(sys.argv[1:])) diff --git a/linenumbers.py b/linenumbers.py index 5710de5..7257e74 100644 --- a/linenumbers.py +++ b/linenumbers.py @@ -12,7 +12,9 @@ def linenumbers_argparse(args): digits = len(str(len(lines))) form = '{no:>0%d} | {line}' % digits for (index, line) in enumerate(lines): - print(form.format(no=index+1, line=line)) + pipeable.stdout(form.format(no=index+1, line=line)) + + return 0 def main(argv): parser = argparse.ArgumentParser(description=__doc__) diff --git a/lint_main_returns.py b/lint_main_returns.py index c0c9a12..4f417eb 100644 --- a/lint_main_returns.py +++ b/lint_main_returns.py @@ -3,7 +3,6 @@ lint_argparse_returns ===================== ''' import ast -import argparse import sys from voussoirkit import pathclass diff --git a/lowercase.py b/lowercase.py index fb0f8fb..f7f5f08 100644 --- a/lowercase.py +++ b/lowercase.py @@ -1,5 +1,4 @@ from voussoirkit import pipeable - for line in pipeable.go(): pipeable.stdout(line.lower()) diff --git a/move_all.py b/move_all.py index bdc31cc..cc79a87 100644 --- a/move_all.py +++ b/move_all.py @@ -8,17 +8,15 @@ import sys from voussoirkit import pathclass from voussoirkit import pipeable -from voussoirkit import winglob def moveall_argparse(args): - files = ( - pathclass.Path(file) - for pattern in pipeable.input(args.source) - for file in winglob.glob(pattern) - ) - destination = pathclass.Path(args.destination) + patterns = pipeable.input(args.source, skip_blank=True, strip=True) + files = pathclass.glob_many(patterns) - if not destination.is_dir: + destination = pathclass.Path(args.destination) + try: + destination.assert_is_directory() + except pathclass.NotDirectory: pipeable.stderr('destination must be a directory.') return 1 @@ -39,6 +37,8 @@ def moveall_argparse(args): pipeable.stdout(new_path.absolute_path) shutil.move(file.absolute_path, new_path.absolute_path) + return 0 + def main(argv): parser = argparse.ArgumentParser(description=__doc__) diff --git a/mp3slice.py b/mp3slice.py index 9b19110..9bc9299 100644 --- a/mp3slice.py +++ b/mp3slice.py @@ -11,7 +11,6 @@ import sys from voussoirkit import bytestring - def parse_rules(lines): rules = [] for (times, title) in lines[::-1]: @@ -125,7 +124,7 @@ def _unitconvert(value): else: return bytestring.parsebytes(value) -def example_argparse(args): +def mp3slice_argparse(args): if len(args.rules) == 1 and os.path.isfile(args.rules[0]): rules = read_rulefile(args.rules[0]) else: @@ -155,14 +154,14 @@ def example_argparse(args): command = 'ffmpeg -i "%s" %s' % (args.input_filename, outputters) print(command) os.system(command) - + return 0 def main(argv): parser = argparse.ArgumentParser() parser.add_argument('input_filename') parser.add_argument('rules', nargs='+', default=None) - parser.set_defaults(func=example_argparse) + parser.set_defaults(func=mp3slice_argparse) args = parser.parse_args(argv) return args.func(args) diff --git a/nonempty_directories.py b/nonempty_directories.py index 3c178db..6e4ce6c 100644 --- a/nonempty_directories.py +++ b/nonempty_directories.py @@ -3,17 +3,17 @@ import sys from voussoirkit import pathclass from voussoirkit import pipeable -from voussoirkit import winglob def nonempty_directories_argparse(args): patterns = pipeable.input_many(args.patterns, skip_blank=True, strip=True) - directories = (pathclass.Path(d) for pattern in patterns for d in winglob.glob(pattern)) - directories = (d for d in directories if d.is_dir) + directories = pathclass.glob_many(patterns, directories=True) for directory in directories: if len(directory.listdir()) != 0: pipeable.stdout(directory.absolute_path) + return 0 + def main(argv): parser = argparse.ArgumentParser(description=__doc__) diff --git a/pickn.py b/pickn.py index fb2729e..f63441e 100644 --- a/pickn.py +++ b/pickn.py @@ -1,26 +1,31 @@ import argparse +import itertools import sys from voussoirkit import pipeable -def shuffle_argparse(args): +def pickn_argparse(args): if args.count < 1: pipeable.stderr('count must be >= 1.') return 1 lines = pipeable.input(args.source, read_files=True, skip_blank=True, strip=True) - lines = list(lines) - lines = lines[:args.count] - for line in lines: + for line in itertools.islice(lines, args.count): pipeable.stdout(line) + # Exhaust the rest of stdin so we don't get Broken Pipe error + for line in lines: + pass + + return 0 + def main(argv): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('source') parser.add_argument('count', type=int) - parser.set_defaults(func=shuffle_argparse) + parser.set_defaults(func=pickn_argparse) args = parser.parse_args(argv) return args.func(args) diff --git a/pip_download.py b/pip_download.py index 8d689f3..88b5acc 100644 --- a/pip_download.py +++ b/pip_download.py @@ -38,11 +38,12 @@ def pip_download(package): os.rename(os.path.join(tmpdir.name, filename), os.path.join(new_directory, filename)) tmpdir.cleanup() - def pip_download_argparse(args): for package in args.packages: pip_download(package) + return 0 + def main(argv): parser = argparse.ArgumentParser(description=__doc__) diff --git a/prune_dirs.py b/prune_dirs.py index 3a4851e..a01f7a7 100644 --- a/prune_dirs.py +++ b/prune_dirs.py @@ -14,7 +14,6 @@ from voussoirkit import betterhelp from voussoirkit import spinal from voussoirkit import pathclass - def prune_dirs(starting): starting = pathclass.Path(starting) walker = spinal.walk(starting, yield_directories=True, yield_files=False) @@ -36,7 +35,6 @@ def prune_dirs(starting): directory = double_check.pop() pruneme(directory) - def prune_dirs_argparse(args): return prune_dirs(args.starting) diff --git a/randomfile.py b/randomfile.py index 37d3529..477e268 100644 --- a/randomfile.py +++ b/randomfile.py @@ -28,7 +28,6 @@ def make_randomfile(length, filename=None): f.close() print('Created %s' % filename) - bytes = listget(sys.argv, 1, None) if bytes is None: bytes = 2 ** 10 diff --git a/recycle.py b/recycle.py index 95b1576..af4cb2e 100644 --- a/recycle.py +++ b/recycle.py @@ -1,11 +1,14 @@ -import os import send2trash +import sys +from voussoirkit import pathclass from voussoirkit import pipeable -from voussoirkit import winglob -for pattern in pipeable.go(skip_blank=True): - for name in winglob.glob(pattern): - name = os.path.abspath(name) - pipeable.stdout(name) - send2trash.send2trash(name) +def main(argv): + for path in pathclass.glob_many(pipeable.go(argv, skip_blank=True)): + pipeable.stdout(path.absolute_path) + send2trash.send2trash(path.absolute_path) + return 0 + +if __name__ == '__main__': + raise SystemExit(main(sys.argv[1:])) diff --git a/recycle_files.py b/recycle_files.py index c6af98f..7c42002 100644 --- a/recycle_files.py +++ b/recycle_files.py @@ -5,7 +5,7 @@ from voussoirkit import pipeable for line in pipeable.go(): if os.path.isfile(line): - print('Recycling', line) + pipeable.stdout(line) send2trash.send2trash(line) else: - print('Not a file', line) + pipeable.stderr('Not a file', line) diff --git a/rejpg.py b/rejpg.py index 80e450b..092bbfa 100644 --- a/rejpg.py +++ b/rejpg.py @@ -12,6 +12,9 @@ from voussoirkit import bytestring from voussoirkit import imagetools from voussoirkit import pipeable from voussoirkit import spinal +from voussoirkit import vlogging + +log = vlogging.getLogger(__name__, 'rejpg') PIL.ImageFile.LOAD_TRUNCATED_IMAGES = True @@ -24,7 +27,7 @@ def rejpg_argparse(args): bytes_saved = 0 remaining_size = 0 for filename in files: - print(filename) + log.info('Processing %s.', filename) bytesio = io.BytesIO() image = PIL.Image.open(filename) @@ -43,9 +46,11 @@ def rejpg_argparse(args): f.write(new_bytes) f.close() - print('Saved', bytestring.bytestring(bytes_saved)) - print('Remaining are', bytestring.bytestring(remaining_size)) + log.info('Saved', bytestring.bytestring(bytes_saved)) + log.info('Remaining are', bytestring.bytestring(remaining_size)) + return 0 +@vlogging.main_decorator def main(argv): parser = argparse.ArgumentParser(description=__doc__) diff --git a/repeat.py b/repeat.py index fd47dad..670ab55 100644 --- a/repeat.py +++ b/repeat.py @@ -2,6 +2,9 @@ Repeat the input as many times as you want. > repeat "hello" 8 + +> repeat "yowza" inf + > echo hi | repeat !i 4 ''' import argparse @@ -9,30 +12,36 @@ import sys from voussoirkit import pipeable +def repeat_inf(text): + try: + while True: + pipeable.stdout(text) + except KeyboardInterrupt: + return 0 + +def repeat_times(text, times): + try: + times = int(times) + except ValueError: + pipeable.stderr('times should be an integer >= 1.') + return 1 + + if times < 1: + pipeable.stderr('times should be >= 1.') + return 1 + + try: + for t in range(times): + pipeable.stdout(text) + except KeyboardInterrupt: + return 1 + def repeat_argparse(args): text = pipeable.input(args.text, split_lines=False) if args.times == 'inf': - try: - while True: - print(text) - except KeyboardInterrupt: - return 0 + return repeat_inf(text) else: - try: - times = int(args.times) - except ValueError: - pipeable.stderr('times should be an integer >= 1.') - return 1 - - if times < 1: - pipeable.stderr('times should be >= 1.') - return 1 - - try: - for t in range(times): - print(text) - except KeyboardInterrupt: - return 1 + return repeat_times(text, args.times) def main(argv): parser = argparse.ArgumentParser(description=__doc__) diff --git a/replace.py b/replace.py index d265415..f3b4d7f 100644 --- a/replace.py +++ b/replace.py @@ -2,7 +2,6 @@ import sys from voussoirkit import pipeable - lines = pipeable.input(sys.argv[1]) replace_from = sys.argv[2] replace_to = sys.argv[3] diff --git a/repr.py b/repr.py index 75e5e4b..b574464 100644 --- a/repr.py +++ b/repr.py @@ -1,5 +1,4 @@ from voussoirkit import pipeable - for line in pipeable.go(): print(repr(line)) diff --git a/resize.py b/resize.py index d6bb097..3046daa 100644 --- a/resize.py +++ b/resize.py @@ -38,7 +38,6 @@ from voussoirkit import imagetools from voussoirkit import pathclass from voussoirkit import pipeable from voussoirkit import vlogging -from voussoirkit import winglob log = vlogging.getLogger(__name__, 'resize') @@ -102,10 +101,11 @@ def resize( image.save(new_name.absolute_path, exif=image.info.get('exif', b''), quality=quality) def resize_argparse(args): - filenames = winglob.glob(args.pattern) - for filename in filenames: + patterns = pipeable.input(args.pattern, skip_blank=True, strip=True) + files = pathclass.glob_many(patterns, files=True) + for file in files: resize( - filename, + file, args.new_w, args.new_h, inplace=args.inplace, @@ -115,6 +115,8 @@ def resize_argparse(args): quality=args.quality, ) + return 0 + @vlogging.main_decorator def main(argv): parser = argparse.ArgumentParser(description=__doc__) diff --git a/reverse.py b/reverse.py index e684c04..7d87e6c 100644 --- a/reverse.py +++ b/reverse.py @@ -4,13 +4,17 @@ Reverse a string. import argparse import sys +from voussoirkit import pipeable + def reverse_argparse(args): - print(''.join(reversed(args.string))) + text = pipeable.input(args.text, split_lines=False) + pipeable.stdout(''.join(reversed(text))) + return 0 def main(argv): parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('string') + parser.add_argument('text') parser.set_defaults(func=reverse_argparse) args = parser.parse_args(argv) diff --git a/reversed.py b/reversed.py index d6602bb..548875f 100644 --- a/reversed.py +++ b/reversed.py @@ -8,8 +8,8 @@ from voussoirkit import pipeable def reverse_argparse(args): lines = list(pipeable.input(args.lines)) - lines.reverse() - print('\n'.join(lines)) + pipeable.stdout('\n'.join(reversed(lines))) + return 0 def main(argv): parser = argparse.ArgumentParser(description=__doc__) diff --git a/rotate.py b/rotate.py index 9971c03..c2ec90c 100644 --- a/rotate.py +++ b/rotate.py @@ -1,28 +1,29 @@ import argparse -import os import PIL.Image import sys from voussoirkit import imagetools +from voussoirkit import pathclass from voussoirkit import pipeable from voussoirkit import vlogging -from voussoirkit import winglob log = vlogging.getLogger(__name__, 'rotate') def rotate_argparse(args): if args.angle is None and not args.exif: - pipeable.stderr('Either an angle or --exif must be provided.') + log.fatal('Either an angle or --exif must be provided.') return 1 - filenames = winglob.glob(args.pattern) - for filename in filenames: - image = PIL.Image.open(filename) + patterns = pipeable.input(args.pattern, skip_blank=True, strip=True) + files = pathclass.glob_many(patterns, files=True) + + for file in files: + image = PIL.Image.open(file.absolute_path) if args.exif: (new_image, exif) = imagetools.rotate_by_exif(image) if new_image is image: - log.debug('%s doesn\'t need exif rotation.', filename) + log.debug('%s doesn\'t need exif rotation.', file.absolute_path) continue image = new_image else: @@ -30,16 +31,19 @@ def rotate_argparse(args): image = image.rotate(args.angle, expand=True) if args.inplace: - newname = filename + newname = file else: if args.exif: suffix = f'_exifrot' else: suffix = f'_{args.angle}' - (base, extension) = os.path.splitext(filename) - newname = base + suffix + extension + + base = file.replace_extension('').basename + newname = base + suffix + newname = file.parent.with_child(newname).add_extension(file.extension) + pipeable.stdout(newname) - image.save(newname, exif=exif, quality=args.quality) + image.save(file.absolute_path, exif=exif, quality=args.quality) @vlogging.main_decorator def main(argv): diff --git a/search.py b/search.py index a00b556..bd01b99 100644 --- a/search.py +++ b/search.py @@ -251,6 +251,7 @@ def _search_argparse(args): result_count += 1 if args.show_count: print('%d items.' % result_count) + return 0 @pipeable.ctrlc_return1 def search_argparse(args): diff --git a/shuffle.py b/shuffle.py index 07a1bcd..13e4976 100644 --- a/shuffle.py +++ b/shuffle.py @@ -12,6 +12,8 @@ def shuffle_argparse(args): for line in lines: pipeable.stdout(line) + return 0 + def main(argv): parser = argparse.ArgumentParser(description=__doc__) diff --git a/size.py b/size.py index 7d31670..2a62312 100644 --- a/size.py +++ b/size.py @@ -14,7 +14,8 @@ def main(argv): elif path.is_dir: total += spinal.get_dir_size(path) - print(total) + pipeable.stdout(total) + return 0 if __name__ == '__main__': raise SystemExit(main(sys.argv[1:])) diff --git a/sole_subdir_lift.py b/sole_subdir_lift.py index c5ef49d..93ad8f9 100644 --- a/sole_subdir_lift.py +++ b/sole_subdir_lift.py @@ -53,6 +53,8 @@ def sole_lift_argparse(args): os.rmdir(temp_dir.absolute_path) queue.append(directory.parent) + return 0 + @vlogging.main_decorator def main(argv): parser = argparse.ArgumentParser(description=__doc__) diff --git a/sorted.py b/sorted.py index db99d6d..5a15ffd 100644 --- a/sorted.py +++ b/sorted.py @@ -14,6 +14,8 @@ def sorted_argparse(args): for line in lines: pipeable.stdout(line) + return 0 + def main(argv): parser = argparse.ArgumentParser(description=__doc__) diff --git a/stderr.py b/stderr.py index f517b96..91f5f0a 100644 --- a/stderr.py +++ b/stderr.py @@ -12,6 +12,8 @@ def printstderr_argparse(args): for line in text: pipeable.stderr(line) + return 0 + @vlogging.main_decorator def main(argv): parser = argparse.ArgumentParser(description=__doc__) diff --git a/stdout.py b/stdout.py index 11713e3..e9d9757 100644 --- a/stdout.py +++ b/stdout.py @@ -12,6 +12,8 @@ def printstdout_argparse(args): for line in text: pipeable.stdout(line) + return 0 + @vlogging.main_decorator def main(argv): parser = argparse.ArgumentParser(description=__doc__) diff --git a/stitch.py b/stitch.py index 57e3eba..9190514 100644 --- a/stitch.py +++ b/stitch.py @@ -2,10 +2,10 @@ import PIL.Image import argparse import sys +from voussoirkit import pathclass from voussoirkit import pipeable from voussoirkit import sentinel from voussoirkit import vlogging -from voussoirkit import winglob log = vlogging.getLogger(__name__, 'stitch') @@ -14,8 +14,8 @@ HORIZONTAL = sentinel.Sentinel('horizontal') def stitch_argparse(args): patterns = pipeable.input_many(args.image_files, skip_blank=True, strip=True) - files = [file for pattern in patterns for file in winglob.glob(pattern)] - images = [PIL.Image.open(file) for file in files] + files = pathclass.glob_many(patterns, files=True) + images = [PIL.Image.open(file.absolute_path) for file in files] if args.vertical: direction = VERTICAL else: @@ -42,6 +42,7 @@ def stitch_argparse(args): log.info(args.output) final_image.save(args.output) + return 0 @vlogging.main_decorator def main(argv): diff --git a/sum.py b/sum.py index f7d1e5a..a82c516 100644 --- a/sum.py +++ b/sum.py @@ -1,5 +1,4 @@ from voussoirkit import pipeable - total = sum(float(x) for x in pipeable.go() if x.strip()) -pipeable.stdout(f'{total}\n') +pipeable.stdout(f'{total}') diff --git a/svgrender.py b/svgrender.py index b5f6cd6..f8d9d47 100644 --- a/svgrender.py +++ b/svgrender.py @@ -88,6 +88,8 @@ def svgrender_argparse(args): axis='y' if args.y else 'x', ) + return 0 + @vlogging.main_decorator def main(argv): parser = argparse.ArgumentParser(description=__doc__) diff --git a/threaded_dl.py b/threaded_dl.py index 5f4d0ef..0557e9e 100644 --- a/threaded_dl.py +++ b/threaded_dl.py @@ -8,6 +8,10 @@ import time from voussoirkit import bytestring from voussoirkit import downloady from voussoirkit import pipeable +from voussoirkit import vlogging + +log = vlogging.getLogger(__name__, 'threaded_dl') +downloady.log.setLevel(vlogging.WARNING) def clean_url_list(urls): for url in urls: @@ -29,7 +33,7 @@ def clean_url_list(urls): yield url def download_thread(url, filename, *, bytespersecond=None, headers=None, timeout=None): - print(f' Starting "{filename}"') + log.info(f'Starting "{filename}"') downloady.download_file( url, filename, @@ -37,7 +41,7 @@ def download_thread(url, filename, *, bytespersecond=None, headers=None, timeout headers=headers, timeout=timeout, ) - print(f'+Finished "{filename}"') + log.info(f'Finished "{filename}"') def remove_finished(threads): return [t for t in threads if t.is_alive()] @@ -87,7 +91,7 @@ def threaded_dl( ) if os.path.exists(filename): - print(f'Skipping existing file "{filename}"') + log.info(f'Skipping existing file "{filename}"') else: kwargs = { @@ -103,7 +107,7 @@ def threaded_dl( while len(threads) > 0: threads = remove_finished(threads) - print(f'{len(threads)} threads remaining\r', end='', flush=True) + pipeable.stderr(f'{len(threads)} threads remaining\r', end='') time.sleep(0.1) def threaded_dl_argparse(args): @@ -132,6 +136,9 @@ def threaded_dl_argparse(args): timeout=args.timeout, ) + return 0 + +@vlogging.main_decorator def main(argv): parser = argparse.ArgumentParser(description=__doc__) diff --git a/touch.py b/touch.py index f33776d..26b2cc2 100644 --- a/touch.py +++ b/touch.py @@ -21,6 +21,8 @@ def touch_argparse(args): os.utime(filename) pipeable.stdout(filename) + return 0 + def main(argv): parser = argparse.ArgumentParser(description=__doc__) diff --git a/unique.py b/unique.py index e548634..2153942 100644 --- a/unique.py +++ b/unique.py @@ -10,6 +10,7 @@ def unique_argparse(args): if line not in seen: pipeable.stdout(line) seen.add(line) + return 0 def main(argv): parser = argparse.ArgumentParser(description=__doc__) diff --git a/watchforlinks.py b/watchforlinks.py index 88eb948..db307d5 100644 --- a/watchforlinks.py +++ b/watchforlinks.py @@ -42,6 +42,7 @@ def watchforlinks_argparse(args): loop_forever(extension=args.extension, regex=args.regex) except KeyboardInterrupt: pass + return 0 def main(argv): parser = argparse.ArgumentParser(description=__doc__) diff --git a/zerofile.py b/zerofile.py index e933cd5..0a092e4 100644 --- a/zerofile.py +++ b/zerofile.py @@ -4,10 +4,8 @@ import sys from voussoirkit import bytestring - filename = os.path.abspath(sys.argv[1]) - def zerofile(filename, length): if os.path.exists(filename): raise ValueError(f'{filename} already exists.')