summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDennis Fink2017-12-17 15:10:04 +0100
committerDennis Fink2017-12-17 15:10:04 +0100
commita121f0dc2d9cfb7e91373892959829bb2e632d62 (patch)
treea730281437ce5bc288c73a99ed64660a4bb25cdf
parent38f259e61f9eb4194e88695d9e5f76b253ac9d4b (diff)
downloadpatternutils-a121f0dc2d9cfb7e91373892959829bb2e632d62.tar.gz
patternutils-a121f0dc2d9cfb7e91373892959829bb2e632d62.zip
Begin project
Diffstat (limited to '')
-rw-r--r--patternutils/__init__.py2
-rw-r--r--patternutils/commands/__init__.py0
-rw-r--r--patternutils/commands/pmatch.py96
-rw-r--r--patternutils/commands/targs.py58
-rw-r--r--patternutils/commands/tmv.py0
-rw-r--r--patternutils/match.py13
-rw-r--r--patternutils/template.py45
-rw-r--r--requirements.txt3
-rw-r--r--setup.py41
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',
+ ],
+)