"""Main Module for JMBuilder
Copyright (c) 2023-2024 Ryuu Mitsuki.
"""
import os as __os
import sys as __sys
import re as __re
from pathlib import Path as __Path
from typing import (
Any,
Iterable,
Union,
Set,
List,
Dict,
Tuple,
TextIO
)
try:
from . import utils as __jmutils
from . import exception as __jmexc
from ._globals import AUTHOR, VERSION, VERSION_INFO, __jmsetup__
from .core import PomParser as __core_PomParser, JMRepairer as __core_JMRepairer
except (ImportError, ModuleNotFoundError, ValueError):
# Add a new Python search path to the first index
__sys.path.insert(0, str(__Path(__sys.path[0]).parent))
from . import utils as __jmutils
from jmbuilder._globals import AUTHOR, VERSION, VERSION_INFO, __jmsetup__
from jmbuilder.core import PomParser as __core_PomParser, JMRepairer as __core_JMRepairer
finally:
del __Path # This no longer being used
CLEAN_ARGS: Tuple[str] = __jmutils.remove_duplicates(__sys.argv[1:])
[docs]def __print_version(_exit: bool = False, *,
only_ver: bool = False,
file: TextIO = __sys.stdout) -> None:
"""
Print the version info to specific opened file.
Parameters
----------
exit : bool, optional
Whether to exit and terminate the Python after printed the version.
Defaults to False (disabled).
only_ver: bool, optional
Whether to print the version only. By activating this option,
other information like program name, license, and copyright
will not be printed. Defaults to False.
file : TextIO, optional
The file to print the version info.
Defaults to console standard output (`sys.stdout`).
"""
if not file:
raise ValueError(f"File must be a file object, got {type(file).__name__!r}")
program_name: str = __jmsetup__.progname
version: str = f"v{'.'.join(map(str, __jmsetup__.version))}"
author: str = __jmsetup__.author
# Check if only_ver is False (or not specified)
if not only_ver:
print(
program_name, version, f'- {__jmsetup__.license}', # Program name and version
__os.linesep + \
f'Copyright (C) 2023-2024 by {author}.', # Copyright notice
file=file
)
else:
print(version, file=file)
if _exit:
__sys.exit(0)
[docs]def __argchck(targets: Union[str, Iterable], args: Union[List[str], Tuple[str]]) -> bool:
"""
Check whether specified argument are presented in `args`.
Paramaters
----------
targets : str or iterable
An argument or a list of arguments (must iterable) to searched for.
args : list or tuple of str
A list of arguments.
Returns
-------
bool :
Returns True if the specified argument are presented in `args`,
otherwise returns False.
"""
if isinstance(targets, str):
return targets in args
found: bool = False
for target in targets:
if str(target) in args:
found = True
return found
[docs]def __find_arg(val: Union[str, __re.Pattern]) -> int:
"""
Find the index of specified argument from the command-line arguments.
Parameters
----------
val : str or re.Pattern
A regular expression pattern used to search for the argument within
the command-line arguments. Accepts a string literal representing
the regular expression or a compiled regular expression.
Returns
-------
int :
The index of the specified argument in the command-line arguments.
Returns -1 if the argument cannot be found or if the command-line
arguments are empty.
Notes
-----
This function utilizes the global constant ``CLEAN_ARGS``, ensuring that
it searches for the desired argument within the command-line arguments
with all duplicate arguments omitted.
"""
# Use the fixed arguments; global constant
if len(CLEAN_ARGS) == 0:
return -1
# Convert to regular expression
val = __re.compile(val) if isinstance(val, str) else val
res: __re.Match = None
for arg in CLEAN_ARGS:
res = val.search(arg)
if res:
break
return CLEAN_ARGS.index(res.group()) if res else -1
[docs]def __print_help() -> None:
"""Print the help message to the standard output."""
program_name: str = __jmsetup__.progname
version: str = f"v{'.'.join(map(str, __jmsetup__.version))}"
author: str = __jmsetup__.author
header: str = f'{program_name} {version}'
print(f"""\
{header}
{''.join(['-' for _ in range(len(header))])}
USAGE:
python -m {__package__} [OPTIONS]
OPTIONS:
--fix-mf <pom> <in> [out],
--fix-manifest <pom> <in> [out]
Run the builder to correct the specified manifest file containing
Maven's variables. Utilizes information from the provided POM file.
The output will be written to the given output file, if provided;
otherwise, it will overwrite the input file.
--fix-prop <pom> <in> [out],
--fix-properties <pom> <in> [out]
Run the builder to rectify the specified properties file with
Maven's variables. Incorporates information from the provided POM file.
The output will be written to the given output file, if provided;
otherwise, it will overwrite the input file.
-V, --version, -version
Print the version and copyright information. All details will be printed
directly to the standard output, except for '-version', it goes
to the standard error.
-VV, --only-ver, --only-version
Print the version number only.
-h, --help
Print this help message.
ISSUES:
Report some issues and help us improve this builder.
<https://github.com/mitsuki31/JMBuilder/issues/new>
AUTHOR:
{author}\
""")
#::# Main Driver #::#
def main() -> None:
"""Main function for JMBuilder."""
help_args: Tuple[str] = ('-h', '--help')
version_args: Tuple[str] = ('-V', '--version', '-version')
only_version_args: Tuple[str] = ('-VV', '--only-ver', '--only-version')
fix_mf_args: Tuple[str] = ('--fix-manifest', '--fix-mf')
fix_prop_args: Tuple[str] = ('--fix-properties', '--fix-prop')
all_known_args: Set[str] = {
*help_args, *version_args, *only_version_args,
*fix_mf_args, *fix_prop_args
}
if len(CLEAN_ARGS) == 0:
print(
f'Nothing to run.{__os.linesep * 2}' +
f'USAGE: python -m {__package__} [-h | -V | -VV]{__os.linesep}' +
'\t\t[--fix-mf <pom> <in> [out] | --fix-prop <pom> <in> [out]]'
)
__sys.exit(0)
# Check for `-V` or `--version` in the arguments
# If found, print the version info then exit with exit code zero (success)
#
if __argchck(version_args[:-1], CLEAN_ARGS):
__print_version(True)
# For `-version` argument, the output will be redirected
# to the standard error (`sys.stderr`)
# `-V`, `--version` -> sys.stdout
# `-version` -> sys.stderr
#
elif __argchck(version_args[-1], CLEAN_ARGS):
__print_version(True, file=__sys.stderr)
# To print the version only, user can use several arguments. See 'only_version_args'
elif __argchck(only_version_args, CLEAN_ARGS):
__print_version(True, only_ver=True)
# Print the help message
elif __argchck(help_args, CLEAN_ARGS):
__print_help()
# Only executed if and only if both options not specified simultaneously
elif __argchck(fix_mf_args, CLEAN_ARGS) ^ __argchck(fix_prop_args, CLEAN_ARGS):
fix_args = fix_mf_args if __argchck(fix_mf_args, CLEAN_ARGS) else fix_prop_args
opt_idx = __find_arg('(' + '|'.join(fix_args) + ')') \
if __argchck(fix_args, CLEAN_ARGS) else -1
del fix_args
# Keep the unformatted brackets, with this we can format and
# write the actual error message on later.
option_err_msg: str = \
'{}' + f'.{__os.linesep * 2}' + \
f'USAGE: python -m {__package__} {CLEAN_ARGS[opt_idx]} <pom> <in> [out]'
# This dictionary will holds the builder arguments
option_args: Dict[str, Any] = {
'pom': None, # 1
'infile': None, # 2
'outfile': None # 3
}
# Get the builder method name
method_name: Any = 'fix_manifest' \
if CLEAN_ARGS[opt_idx] in fix_mf_args else 'fix_properties'
if not (len(CLEAN_ARGS) - 1) >= (opt_idx + 1):
raise __jmexc.JMException(
option_err_msg.format('No POM file were specified'))
if not (len(CLEAN_ARGS) - 1) >= (opt_idx + 2):
raise __jmexc.JMException(
option_err_msg.format('No input file were specified'))
# Get the argument right after the option argument, and treat it
# as a path to specific POM file, otherwise an error raised if not specified.
option_args['pom'] = __core_PomParser.parse(CLEAN_ARGS[opt_idx + 1])
# Get the input file (infile) argument,
# otherwise raise an error if not specified
option_args['infile'] = CLEAN_ARGS[opt_idx + 2]
# Get the outfile argument, otherwise use the infile argument
# if not specified (i.e., overwrite the input file)
if (len(CLEAN_ARGS) - 1) >= (opt_idx + 3):
option_args['outfile'] = CLEAN_ARGS[opt_idx + 3]
else:
option_args['outfile'] = option_args.get('infile')
repairer: __core_JMRepairer = __core_JMRepairer(option_args.pop('pom'))
# Call the method dynamically
args_vals: Tuple[str] = (*option_args.values(),)
getattr(repairer, method_name)(args_vals[0], outfile=args_vals[1])
# This for causing the error when both options are specified
elif __argchck(fix_mf_args, CLEAN_ARGS) and __argchck(fix_prop_args, CLEAN_ARGS):
raise __jmexc.JMException(
'Program was unable to run both options simultaneously')
elif not __argchck(CLEAN_ARGS, all_known_args):
err: str = "Unknown argument detected: '{}'" + (__os.linesep * 2) + \
'For more details, type argument `-h` or `--help`.'
arg_idx: int = next((idx for idx, arg in enumerate(CLEAN_ARGS)
if not __argchck(arg, all_known_args)), -1)
raise __jmexc.JMException(err.format(CLEAN_ARGS[arg_idx]))
__author__ = AUTHOR
__version__ = VERSION
__version_info__ = VERSION_INFO
# Delete unused imported objects
del AUTHOR, VERSION, VERSION_INFO
del Iterable, Union, Any, TextIO, List, Tuple, Dict, Set
if __name__ == '__main__':
main()
else:
# Remove the `main` function when this module is being imported,
# but it's a bit silly if there someone imports this module.
# The reason is the function implementation are strongly depends on
# the command-line arguments (`sys.argv`). However, if this is imported
# users can still modify the `sys.argv` manually, right?
# But we restricted that, we want the function to run as main driver only.
del main