Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: GitHub Release

on:
push:
tags:
- "v*"

jobs:
release:
name: Create GitHub Release
runs-on: ubuntu-latest
permissions:
contents: write

steps:
- name: Checkout source code
uses: actions/checkout@v4

- name: Set up uv
uses: astral-sh/setup-uv@v4
with:
enable-cache: true

- name: Build package
run: uv build

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
files: dist/*
generate_release_notes: true
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,7 @@ wheels/
.coverage
htmlcov/
.tox/
.nox/
.nox/

# specific files/dirs
src/pyplatez/templates/
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ pyplatez/
│ └── PULL_REQUEST_TEMPLATE.md
├── assets/ # Images and static assets for docs
├── src/ # Application source code
│ └── pyplatez/ # The main package directory
│ └── pyplatez/ # The main package directory
│ └── cli.py # Command-line interface logic for scaffolding via `pyplatez init`
├── tests/ # Unit tests via pytest
├── CODE_OF_CONDUCT.md # Community guidelines
├── CONTRIBUTING.md # Instructions for dev setup and PRs
├── hatch_build.py # Custom build hook that dynamically bundles template files into the wheel
├── LICENSE # Open source license
├── main.py # Default application entry point
├── pyproject.toml # The heart of the project configuration
Expand Down
37 changes: 37 additions & 0 deletions hatch_build.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from pathlib import Path
from hatchling.builders.hooks.plugin.interface import BuildHookInterface

EXCLUDE = {
"src",
"dist",
"__pycache__",
".git",
".venv",
"hatch_build.py",
"uv.lock",
".mypy_cache",
".pytest_cache",
".ruff_cache",
"PKG-INFO",
".hatch",
}


class BuildHook(BuildHookInterface):
def initialize(self, _version, build_data):
if self.target_name != "wheel":
return
# shit doesn't package unless you force it down
root = Path(self.root)
force_include = build_data.setdefault("force_include", {})

for item in root.iterdir():
if item.name in EXCLUDE:
continue

dest = f"pyplatez/templates/{item.name}"
force_include[str(item)] = dest

print(
f"[pyplatez] Bundling {len(force_include)} root items into pyplatez/templates/"
)
15 changes: 11 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
[project]
name = "pyplatez"
version = "0.1.0"
description = "minimalist python template for professional/hobbyist works"
version = "0.1.1"
description = "Batteries-included python-starter project template"
readme = "README.md"
requires-python = ">=3.14"
dependencies = []

[project.scripts]
pyplatez = "pyplatez.cli:main"

[dependency-groups]
dev = [
"build>=1.4.2",
"hatchling>=1.29.0",
"pyinstaller>=6.19.0",
"pytest>=9.0.2",
"ruff>=0.15.8",
"twine>=6.2.0",
Expand All @@ -20,5 +22,10 @@ dev = [
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.hooks.custom]
[tool.hatch.build.targets.wheel]

packages = ["src/pyplatez"]

[tool.uv]
package = true
package = true
76 changes: 76 additions & 0 deletions src/pyplatez/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import argparse
import shutil
import sys
from importlib import resources
from pathlib import Path


def scaffold(project_name: str, package_name: str, target: Path) -> None:
with resources.as_file(
resources.files("pyplatez").joinpath("templates")
) as tmpl_dir:
shutil.copytree(tmpl_dir, target, dirs_exist_ok=True)

old_pkg = target / "src" / "pyplatez"
new_pkg = target / "src" / package_name
if old_pkg.exists():
old_pkg.rename(new_pkg)

pyproject = target / "pyproject.toml"
if pyproject.exists():
text = pyproject.read_text()
text = text.replace('name = "pyplatez"', f'name = "{project_name}"')
text = text.replace(
'description = "minimalist python template for professional/hobbyist works"',
'description = "Add your description here"',
)
pyproject.write_text(text)

print(f" '{project_name}' ready at ./{target.name}")
print(f" cd {target.name} && uv sync")


def main() -> None:
parser = argparse.ArgumentParser(
prog="pyplatez",
description="A batteries-included Python-starter project-template",
)
sub = parser.add_subparsers(dest="command")

init = sub.add_parser("init", help="Create a new project")
init.add_argument("name", help="Project name")
init.add_argument(
"--path", default=None, help="Where to create it (default: ./<name>)"
)

args = parser.parse_args()

if args.command == "init":
package_name = args.name.replace("-", "_")
if not package_name.isidentifier():
print(
f" Error: '{args.name}' cannot be normalized to a valid Python package name.",
file=sys.stderr,
)
sys.exit(1)

target = Path(args.path) if args.path else Path.cwd() / args.name

if target.exists():
if not target.is_dir():
print(
f" Error: '{target}' already exists and is a file, not a directory.",
file=sys.stderr,
)
sys.exit(1)
if any(target.iterdir()):
print(
f" Error: '{target}' already exists and is not empty.",
file=sys.stderr,
)
sys.exit(1)

target.mkdir(parents=True, exist_ok=True)
scaffold(args.name, package_name, target)
else:
parser.print_help()
84 changes: 1 addition & 83 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading