summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--patternutils/commands/patterncfg.py2
-rw-r--r--patternutils/commands/tln.py122
-rw-r--r--setup.py1
3 files changed, 124 insertions, 1 deletions
diff --git a/patternutils/commands/patterncfg.py b/patternutils/commands/patterncfg.py
index 75272ac..4b29dfd 100644
--- a/patternutils/commands/patterncfg.py
+++ b/patternutils/commands/patterncfg.py
@@ -8,7 +8,7 @@ import click
from .. import config
-CURRENT_SUPPORTED_PROGRAMS = ('pmatch', 'tmv', 'targs')
+CURRENT_SUPPORTED_PROGRAMS = ('pmatch', 'tmv', 'targs', 'tln')
@click.group(context_settings={'help_option_names': ('-h', '--help', '-?')})
diff --git a/patternutils/commands/tln.py b/patternutils/commands/tln.py
new file mode 100644
index 0000000..27112ec
--- /dev/null
+++ b/patternutils/commands/tln.py
@@ -0,0 +1,122 @@
+import json
+import os
+import os.path
+
+import click
+
+from .. import template
+from .. import config
+from .. import utils
+
+
+def link_file(old_filename: str, new_filename: str, symbolic: bool) -> None:
+ dirs = os.path.dirname(new_filename)
+ if dirs:
+ os.makedirs(dirs, exist_ok=True)
+
+ if symbolic:
+ os.symlink(old_filename, new_filename)
+ else:
+ 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.version_option()
+@click.argument('output-pattern')
+def tln(
+ output_pattern: str,
+ interactive: bool,
+ editor: bool,
+ force: bool,
+ symbolic: bool,
+ template_engine: str,
+ use_predefined_template: bool,
+ read_from: click.File,
+ key: str,
+ stream: bool,
+ abort_on_path_exist: bool,
+ dry_run, bool,
+ verbose: bool,
+) -> None:
+
+ try:
+ if not stream:
+ data = json.load(read_from) # type: ignore
+ else:
+ data = [json.loads(line) for line in read_from]
+ except json.JSONDecodeError:
+ 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')
+ except FileNotFoundError:
+ click.secho('File not found!', fg='red', err=True)
+ raise SystemExit
+ except RuntimeError:
+ 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)
+ raise SystemExit
+
+ 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)
+ 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)
+ has_existing = True
+ else:
+ non_existing[old] = new
+
+ if has_existing and abort_on_path_exist:
+ raise SystemExit
+
+ if force:
+ files_to_move = new_filenames.items()
+ else:
+ files_to_move = non_existing.items()
+
+ for old, new in files_to_move:
+ if verbose or interactive:
+ click.echo('{} -> {}'.format(old, new))
+
+ if not (interactive and not click.confirm('Is the match correct?')):
+ if not dry_run:
+ link_file(old, new, symbolic)
diff --git a/setup.py b/setup.py
index 91e6062..12003a1 100644
--- a/setup.py
+++ b/setup.py
@@ -15,6 +15,7 @@ ENTRY_POINTS = {
'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',
],
}