diff options
Diffstat (limited to '')
| -rw-r--r-- | .hgignore | 2 | ||||
| -rw-r--r-- | PKGBUILD/PKGBUILD | 15 | ||||
| -rw-r--r-- | patternutils/commands/patterncfg.py | 135 | ||||
| -rw-r--r-- | patternutils/commands/pmatch.py | 129 | ||||
| -rw-r--r-- | patternutils/commands/targs.py | 105 | ||||
| -rw-r--r-- | patternutils/commands/tln.py | 121 | ||||
| -rw-r--r-- | patternutils/commands/tmv.py | 115 | ||||
| -rw-r--r-- | patternutils/config.py | 43 | ||||
| -rw-r--r-- | patternutils/match.py | 6 | ||||
| -rw-r--r-- | patternutils/template.py | 20 | ||||
| -rw-r--r-- | patternutils/utils.py | 23 | ||||
| -rw-r--r-- | setup.py | 40 |
12 files changed, 472 insertions, 282 deletions
@@ -11,3 +11,5 @@ dist/* PKGBUILD/pkg/* PKGBUILD/src/* PKGBUILD/*.zip +PKGBUILD/.git/* +PKGBUILD/.SRCINFO diff --git a/PKGBUILD/PKGBUILD b/PKGBUILD/PKGBUILD index a24538b..ca4fba9 100644 --- a/PKGBUILD/PKGBUILD +++ b/PKGBUILD/PKGBUILD @@ -1,22 +1,27 @@ # Maintainer: Dennis Fink <dennis.fink@c3l.lu> pkgname=patternutils -pkgver=0.0.5 +pkgver=0.0.8 +_pkghash=853b2c08d397 pkgrel=1 pkgdesc="Combine regular expressions with template engines in a powerful matter." arch=('any') url="https://projects.metalgamer.eu/patternutils" license=('GPL3') depends=('python' 'python-click' 'python-jinja' 'python-pygments') -source=('patternutils-0.0.5.zip::https://projects.metalgamer.eu/patternutils/archive/523a18c4fb635a42f935bc2907b8adba207ca000.zip?subrepos=false') -sha512sums=('466273a330acbd12b3e8004324dd6c03f75d48f91edbc4f495e7929b29b6a8369bfa9c6650d7d6cba0ebff679688f99acf02cb0b380377a447797e81147ec872') +source=('patternutils-0.0.8.zip::https://projects.metalgamer.eu/patternutils/archive/853b2c08d397ad61442b8caf7d8cd4723a1dd04e.zip?subrepos=false') +sha512sums=('dc29e9403e260a04cbe986567a37ecd17bf2b335dbe99c3b274d5568252e22464b7bb52fd2e9dba459ac83d8f79eb39a832641f224fe0911522d60f6c0069a44') + +prepare() { + sed -i 's/==/>=/g' "$srcdir/$pkgname-$_pkghash/setup.py" +} build() { - cd "$srcdir/$pkgname-523a18c4fb63" + cd "$srcdir/$pkgname-$_pkghash" python setup.py build } package() { - cd "$srcdir/$pkgname-523a18c4fb63" + cd "$srcdir/$pkgname-$_pkghash" python setup.py install --root="$pkgdir" --optimize=1 } diff --git a/patternutils/commands/patterncfg.py b/patternutils/commands/patterncfg.py index 062a6a5..f619196 100644 --- a/patternutils/commands/patterncfg.py +++ b/patternutils/commands/patterncfg.py @@ -1,7 +1,6 @@ +import itertools import os import os.path -import itertools - from typing import Optional import click @@ -9,7 +8,7 @@ import click try: from pygments import highlight from pygments.formatters import TerminalFormatter - from pygments.lexers import Python3Lexer, DjangoLexer + from pygments.lexers import DjangoLexer, Python3Lexer except ImportError: has_pygments = False else: @@ -18,101 +17,128 @@ else: from .. import config - -CURRENT_SUPPORTED_PROGRAMS = ('pmatch', 'tmv', 'targs', 'tln') +CURRENT_SUPPORTED_PROGRAMS = ("pmatch", "tmv", "targs", "tln") -@click.group(context_settings={'help_option_names': ('-h', '--help', '-?')}) +@click.group(context_settings={"help_option_names": ("-h", "--help", "-?")}) def patterncfg() -> None: pass -@patterncfg.command(name='init', help='initialize config folders') +@patterncfg.command(name="init", help="initialize config folders") def init() -> None: - for dir in itertools.chain(CURRENT_SUPPORTED_PROGRAMS, ('globals',)): + for dir in itertools.chain(CURRENT_SUPPORTED_PROGRAMS, ("globals",)): os.makedirs(os.path.join(config.CONFIG_PATH, dir), exist_ok=True) -@patterncfg.command(name='list', help='list all patternutils template files') +@patterncfg.command(name="list", help="list all patternutils template files") def list() -> None: try: for d in os.listdir(config.CONFIG_PATH): - click.secho(d, fg='blue') + click.secho(d, fg="blue") for f in os.listdir(os.path.join(config.CONFIG_PATH, d)): - click.echo('└── {}'.format(f)) + click.echo(f"└── {f}") except FileNotFoundError as e: - click.secho('{} not found! Please run patterncfg init first.'.format(e.filename), fg='red', err=True) + click.secho( + f"{e.filename} not found! Please run patterncfg init first.", + fg="red", + err=True, + ) raise SystemExit -@patterncfg.command(name='add', help='add new template file') -@click.option('-t', '--template-engine', default=None, type=click.Choice(('python', 'jinja2')), - help='select the template engine to use. Only applicable for tmv, targs and tln') -@click.argument('command', required=True, type=click.Choice(CURRENT_SUPPORTED_PROGRAMS)) -@click.argument('name') +@patterncfg.command(name="add", help="add new template file") +@click.option( + "-t", + "--template-engine", + default=None, + type=click.Choice(("python", "jinja2")), + help="select the template engine to use. Only applicable for tmv, targs and tln", +) +@click.argument("command", required=True, type=click.Choice(CURRENT_SUPPORTED_PROGRAMS)) +@click.argument("name") def add(template_engine: Optional[str], command: str, name: str) -> None: - if command == 'pmatch': - fileext = 'regex' + if command == "pmatch": + fileext = "regex" else: - if template_engine not in ('python', 'jinja2'): - fileext = 'python' - else: - fileext = template_engine + fileext = "jinja2" if template_engine == "jinja2" else "python" - filename = os.path.join(config.CONFIG_PATH, command, '{}.{}'.format(name, fileext)) + filename = os.path.join(config.CONFIG_PATH, command, f"{name}.{fileext}") click.edit(filename=filename) -@patterncfg.command(name='remove', help='remove template file') -@click.option('-c', '--command', default=None, type=click.Choice(CURRENT_SUPPORTED_PROGRAMS), - help='select the command from which to remove the file') -@click.argument('name') +@patterncfg.command(name="remove", help="remove template file") +@click.option( + "-c", + "--command", + default=None, + type=click.Choice(CURRENT_SUPPORTED_PROGRAMS), + help="select the command from which to remove the file", +) +@click.argument("name") def remove(command: Optional[str], name: str) -> None: files = config.find_templates(name, command) if not files: - click.secho('No files found!', fg='red', err=True) + click.secho("No files found!", fg="red", err=True) raise SystemExit elif len(files) != 1: - click.secho('Multiple files found!', fg='red', err=True) + click.secho( + "Multiple files found! Please specify the command from which to remove the file.", + fg="red", + err=True, + ) for f in files: - click.secho(f, fg='red', err=True) + click.secho(f, fg="red", err=True) raise SystemExit else: os.remove(files[0]) -@patterncfg.command(name='view', help='view template file') -@click.option('-c', '--command', default=None, type=click.Choice(CURRENT_SUPPORTED_PROGRAMS), - help='select the command from which to view the file') -@click.option('--color/--no-color', is_flag=True, default=has_pygments, - help='Colorize output using pygments') -@click.argument('name') +@patterncfg.command(name="view", help="view template file") +@click.option( + "-c", + "--command", + default=None, + type=click.Choice(CURRENT_SUPPORTED_PROGRAMS), + help="select the command from which to view the file", +) +@click.option( + "--color/--no-color", + is_flag=True, + default=has_pygments, + help="Colorize output using pygments", +) +@click.argument("name") def view(name: str, command: str, color: bool) -> None: files = config.find_templates(name, command) if not files: - click.secho('No files found!', fg='red', err=True) + click.secho("No files found!", fg="red", err=True) raise SystemExit elif len(files) != 1: - click.secho('Multiple files found!', fg='red', err=True) + click.secho( + "Multiple files found! Please specify the command from which to view the file.", + fg="red", + err=True, + ) for f in files: - click.secho(f, fg='red', err=True) + click.secho(f, fg="red", err=True) raise SystemExit else: filename = files[0] try: - with open(filename, encoding='utf-8') as fd: + with open(filename, encoding="utf-8") as fd: text = fd.read() except FileNotFoundError: - click.secho('File not found!', fg='red', err=True) + click.secho("File not found!", fg="red", err=True) raise SystemExit else: if color: - if filename.endswith('jinja2'): + if filename.endswith("jinja2"): lexer = DjangoLexer else: lexer = Python3Lexer @@ -120,20 +146,29 @@ def view(name: str, command: str, color: bool) -> None: click.echo(text[:-1]) -@patterncfg.command(name='edit', help='edit a template file') -@click.option('-c', '--command', default=None, type=click.Choice(CURRENT_SUPPORTED_PROGRAMS), - help='select the command from which to edit the file') -@click.argument('name') +@patterncfg.command(name="edit", help="edit a template file") +@click.option( + "-c", + "--command", + default=None, + type=click.Choice(CURRENT_SUPPORTED_PROGRAMS), + help="select the command from which to edit the file", +) +@click.argument("name") def edit(name: str, command: str) -> None: files = config.find_templates(name, command) if not files: - click.secho('No files found!', fg='red', err=True) + click.secho("No files found!", fg="red", err=True) raise SystemExit elif len(files) != 1: - click.secho('Multiple files found!', fg='red', err=True) + click.secho( + "Multiple files found! Please specify the command from which to edit the file.", + fg="red", + err=True, + ) for f in files: - click.secho(f, fg='red', err=True) + click.secho(f, fg="red", err=True) raise SystemExit else: filename = files[0] diff --git a/patternutils/commands/pmatch.py b/patternutils/commands/pmatch.py index 383ef8b..6bbd349 100644 --- a/patternutils/commands/pmatch.py +++ b/patternutils/commands/pmatch.py @@ -4,16 +4,18 @@ import json import os import os.path import re - -from typing import Generator, List, Iterator, Pattern, Any, Tuple, Dict +from typing import Any, Dict, Generator, Iterator, List, Pattern, Tuple import click -from .. import match as mat from .. import config +from .. import match as mat +from .. import utils -def walk(dir: str, recursive: bool = False, match_directories: bool = False) -> Generator[os.DirEntry, None, None]: +def walk( + dir: str, recursive: bool = False, match_directories: bool = False +) -> Generator[os.DirEntry, None, None]: for entry in os.scandir(dir): is_dir = entry.is_dir() if recursive and is_dir: @@ -25,9 +27,9 @@ def walk(dir: str, recursive: bool = False, match_directories: bool = False) -> def apply_regex( - walk_function: Iterator[Any], - regex_pattern: Pattern[str], - match_full_path: bool, + walk_function: Iterator[Any], + regex_pattern: Pattern[str], + match_full_path: bool, ) -> Tuple[List[Dict[str, str]], bool]: matches = [] @@ -37,46 +39,71 @@ def apply_regex( try: match = mat.apply(subject, regex_pattern) except ValueError: - click.secho('{} did not match'.format(subject), fg='red', err=True) + click.secho(f"{subject} did not match", fg="red", err=True) has_invalid_matches = True else: - match['_path'] = f.path - match['_name'] = f.name - match['_abspath'] = os.path.abspath(f.path) - match['_realpath'] = os.path.realpath(f.path) - match['_relpath'] = os.path.relpath(f.path) + match["_path"] = f.path + match["_name"] = f.name + match["_abspath"] = os.path.abspath(f.path) + match["_realpath"] = os.path.realpath(f.path) + match["_relpath"] = os.path.relpath(f.path) matches.append(match) return matches, has_invalid_matches -def json_dumps(data: Any, human_readable: bool = False) -> str: - if human_readable: - return json.dumps(data, indent=4, sort_keys=True) - else: - return json.dumps(data) - - -@click.command(context_settings={'help_option_names': ('-h', '--help', '-?')}) -@click.option('-d', '--directory', multiple=True, default=['./']) -@click.option('-m', '--match-directories', is_flag=True, default=False, - help='search directory name also for matches') -@click.option('-r', '--recursive', is_flag=True, default=False, - help='search directories recursively') -@click.option('--abort-on-no-match', is_flag=True, default=False, - help='exit if a name does not match the INPUT_PATTERN') -@click.option('--match-full-path', is_flag=True, default=False, - help='match on full path instead of only filename') -@click.option('-p', '--use-predefined-pattern', is_flag=True, default=False, - help='use predefined regex pattern') -@click.option('--stream', is_flag=True, default=False, - help='Output a stream of objects instead a list of objects') -@click.option('--human-readable', is_flag=True, default=False, - help='output the JSON in a human readable format') -@click.option('-v', '--verbose', is_flag=True, default=False, - help='explain what is being done') +@click.command(context_settings={"help_option_names": ("-h", "--help", "-?")}) +@click.option("-d", "--directory", multiple=True, default=["./"]) +@click.option( + "-m", + "--match-directories", + is_flag=True, + default=False, + help="search directory name also for matches", +) +@click.option( + "-r", + "--recursive", + is_flag=True, + default=False, + help="search directories recursively", +) +@click.option( + "--abort-on-no-match", + is_flag=True, + default=False, + help="exit if a name does not match the INPUT_PATTERN", +) +@click.option( + "--match-full-path", + is_flag=True, + default=False, + help="match on full path instead of only filename", +) +@click.option( + "-p", + "--use-predefined-pattern", + is_flag=True, + default=False, + help="use predefined regex pattern", +) +@click.option( + "--stream", + is_flag=True, + default=False, + help="Output a stream of objects instead a list of objects", +) +@click.option( + "--human-readable", + is_flag=True, + default=False, + help="output the JSON in a human readable format", +) +@click.option( + "-v", "--verbose", is_flag=True, default=False, help="explain what is being done" +) @click.version_option() -@click.argument('regex-pattern') +@click.argument("regex-pattern") def pmatch( regex_pattern: str, directory: List[str], @@ -92,37 +119,41 @@ def pmatch( if use_predefined_pattern: try: - regex_pattern, _ = config.load_template(regex_pattern, 'pmatch') + regex_pattern, _ = config.load_template(regex_pattern, "pmatch") except FileNotFoundError: - click.secho('File not found!', fg='red', err=True) + click.secho(f"Regex {regex_pattern} not found!", fg="red", err=True) raise SystemExit except RuntimeError: - click.secho('Multiple files found!', fg='red', err=True) + click.secho("Multiple files found!", fg="red", err=True) raise SystemExit try: regex_pattern_c = re.compile(regex_pattern) except re.error as e: if verbose: - click.secho('Regular expression could not be compiled!', fg='red', err=True) - click.secho(e.msg, fg='red', err=True) # type: ignore + click.secho("Regular expression could not be compiled!", fg="red", err=True) + click.secho(e.msg, fg="red", err=True) # type: ignore raise SystemExit # Monkey patch walk function to include commandline options - walk_function = functools.partial(walk, recursive=recursive, match_directories=match_directories) + walk_function = functools.partial( + walk, recursive=recursive, match_directories=match_directories + ) new_walk = itertools.chain.from_iterable(map(walk_function, directory)) - matches, has_invalid_matches = apply_regex(new_walk, regex_pattern_c, match_full_path) + matches, has_invalid_matches = apply_regex( + new_walk, regex_pattern_c, match_full_path + ) if has_invalid_matches and abort_on_no_match: raise SystemExit if stream: for m in matches: - click.echo(json_dumps(m, human_readable)) + click.echo(utils.json_dumps(m, human_readable)) else: - click.echo(json_dumps(matches, human_readable)) + click.echo(utils.json_dumps(matches, human_readable)) -if __name__ == '__main__': +if __name__ == "__main__": pmatch() diff --git a/patternutils/commands/targs.py b/patternutils/commands/targs.py index 63955ae..04c286d 100644 --- a/patternutils/commands/targs.py +++ b/patternutils/commands/targs.py @@ -4,28 +4,18 @@ import multiprocessing import os import shlex import subprocess - -from typing import ( - Union, - List, - Dict, - Callable, -) +from typing import Callable, Dict, List, Union import click -from .. import template -from .. import config -from .. import utils +from .. import config, template, utils def run_command( data: Dict[str, str], - render: Callable[..., str], run: Callable[..., None], shell: bool, ) -> None: - c = render(**data) if not shell: prepared_command = shlex.split(c) # type: Union[str, List[str]] else: @@ -34,25 +24,53 @@ def run_command( run(prepared_command) -@click.command(context_settings={'help_option_names': ('-h', '--help', '-?')}) -@click.option('--read-from', default='-', type=click.File('r')) -@click.option('-s', '--shell', is_flag=True, default=False, - help='run command in a shell') -@click.option('-t', '--template-engine', default='python', type=click.Choice(['python', 'jinja2']), - help='select the template engine to use') -@click.option('-v', '--verbose', is_flag=True, default=False, - help='explain what is being done') -@click.option('-P', '--max-procs', default=1, type=click.IntRange(min=0, clamp=True)) -@click.option('-p', '--use-predefined-template', is_flag=True, default=False, - help='use predefined template') -@click.option('--redirect-stdout', is_flag=True, default=False, - help='Redirect stdout to /dev/null') -@click.option('--redirect-stderr', is_flag=True, default=False, - help='Redirect stderr to /dev/null') -@click.option('--stream', if_flag=True, defaults=False, - help='Input is a stream of JSON objects') +@click.command(context_settings={"help_option_names": ("-h", "--help", "-?")}) +@click.option("--read-from", default="-", type=click.File("r")) +@click.option( + "-s", "--shell", is_flag=True, default=False, help="run command in a shell" +) +@click.option( + "-t", + "--template-engine", + default="python", + type=click.Choice(["python", "jinja2"]), + help="select the template engine to use", +) +@click.option( + "-v", "--verbose", is_flag=True, default=False, help="explain what is being done" +) +@click.option("-P", "--max-procs", default=1, type=click.IntRange(min=0, clamp=True)) +@click.option( + "-p", + "--use-predefined-template", + is_flag=True, + default=False, + help="use predefined template", +) +@click.option( + "--redirect-stdout", + is_flag=True, + default=False, + help="Redirect stdout to /dev/null", +) +@click.option( + "--redirect-stderr", + is_flag=True, + default=False, + help="Redirect stderr to /dev/null", +) +@click.option( + "--stream", is_flag=True, default=False, help="Input is a stream of JSON objects" +) +@click.option( + "-e", + "--editor", + is_flag=True, + default=False, + help="Open an editor before running commands to allow manual changes", +) @click.version_option() -@click.argument('command') +@click.argument("command") def targs( command: str, read_from: click.File, @@ -63,6 +81,7 @@ def targs( redirect_stderr: bool, use_predefined_template: bool, stream: bool, + editor: bool, verbose: bool, ) -> None: @@ -72,25 +91,34 @@ def targs( else: data = [json.loads(line) for line in read_from] except json.JSONDecodeError: - click.secho('JSON not valid!', fg='red', err=True) + click.secho("JSON not valid!", fg="red", err=True) raise SystemExit if use_predefined_template: try: - command, template_engine = config.load_template(command, 'targs') + command, template_engine = config.load_template(command, "targs") except FileNotFoundError: - click.secho('File not found!', fg='red', err=True) + click.secho(f"Command {command} not found!", fg="red", err=True) raise SystemExit except RuntimeError: - click.secho('Multiple files found!', fg='red', err=True) + click.secho("Multiple files found!", fg="red", err=True) raise SystemExit try: render = template.get_render_function(command, engine=template_engine) except RuntimeError: - click.secho('jinja2 not installed!', fg='red', err=True) + click.secho("jinja2 not installed!", fg="red", err=True) raise SystemExit + commands = [render(r) for r in data] + + if editor: + try: + commands = utils.edit(commands) + except RuntimeErro: + click.secho("Your edit is not parseable!", fg="red", err=True) + raise SystemExit + max_procs = utils.max_procs() if max_procs == 0 else max_procs stdout = subprocess.DEVNULL if redirect_stdout else None @@ -103,17 +131,16 @@ def targs( ) patched_run_command = functools.partial( run_command, - render=render, run=patched_subprocess, shell=shell, ) if max_procs == 1: - for r in data: + for r in commands: patched_run_command(r) else: with multiprocessing.Pool(max_procs) as p: - p.map(patched_run_command, data) + p.map(patched_run_command, commands) -if __name__ == '__main__': +if __name__ == "__main__": targs() diff --git a/patternutils/commands/tln.py b/patternutils/commands/tln.py index 27112ec..295cda9 100644 --- a/patternutils/commands/tln.py +++ b/patternutils/commands/tln.py @@ -4,9 +4,7 @@ import os.path import click -from .. import template -from .. import config -from .. import utils +from .. import config, template, utils def link_file(old_filename: str, new_filename: str, symbolic: bool) -> None: @@ -20,32 +18,74 @@ def link_file(old_filename: str, new_filename: str, symbolic: bool) -> None: os.link(old_filename, new_filename) -@click.command(context_settings={'help_option_names': ('-h', '--help', '-?')}) -@click.option('-i', '--interactive', is_flag=True, default=False, - help='prompt wether to remove destinations') -@click.option('-e', '--editor', is_flag=True, default=False, - help='Open an editor before renaming files to allow manual changes') -@click.option('-f', '--force', is_flag=True, default=False, - help='remove existing destination files') -@click.option('-s', '--symbolic', is_flag=True, default=False, - help='make symbolic links instead of hard links') -@click.option('-t', '--template-engine', default='python', type=click.Choice(['python', 'jinja2']), - help='select the template engine to use') -@click.option('-k', '--key', default='_abspath', - help='select the key to use as the source file') -@click.option('--stream', is_flag=True, default=False, - help='Input is a stream of JSON objects') -@click.option('-p', '--use-predefined-template', is_flag=True, default=False, - help='use predefined template') -@click.option('--read-from', default='-', type=click.File('r')) -@click.option('--abort-on-path-exist', is_flag=True, default=False, - help='exit if a renamed path already exists') -@click.option('-n', '--dry-run', is_flag=True, default=False, - help='perform a trial run with no changes made') -@click.option('-v', '--verbose', is_flag=True, default=False, - help='explain what is being done') +@click.command(context_settings={"help_option_names": ("-h", "--help", "-?")}) +@click.option( + "-i", + "--interactive", + is_flag=True, + default=False, + help="prompt wether to remove destinations", +) +@click.option( + "-e", + "--editor", + is_flag=True, + default=False, + help="Open an editor before renaming files to allow manual changes", +) +@click.option( + "-f", + "--force", + is_flag=True, + default=False, + help="remove existing destination files", +) +@click.option( + "-s", + "--symbolic", + is_flag=True, + default=False, + help="make symbolic links instead of hard links", +) +@click.option( + "-t", + "--template-engine", + default="python", + type=click.Choice(["python", "jinja2"]), + help="select the template engine to use", +) +@click.option( + "-k", "--key", default="_abspath", help="select the key to use as the source file" +) +@click.option( + "--stream", is_flag=True, default=False, help="Input is a stream of JSON objects" +) +@click.option( + "-p", + "--use-predefined-template", + is_flag=True, + default=False, + help="use predefined template", +) +@click.option("--read-from", default="-", type=click.File("r")) +@click.option( + "--abort-on-path-exist", + is_flag=True, + default=False, + help="exit if a renamed path already exists", +) +@click.option( + "-n", + "--dry-run", + is_flag=True, + default=False, + help="perform a trial run with no changes made", +) +@click.option( + "-v", "--verbose", is_flag=True, default=False, help="explain what is being done" +) @click.version_option() -@click.argument('output-pattern') +@click.argument("output-pattern") def tln( output_pattern: str, interactive: bool, @@ -58,7 +98,8 @@ def tln( key: str, stream: bool, abort_on_path_exist: bool, - dry_run, bool, + dry_run, + bool, verbose: bool, ) -> None: @@ -68,39 +109,41 @@ def tln( else: data = [json.loads(line) for line in read_from] except json.JSONDecodeError: - click.secho('JSON not valid!', fg='red', err=True) + click.secho("JSON not valid!", fg="red", err=True) raise SystemExit if use_predefined_template: try: - output_pattern, template_engine = config.load_template(output_pattern, 'tln') + output_pattern, template_engine = config.load_template( + output_pattern, "tln" + ) except FileNotFoundError: - click.secho('File not found!', fg='red', err=True) + click.secho(f"Template {output_pattern} not found!", fg="red", err=True) raise SystemExit except RuntimeError: - click.secho('Multiple files found!', fg='red', err=True) + click.secho("Multiple files found!", fg="red", err=True) raise SystemExit try: render = template.get_render_function(output_pattern, engine=template_engine) except RuntimeError: - click.secho('jinja2 not installed!', fg='red', err=True) + click.secho("jinja2 not installed!", fg="red", err=True) raise SystemExit - new_filenames = dict((entry['_abspath'], render(**entry)) for entry in data) + new_filenames = dict((entry["_abspath"], render(**entry)) for entry in data) if editor: try: new_filenames = utils.edit(new_filenames) except RuntimeError: - click.secho('You edit is not parseable!', fg='red', err=True) + click.secho("You edit is not parseable!", fg="red", err=True) raise SystemExit has_existing = False non_existing = {} for old, new in new_filenames.items(): if os.path.exists(new): - click.secho('{} does exist!'.format(new), fg='red', err=True) + click.secho(f"{new} does exist!", fg="red", err=True) has_existing = True else: non_existing[old] = new @@ -115,8 +158,8 @@ def tln( for old, new in files_to_move: if verbose or interactive: - click.echo('{} -> {}'.format(old, new)) + click.echo(f"{old} -> {new}") - if not (interactive and not click.confirm('Is the match correct?')): + if not (interactive and not click.confirm("Is the match correct?")): if not dry_run: link_file(old, new, symbolic) diff --git a/patternutils/commands/tmv.py b/patternutils/commands/tmv.py index 25932bc..3e92342 100644 --- a/patternutils/commands/tmv.py +++ b/patternutils/commands/tmv.py @@ -4,9 +4,7 @@ import shutil import click -from .. import template -from .. import config -from .. import utils +from .. import config, template, utils def move_file(old_filename: str, new_filename: str) -> None: @@ -18,30 +16,65 @@ def move_file(old_filename: str, new_filename: str) -> None: shutil.move(old_filename, new_filename) -@click.command(context_settings={'help_option_names': ('-h', '--help', '-?')}) -@click.option('-i', '--interactive', is_flag=True, default=False, - help='prompt before renaming files') -@click.option('-e', '--editor', is_flag=True, default=False, - help='Open an editor before renaming files to allow manual changes') -@click.option('-f', '--force', is_flag=True, default=False, - help='force rename') -@click.option('-t', '--template-engine', default='python', type=click.Choice(['python', 'jinja2']), - help='select the template engine to use') -@click.option('-k', '--key', default='_abspath', - help='select the key to use as the source file') -@click.option('-s', '--stream', is_flag=True, default=False, - help='Input is a stream of JSON objects') -@click.option('-p', '--use-predefined-template', is_flag=True, default=False, - help='use predefined template') -@click.option('--read-from', default='-', type=click.File('r')) -@click.option('--abort-on-path-exist', is_flag=True, default=False, - help='exit if a renamed path already exists') -@click.option('-n', '--dry-run', is_flag=True, default=False, - help='perform a trial run with no changes made') -@click.option('-v', '--verbose', is_flag=True, default=False, - help='explain what is being done') +@click.command(context_settings={"help_option_names": ("-h", "--help", "-?")}) +@click.option( + "-i", + "--interactive", + is_flag=True, + default=False, + help="prompt before renaming files", +) +@click.option( + "-e", + "--editor", + is_flag=True, + default=False, + help="Open an editor before renaming files to allow manual changes", +) +@click.option("-f", "--force", is_flag=True, default=False, help="force rename") +@click.option( + "-t", + "--template-engine", + default="python", + type=click.Choice(["python", "jinja2"]), + help="select the template engine to use", +) +@click.option( + "-k", "--key", default="_abspath", help="select the key to use as the source file" +) +@click.option( + "-s", + "--stream", + is_flag=True, + default=False, + help="Input is a stream of JSON objects", +) +@click.option( + "-p", + "--use-predefined-template", + is_flag=True, + default=False, + help="use predefined template", +) +@click.option("--read-from", default="-", type=click.File("r")) +@click.option( + "--abort-on-path-exist", + is_flag=True, + default=False, + help="exit if a renamed path already exists", +) +@click.option( + "-n", + "--dry-run", + is_flag=True, + default=False, + help="perform a trial run with no changes made", +) +@click.option( + "-v", "--verbose", is_flag=True, default=False, help="explain what is being done" +) @click.version_option() -@click.argument('output-pattern') +@click.argument("output-pattern") def tmv( output_pattern: str, interactive: bool, @@ -59,11 +92,11 @@ def tmv( """Rename files based on the OUTPUT_PATTERN. - It read a JSON list containing objects or a stream of objects - (if --stream option is defined). The object needs to have a - key named as the option KEY (default: _abspath), which points - to the file to move. The rest of the keys will be supplied to - the templating engine to generate the new filename. + It reads a JSON list containing objects or a stream of objects + (if --stream option is defined). The object needs to have a + key named as the option KEY (default: _abspath), which points + to the file to move. The rest of the keys will be supplied to + the templating engine to generate the new filename. """ try: @@ -72,23 +105,25 @@ def tmv( else: data = [json.loads(line) for line in read_from] except json.JSONDecodeError: - click.secho('JSON not valid!', fg='red', err=True) + click.secho("JSON not valid!", fg="red", err=True) raise SystemExit if use_predefined_template: try: - output_pattern, template_engine = config.load_template(output_pattern, 'tmv') + output_pattern, template_engine = config.load_template( + output_pattern, "tmv" + ) except FileNotFoundError: - click.secho('File not found!', fg='red', err=True) + click.secho(f"Template {output_pattern} not found!", fg="red", err=True) raise SystemExit except RuntimeError: - click.secho('Multiple files found!', fg='red', err=True) + click.secho("Multiple files found!", fg="red", err=True) raise SystemExit try: render = template.get_render_function(output_pattern, engine=template_engine) except RuntimeError: - click.secho('jinja2 not installed!', fg='red', err=True) + click.secho("jinja2 not installed!", fg="red", err=True) raise SystemExit new_filenames = dict((entry[key], render(**entry)) for entry in data) @@ -97,14 +132,14 @@ def tmv( try: new_filenames = utils.edit(new_filenames) except RuntimeError: - click.secho('You edit is not parseable!', fg='red', err=True) + click.secho("Your edit is not parseable!", fg="red", err=True) raise SystemExit has_existing = False non_existing = {} for old, new in new_filenames.items(): if os.path.exists(new): - click.secho('{} does exist!'.format(new), fg='red', err=True) + click.secho(f"{new} does exist!", fg="red", err=True) has_existing = True else: non_existing[old] = new @@ -119,8 +154,8 @@ def tmv( for old, new in files_to_move: if verbose or interactive: - click.echo('{} -> {}'.format(old, new)) + click.echo(f"{old} -> {new}") - if not (interactive and not click.confirm('Is the match correct?')): + if not (interactive and not click.confirm("Is the match correct?")): if not dry_run: move_file(old, new) diff --git a/patternutils/config.py b/patternutils/config.py index ec38ebe..a124644 100644 --- a/patternutils/config.py +++ b/patternutils/config.py @@ -1,15 +1,14 @@ -import os.path import glob +import os.path import re +from typing import List, Optional, Tuple import click -from typing import Tuple, List, Optional - -CONFIG_PATH = click.get_app_dir('patternutils') +CONFIG_PATH = click.get_app_dir("patternutils") -REGEX_PROGRAMS = ('pmatch', ) -TEMPLATE_PROGRAMS = ('tmv', 'targs', 'tln') +REGEX_PROGRAMS = ("pmatch",) +TEMPLATE_PROGRAMS = ("tmv", "targs", "tln") def load_template(template_name: str, command_name: Optional[str]) -> Tuple[str, str]: @@ -18,14 +17,14 @@ def load_template(template_name: str, command_name: Optional[str]) -> Tuple[str, files = find_templates(template_name, command_name) if len(files) == 1: template_file = files[0] - with open(template_file, encoding='utf-8') as f: + with open(template_file, encoding="utf-8") as f: template_lines = f.read() elif not files: raise FileNotFoundError elif len(files) > 1: - raise RuntimeError('Multiple files found!') + raise RuntimeError("Multiple files found!") - output_pattern = template_lines.replace('\n', '') + output_pattern = template_lines.replace("\n", "") template_engine = os.path.splitext(template_file)[1][1:] return output_pattern, template_engine @@ -33,28 +32,30 @@ def load_template(template_name: str, command_name: Optional[str]) -> Tuple[str, def find_templates(template_name: str, command_name: Optional[str] = None) -> List[str]: - regex = os.path.join(CONFIG_PATH, '{path_sub_regex}', r'{template_name}\.{extension}') + regex = os.path.join( + CONFIG_PATH, "{path_sub_regex}", r"{template_name}\.{extension}" + ) if command_name is None: # CONFIG_PATH/*/TEMPLATE_NAME.* - path_sub_regex = '.*' - extension = '.*' + path_sub_regex = ".*" + extension = ".*" elif command_name in REGEX_PROGRAMS: - path_sub_regex = '(globals|{command_name})' - extension = 'regex' + path_sub_regex = "(globals|{command_name})" + extension = "regex" elif command_name in TEMPLATE_PROGRAMS: # CONFIG_PATH/globals/TEMPLATE_NAME.jinja2 # CONFIG_PATH/globals/TEMPLATE_NAME.python # CONFIG_PATH/COMMAND_NAME/TEMPLATE_NAME.jinja2 # CONFIG_PATH/COMMAND_NAME/TEMPLATE_NAME.python - path_sub_regex = '(globals|{command_name})' - extension = '(jinja2|python)' + path_sub_regex = "(globals|{command_name})" + extension = "(jinja2|python)" else: # Fail over if command name is not know # This should normally not happen # CONFIG_PATH/COMMAND_NAME/TEMPLATE_NAME.* - path_sub_regex = '{command_name}' - extension = '.*' + path_sub_regex = "{command_name}" + extension = ".*" # We need to format command_name twice, as it can be used in the # path_sub_regex @@ -62,10 +63,12 @@ def find_templates(template_name: str, command_name: Optional[str] = None) -> Li path_sub_regex=path_sub_regex.format(command_name=command_name), command_name=command_name, template_name=template_name, - extension=extension + extension=extension, ) compiled_regex = re.compile(regex) - files = filter(compiled_regex.search, glob.iglob(os.path.join(CONFIG_PATH, '*', '*'))) + files = filter( + compiled_regex.search, glob.iglob(os.path.join(CONFIG_PATH, "*", "*")) + ) return list(files) diff --git a/patternutils/match.py b/patternutils/match.py index 01411c2..80f9553 100644 --- a/patternutils/match.py +++ b/patternutils/match.py @@ -1,11 +1,11 @@ -from typing import Pattern, Dict +from typing import Dict, Pattern def apply(subject: str, regex_pattern: Pattern[str]) -> Dict[str, str]: match = regex_pattern.search(subject) if match is not None: groupdict = match.groupdict() - groupdict['_subject'] = subject + groupdict["_subject"] = subject return groupdict else: - raise ValueError('subject doesn\'t match regex pattern') + raise ValueError("subject doesn't match regex pattern") diff --git a/patternutils/template.py b/patternutils/template.py index a4cf794..53746a2 100644 --- a/patternutils/template.py +++ b/patternutils/template.py @@ -12,10 +12,10 @@ else: jinja_feature = True jinja_env = jinja2.Environment() - def to_datetime(value: str, format: str = '%Y-%m-%d') -> datetime: + def to_datetime(value: str, format: str = "%Y-%m-%d") -> datetime: return datetime.strptime(value, format) - def format_datetime(value: datetime, format: str = '%Y-%m-%d') -> str: + def format_datetime(value: datetime, format: str = "%Y-%m-%d") -> str: return value.strftime(format) def to_int(value: str, base: int = 10) -> int: @@ -27,20 +27,20 @@ else: def quote(value: str) -> str: return shlex.quote(value) - jinja_env.filters['datetime'] = to_datetime - jinja_env.filters['datetimeformat'] = format_datetime - jinja_env.filters['int'] = to_int - jinja_env.filters['float'] = to_float - jinja_env.filters['quote'] = quote + jinja_env.filters["datetime"] = to_datetime + jinja_env.filters["datetimeformat"] = format_datetime + jinja_env.filters["int"] = to_int + jinja_env.filters["float"] = to_float + jinja_env.filters["quote"] = quote -def get_render_function(template: str, *, engine: str = 'python') -> Callable[..., str]: - if engine == 'jinja2': +def get_render_function(template: str, *, engine: str = "python") -> Callable[..., str]: + if engine == "jinja2": if jinja_feature: env_template = jinja_env.from_string(template) render_function = env_template.render else: - raise RuntimeError('jinja2 not installed!') + raise RuntimeError("jinja2 not installed!") else: render_function = template.format return render_function diff --git a/patternutils/utils.py b/patternutils/utils.py index e78a86b..266f3b9 100644 --- a/patternutils/utils.py +++ b/patternutils/utils.py @@ -1,9 +1,9 @@ import os +import json +from typing import Dict, Any import click -from typing import Dict - def max_procs() -> int: cpu_count = os.cpu_count() @@ -13,14 +13,16 @@ def max_procs() -> int: def edit(data: Dict[str, str]) -> Dict[str, str]: longest = len(max(data, key=len)) - temporary = ['{}\t{}\n'.format(key.ljust(longest), value) for key, value in data.items()] - edited = click.edit(''.join(temporary)) + temporary = [ + "{}\t{}\n".format(key.ljust(longest), value) for key, value in data.items() + ] + edited = click.edit("".join(temporary)) if edited is not None: new_data = dict() - for line in edited.split('\n'): - if line and '\t' in line: - old, new = line.split('\t') + for line in edited.split("\n"): + if line and "\t" in line: + old, new = line.split("\t") new_data[old.strip()] = new if new_data: return new_data @@ -28,3 +30,10 @@ def edit(data: Dict[str, str]) -> Dict[str, str]: raise RuntimeError else: return data + + +def json_dumps(data: Any, humand_readable: bool = False) -> str: + if humand_readable: + return json.dumps(data, indent=4, sort_keys=True) + else: + return json.dumps(data, separators=(",", ":")) @@ -3,31 +3,31 @@ from setuptools import find_packages, setup -NAME = 'patternutils' -DESCRIPTION = 'Combine regular expressions with template engines in a powerful matter.' -URL = 'https://projects.metalgamer.eu/patternutils' -EMAIL = 'dennis.fink@c3l.lu' -AUTHOR = 'Dennis Fink' +NAME = "patternutils" +DESCRIPTION = "Combine regular expressions with template engines in a powerful matter." +URL = "https://projects.metalgamer.eu/patternutils" +EMAIL = "dennis.fink@c3l.lu" +AUTHOR = "Dennis Fink" -VERSION = '0.0.5' +VERSION = "0.0.8" ENTRY_POINTS = { - 'console_scripts': [ - 'pmatch=patternutils.commands.pmatch:pmatch', - 'targs=patternutils.commands.targs:targs', - 'tmv=patternutils.commands.tmv:tmv', - 'tln=patternutils.commands.tln:tln', - 'patterncfg=patternutils.commands.patterncfg:patterncfg', + "console_scripts": [ + "pmatch=patternutils.commands.pmatch:pmatch", + "targs=patternutils.commands.targs:targs", + "tmv=patternutils.commands.tmv:tmv", + "tln=patternutils.commands.tln:tln", + "patterncfg=patternutils.commands.patterncfg:patterncfg", ], } REQUIRED = [ - 'click==7.0', + "click==7.0", ] OPTIONAL = { - 'jinja2': ['Jinja2==2.10.1'], - 'pygments': ['Pygments==2.4.2'], + "jinja2": ["Jinja2==2.10.1"], + "pygments": ["Pygments==2.4.2"], } setup( @@ -37,17 +37,17 @@ setup( author=AUTHOR, author_email=EMAIL, url=URL, - packages=find_packages(exclude=('tests',)), + packages=find_packages(exclude=("tests",)), entry_points=ENTRY_POINTS, install_requires=REQUIRED, extras_requires=OPTIONAL, include_package_data=True, - license='GPLv3+', + license="GPLv3+", classifiers=[ # Trove classifiers # Full list: https://pypi.python.org/pypi?%3Aaction=list_classifiers - 'Programming Language :: Python', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: Implementation :: CPython', + "Programming Language :: Python", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: Implementation :: CPython", ], ) |
