diff options
| author | Dennis Fink | 2017-12-17 15:10:04 +0100 |
|---|---|---|
| committer | Dennis Fink | 2017-12-17 15:10:04 +0100 |
| commit | a121f0dc2d9cfb7e91373892959829bb2e632d62 (patch) | |
| tree | a730281437ce5bc288c73a99ed64660a4bb25cdf | |
| parent | 38f259e61f9eb4194e88695d9e5f76b253ac9d4b (diff) | |
| download | patternutils-a121f0dc2d9cfb7e91373892959829bb2e632d62.tar.gz patternutils-a121f0dc2d9cfb7e91373892959829bb2e632d62.zip | |
Begin project
Diffstat (limited to '')
| -rw-r--r-- | patternutils/__init__.py | 2 | ||||
| -rw-r--r-- | patternutils/commands/__init__.py | 0 | ||||
| -rw-r--r-- | patternutils/commands/pmatch.py | 96 | ||||
| -rw-r--r-- | patternutils/commands/targs.py | 58 | ||||
| -rw-r--r-- | patternutils/commands/tmv.py | 0 | ||||
| -rw-r--r-- | patternutils/match.py | 13 | ||||
| -rw-r--r-- | patternutils/template.py | 45 | ||||
| -rw-r--r-- | requirements.txt | 3 | ||||
| -rw-r--r-- | setup.py | 41 |
9 files changed, 258 insertions, 0 deletions
diff --git a/patternutils/__init__.py b/patternutils/__init__.py new file mode 100644 index 0000000..b85f606 --- /dev/null +++ b/patternutils/__init__.py @@ -0,0 +1,2 @@ +from . import match +from . import template diff --git a/patternutils/commands/__init__.py b/patternutils/commands/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/patternutils/commands/__init__.py diff --git a/patternutils/commands/pmatch.py b/patternutils/commands/pmatch.py new file mode 100644 index 0000000..6dc835f --- /dev/null +++ b/patternutils/commands/pmatch.py @@ -0,0 +1,96 @@ +import functools +import itertools +import json +import os +import os.path +import re + +from typing import Generator, List, Iterator, Pattern, Any, Tuple, Dict, Match + +import click + +import patternutils + + +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: + yield from walk(entry.path, recursive, match_directories) + elif entry.is_file() or (match_directories and is_dir): + yield entry + else: + continue + + +def apply_regex( + walk_function: Iterator[Any], + regex_pattern: Pattern, + match_full_path: bool, +) -> Tuple[List[Dict], bool]: + matches = [] + has_invalid_matches = False + for f in walk_function: + subject = f.path if match_full_path else f.name + try: + match = patternutils.match.apply(subject, regex_pattern) + except ValueError: + click.secho('{} did not match'.format(subject), fg='red') + 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) + matches.append(match) + + return matches, has_invalid_matches + + +@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('-v', '--verbose', is_flag=True, default=False, + help='explain what is being done') +@click.version_option() +@click.argument('regex-pattern') +def tmatch( + regex_pattern: str, + directory: List[str], + match_directories: bool, + recursive: bool, + abort_on_no_match: bool, + match_full_path: bool, + verbose: bool, +) -> None: + + 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') + click.secho(e.msg, fg='red') + raise SystemExit + + # Monkey patch walk function to include commandline options + 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) + + if has_invalid_matches and abort_on_no_match: + raise SystemExit + + click.echo(json.dumps(matches)) + + +if __name__ == '__main__': + tmatch() diff --git a/patternutils/commands/targs.py b/patternutils/commands/targs.py new file mode 100644 index 0000000..41334ed --- /dev/null +++ b/patternutils/commands/targs.py @@ -0,0 +1,58 @@ +import json +import multiprocessing +import shlex +import subprocess + +from typing import ( + Union, + List, +) + +import click + +import patternutils + + +@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.version_option() +@click.argument('command') +def targs( + command: str, + read_from: click.File, + shell: bool, + template_engine: str, + verbose: bool, +) -> None: + + try: + data = json.load(read_from) + except json.JSONDecodeError: + click.secho('JSON not valid!', fg='red') + raise SystemExit + + use_jinja = True if template_engine == 'jinja2' else False + try: + render = patternutils.template.get_render_function(command, use_jinja=use_jinja) + except RuntimeError: + click.secho('jinja2 not installed!', fg='red') + raise SystemExit + + for r in data: + c = render(**r) + if not shell: + prepared_command = shlex.split(c) # type: Union[str, List[str]] + else: + prepared_command = c + + subprocess.run(prepared_command, shell=shell) + + +if __name__ == '__main__': + targs() diff --git a/patternutils/commands/tmv.py b/patternutils/commands/tmv.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/patternutils/commands/tmv.py diff --git a/patternutils/match.py b/patternutils/match.py new file mode 100644 index 0000000..f9a857c --- /dev/null +++ b/patternutils/match.py @@ -0,0 +1,13 @@ +import re + +from typing import Pattern, Dict + + +def apply(subject: str, regex_pattern: Pattern) -> Dict: + match = regex_pattern.search(subject) + if match is not None: + groupdict = match.groupdict() + groupdict['_subject'] = subject + return groupdict + else: + raise ValueError('subject doesn\'t match regex pattern') diff --git a/patternutils/template.py b/patternutils/template.py new file mode 100644 index 0000000..de1b94c --- /dev/null +++ b/patternutils/template.py @@ -0,0 +1,45 @@ +import datetime +import shlex + +from typing import Callable + +try: + import jinja2 +except ImportError: + jinja_feature = False +else: + jinja_feature = True + jinja_env = jinja2.Environment() + + def to_datetime(value, format='%Y-%m-%d'): + return datetime.strptime(value, format) + + def format_datetime(value, format='%Y-%m-%d'): + return value.strftime(format) + + def to_int(value, base=10): + return int(value, base=base) + + def to_float(value): + return float(value) + + def quote(value): + 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 + + +def get_render_function(template: str, *, use_jinja: bool=False) -> Callable[..., str]: + if use_jinja: + if jinja_feature: + env_template = jinja_env.from_string(template) + render_function = env_template.render + else: + raise RuntimeError('jinja2 not installed!') + else: + render_function = template.format + return render_function diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4b7a39a --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +click==6.7 +Jinja2==2.9.6 +MarkupSafe==1.0 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..88f1414 --- /dev/null +++ b/setup.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from setuptools import find_packages, setup + +NAME = 'patternutils' +DESCRIPTION = 'My short description for my project.' +URL = 'https://github.com/me/myproject' +EMAIL = 'dennis.fink@c3l.lu' +AUTHOR = 'Dennis Fink' + +REQUIRED = [ + 'click==6.7', + 'Jinja2==2.9.6', +] + +setup( + name=NAME, + version='0.0.1-dev', + description=DESCRIPTION, + author=AUTHOR, + author_email=EMAIL, + url=URL, + packages=find_packages(exclude=('tests',)), + entry_points={ + 'console_scripts': [ + 'pmatch=patternutils.commands.pmatch:pmatch', + 'targs=patternutils.commands.targs:targs', + ], + }, + install_requires=REQUIRED, + include_package_data=True, + 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', + ], +) |
