diff options
Diffstat (limited to '')
| -rw-r--r-- | patternutils/commands/patterncfg.py | 40 | ||||
| -rw-r--r-- | patternutils/commands/pmatch.py | 100 | ||||
| -rw-r--r-- | patternutils/commands/pmediainfo.py | 2 | ||||
| -rw-r--r-- | patternutils/commands/targs.py | 122 | ||||
| -rw-r--r-- | patternutils/commands/tmv.py | 85 |
5 files changed, 226 insertions, 123 deletions
diff --git a/patternutils/commands/patterncfg.py b/patternutils/commands/patterncfg.py index f30a39a..07c815a 100644 --- a/patternutils/commands/patterncfg.py +++ b/patternutils/commands/patterncfg.py @@ -4,6 +4,7 @@ import os.path from typing import Literal, get_args import click +from click_aliases import ClickAliasedGroup try: from pygments import highlight # type: ignore @@ -24,27 +25,46 @@ from ..types import ( CURRENT_SUPPORTED_PROGRAMS: tuple[PredefinedCommands] = get_args(PredefinedCommands) -@click.group(context_settings={"help_option_names": ("-h", "--help", "-?")}) +@click.group( + context_settings={"help_option_names": ("-h", "--help", "-?")}, + cls=ClickAliasedGroup, +) def patterncfg() -> None: """CLI tool for managing patternutils templates.""" pass @patterncfg.command(name="init", help="Initialize config folders") -def init() -> None: +@click.option( + "-v", + "--verbose", + is_flag=True, + default=False, + help="Explain what is being done.", +) +def init(verbose: bool) -> None: """Creates necessary config directories if they do not exist.""" for directory in itertools.chain(CURRENT_SUPPORTED_PROGRAMS, ("globals",)): - os.makedirs(os.path.join(config.CONFIG_PATH, directory), exist_ok=True) + path = config.CONFIG_PATH / directory + try: + path.mkdir(parents=True) + except FileExistsError: + if verbose: + click.secho(f"{path} already exists!", fg="red", err=True) + else: + if verbose: + click.echo(f"Created {path}") + click.secho("Initialized patternutils!", fg="green") @patterncfg.command(name="list", help="List all patternutils template files") def list() -> None: """Lists all available template files in the config directory.""" try: - for directory in os.listdir(config.CONFIG_PATH): - click.secho(directory, fg="blue") - for filename in os.listdir(os.path.join(config.CONFIG_PATH, directory)): - click.echo(f"└── {filename}") + for directory in config.CONFIG_PATH.iterdir(): + click.secho(directory.name, fg="blue") + for filename in directory.iterdir(): + click.echo(f"└── {filename.name}") except FileNotFoundError: click.secho( f"Config directory '{config.CONFIG_PATH}' not found! Run 'patterncfg init' first.", @@ -75,8 +95,8 @@ def add( if command == "pmatch": fileext = "regex" - filename = os.path.join(config.CONFIG_PATH, command, f"{name}.{fileext}") - click.edit(filename=filename) + filename = config.CONFIG_PATH / command / f"{name}.{fileext}" + click.edit(filename=str(filename)) @patterncfg.command(name="remove", help="Remove a template file") @@ -112,7 +132,7 @@ def remove(command: PredefinedCommands | None, name: str) -> None: os.remove(files[0]) -@patterncfg.command(name="view", help="View a template file") +@patterncfg.command(name="view", help="View a template file", aliases=["show"]) @click.option( "-c", "--command", diff --git a/patternutils/commands/pmatch.py b/patternutils/commands/pmatch.py index 7d02ac3..68b166a 100644 --- a/patternutils/commands/pmatch.py +++ b/patternutils/commands/pmatch.py @@ -6,6 +6,9 @@ import re from typing import Generator, Iterator, Pattern import click +from click_option_group import MutuallyExclusiveOptionGroup, optgroup + +from patternutils.types import RegexFlags, TemplatePrefer from .. import config, utils from .. import match as mat @@ -49,64 +52,101 @@ def apply_regex( @click.command(context_settings={"help_option_names": ("-h", "--help", "-?")}) -@click.option( +@optgroup.group("General options") +@optgroup.option( + "-v", + "--verbose", + is_flag=True, + default=False, + help="Explain what is being done.", +) +@optgroup.group("Output Options") +@optgroup.option( + "--stream", + is_flag=True, + default=False, + help="Output a stream of objects instead a list.", +) +@optgroup.option( + "--human-readable", + is_flag=True, + default=False, + help="Output the JSON in a human readable format.", +) +@optgroup.group("Match options") +@optgroup.option( "-d", "--directory", multiple=True, default=["./"], help="directories to search. Can be specified multiple times. Default: ./", ) -@click.option( +@optgroup.option( "-m", "--match-directories", is_flag=True, default=False, help="Match on directories as well.", ) -@click.option( +@optgroup.option( "-r", "--recursive", is_flag=True, default=False, help="Search directories recursively.", ) -@click.option( +@optgroup.option( "--abort-on-no-match", is_flag=True, default=False, help="Exit if a name does not match the INPUT_PATTERN.", ) -@click.option( +@optgroup.option( "--match-full-path", is_flag=True, default=False, help="Match on full absolute path instead of only the filename.", ) -@click.option( +@optgroup.group("Regex Options") +@optgroup.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.", +@optgroup.option( + "--flag", + type=click.Choice( + [ + "ascii", + "a", + "ignorecase", + "i", + "multiline", + "m", + "dotall", + "s", + "verbose", + "x", + ], + case_sensitive=False, + ), + multiple=True, + help="Specify the flags for the regex", ) -@click.option( - "--human-readable", - is_flag=True, - default=False, - help="Output the JSON in a human readable format.", +@optgroup.group("Pattern Loading Options", cls=MutuallyExclusiveOptionGroup) +@optgroup.option( + "--prefer-global", + "prefer", + flag_value="global", + help="Prefer loading predefined pattern from the globals directory", ) -@click.option( - "-v", - "--verbose", - is_flag=True, - default=False, - help="Explain what is being done.", +@optgroup.option( + "--prefer-command", + "prefer", + flag_value="command", + help="Prefer loading predefined pattern from the pmatch directory", ) @click.version_option() @click.argument("regex-pattern") @@ -121,12 +161,18 @@ def pmatch( stream: bool, human_readable: bool, verbose: bool, + flags: list[RegexFlags], + prefer: TemplatePrefer, ) -> None: """Match the REGEX_PATTERN against files in specified directories and output JSON.""" if use_predefined_pattern: try: - regex_pattern, _ = config.load_template(regex_pattern, "pmatch") + regex_pattern, _ = config.load_template( + regex_pattern, + "pmatch", + prefer, + ) except FileNotFoundError: click.secho( f"Regex pattern '{regex_pattern}' not found!", fg="red", err=True @@ -140,8 +186,10 @@ def pmatch( ) raise SystemExit(1) + concrete_flags = utils.parse_regex_flags(flags) + try: - regex_pattern_c = re.compile(regex_pattern) + regex_pattern_c = re.compile(regex_pattern, flags=concrete_flags) except re.error as e: if verbose: click.secho("Regular expression could not be compiled!", fg="red", err=True) @@ -150,7 +198,9 @@ def pmatch( # Monkey patch walk function to include commandline options walk_function = functools.partial( - utils.walk, recursive=recursive, match_directories=match_directories + utils.walk, + recursive=recursive, + match_directories=match_directories, ) new_walk = itertools.chain.from_iterable(map(walk_function, directory)) diff --git a/patternutils/commands/pmediainfo.py b/patternutils/commands/pmediainfo.py index 2cc0f8f..b782ab5 100644 --- a/patternutils/commands/pmediainfo.py +++ b/patternutils/commands/pmediainfo.py @@ -1,7 +1,7 @@ import functools import itertools import os.path -from typing import Any, Generator, Iterator, +from typing import Any, Generator, Iterator import click from pymediainfo import MediaInfo diff --git a/patternutils/commands/targs.py b/patternutils/commands/targs.py index a4f91e0..e7c85cd 100644 --- a/patternutils/commands/targs.py +++ b/patternutils/commands/targs.py @@ -1,11 +1,12 @@ +import concurrent.futures import functools -import multiprocessing import shlex import subprocess from collections.abc import Iterable from typing import Any, Callable, TextIO import click +from click_option_group import optgroup from .. import config, template, utils from ..types import TemplateExtensions @@ -27,78 +28,89 @@ def run_command( @click.command(context_settings={"help_option_names": ("-h", "--help", "-?")}) -@click.option( +@optgroup.group("General options") +@optgroup.option( + "-i", + "--interactive", + is_flag=True, + default=False, + help="Prompt before running command.", +) +@optgroup.option( + "-e", + "--editor", + is_flag=True, + default=False, + help="Open an editor before running commands to allow manual changes.", +) +@optgroup.option( + "-n", + "--dry-run", + is_flag=True, + default=False, + help="Perform a trial run without executing commands.", +) +@optgroup.option( + "-v", + "--verbose", + is_flag=True, + default=False, + help="Explain what is being done.", +) +@optgroup.group("Input options") +@optgroup.option( "--read-from", default="-", type=click.File("r"), - help="Read input from file or stdin.", + help="Input file (default: stdin)", ) -@click.option( +@optgroup.option( "-s", - "--shell", + "--stream", is_flag=True, default=False, - help="Run command in a shell.", + help="Read input as stream of JSON objects.", ) -@click.option( +@optgroup.group("Template options") +@optgroup.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), - help="Max parallel processes.", -) -@click.option( +@optgroup.option( "-p", "--use-predefined-template", is_flag=True, default=False, help="Use a predefined template.", ) -@click.option( +@optgroup.group("Command options") +@optgroup.option( + "--shell", + is_flag=True, + default=False, + help="Run command in a shell.", +) +@optgroup.option( "--redirect-stdout", is_flag=True, default=False, help="Redirect stdout to /dev/null.", ) -@click.option( +@optgroup.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.option( - "-n", - "--dry-run", - is_flag=True, - default=False, - help="Perform a trial run without executing commands.", +@optgroup.option( + "-P", + "--max-procs", + default=1, + type=click.IntRange(min=0, clamp=True), + help="Max parallel processes.", ) @click.version_option() @click.argument("command") @@ -123,6 +135,9 @@ def targs( --stream option is used), which are then supplied to the templating engine, to generate the command to run. """ + if interactive and max_procs != 1: + pass + data: Iterable[dict[str, Any]] if stream: data = map(utils.json_loads, read_from) @@ -176,12 +191,25 @@ def targs( max_procs = utils.max_procs() if max_procs == 0 else max_procs - if max_procs == 1: + if max_procs == 1 or interactive: for cmd in commands: - execute_command(cmd) + if verbose or interactive: + click.secho(cmd) + + if not (interactive and not click.confirm("Run the command?")): + execute_command(cmd) else: - with multiprocessing.Pool(max_procs) as pool: - pool.map(execute_command, commands) + with concurrent.futures.ProcessPoolExecutor(max_workers=max_procs) as executor: + futures = [ + executor.submit(execute_command, command) for command in commands + ] + for future in concurrent.futures.as_completed(futures): + try: + result = future.result() + if result is not None and result.returncode != 0: + pass + except Exception: + pass if __name__ == "__main__": diff --git a/patternutils/commands/tmv.py b/patternutils/commands/tmv.py index 58a75e5..ecc680b 100644 --- a/patternutils/commands/tmv.py +++ b/patternutils/commands/tmv.py @@ -4,6 +4,7 @@ from collections.abc import Iterable from typing import Any, TextIO import click +from click_option_group import optgroup from .. import config, template, utils from ..types import TemplateExtensions @@ -19,86 +20,90 @@ def move_file(old_filename: str, new_filename: str) -> None: @click.command(context_settings={"help_option_names": ("-h", "--help", "-?")}) -@click.option( +@optgroup.group("General options") +@optgroup.option( "-i", "--interactive", is_flag=True, default=False, help="Prompt before renaming files.", ) -@click.option( +@optgroup.option( "-e", "--editor", is_flag=True, default=False, help="Open an editor before renaming files to allow manual changes.", ) -@click.option( - "-f", - "--force", +@optgroup.option( + "-n", + "--dry-run", is_flag=True, default=False, - help="Force rename, even if a file exists.", + help="Perform a trial run without making changes.", ) -@click.option( - "-t", - "--template-engine", - default="python", - type=click.Choice(["python", "jinja2"]), - help="Select the template engine to use.", +@optgroup.option( + "-v", + "--verbose", + is_flag=True, + default=False, + help="Explain what is being done.", ) -@click.option( - "-k", - "--key", - default="_abspath", - help="Select the key to use as the source file.", +@optgroup.group("Input options") +@optgroup.option( + "--read-from", + default="-", + type=click.File("r"), + help="Input file (default: stdin)", ) -@click.option( +@optgroup.option( "-s", "--stream", is_flag=True, default=False, help="Read input as stream of JSON objects.", ) -@click.option( +@optgroup.group("Template options") +@optgroup.option( + "-t", + "--template-engine", + default="python", + type=click.Choice(["python", "jinja2"]), + help="Select the template engine to use.", +) +@optgroup.option( "-p", "--use-predefined-template", is_flag=True, default=False, help="Use a predefined template.", ) -@click.option( - "--read-from", - default="-", - type=click.File("r"), - help="Input file (default: stdin)", +@optgroup.group("Renaming options") +@optgroup.option( + "-k", + "--key", + default="_abspath", + help="Select the key to use as the source file.", +) +@optgroup.option( + "-f", + "--force", + is_flag=True, + default=False, + help="Force rename, even if a file exists.", ) -@click.option( +@optgroup.option( "--abort-on-path-exist", is_flag=True, default=False, help="Exit if a renamed path already exists.", ) -@click.option( +@optgroup.option( "--abort-on-key-not-found", is_flag=True, default=False, help="Exit if the specified key (specified by the --key option) is not found in the input JSON.", ) -@click.option( - "-n", - "--dry-run", - is_flag=True, - default=False, - help="Perform a trial run without making changes.", -) -@click.option( - "-v", - "--verbose", - is_flag=True, - default=False, - help="Explain what is being done.", -) @click.version_option() @click.argument("output-pattern") def tmv( |
