summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDennis Fink2020-09-26 08:43:48 +0200
committerDennis Fink2020-09-26 08:43:48 +0200
commitfa9fb532880bccb741d0507a0006624595b59eb9 (patch)
tree2dbbe420fbfbd7570bcbe0ecd34cd5c63008d7ca
parent6eeaee688f81f63020683409d7018a831c856ee6 (diff)
parent652070bb3e233c5333dc70325449d7a80ba13c92 (diff)
downloadpatternutils-fa9fb532880bccb741d0507a0006624595b59eb9.tar.gz
patternutils-fa9fb532880bccb741d0507a0006624595b59eb9.zip
Merged default
Diffstat (limited to '')
-rw-r--r--.hgignore2
-rw-r--r--PKGBUILD/PKGBUILD15
-rw-r--r--patternutils/commands/patterncfg.py135
-rw-r--r--patternutils/commands/pmatch.py129
-rw-r--r--patternutils/commands/targs.py105
-rw-r--r--patternutils/commands/tln.py121
-rw-r--r--patternutils/commands/tmv.py115
-rw-r--r--patternutils/config.py43
-rw-r--r--patternutils/match.py6
-rw-r--r--patternutils/template.py20
-rw-r--r--patternutils/utils.py23
-rw-r--r--setup.py40
12 files changed, 472 insertions, 282 deletions
diff --git a/.hgignore b/.hgignore
index cfd8ddc..fc3ed61 100644
--- a/.hgignore
+++ b/.hgignore
@@ -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=(",", ":"))
diff --git a/setup.py b/setup.py
index 80aca3e..95a892f 100644
--- a/setup.py
+++ b/setup.py
@@ -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",
],
)