Use new betterhelp.

This commit is contained in:
voussoir 2022-02-12 19:50:00 -08:00
parent 389f22faff
commit 8616cdc5dd
No known key found for this signature in database
GPG key ID: 5F7554F8C26DACCB
29 changed files with 1133 additions and 706 deletions

View file

@ -1,11 +1,3 @@
'''
bitwise_or
==========
Merge two or more files by performing bitwise or on their bits.
> bitwise_or file1 file2 --output file3
'''
import argparse
import sys
@ -40,6 +32,8 @@ def bitwise_or_argparse(args):
pass
elif args.overwrite:
pass
elif not pipeable.in_tty():
return 1
elif not interactive.getpermission(f'Overwrite "{output.absolute_path}"?'):
return 1
@ -61,14 +55,27 @@ def bitwise_or_argparse(args):
@vlogging.main_decorator
def main(argv):
parser = argparse.ArgumentParser(description=__doc__)
parser = argparse.ArgumentParser(
description='''
Merge two or more files by performing bitwise or on their bits.
That is, every byte of the output file will be the bitwise or of the
corresponding byte from all of the input files.
''',
)
parser.add_argument('files', nargs='+')
parser.add_argument('--output', required=True)
parser.add_argument('--overwrite', action='store_true')
parser.add_argument('--output', required=True, type=pathclass.Path)
parser.add_argument(
'--overwrite',
action='store_true',
help='''
Provide this flag if the output file already exists and you'd like to
overwrite it.
''',
)
parser.set_defaults(func=bitwise_or_argparse)
return betterhelp.single_main(argv, parser, __doc__)
return betterhelp.go(parser, argv)
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))

68
blankimage.py Normal file
View file

@ -0,0 +1,68 @@
import argparse
import PIL.Image
import sys
from voussoirkit import betterhelp
from voussoirkit import vlogging
log = vlogging.getLogger(__name__, 'blankimage')
def blankimage_argparse(args):
if args.width and args.height:
size = (args.width, args.height)
else:
size = (512, 512)
if args.color:
color = args.color
else:
color = (255, 255, 255, 255)
image = PIL.Image.new('RGBA', size, color=color)
for filename in args.names:
image.save(filename)
return 0
@vlogging.main_decorator
def main(argv):
parser = argparse.ArgumentParser(
description='''
Create a blank image file.
''',
)
parser.add_argument(
'names',
nargs='+',
help='''
One or more filenames. The same image will be saved to each.
''',
)
parser.add_argument(
'--width',
default=None,
type=int,
help='''
''',
)
parser.add_argument(
'--height',
default=None,
type=int,
help='''
''',
)
parser.add_argument(
'--color',
default=None,
type=str,
help='''
A hex color like #fff or #0a0a0a or #ff0000ff.
''',
)
parser.set_defaults(func=blankimage_argparse)
return betterhelp.go(parser, argv)
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))

View file

@ -1,19 +1,3 @@
'''
Batch rename files by providing a string to be `eval`ed, using variable `x` as
the current filename. Yes I know this is weird, but for certain tasks it's just
too quick and easy to pass up.
Examples:
Prefix all the files:
brename.py "f'Test_{x}'"
Rename files to their index with 0 padding:
brename.py "f'{index1:>03}{dot_ext}'"
Keep the first word and extension:
brename.py "(x.split(' ')[0] + dot_ext) if ' ' in x else x"
'''
import argparse
import os
import random
@ -125,53 +109,70 @@ def brename_argparse(args):
recurse=args.recurse,
)
DOCSTRING = '''
brename - batch file renaming
=============================
> brename.py eval_string <flags>
eval_string:
A string which will be evaluated by Python's eval. The name of the file or
folder will be in the variable `x`. In addition, many other variables are
provided for your convenience:
`quote` ("), `apostrophe` (') so you don't have to escape command quotes.
`hyphen` (-) because leading hyphens often cause problems with argparse.
`stringtools` entire stringtools module. See voussoirkit/stringtools.py.
`space` ( ), `dot` (.), `underscore` (_) so you don't have to add quotes to
your command while using these common characters.
`index` the file's index within the loop.
`index1` the file's index+1, in case you want your names to start from 1.
`parent` a pathclass.Path object for the directory containing the file.
`cwd` a pathclass.Path object for the cwd of this program session.
`noext` the name of the file, but without its extension.
`ext` the file's extension, with no dot.
-y | --yes:
Accept the results without confirming.
--recurse:
Recurse into subfolders and rename those files too.
--naturalsort:
Before renaming, the files will be sorted using natural sort instead of the
default lexicographic sort. Natural sort means that "My file 20" will come
before "My file 100" because 20<100. Lexicographic sort means 100 will come
first because 1 is before 2.
The purpose of this flag is so your index and index1 variables are applied
in the order you desire.
'''
def main(argv):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('transformation', help='python command using x as variable name')
parser.add_argument('-y', '--yes', dest='autoyes', action='store_true')
parser.add_argument('--recurse', action='store_true')
parser.add_argument('--naturalsort', action='store_true')
parser = argparse.ArgumentParser(
description='''
Batch rename files by providing a string to be `eval`ed, using variable `x` as
the current filename. Yes I know this is weird, but for certain tasks it's just
too quick and easy to pass up.
''',
)
parser.examples = [
{'args': ['f\'Test_{x}\''], 'comment': 'Prefix all the files'},
{'args': ['f\'{index1:>03}{dot_ext}\''], 'comment': 'Rename files to their index with 0 padding'},
{'args': ['(x.split(space)[0] + dot_ext) if space in x else x'], 'comment': 'Keep the first word and extension'},
]
parser.add_argument(
'transformation',
help='''
A string which will be evaluated by Python's eval. The name of the file or
folder will be in the variable `x`. In addition, many other variables are
provided for your convenience:
`quote` ("), `apostrophe` (') so you don't have to escape command quotes.
`hyphen` (-) because leading hyphens often cause problems with argparse.
`stringtools` entire stringtools module. See voussoirkit/stringtools.py.
`space` ( ), `dot` (.), `underscore` (_) so you don't have to add quotes to
your command while using these common characters.
`index` the file's index within the loop.
`index1` the file's index+1, in case you want your names to start from 1.
`parent` a pathclass.Path object for the directory containing the file.
`cwd` a pathclass.Path object for the cwd of this program session.
`noext` the name of the file, but without its extension.
`ext` the file's extension, with no dot.
`dot_ext` the file's extension, with dot.
''',
)
parser.add_argument(
'-y',
'--yes',
dest='autoyes',
action='store_true',
help='''
Accept the results without confirming.
''',
)
parser.add_argument(
'--recurse',
action='store_true',
help='''
Recurse into subfolders and rename those files too.
''',
)
parser.add_argument(
'--naturalsort',
action='store_true',
help='''
Before renaming, the files will be sorted using natural sort instead of the
default lexicographic sort. Natural sort means that "My file 20" will come
before "My file 100" because 20<100. Lexicographic sort means 100 will come
first because 1 is before 2.
The purpose of this flag is so your index and index1 variables are applied
in the order you desire.
''',
)
parser.set_defaults(func=brename_argparse)
return betterhelp.single_main(argv, parser, DOCSTRING)
return betterhelp.go(parser, argv)
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))

View file

@ -1,36 +1,3 @@
'''
contentreplace - find-and-replace en masse
==========================================
> contentreplace filename_glob replace_from replace_to <flags>
filename_glob:
A glob pattern that targets the files of interest.
replace_from:
String to be replaced.
replace_to:
String with which to replace.
flags:
--recurse:
If provided, we will recurse into subdirectories and look for glob matches
there too. If not provided, only files in the cwd are affected.
--regex:
If provided, the given replace_from, replace_to will be treated as regex
strings. If not provided, we use regular str.replace
--clip_prompt:
If you want to do contentreplace with unicode that is difficult to enter
into your terminal, or multi-line strings that don't work as command line
arguments, this option might help you. The program will wait for you to put
the text of interest into your clipboard and press Enter.
--yes:
If provided, replacements will occur automatically without prompting.
'''
import argparse
import codecs
import pyperclip
@ -106,18 +73,69 @@ def contentreplace_argparse(args):
@vlogging.main_decorator
def main(argv):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('filename_glob')
parser.add_argument('replace_from')
parser.add_argument('replace_to')
parser.add_argument('--yes', dest='autoyes', action='store_true')
parser.add_argument('--recurse', action='store_true')
parser.add_argument('--regex', dest='do_regex', action='store_true')
parser.add_argument('--clip_prompt', '--clip-prompt', action='store_true')
parser = argparse.ArgumentParser(
description='''
find-and-replace en masse
''',
)
parser.add_argument(
'filename_glob',
help='''
A glob pattern that targets the files of interest.
''',
)
parser.add_argument(
'replace_from',
help='''
String to be replaced. You can use backslash-escaped symbols like
\\n for newline.
''',
)
parser.add_argument(
'replace_to',
help='''
String with which to replace. Can use backslash-escaped symbols.
''',
)
parser.add_argument(
'--yes',
dest='autoyes',
action='store_true',
help='''
If provided, replacements will occur automatically without prompting.
''',
)
parser.add_argument(
'--recurse',
action='store_true',
help='''
If provided, we will recurse into subdirectories and look for glob matches
there too. If not provided, only files in the cwd are affected.
''',
)
parser.add_argument(
'--regex',
dest='do_regex',
action='store_true',
help='''
If provided, the given replace_from, replace_to will be treated as regex
strings. If not provided, we use regular str.replace.
''',
)
parser.add_argument(
'--clip_prompt',
'--clip-prompt',
action='store_true',
help='''
If you want to do contentreplace with unicode that is difficult to enter
into your terminal, or multi-line strings that don't work as command line
arguments, this option might help you. The program will wait for you to put
the text of interest into your clipboard and press Enter.
''',
)
parser.set_defaults(func=contentreplace_argparse)
return betterhelp.single_main(argv, parser, __doc__)
return betterhelp.go(parser, argv)
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))

View file

@ -1,16 +1,3 @@
'''
directory_discrepancy
=====================
This program compares two directory and shows which files exist in each
directory that do not exist in the other.
> directory_discrepancy dir1 dir2
flags:
--recurse:
Also check subdirectories.
'''
import argparse
import sys
@ -48,14 +35,24 @@ def directory_discrepancy_argparse(args):
@vlogging.main_decorator
def main(argv):
parser = argparse.ArgumentParser(description=__doc__)
parser = argparse.ArgumentParser(
description='''
This program compares two directory and shows which files exist in each
directory that do not exist in the other.
''',
)
parser.add_argument('dir1')
parser.add_argument('dir2')
parser.add_argument('--recurse', action='store_true')
parser.add_argument(
'--recurse',
action='store_true',
help='''
Also check subdirectories.
''',
)
parser.set_defaults(func=directory_discrepancy_argparse)
return betterhelp.single_main(argv, parser, __doc__)
return betterhelp.go(parser, argv)
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))

View file

@ -1,22 +1,3 @@
'''
fdroidapk - F-Droid APK downloader
==================================
> fdroidapk package_names <flags>
package_names:
One or more package names to download, separated by spaces. You can find
the package name in the URL on f-droid.org.
For example, com.nutomic.syncthingandroid from the URL
https://f-droid.org/en/packages/com.nutomic.syncthingandroid/
--destination path:
Alternative path to download the apk files to. Default is cwd.
--folders:
If provided, each apk will be downloaded into a separate folder named after
the package.
'''
import argparse
import bs4
import io
@ -33,6 +14,7 @@ from voussoirkit import httperrors
from voussoirkit import operatornotify
from voussoirkit import pathclass
from voussoirkit import pipeable
from voussoirkit import progressbars
from voussoirkit import vlogging
log = vlogging.getLogger(__name__, 'fdroidapk')
@ -52,7 +34,7 @@ def download_file(url, path):
return downloady.download_file(
url,
path,
callback_progress=downloady.Progress2,
progressbar=progressbars.bar1_bytestring,
timeout=30,
)
@ -88,8 +70,7 @@ def normalize_package_name(package_name):
@pipeable.ctrlc_return1
def fpk_argparse(args):
destination = pathclass.Path(args.destination)
destination.assert_is_directory()
args.destination.assert_is_directory()
return_status = 0
@ -117,10 +98,10 @@ def fpk_argparse(args):
apk_url = f'https://f-droid.org/repo/{apk_basename}'
if args.folders:
this_dest = destination.with_child(package)
this_dest = args.destination.with_child(package)
this_dest.makedirs(exist_ok=True)
else:
this_dest = destination
this_dest = args.destination
this_dest = this_dest.with_child(apk_basename)
if this_dest.exists:
@ -145,14 +126,39 @@ def fpk_argparse(args):
@operatornotify.main_decorator(subject='fdroidapk.py')
@vlogging.main_decorator
def main(argv):
parser = argparse.ArgumentParser(description=__doc__)
parser = argparse.ArgumentParser(description='F-Droid APK downloader.')
parser.add_argument('packages', nargs='+')
parser.add_argument('--folders', action='store_true')
parser.add_argument('--destination', default='.')
parser.add_argument(
'packages',
nargs='+',
type=str,
help='''
One or more package names to download, separated by spaces. You can find
the package name in the URL on f-droid.org.
For example, com.nutomic.syncthingandroid from the URL
https://f-droid.org/en/packages/com.nutomic.syncthingandroid/
''',
)
parser.add_argument(
'--folders',
action='store_true',
help='''
If provided, each apk will be downloaded into a separate folder named after
the package.
If omitted, the apks are downloaded into the destination folder directly.
''',
)
parser.add_argument(
'--destination',
default=pathclass.cwd(),
type=pathclass.Path,
help='''
Alternative path to download the apk files to. Default is cwd.
''',
)
parser.set_defaults(func=fpk_argparse)
return betterhelp.single_main(argv, parser, __doc__)
return betterhelp.go(parser, argv)
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))

View file

@ -1,6 +1,3 @@
'''
Pull all of the files in nested directories into the current directory.
'''
import argparse
import os
import sys
@ -9,10 +6,15 @@ from voussoirkit import interactive
from voussoirkit import pathclass
from voussoirkit import pipeable
from voussoirkit import spinal
from voussoirkit import winglob
def filepull(pull_from='.', autoyes=False):
def filepull(pull_from='.', globs=None, autoyes=False):
start = pathclass.Path(pull_from)
files = [file for d in start.listdir_directories() for file in d.walk_files()]
files = [
file
for d in start.listdir_directories()
for file in spinal.walk(d, glob_filenames=globs)
]
if len(files) == 0:
pipeable.stderr('No files to move')
@ -45,12 +47,23 @@ def filepull(pull_from='.', autoyes=False):
return 1
def filepull_argparse(args):
return filepull(pull_from=args.pull_from, autoyes=args.autoyes)
return filepull(pull_from=args.pull_from, globs=args.glob, autoyes=args.autoyes)
def main(argv):
parser = argparse.ArgumentParser()
parser = argparse.ArgumentParser(
description='''
Pull all of the files in nested directories into the current directory.
''',
)
parser.add_argument('pull_from', nargs='?', default='.')
parser.add_argument(
'--glob',
nargs='+',
help='''
Only pull files whose basename matches any of these glob patterns.
''',
)
parser.add_argument('-y', '--yes', dest='autoyes', action='store_true')
parser.set_defaults(func=filepull_argparse)

64
fuchsiatransparent.py Normal file
View file

@ -0,0 +1,64 @@
import argparse
import PIL.Image
import sys
from voussoirkit import betterhelp
from voussoirkit import imagetools
from voussoirkit import pathclass
from voussoirkit import pipeable
from voussoirkit import vlogging
log = vlogging.getLogger(__name__, 'fuchsiatransparent')
FUCHSIA = (255, 0, 255, 255)
TRANSPARENT = (0, 0, 0, 0)
def fuchsiatransparent_argparse(args):
patterns = pipeable.input_many(args.patterns)
files = pathclass.glob_many_files(patterns)
for file in files:
image = PIL.Image.open(file.absolute_path)
if image.mode == 'RGB':
image = image.convert('RGBA')
if image.mode == 'RGBA':
image = imagetools.replace_color(image, FUCHSIA, TRANSPARENT)
else:
log.info('Can\'t process %s', file.absolute_path)
continue
if args.inplace:
outpath = file
else:
outname = file.replace_extension('').basename + '_transparent'
outpath = file.parent.with_child(outname).add_extension(file.extension)
pipeable.stderr(outpath.absolute_path)
image.save(outpath.absolute_path)
return 0
@vlogging.main_decorator
def main(argv):
parser = argparse.ArgumentParser(
description='''
Replace #FF00FF colored pixels with transparent.
''',
)
parser.add_argument(
'patterns',
help='''
One or more glob patterns for input files.
''',
)
parser.add_argument(
'--inplace',
action='store_true',
help='''
Overwrite the input file instead of saving it as _transparent.
''',
)
parser.set_defaults(func=fuchsiatransparent_argparse)
return betterhelp.go(parser, argv)
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))

View file

@ -127,6 +127,7 @@ def getcrx_argparse(args):
else:
auto_overwrite = None
return_status = 0
for extension_id in extension_ids:
try:
download_crx(extension_id, auto_overwrite=auto_overwrite)
@ -136,7 +137,8 @@ def getcrx_argparse(args):
else:
log.error(traceback.format_exc())
pipeable.stderr('Resuming...')
return 0
return_status = 1
return return_status
@operatornotify.main_decorator(subject='getcrx')
@vlogging.main_decorator

View file

@ -1,17 +1,3 @@
'''
getpid
======
Get PIDs for running processes that match the given process name.
Error level will be 0 if any processes are found, 1 if none are found.
> getpid process_name
Examples:
> getpid python.exe
> getpid chrome.exe
'''
import argparse
import psutil
import sys
@ -32,12 +18,24 @@ def getpid_argparse(args):
@vlogging.main_decorator
def main(argv):
parser = argparse.ArgumentParser(description=__doc__)
parser = argparse.ArgumentParser(
description='''
Get PIDs for running processes that match the given process name.
parser.add_argument('process_name')
Error level will be 0 if any processes are found, 1 if none are found.
''',
)
parser.add_argument(
'process_name',
type=str,
help='''
Name like "python.exe" or "chrome" as it appears in your task manager / ps.
''',
)
parser.set_defaults(func=getpid_argparse)
return betterhelp.single_main(argv, parser, __doc__)
return betterhelp.go(parser, argv)
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))

View file

@ -1,50 +1,3 @@
'''
gitcheckup
==========
This program helps you check the commit and push status of your favorite git
repositories. The output looks like this:
[ ][P] D:\\Git\\cmd (~1)
[C][P] D:\\Git\\Etiquette
[ ][P] D:\\Git\\voussoirkit (+1)
[C][ ] D:\\Git\\YCDL (3)
To specify the list of git directories, you may either:
- Create a gitcheckup.txt file in the same directory as this file, where every
line contains an absolute path to the directory, or
- Pass directories as a series of positional arguments to this program.
> gitcheckup.py <flags>
> gitcheckup.py dir1 dir2 <flags>
flags:
--fetch:
Run `git fetch --all` in each directory.
--pull:
Run `git pull --all` in each directory.
--push:
Run `git push` in each directory.
--run <command>:
Run `git <command>` in each directory. You can use \- to escape - in your
git arguments, since they would confuse this program's argparse.
If this is used, any --fetch, --pull, --push is ignored.
--add path:
Add path to the gitcheckup.txt file.
--remove path:
Remove path from the gitcheckup.txt file.
Examples:
> gitcheckup
> gitcheckup --fetch
> gitcheckup D:\\Git\\cmd D:\\Git\\YCDL --pull
> gitcheckup --run add README.md
'''
import argparse
import os
import re
@ -349,19 +302,91 @@ def gitcheckup_argparse(args):
@vlogging.main_decorator
def main(argv):
parser = argparse.ArgumentParser(description=__doc__)
parser = argparse.ArgumentParser(
description='''
This program helps you check the commit and push status of your favorite git
repositories. The output looks like this:
parser.add_argument('directories', nargs='*')
parser.add_argument('--fetch', dest='do_fetch', action='store_true')
parser.add_argument('--pull', dest='do_pull', action='store_true')
parser.add_argument('--push', dest='do_push', action='store_true')
parser.add_argument('--add', dest='add_directory')
parser.add_argument('--run', dest='run_command', nargs='+')
parser.add_argument('--remove', dest='remove_directory')
[ ][P] D:\\Git\\cmd (~1)
[C][P] D:\\Git\\Etiquette
[ ][P] D:\\Git\\voussoirkit (+1)
[C][ ] D:\\Git\\YCDL (3)
''',
)
parser.examples = [
'',
'--fetch',
'D:\\Git\\cmd D:\\Git\\YCDL --pull',
'--run add README.md',
]
parser.add_argument(
'directories',
nargs='*',
help='''
One or more directories to check up.
If omitted, you should have a file called gitcheckup.txt in the same
directory as this file, where every line contains an absolute path to
a directory.
''',
)
parser.add_argument(
'--fetch',
dest='do_fetch',
action='store_true',
help='''
Run `git fetch --all` in each directory.
''',
)
parser.add_argument(
'--pull',
dest='do_pull',
action='store_true',
help='''
Run `git pull --all` in each directory.
''',
)
parser.add_argument(
'--push',
dest='do_push',
action='store_true',
help='''
Run `git push` in each directory.
''',
)
parser.add_argument(
'--run',
dest='run_command',
nargs='+',
type=str,
help='''
Run `git <command>` in each directory. You can use \- to escape - in your
git arguments, since they would confuse this program's argparse.
If this is used, any --fetch, --pull, --push is ignored.
''',
)
parser.add_argument(
'--add',
dest='add_directory',
metavar='path',
type=str,
help='''
Add path to the gitcheckup.txt file.
''',
)
parser.add_argument(
'--remove',
dest='remove_directory',
metavar='path',
type=str,
help='''
Remove path from the gitcheckup.txt file.
''',
)
parser.set_defaults(func=gitcheckup_argparse)
try:
return betterhelp.single_main(argv, parser, docstring=__doc__)
return betterhelp.go(parser, argv)
except GitCheckupException as exc:
print(exc)
return 1

View file

@ -85,11 +85,17 @@
# | 40 | n | Pixel bytes, r, g, b, a. |
# |________|______________|_______________________________________________________|
import argparse
import os
import PIL.Image
import sys
from voussoirkit import betterhelp
from voussoirkit import imagetools
from voussoirkit import pipeable
from voussoirkit import vlogging
log = vlogging.get_logger(__name__, 'icoconvert')
ICO_HEADER_LENGTH = 6
ICON_DIRECTORY_ENTRY_LENGTH = 16
@ -230,17 +236,36 @@ def images_to_ico(images):
final_data = b''.join(datablobs)
return final_data
if __name__ == '__main__':
try:
inputfiles = sys.argv[1:]
except Exception:
print('Please provide an image file')
raise SystemExit
print('Iconifying', inputfiles)
images = [load_image(filename) for filename in inputfiles]
def icoconvert_argparse(args):
log.info('Iconifying %s', args.files)
images = [load_image(filename) for filename in args.files]
final_data = images_to_ico(images)
name = os.path.splitext(inputfiles[0])[0] + '.ico'
output_file = open(name, 'wb')
iconame = os.path.splitext(args.files[0])[0] + '.ico'
output_file = open(iconame, 'wb')
output_file.write(final_data)
output_file.close()
print('Finished %s.' % name)
pipeable.stderr(iconame)
return 0
@vlogging.main_decorator
def main(argv):
parser = argparse.ArgumentParser(
description='''
Create a Windows .ico icon file from one or more images.
''',
)
parser.add_argument(
'files',
nargs='+',
help='''
One or more image files to put into the ico.
''',
)
parser.set_defaults(func=icoconvert_argparse)
return betterhelp.go(parser, argv)
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))

View file

@ -13,12 +13,22 @@ def inodes_argparse(args):
return 0
def main(argv):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('patterns', nargs='+')
parser = argparse.ArgumentParser(
description='''
Show the st_dev, st_ino of files.
''',
)
parser.add_argument(
'patterns',
nargs='+',
help='''
One or more glob patterns. Supports pipeable !c clipboard, !i stdin
lines of patterns.
''',
)
parser.set_defaults(func=inodes_argparse)
return betterhelp.single_main(argv, parser, __doc__)
return betterhelp.go(parser, argv)
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))

View file

@ -1,22 +1,3 @@
'''
named_python
============
Because Python is interpreted, when you look at the task manager / process list
you'll see that every running python instance has the same name, python.exe.
This script helps you name the executables so they stand out.
For the time being this script doesn't automatically call your new exe, you
have to write a second command to actually run it. I tried using
subprocess.Popen to spawn the new python with the rest of argv but the behavior
was different on Linux and Windows and neither was really clean.
> named_python name
Examples:
> named_python myserver && python-myserver server.py --port 8080
> named_python hnarchive && python-hnarchive hnarchive.py livestream
'''
import argparse
import os
import sys
@ -40,12 +21,29 @@ def namedpython_argparse(args):
return 0
def main(argv):
parser = argparse.ArgumentParser(description=__doc__)
parser = argparse.ArgumentParser(
description='''
Because Python is interpreted, when you look at the task manager / process list
you'll see that every running python instance has the same name, python.exe.
This script helps you name the executables so they stand out.
parser.add_argument('name')
For the time being this script doesn't automatically call your new exe, you
have to write a second command to actually run it. I tried using
subprocess.Popen to spawn the new python with the rest of argv but the behavior
was different on Linux and Windows and neither was really clean.
''',
)
parser.add_argument(
'name',
type=str,
help='''
If you invoke this script with python.exe, a hardlink python-{name}.exe
will be created. Also works with pythonw.
''',
)
parser.set_defaults(func=namedpython_argparse)
return betterhelp.single_main(argv, parser, __doc__)
return betterhelp.go(parser, argv)
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))

View file

@ -1,17 +1,10 @@
'''
no smart quotes
===============
Replace smart quotes and smart apostrophes with regular ASCII values.
Just say no to smart quotes!
'''
import argparse
import os
import sys
from voussoirkit import betterhelp
from voussoirkit import pathclass
from voussoirkit import pipeable
from voussoirkit import spinal
from voussoirkit import vlogging
@ -27,8 +20,9 @@ def replace_smartquotes(text):
return text
def nosmartquotes_argparse(args):
globs = list(pipeable.input_many(args.patterns))
files = spinal.walk(
glob_filenames=args.filename_glob,
glob_filenames=globs,
exclude_filenames={THIS_FILE},
recurse=args.recurse,
)
@ -43,19 +37,39 @@ def nosmartquotes_argparse(args):
continue
file.write('w', text, encoding='utf-8')
print(file.absolute_path)
pipeable.stdout(file.absolute_path)
return 0
@vlogging.main_decorator
def main(argv):
parser = argparse.ArgumentParser(description=__doc__)
parser = argparse.ArgumentParser(
description='''
Replace smart quotes and smart apostrophes with regular ASCII values.
parser.add_argument('filename_glob')
parser.add_argument('--recurse', action='store_true')
Just say no to smart quotes!
''',
)
parser.examples = [
'*.md --recurse',
]
parser.add_argument(
'patterns',
nargs='+',
help='''
One or more glob patterns for input files.
''',
)
parser.add_argument(
'--recurse',
action='store_true',
help='''
If provided, recurse into subdirectories and process those files too.
''',
)
parser.set_defaults(func=nosmartquotes_argparse)
return betterhelp.single_main(argv, parser, __doc__)
return betterhelp.go(parser, argv)
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))

View file

@ -1,11 +1,3 @@
'''
This program deletes all empty directories which are children of the given
starting directory. The starting directory itself will not be deleted even
if it is empty.
> prune_dirs .
> prune_dirs C:\\somepath
'''
import argparse
import os
import sys
@ -46,12 +38,17 @@ def prune_dirs_argparse(args):
@vlogging.main_decorator
def main(argv):
parser = argparse.ArgumentParser(description=__doc__)
parser = argparse.ArgumentParser(
description='''
This program deletes all empty directories which are children of the given
starting directory. The starting directory itself will not be deleted even
if it is empty.
''',
)
parser.add_argument('starting')
parser.set_defaults(func=prune_dirs_argparse)
return betterhelp.single_main(argv, parser, docstring=__doc__)
return betterhelp.go(parser, argv)
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))

193
rarpar.py
View file

@ -460,64 +460,6 @@ def rarpar(
# COMMAND LINE #####################################################################################
DOCSTRING = '''
rarpar
======
> rarpar path <flags>
path:
The input file or directory to rarpar.
--volume X | X% | min(A, B) | max(A, B):
Split rars into volumes of this many megabytes. Should be
An integer number of megabytes, or;
A percentage "X%" to calculate volumes as X% of the file size, down to
a 1 MB minimum, or;
A string "min(A, B)" or "max(A, B)" where A and B follow the above rules.
--rec X:
An integer to generate X% recovery record in the rars.
See winrar documentation for information about recovery records.
--rev X:
An integer to generate X% recovery volumes.
Note that winrar's behavior is the number of revs will always be less than
the number of rars. If you don't split volumes, you will have 1 rar and
thus 0 revs even if you ask for 100% rev.
See winrar documentation for information about recovery volumes.
--par X:
A number to generate X% recovery with par2.
--basename X:
A basename for the rar and par files. You will end up with
basename.partXX.rar and basename.par2.
Without this argument, the default basename is "{basename} ({timestamp})".
Your string may include {basename}, {timestamp} and/or {date} including the
braces to insert that value there.
--compression X:
Level of compression. Can be "store" or "max" or integer 0-5.
--password X:
A password with which to encrypt the rar files.
--workdir X:
The directory in which the rars and pars will be generated while the
program is working.
--moveto X:
The directory to which the rars and pars will be moved after the program
has finished working.
--recycle:
The input file or directory will be recycled at the end.
--dry:
Print the commands that will be run, but don't actually run them.
'''
def rarpar_argparse(args):
status = 0
try:
@ -549,24 +491,127 @@ def rarpar_argparse(args):
def main(argv):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('path')
parser.add_argument('--volume')
parser.add_argument('--rec')
parser.add_argument('--rev')
parser.add_argument('--par')
parser.add_argument('--basename')
parser.add_argument('--compression')
parser.add_argument('--password')
parser.add_argument('--profile', dest='rar_profile')
parser.add_argument('--workdir', default='.')
parser.add_argument('--moveto')
parser.add_argument('--recycle', dest='recycle_original', action='store_true')
parser.add_argument('--dictionary', dest='dictionary_size')
parser.add_argument('--solid', action='store_true')
parser.add_argument('--dry', action='store_true')
parser.add_argument(
'path',
type=pathclass.Path,
help='''
The input file or directory to rarpar.
''',
)
parser.add_argument(
'--volume',
help='''
Split rars into volumes of this many megabytes. Should be:
- An integer number of megabytes, or
- A percentage "X%" to calculate volumes as X% of the file size, down to
a 1 MB minimum, or
- A string "min(A, B)" or "max(A, B)" where A and B follow the above rules.
''',
)
parser.add_argument(
'--rec',
type=int,
help='''
An integer to generate X% recovery record in the rars.
See winrar documentation for information about recovery records.
''',
)
parser.add_argument(
'--rev',
type=int,
help='''
An integer to generate X% recovery volumes.
Note that winrar's behavior is the number of revs will always be less than
the number of rars. If you don't split volumes, you will have 1 rar and
thus 0 revs even if you ask for 100% rev.
See winrar documentation for information about recovery volumes.
''',
)
parser.add_argument(
'--par',
type=int,
help='''
A number to generate X% recovery with par2.
''',
)
parser.add_argument(
'--basename',
type=str,
help='''
A basename for the rar and par files. You will end up with
basename.partXX.rar and basename.par2.
Without this argument, the default basename is "{basename} ({timestamp})".
Your string may include {basename}, {timestamp} and/or {date} including the
braces to insert that value there.
''',
)
parser.add_argument(
'--compression',
help='''
Level of compression. Can be "store" or "max" or integer 0-5.
''',
)
parser.add_argument(
'--password',
type=str,
help='''
A password with which to encrypt the rar files.
''',
)
parser.add_argument(
'--profile', dest='rar_profile',
)
parser.add_argument(
'--workdir',
type=pathclass.Path,
default='.',
help='''
The directory in which the rars and pars will be generated while the
program is working.
''',
)
parser.add_argument(
'--moveto',
type=pathclass.Path,
help='''
The directory to which the rars and pars will be moved after the program
has finished working.
''',
)
parser.add_argument(
'--recycle',
dest='recycle_original',
action='store_true',
help='''
The input file or directory will be recycled at the end.
''',
)
parser.add_argument(
'--dictionary',
dest='dictionary_size',
help='''
Larger dictionary sizes can improve compression in exchange for higher
memory usage. Accepted values are 128k, 256k, 512k, 1m, 2m, 4m, 8m, 16m,
32m, 64m, 128m, 256m, 512m, 1g.
'''
)
parser.add_argument(
'--solid',
action='store_true',
help='''
Generate a 'solid' rar archive. See winrar's documentation for details.
''',
)
parser.add_argument(
'--dry',
action='store_true',
help='''
Print the commands that will be run, but don't actually run them.
''',
)
parser.set_defaults(func=rarpar_argparse)
return betterhelp.single_main(argv, parser, DOCSTRING)
return betterhelp.go(parser, argv)
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))

View file

@ -1,32 +1,3 @@
'''
reg_extension_icon
==================
This script edits the windows registry HKEY_CLASSES_ROOT to assign a file
extension icon and optionally a human-friendly name string.
Must run as administrator.
WARNING, if the extension is already associated with a program, or is otherwise
connected to a progid, this will break it.
> reg_extension_icon ico_file <flags>
ico_file:
Filepath of the icon file.
--extension:
If you omit this option, your file should be named "png.ico" or "py.ico" to
set the icon for png and py types. If the name of your ico file is not the
name of the extension you want to control, specify the extension here.
--name:
A human-friendly name string which will show on Explorer under the "Type"
column and in the properties dialog.
--shellopen:
A command-line string to use as the shell\open\command
'''
import argparse
import sys
import winreg
@ -97,16 +68,56 @@ def extension_registry_argparse(args):
)
def main(argv):
parser = argparse.ArgumentParser(description=__doc__)
parser = argparse.ArgumentParser(
description='''
This script edits the windows registry HKEY_CLASSES_ROOT to assign a file
extension icon and optionally a human-friendly name string.
parser.add_argument('ico_file')
parser.add_argument('--extension', default=None)
parser.add_argument('--name', default=None)
parser.add_argument('--shellopen', default=None)
parser.add_argument('--yes', dest='autoyes', action='store_true')
Must run as administrator.
WARNING, if the extension is already associated with a program, or is otherwise
connected to a progid, this will break it.
''',
)
parser.add_argument(
'ico_file',
help='''
Filepath of the icon file.
''',
)
parser.add_argument(
'--extension',
default=None,
help='''
If you omit this option, your file should be named "png.ico" or "py.ico" to
set the icon for png and py types. If the name of your ico file is not the
name of the extension you want to control, specify the extension here.
''',
)
parser.add_argument(
'--name',
type=str,
default=None,
help='''
A human-friendly name string which will show on Explorer under the "Type"
column and in the properties dialog.
''',
)
parser.add_argument(
'--shellopen',
default=None,
help='''
A command-line string to use as the shell\\open\\command
''',
)
parser.add_argument(
'--yes',
dest='autoyes',
action='store_true',
)
parser.set_defaults(func=extension_registry_argparse)
return betterhelp.single_main(argv, parser, __doc__)
return betterhelp.go(parser, argv)
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))

View file

@ -1,17 +1,3 @@
'''
reserve_disk_space
==================
Exits with status of 0 if the disk has the requested amount of space, 1 if not.
> reserve_disk_space reserve [drive]
reserve:
A string like "50g" or "100 gb"
drive:
Filepath to the drive you want to check. Defaults to cwd drive.
'''
import argparse
import sys
@ -39,13 +25,29 @@ def reserve_disk_space_argparse(args):
@vlogging.main_decorator
def main(argv):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('reserve')
parser.add_argument('drive', nargs='?', default='.')
parser = argparse.ArgumentParser(
description='''
Exits with status of 0 if the disk has the requested amount of space, 1 if not.
''',
)
parser.add_argument(
'reserve',
type=str,
help='''
A string like "50g" or "100 gb"
''',
)
parser.add_argument(
'drive',
nargs='?',
default='.',
help='''
Filepath to the drive you want to check.
''',
)
parser.set_defaults(func=reserve_disk_space_argparse)
return betterhelp.single_main(argv, parser, __doc__)
return betterhelp.go(parser, argv)
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))

195
resize.py
View file

@ -1,65 +1,3 @@
'''
resize
======
Resize image files.
> resize patterns <flags>
patterns:
One or more glob patterns for input files.
Uses pipeable to support !c clipboard, !i stdin lines of glob patterns.
flags:
--width X:
--height X:
New dimensions for the image. If either of these is omitted, then that
dimension will be calculated automatically based on the aspect ratio.
--break_aspect_ratio:
If provided, the given --width and --height will be used exactly. You will
need to provide both --width and --height.
If omitted, the image will be resized to fit within the bounds provided by
--width and --height while preserving its aspect ratio.
--output X:
A string that controls the output filename format. Suppose the input file
was myphoto.jpg. You can use these variables in your format string:
{base} = myphoto
{filename} = myphoto.jpg
{width} = an integer
{height} = an integer
{extension} = .jpg
You may omit {extension} from your format string and it will automatically
be added to the end, unless you already provided a different extension.
If your format string only designates a basename, output files will go to
the same directory as the corresponding input file. If your string contains
path separators, all output files will go to that directory.
The directory
part is not formatted with the variables.
--inplace:
Overwrite the input files. Cannot be used along with --output.
Be careful!
--nearest:
If provided, use nearest-neighbor scaling to preserve pixelated images.
If omitted, use antialiased scaling.
--only_shrink:
If the input image is smaller than the requested dimensions, do nothing.
Useful when globbing in a directory with many differently sized images.
--quality X:
JPEG compression quality.
--scale X:
Scale the image by factor X.
Use this option instead of --width, --height.
'''
import argparse
import os
import PIL.Image
@ -77,15 +15,6 @@ log = vlogging.getLogger(__name__, 'resize')
OUTPUT_INPLACE = sentinel.Sentinel('output inplace')
DEFAULT_OUTPUT_FORMAT = '{base}_{width}x{height}{extension}'
def resize_core(
image,
height=None,
only_shrink=False,
scale=None,
width=None,
):
pass
def resize(
filename,
*,
@ -215,21 +144,121 @@ def resize_argparse(args):
@vlogging.main_decorator
def main(argv):
parser = argparse.ArgumentParser(description=__doc__)
parser = argparse.ArgumentParser(description='Resize image files.')
parser.examples = [
'myphoto.jpg --scale 0.5 --inplace',
'*.jpg --only_shrink --width 500 --height 500 --output thumbs\\{base}.jpg',
'sprite*.png sprite*.bmp --width 1024 --nearest --output {base}_big{extension}',
]
parser.add_argument('patterns', nargs='+')
parser.add_argument('--width', type=int, default=None)
parser.add_argument('--height', type=int, default=None)
parser.add_argument('--inplace', action='store_true')
parser.add_argument('--nearest', dest='nearest_neighbor', action='store_true')
parser.add_argument('--only_shrink', '--only-shrink', action='store_true')
parser.add_argument('--break_aspect_ratio', '--break-aspect-ratio', action='store_true')
parser.add_argument('--output', default=None)
parser.add_argument('--scale', type=float, default=None)
parser.add_argument('--quality', type=int, default=100)
parser.add_argument(
'patterns',
nargs='+',
type=str,
help='''
One or more glob patterns for input files.
Uses pipeable to support !c clipboard, !i stdin lines of glob patterns.
''',
)
parser.add_argument(
'--width',
type=int,
default=None,
help='''
New width of the image. If --width is omitted and --height is given, then
width will be calculated automatically based on the aspect ratio.
''',
)
parser.add_argument(
'--height',
type=int,
default=None,
help='''
New width of the image. If --height is omitted and --width is given, then
height will be calculated automatically based on the aspect ratio.
''',
)
parser.add_argument(
'--break_aspect_ratio',
'--break-aspect-ratio',
action='store_true',
help='''
If provided, the given --width and --height will be used exactly. You will
need to provide both --width and --height.
If omitted, the image will be resized to fit within the bounds provided by
--width and --height while preserving its aspect ratio.
''',
)
parser.add_argument(
'--inplace',
action='store_true',
help='''
Overwrite the input files. Cannot be used along with --output.
Be careful!
''',
)
parser.add_argument(
'--nearest',
'--nearest_neighbor',
'--nearest-neighbor',
dest='nearest_neighbor',
action='store_true',
help='''
If provided, use nearest-neighbor scaling to preserve pixelated images.
If omitted, use antialiased scaling.
''',
)
parser.add_argument(
'--only_shrink',
'--only-shrink',
action='store_true',
help='''
If the input image is smaller than the requested dimensions, do nothing.
Useful when globbing in a directory with many differently sized images.
''',
)
parser.add_argument(
'--output',
default=None,
help='''
A string that controls the output filename format. Suppose the input file
was myphoto.jpg. You can use these variables in your format string:
{base} = myphoto
{filename} = myphoto.jpg
{width} = an integer
{height} = an integer
{extension} = .jpg
You may omit {extension} from your format string and it will automatically
be added to the end, unless you already provided a different extension.
If your format string only designates a basename, output files will go to
the same directory as the corresponding input file. If your string contains
path separators, all output files will go to that directory.
The directory part is not formatted with the variables.
''',
)
parser.add_argument(
'--scale',
type=float,
default=None,
help='''
Scale the image by this factor, where 1.00 is regular size.
Use this option instead of --width, --height.
''',
)
parser.add_argument(
'--quality',
type=int,
default=100,
help='''
JPEG compression quality.
'''
)
parser.set_defaults(func=resize_argparse)
return betterhelp.single_main(argv, parser, __doc__)
return betterhelp.go(parser, argv)
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))

View file

@ -2,6 +2,7 @@ import argparse
import subprocess
import sys
import time
from voussoirkit import betterhelp
class NoMoreRetries(Exception):
pass
@ -43,15 +44,39 @@ def retry_argparse(args):
)
def main(argv):
parser = argparse.ArgumentParser(description=__doc__)
parser = argparse.ArgumentParser(
description='''
Run a command line command multiple times until it returns 0.
''',
)
parser.add_argument('command', nargs='+')
parser.add_argument('--limit', type=int, default=None)
parser.add_argument('--sleep', type=float, default=None)
parser.add_argument(
'command',
nargs='+',
help='''
A command line command. You may need to put this after -- to avoid
confusion with arguments to this program.
''',
)
parser.add_argument(
'--limit',
type=int,
default=None,
help='''
Maximum number of retries before giving up.
''',
)
parser.add_argument(
'--sleep',
type=float,
default=None,
help='''
Number of seconds of sleep between each retry.
''',
)
parser.set_defaults(func=retry_argparse)
args = parser.parse_args(argv)
return args.func(args)
return betterhelp.go(parser, argv)
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))

View file

@ -38,29 +38,6 @@ def shortcut(lnk_name, target, start_in=None, icon=None):
shortcut.write()
return lnk
DOCSTRING = '''
shortcut
========
> shortcut lnk_path target <flags>
lnk_path:
The filepath of the lnk file you want to create.
target:
The filepath of the target file and any additional arguments separated
by spaces. If you want to include an argument that starts with hyphens,
consider putting this last and use `--` to indicate the end of named
arguments. For example:
> shortcut game.lnk --icon game.ico -- javaw.exe -jar game.jar
--start-in:
Directory to use as CWD for the program.
--icon:
Path to an .ico file.
'''
def shortcut_argparse(args):
try:
lnk = shortcut(
@ -77,14 +54,48 @@ def shortcut_argparse(args):
def main(argv):
parser = argparse.ArgumentParser(description=__doc__)
parser.examples = [
'game.lnk --icon game.ico -- javaw.exe -jar game.jar',
'game.lnk --icon game.ico -- 4 8 6',
]
parser.add_argument('lnk_name')
parser.add_argument('target', nargs='+')
parser.add_argument('--start_in', '--start-in', '--startin', default=None)
parser.add_argument('--icon', default=None)
parser.add_argument(
'lnk_name',
help='''
The filepath of the lnk file you want to create.
''',
)
parser.add_argument(
'target',
nargs='+',
type=int,
help='''
The filepath of the target file and any additional arguments separated
by spaces. If you want to include an argument that starts with hyphens,
consider putting this last and use `--` to indicate the end of named
arguments, since they might otherwise be mistaken for arguments to this
program.
''',
)
parser.add_argument(
'--start_in',
'--start-in',
'--startin',
default=None,
help='''
Directory to use as CWD for the program.
''',
)
parser.add_argument(
'--icon',
default=None,
help='''
Path to an .ico file.
''',
)
parser.set_defaults(func=shortcut_argparse)
return betterhelp.single_main(argv, parser, DOCSTRING)
return betterhelp.go(parser, argv)
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))

View file

@ -2,6 +2,7 @@ import PIL.Image
import argparse
import sys
from voussoirkit import betterhelp
from voussoirkit import pathclass
from voussoirkit import pipeable
from voussoirkit import sentinel
@ -49,14 +50,35 @@ def main(argv):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('image_files', nargs='+')
parser.add_argument('--output', required=True)
parser.add_argument('--horizontal', action='store_true')
parser.add_argument('--vertical', action='store_true')
parser.add_argument('--gap', type=int, default=0)
parser.add_argument(
'--output',
required=True,
)
parser.add_argument(
'--horizontal',
action='store_true',
help='''
Stitch the images together horizontally.
''',
)
parser.add_argument(
'--vertical',
action='store_true',
help='''
Stitch the images together vertically.
''',
)
parser.add_argument(
'--gap',
type=int,
default=0,
help='''
This many pixels of transparent gap between each row / column.
''',
)
parser.set_defaults(func=stitch_argparse)
args = parser.parse_args(argv)
return args.func(args)
return betterhelp.go(parser, argv)
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))

View file

@ -1,29 +1,3 @@
'''
svgrender
=========
Calls the Inkscape command line to render svg files to png. A link to inkscape
should be on your PATH.
> svgrender svg_file scales <flags>
scales:
One or more integers. Each integer will be the size of one output file.
flags:
--destination:
A path to a directory where the png files should be saved. By default,
they go to the same folder as the svg file.
--y:
By default, the scales control the width of the output image.
Pass this if you want the scales to control the height.
--basename-only:
By default, the png filenames will have suffixes like _{scale}.
Pass this if you want the png to have the same name as the svg file.
Naturally, this only works if you're only using a single scale.
'''
import argparse
import glob
import os
@ -79,11 +53,10 @@ def svgrender(filepath, scales, destination, scale_suffix=True, axis='x'):
def svgrender_argparse(args):
svg_paths = glob.glob(args.svg_filepath)
scales = [int(x) for x in args.scales]
for svg_path in svg_paths:
svgrender(
svg_path,
scales,
scales=args.scales,
destination=args.destination,
scale_suffix=args.scale_suffix,
axis='y' if args.y else 'x',
@ -93,16 +66,57 @@ def svgrender_argparse(args):
@vlogging.main_decorator
def main(argv):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('svg_filepath')
parser.add_argument('scales', nargs='+')
parser.add_argument('--destination', default=None)
parser.add_argument('--y', dest='y', action='store_true')
parser.add_argument('--basename_only', '--basename-only', dest='scale_suffix', action='store_false')
parser = argparse.ArgumentParser(
description='''
Calls the Inkscape command line to render svg files to png. A link to Inkscape
should be on your PATH.
''',
)
parser.add_argument(
'svg_filepath',
help='''
Input svg file to be rendered.
''',
)
parser.add_argument(
'scales',
type=int,
nargs='+',
help='''
One or more integers. Each integer will be the size of one output file.
''',
)
parser.add_argument(
'--destination',
default=None,
help='''
A path to a directory where the png files should be saved. By default,
they go to the same folder as the svg file.
''',
)
parser.add_argument(
'--y',
dest='y',
action='store_true',
help='''
By default, the scales control the width of the output image.
Pass this if you want the scales to control the height.
''',
)
parser.add_argument(
'--basename_only',
'--basename-only',
dest='scale_suffix',
action='store_false',
help='''
By default, the png filenames will have suffixes like _{scale}.
Pass this if you want the png to have the same name as the svg file.
Naturally, this only works if you're only using a single scale.
''',
)
parser.set_defaults(func=svgrender_argparse)
return betterhelp.single_main(argv, parser, __doc__)
return betterhelp.go(parser, argv)
if __name__ == '__main__':
main(sys.argv[1:])

View file

@ -1,21 +1,3 @@
'''
tempeditor
==========
This program allows you to use your preferred text editor as an intermediate
step in a processing pipeline. The user will use the text editor to edit a temp
file, and when they close the editor the contents of the temp file will be sent
to stdout.
Command line usage:
> tempeditor [--text X]
--text X:
The initial text in the document.
Uses pipeable to support !c clipboard, !i stdin.
If not provided, the user starts with a blank document.
'''
import argparse
import os
import shlex
@ -90,12 +72,29 @@ def tempeditor_argparse(args):
@vlogging.main_decorator
def main(argv):
parser = argparse.ArgumentParser(description=__doc__)
parser = argparse.ArgumentParser(
description='''
This program allows you to use your preferred text editor as an
intermediate step in a processing pipeline. The user will use the text
editor to edit a temp file, and when they close the editor the contents
of the temp file will be sent to stdout.
''',
)
parser.add_argument('--text', dest='initial_text', default=None)
parser.add_argument(
'--text',
dest='initial_text',
default=None,
type=str,
help='''
The initial text in the document.
Uses pipeable to support !c clipboard, !i stdin.
If not provided, the user starts with a blank document.
''',
)
parser.set_defaults(func=tempeditor_argparse)
return betterhelp.single_main(argv, parser, __doc__)
return betterhelp.go(parser, argv)
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))

View file

@ -1,40 +1,3 @@
'''
threaded_dl
===========
> threaded_dl links thread_count filename_format <flags>
links:
The name of a file containing links to download, one per line.
Uses pipeable to support !c clipboard, !i stdin lines of urls.
thread_count:
Integer number of threads to use for downloading.
filename_format:
A string that controls the names of the downloaded files. Uses Python's
brace-style formatting. Available formatters are:
- {basename}: The name of the file as indicated by the URL.
E.g. example.com/image.jpg -> image.jpg
- {extension}: The extension of the file as indicated by the URL, including
the dot. E.g. example.com/image.jpg -> .jpg
- {index}: The index of this URL within the sequence of all downloaded URLs.
Starts from 0.
- {now}: The unix timestamp at which this download job was started. It might
be ugly but at least it's unambiguous when doing multiple download batches
with similar filenames.
flags:
--bytespersecond X:
Limit the overall download speed to X bytes per second. Uses
bytestring.parsebytes to support strings like "1m", "500k", "2 mb", etc.
--headers X:
;
--timeout X:
Integer number of seconds to use as HTTP request timeout for each download.
'''
import argparse
import ast
import os
@ -194,7 +157,7 @@ def threaded_dl(
ui_thread.join()
def ui_thread_func(meter, pool, stop_event):
if pipeable.OUT_PIPE:
if pipeable.stdout_pipe():
return
while not stop_event.is_set():
@ -236,17 +199,62 @@ def threaded_dl_argparse(args):
@vlogging.main_decorator
def main(argv):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('url_file')
parser.add_argument('thread_count', type=int)
parser.add_argument('filename_format', nargs='?', default='{now}_{index}_{basename}')
parser.add_argument('--bytespersecond', default=None)
parser.add_argument('--timeout', default=15)
parser.add_argument('--headers', nargs='+', default=None)
parser = argparse.ArgumentParser()
parser.add_argument(
'url_file',
metavar='links',
help='''
The name of a file containing links to download, one per line.
Uses pipeable to support !c clipboard, !i stdin lines of urls.
''',
)
parser.add_argument(
'thread_count',
type=int,
help='''
Integer number of threads to use for downloading.
''',
)
parser.add_argument(
'filename_format',
nargs='?',
type=str,
default='{now}_{index}_{basename}',
help='''
A string that controls the names of the downloaded files. Uses Python's
brace-style formatting. Available formatters are:
- {basename}: The name of the file as indicated by the URL.
E.g. example.com/image.jpg -> image.jpg
- {extension}: The extension of the file as indicated by the URL, including
the dot. E.g. example.com/image.jpg -> .jpg
- {index}: The index of this URL within the sequence of all downloaded URLs.
Starts from 0.
- {now}: The unix timestamp at which this download job was started. It might
be ugly but at least it's unambiguous when doing multiple download batches
with similar filenames.
''',
)
parser.add_argument(
'--bytespersecond',
default=None,
help='''
Limit the overall download speed to X bytes per second. Uses
bytestring.parsebytes to support strings like "1m", "500k", "2 mb", etc.
''',
)
parser.add_argument(
'--timeout',
default=15,
help='''
Integer number of seconds to use as HTTP request timeout for each download.
''',
)
parser.add_argument(
'--headers', nargs='+', default=None,
)
parser.set_defaults(func=threaded_dl_argparse)
return betterhelp.single_main(argv, parser, __doc__)
return betterhelp.go(parser, argv)
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))

View file

@ -1,10 +1,8 @@
'''
Create the file, or update the last modified timestamp.
'''
import argparse
import os
import sys
from voussoirkit import betterhelp
from voussoirkit import pipeable
from voussoirkit import winglob
@ -24,13 +22,31 @@ def touch_argparse(args):
return 0
def main(argv):
parser = argparse.ArgumentParser(description=__doc__)
parser = argparse.ArgumentParser(
description='''
Create blank files, or update mtimes of existing files.
''',
)
parser.add_argument('patterns', nargs='+')
parser.add_argument(
'patterns',
type=str,
nargs='+',
help='''
One or more filenames or glob patterns.
''',
)
parser.add_argument(
'--sleep',
type=float,
default=None,
help='''
Sleep for this many seconds between touching each file.
''',
)
parser.set_defaults(func=touch_argparse)
args = parser.parse_args(argv)
return args.func(args)
return betterhelp.go(parser, argv)
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))

View file

@ -1,16 +1,3 @@
'''
wait_for_internet
=================
This program will block until internet access is available. It can be useful to
run this program before running another program that expects an internet
connection.
> wait_for_internet timeout
timeout:
An integer number of seconds, after which to give up and return 1.
'''
import argparse
import sys
@ -27,12 +14,23 @@ def wait_for_internet_argparse(args):
@vlogging.main_decorator
def main(argv):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('timeout', type=int)
parser = argparse.ArgumentParser(
description='''
This program will block until internet access is available. It can be useful to
run this program before running another program that expects an internet
connection.
''',
)
parser.add_argument(
'timeout',
type=int,
help='''
An integer number of seconds, after which to give up and return 1.
''',
)
parser.set_defaults(func=wait_for_internet_argparse)
return betterhelp.single_main(argv, parser, __doc__)
return betterhelp.go(parser, argv)
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))

View file

@ -1,21 +1,3 @@
'''
watchforlinks
=============
This program will continuously watch your clipboard for URLs and save them to
individual files. The files will have randomly generated names. The current
contents of your clipboard will be erased.
> watchforlinks [extension] <flags>
extension:
The saved files will have this extension.
If not provided, the default is "generic".
flags:
--regex X:
A regex pattern. Only URLs that match this pattern will be saved.
'''
import argparse
import pyperclip
import re
@ -64,13 +46,35 @@ def watchforlinks_argparse(args):
return 0
def main(argv):
parser = argparse.ArgumentParser(description=__doc__)
parser = argparse.ArgumentParser(
description='''
This program will continuously watch your clipboard for http:// and
https:// URLs and save them to individual files. The files will have
randomly generated names. The current contents of your clipboard will
be erased.
''',
)
parser.add_argument('extension', nargs='?', default='generic')
parser.add_argument('--regex', default=None)
parser.add_argument(
'extension',
nargs='?',
type=str,
default='generic',
help='''
The saved files will have this extension.
''',
)
parser.add_argument(
'--regex',
type=str,
default=None,
help='''
A regex pattern. Only URLs that match this pattern will be saved.
''',
)
parser.set_defaults(func=watchforlinks_argparse)
return betterhelp.single_main(argv, parser, __doc__)
return betterhelp.go(parser, argv)
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))