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..189b6ea
--- /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, '.:dotgift', --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'
\ No newline at end of file
diff --git a/README.md b/README.md
index 13bbdf1..a2e2b03 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,28 @@
+
# dotgift
-Christmas gift list project
\ No newline at end of file
+**dotgift** is a Christmas manager
+
+for large double families like mine!
+
+[Installation](#installation) •
+[Examples](#examples) •
+[Contributing](#contributing) •
+[License](#license)
+
+
+## Installation
+
+TBD
+
+## Examples
+
+TBD
+
+## Contributing
+
+If you would like to contribute to this project, feel free to submit a pull request or open an issue on GitHub.
+
+## License
+
+This project is licensed under the current AGPL 3.0+ License. See the `LICENSE` file for details.
diff --git a/dotgift/__init__.py b/dotgift/__init__.py
new file mode 100644
index 0000000..253e652
--- /dev/null
+++ b/dotgift/__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 dotgift'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/dotgift/cli.py b/dotgift/cli.py
new file mode 100644
index 0000000..d28aec8
--- /dev/null
+++ b/dotgift/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 dotgift.
+
+ 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="A Christmas list manager for families like mine!")
+ 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/dotgift/logger.py b/dotgift/logger.py
new file mode 100644
index 0000000..3fe05e6
--- /dev/null
+++ b/dotgift/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/pre-commit.sh b/pre-commit.sh
new file mode 100644
index 0000000..94399b2
--- /dev/null
+++ b/pre-commit.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+PROJECT_ROOT=$(git rev-parse --show-toplevel)
+VERSION=$(grep 'version =' $PROJECT_ROOT/pyproject.toml | head -1 | cut -d '"' -f 2)
+INIT_FILE="$PROJECT_ROOT/dotgift/dotgift/__init__.py"
+
+# Portable workaround for macOS/Linux sed differences
+sed -i.bak "s/__version__ = .*/__version__ = \"$VERSION\"/" $INIT_FILE && \
+ rm "$INIT_FILE.bak"
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..79e1b84
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,29 @@
+[build-system]
+requires = ["setuptools>=68.0"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "dotgift"
+version = "0.0.1"
+authors = [{ name = "pdf", email = "git@nixon.mozmail.com" }]
+description = "Another Christmas list manager for families"
+readme = "README.md"
+requires-python = ">=3.11"
+license = { text = "AGPL 3.0" }
+dependencies = ["uvloop>=0.17.0", "asyncstdlib>=3.10.8"]
+
+[project.scripts]
+dotgift = "dotgift.cli:run"
+
+[project.urls]
+homepage = "https://sillyhats.mips.uk/pdf/dotgift"
+repository = "https://sillyhats.mips.uk/pdf/dotgift"
+
+[tool.setuptools]
+py-modules = ["dotgift"]
+
+[tool.bandit]
+exclude_dirs = ["/docs", "/build"]
+
+[tool.black]
+line-length = 120