diff --git a/${REPO_NAME_SNAKE}/__init__.py b/${REPO_NAME_SNAKE}/__init__.py new file mode 100644 index 0000000..7b381cd --- /dev/null +++ b/${REPO_NAME_SNAKE}/__init__.py @@ -0,0 +1,46 @@ +import argparse +import asyncio +import sys + +import uvloop + +__version__ = "0.0.1" + + +async def main(args: argparse.Namespace) -> None: + """ + Main function of the program (stub). + + This function serves as ${REPO_NAME_SNAKE}'s critical path for primary logic. + It receives an `argparse.Namespace` object as input from invoke_maiun, which includes + the command-line arguments that were defined and parsed in the `run` function. + + Args: + args (argparse.Namespace): The command-line arguments object. + + Returns: + None + """ + pass + + +def invoke_main(args: argparse.Namespace) -> None: + """ + Function to invoke the main function using asyncio and uvloop (stub). + + This function serves as a bridge to run the async main function in + a uvloop event loop. It handles the differences in asyncio API across + different Python versions. + + Args: + args (argparse.Namespace): The command-line arguments object. + + Returns: + None + """ + if sys.version_info >= (3, 11): + with asyncio.Runner(loop_factory=uvloop.new_event_loop) as runner: + runner.run(main(args)) + else: + uvloop.install() + asyncio.run(main(args)) diff --git a/${REPO_NAME_SNAKE}/cli.py b/${REPO_NAME_SNAKE}/cli.py new file mode 100644 index 0000000..feec62f --- /dev/null +++ b/${REPO_NAME_SNAKE}/cli.py @@ -0,0 +1,44 @@ +from argparse import ArgumentParser +from pathlib import Path + +from . import __version__ +from .logger import logger +from .validation import validate_file_folder + + +def run() -> None: + """ + Command-line interface for ${REPO_NAME_SNAKE}. + + This function defines and handles command-line arguments for the program. + The available options are: + + --version: Show the version of the program and exit. + --verbose: Enable verbose logging. This will produce detailed output messages. + --output/-o: Specify the output file. This must be a valid file path. + paths: Specify any number of existing files or directories to be processed. + These paths must point to existing files or directories. + + Upon parsing the command-line arguments, the function configures the logger + and invokes the `main` function with the parsed arguments. + + Returns: + None + """ + # your code here + parser = ArgumentParser(description="${REPO_DESCRIPTION}") + parser.add_argument("--version", action="version", version=__version__) + parser.add_argument("--verbose", action="store_true", help="Enable verbose logging") + parser.add_argument("--output", "-o", type=Path, help="Specify output file") + parser.add_argument( + "paths", + nargs="+", + type=validate_file_folder, + help="Specify any number of existing files or directories to be processed.", + ) + args = parser.parse_args() + + if args.verbose: + logger.debug("Verbose logging enabled") + + run(args) diff --git a/${REPO_NAME_SNAKE}/logger.py b/${REPO_NAME_SNAKE}/logger.py new file mode 100644 index 0000000..3fe05e6 --- /dev/null +++ b/${REPO_NAME_SNAKE}/logger.py @@ -0,0 +1,4 @@ +import logging + +logging.basicConfig(format="%(asctime)s - %(message)s", datefmt="%d-%b-%y %H:%M:%S") +logger = logging.getLogger(__name__) diff --git a/${REPO_NAME_SNAKE}/validation.py b/${REPO_NAME_SNAKE}/validation.py new file mode 100644 index 0000000..ad9581e --- /dev/null +++ b/${REPO_NAME_SNAKE}/validation.py @@ -0,0 +1,12 @@ +from pathlib import Path + +from aiopath import AsyncPath + + +def validate_file_folder(value: str) -> Path: + file_folder_path = Path(value) + if not file_folder_path.exists(): + raise FileNotFoundError(f"No such file or folder: {value}") + if not file_folder_path.is_file() and not file_folder_path.is_dir(): + raise TypeError(f"Not a file or directory: {value}") + return AsyncPath(value) diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..f3d0278 --- /dev/null +++ b/.flake8 @@ -0,0 +1,3 @@ +[flake8] +max-line-length = 160 +exclude = docs/*, build/*, .git, __pycache__, build diff --git a/.gitignore b/.gitignore index 34207bd..fa764ad 100644 --- a/.gitignore +++ b/.gitignore @@ -28,8 +28,6 @@ share/python-wheels/ MANIFEST # PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec @@ -83,34 +81,10 @@ target/ profile_default/ ipython_config.py -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock - -# pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -#pdm.lock -# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it -# in version control. -# https://pdm.fming.dev/#use-with-ide +# PDM .pdm.toml -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +# PEP 582 __pypackages__/ # Celery stuff @@ -154,11 +128,7 @@ dmypy.json cython_debug/ # PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ +.idea/ # ---> macOS # General @@ -167,7 +137,8 @@ cython_debug/ .LSOverride # Icon must end with two \r -Icon +Icon + # Thumbnails ._* @@ -228,4 +199,3 @@ $RECYCLE.BIN/ # .nfs files are created when an open file is removed but is still being accessed .nfs* - diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..230afda --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,35 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.2.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files +- repo: https://github.com/asottile/reorder_python_imports + rev: v3.9.0 + hooks: + - id: reorder-python-imports + args: [--application-directories, '.:${REPO_NAME_SNAKE}', --py39-plus] +- repo: https://github.com/psf/black + rev: 23.3.0 + hooks: + - id: black + language_version: python3.11 +- repo: https://github.com/PyCQA/bandit + rev: 1.7.5 + hooks: + - id: bandit + args: ["-c", "pyproject.toml"] + additional_dependencies: ["bandit[toml]"] +- repo: https://github.com/pycqa/flake8 + rev: 6.0.0 + hooks: + - id: flake8 +- repo: local + hooks: + - id: version + name: Update version + entry: ./pre-commit.sh + language: script + files: 'pyproject.toml' diff --git a/README.md b/README.md index 6f502f0..30ecbe4 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,33 @@ -# python-module +