Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,4 @@ dist

artifacts

bin
36 changes: 36 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
## Makefile for config-templates: developer tasks orchestrated via go-task
#
# This Makefile wraps the Taskfile.yml commands and provides a friendly
# `make help` index. Lines with `##` after a target are parsed into help text,
# and lines starting with `##@` create section headers in the help output.
#
# Colors for pretty output in help messages
BLUE := \033[36m
BOLD := \033[1m
GREEN := \033[32m
RED := \033[31m
RESET := \033[0m

# Default goal when running `make` with no target
.DEFAULT_GOAL := help

# Declare phony targets (they don't produce files)
.PHONY: install-task

UV_INSTALL_DIR := "./bin"

##@ Bootstrap
install-task: ## ensure go-task (Taskfile) is installed
@mkdir -p ${UV_INSTALL_DIR}

@if [ ! -x "${UV_INSTALL_DIR}/task" ]; then \
printf "$(BLUE)Installing go-task (Taskfile)$(RESET)\n"; \
curl --location https://taskfile.dev/install.sh | sh -s -- -d -b ${UV_INSTALL_DIR}; \
fi

##@ Meta
help: ## Display this help message
+@printf "$(BOLD)Usage:$(RESET)\n"
+@printf " make $(BLUE)<target>$(RESET)\n\n"
+@printf "$(BOLD)Targets:$(RESET)\n"
+@awk 'BEGIN {FS = ":.*##"; printf ""} /^[a-zA-Z_-]+:.*?##/ { printf " $(BLUE)%-15s$(RESET) %s\n", $$1, $$2 } /^##@/ { printf "\n$(BOLD)%s$(RESET)\n", substr($$0, 5) }' $(MAKEFILE_LIST)
74 changes: 74 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# This file is part of the tschm/.config-templates repository
# (https://github.com/tschm/.config-templates).
#
# Taskfile: Root Taskfile
#
# Purpose: Provide a unified entry point for developer tasks by grouping
# build, quality, documentation, and cleanup commands. Exposes
# helpful default and help targets.
#
# Components:
# - includes: build, quality, docs, cleanup
# - tasks:
# - default: show top-level tasks
# - help: show all tasks including nested ones

version: '3'
silent: true

# Variables
vars:
TESTS_FOLDER: tests
MARIMO_FOLDER: book/marimo
BOOK_TITLE: '{{env "REPO_NAME"}}' # defined below env section
BOOK_SUBTITLE: 'Documentation and Reports'
OPTIONS: ''

# Environment variables
env:
# Colors for pretty output
BLUE: '\x1b[36m'
GREEN: '\x1b[32m'
RED: '\x1b[31m'
YELLOW: '\x1b[33m'
CYAN: '\x1b[36m'
MAGENTA: '\x1b[35m'
BOLD: '\x1b[1m'
RESET: '\x1b[0m'
# Set repository name
REPO_NAME:
sh: basename $(pwd)

#LOG_INFO: 'printf "${BLUE}[INFO] %s${RESET}\n"'
#LOG_SUCCESS: 'printf "${GREEN}[SUCCESS] %s${RESET}\n"'
#LOG_ERROR: 'printf "${RED}[ERROR] %s${RESET}\n"'


# Include task groups
includes:
build:
taskfile: ./taskfiles/build.yml
desc: Build tasks for managing dependencies and building packages
quality:
taskfile: ./taskfiles/quality.yml
desc: Code quality tasks for formatting, linting, and checking code
docs:
taskfile: ./taskfiles/docs.yml
desc: Documentation tasks for building docs, notebooks, and books
cleanup:
taskfile: ./taskfiles/cleanup.yml
desc: Cleanup tasks for removing generated files and directories

# Task definitions
tasks:
default:
desc: Display help information
silent: true
cmds:
- ./bin/task --list

help:
desc: Display help information with all tasks
silent: true
cmds:
- ./bin/task --list-all
72 changes: 72 additions & 0 deletions taskfiles/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# This file is part of the tschm/.config-templates repository
# (https://github.com/tschm/.config-templates).
#
# Taskfile: Build tasks
#
# Purpose: Provide build-related utilities for Python projects using uv, including:
# - Installing uv and uvx into a local ./bin directory
# - Creating a virtual environment and syncing dependencies
# - Building the package with Hatch when a pyproject.toml is present
#
# Components:
# - uv: install uv/uvx if missing
# - install: create venv and install dependencies (if pyproject.toml exists)
# - build: build the package with Hatch (if pyproject.toml exists)

version: '3'

dir: .

tasks:
uv:
desc: Install uv and uvx
env:
UV_INSTALL_DIR: ./bin
cmds:
- |
if [ -x "${UV_INSTALL_DIR}/uv" ] && [ -x "${UV_INSTALL_DIR}/uvx" ]; then
# already installed — stay quiet
exit 0
fi
printf "${BLUE}Installing uv${RESET}\n"
curl -LsSf https://astral.sh/uv/install.sh | sh 2>/dev/null || { printf "${RED}[ERROR] Failed to install uv ${RESET}\n"; exit 1; }


install:
desc: Install all dependencies using uv
deps: [uv]
env:
UV_VENV_CLEAR: 1
cmds:
- |
# we need the virtual environment at least for the tests to work
# even if we don't install anything

printf "${BLUE}[INFO] Creating virtual environment...${RESET}\n"

# Create the virtual environment
./bin/uv venv --python 3.12 || { printf "${RED}[ERROR] Failed to create virtual environment${RESET}\n"; exit 1; }

if [ -f "tests/requirements.txt" ]; then
./bin/uv pip install -r tests/requirements.txt || { printf "${RED}[ERROR] Failed to install test requirements${RESET}\n"; exit 1; }
fi

if [ -f "pyproject.toml" ]; then
printf "${BLUE}[INFO] Installing dependencies${RESET}\n"
./bin/uv sync --all-extras --frozen || { printf "${RED}[ERROR] Failed to install dependencies${RESET}\n"; exit 1; }
else
printf "${YELLOW}[WARN] No pyproject.toml found, skipping install${RESET}\n"
fi

build:
desc: Build the package using hatch
deps: [install]
cmds:
- |
if [ -f "pyproject.toml" ]; then
printf "${BLUE}[INFO] Building package...${RESET}\n"
./bin/uv pip install hatch
./bin/uv run hatch build
else
printf "${YELLOW}[WARN] No pyproject.toml found, skipping build${RESET}\n"
fi
29 changes: 29 additions & 0 deletions taskfiles/cleanup.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# This file is part of the tschm/.config-templates repository
# (https://github.com/tschm/.config-templates).
#
# Taskfile: Cleanup tasks
#
# Purpose: Remove build artifacts, caches, and orphaned Git branches to keep the
# working tree clean while preserving environment files.
#
# Components:
# - clean: delete generated files (dist, build, egg-info, caches) and prune
# local branches without a remote counterpart

version: '3'

tasks:
clean:
desc: Clean generated files and directories
cmds:
- |
printf "${BLUE}Cleaning project...${RESET}\n"
# do not clean .env files
git clean -d -X -f -e .env -e '.env.*'
rm -rf dist build *.egg-info .coverage .pytest_cache
printf "${BLUE}Removing local branches with no remote counterpart...${RESET}\n"
git fetch --prune
git branch -vv \
| grep ': gone]' \
| awk '{print $1}' \
| xargs -r git branch -D 2>/dev/null || true
173 changes: 173 additions & 0 deletions taskfiles/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
# This file is part of the tschm/.config-templates repository
# (https://github.com/tschm/.config-templates).
#
# Taskfile: Documentation and testing tasks
#
# Purpose: Provide tasks to run tests, generate API docs, export Marimo notebooks,
# assemble a combined documentation site ("book"), and run a Marimo server.
#
# Components:
# - test: run pytest with coverage and HTML report
# - docs: build API documentation with pdoc
# - marimushka: export Marimo notebooks to HTML and generate an index
# - book: assemble docs, test reports, and notebooks into a static site
# - marimo: start a Marimo server for interactive editing

version: '3'

includes:
build:
taskfile: ./build.yml
internal: true

tasks:
test:
desc: Run all tests
cmds:
- ./bin/task build:install
- printf "${BLUE}[INFO] Running tests...${RESET}\n"
- |
# Find source folder
SOURCE_FOLDER="src/$(find src -mindepth 1 -maxdepth 1 -type d -not -path '*/\.*' | head -1 | sed 's|^src/||')"

if [ -z "$SOURCE_FOLDER" ] || [ -z "{{.TESTS_FOLDER}}" ]; then
printf "${YELLOW}[WARN] No valid source folder structure found, skipping tests${RESET}\n"
else
./bin/uv pip install pytest pytest-cov pytest-html
mkdir -p _tests/html-coverage _tests/html-report
./bin/uv run pytest {{.TESTS_FOLDER}} --cov=$SOURCE_FOLDER --cov-report=term --cov-report=html:_tests/html-coverage --html=_tests/html-report/report.html
fi

docs:
desc: Build documentation using pdoc
cmds:
- ./bin/task build:install
- |
if [ -f "pyproject.toml" ]; then
printf "${BLUE}[INFO] Building documentation...${RESET}\n"
if [ -d "src" ]; then
SOURCE_FOLDER=$(LC_ALL=C find src -mindepth 1 -maxdepth 1 -type d -not -path '*/\.*' | sort | head -1)
if [ -z "$SOURCE_FOLDER" ]; then
printf "${YELLOW}[WARN] src/ exists but contains no top-level packages, skipping docs${RESET}\n"
else
./bin/uv pip install pdoc
./bin/uv run pdoc -o _pdoc "$SOURCE_FOLDER"
fi
else
printf "${YELLOW}[WARN] No src/ directory found, skipping docs${RESET}\n"
fi
else
printf "${YELLOW}[WARN] No pyproject.toml found, skipping docs${RESET}\n"
fi

marimushka:
desc: Export Marimo notebooks to HTML
cmds:
- ./bin/task build:install
- printf "${BLUE}[INFO] Exporting notebooks from {{.MARIMO_FOLDER}}...${RESET}\n"
- |
if [ ! -d "{{.MARIMO_FOLDER}}" ]; then
printf "${YELLOW}[WARN] Directory '{{.MARIMO_FOLDER}}' does not exist. Skipping marimushka.${RESET}\n"
else
./bin/uv pip install marimo
mkdir -p _marimushka
py_files=$(find "{{.MARIMO_FOLDER}}" -maxdepth 1 -name "*.py" | tr '\n' ' ')
if [ -z "$py_files" ]; then
printf "${YELLOW}[WARN] No Python files found in '{{.MARIMO_FOLDER}}'.${RESET}\n"
echo "<html><head><title>Marimo Notebooks</title></head><body><h1>Marimo Notebooks</h1><p>No notebooks found.</p></body></html>" > _marimushka/index.html
else
printf "${BLUE}[INFO] Found Python files: %s${RESET}\n" "$py_files"
for py_file in $py_files; do
printf " ${BLUE}[INFO] Processing %s...${RESET}\n" "$py_file"
rel_path=$(echo "$py_file" | sed "s|^{{.MARIMO_FOLDER}}/||")
dir_path=$(dirname "$rel_path")
base_name=$(basename "$rel_path" .py)
mkdir -p "_marimushka/$dir_path"

# Check if the file has a script header ("# /// script")
if grep -q "^# /// script" "$py_file"; then
printf " ${BLUE}[INFO] Script header detected, using --sandbox flag...${RESET}\n"
./bin/uvx marimo export html --sandbox --include-code --output "_marimushka/$dir_path/$base_name.html" "$py_file"
else
printf " ${BLUE}[INFO] No script header detected, using standard export...${RESET}\n"
./bin/uv run marimo export html --include-code --output "_marimushka/$dir_path/$base_name.html" "$py_file"
fi
done
echo "<html><head><title>Marimo Notebooks</title></head><body><h1>Marimo Notebooks</h1><ul>" > _marimushka/index.html
find _marimushka -name "*.html" -not -path "*index.html" | sort | while read html_file; do
rel_path=$(echo "$html_file" | sed "s|^_marimushka/||")
name=$(basename "$rel_path" .html)
echo "<li><a href=\"$rel_path\">$name</a></li>" >> _marimushka/index.html
done
echo "</ul></body></html>" >> _marimushka/index.html
touch _marimushka/.nojekyll
fi
fi

book:
desc: Build the companion book with test results and notebooks
cmds:
- ./bin/task build:install
- printf "${BLUE}[INFO] Building combined documentation...${RESET}\n"
- printf "${BLUE}[INFO] Delete the _book folder...${RESET}\n"
- rm -rf _book
- printf "${BLUE}[INFO] Create empty _book folder...${RESET}\n"
- mkdir -p _book
- touch _book/links.json
- |
printf "${BLUE}[INFO] Copy API docs...${RESET}\n"
if [ -d _pdoc ]; then
mkdir -p _book/pdoc
cp -r _pdoc/* _book/pdoc
echo '{"API": "./pdoc/index.html"}' > _book/links.json
else
echo '{}' > _book/links.json
fi

printf "${BLUE}[INFO] Copy coverage report...${RESET}\n"
if [ -d _tests/html-coverage ] && [ "$(ls -A _tests/html-coverage 2>/dev/null)" ]; then
mkdir -p _book/tests/html-coverage
cp -r _tests/html-coverage/* _book/tests/html-coverage
jq '. + {"Coverage": "./tests/html-coverage/index.html"}' _book/links.json > _book/tmp && mv _book/tmp _book/links.json
else
printf "${YELLOW}[WARN] No coverage report found or directory is empty${RESET}\n"
fi

printf "${BLUE}[INFO] Copy test report...${RESET}\n"
if [ -d _tests/html-report ] && [ "$(ls -A _tests/html-report 2>/dev/null)" ]; then
mkdir -p _book/tests/html-report
cp -r _tests/html-report/* _book/tests/html-report
jq '. + {"Test Report": "./tests/html-report/report.html"}' _book/links.json > _book/tmp && mv _book/tmp _book/links.json
else
printf "${YELLOW}[WARN] No test report found or directory is empty${RESET}\n"
fi

printf "${BLUE}[INFO] Copy notebooks...${RESET}\n"
if [ -d _marimushka ] && [ "$(ls -A _marimushka 2>/dev/null)" ]; then
mkdir -p _book/marimushka
cp -r _marimushka/* _book/marimushka
jq '. + {"Notebooks": "./marimushka/index.html"}' _book/links.json > _book/tmp && mv _book/tmp _book/links.json
printf "${BLUE}[INFO] Copied notebooks from {{.MARIMO_FOLDER}} to _book/marimushka${RESET}\n"
else
printf "${YELLOW}[WARN] No notebooks found or directory is empty${RESET}\n"
fi

printf "${BLUE}[INFO] Generated links.json:${RESET}\n"
cat _book/links.json

./bin/uvx minibook --title "{{.BOOK_TITLE}}" --subtitle "{{.BOOK_SUBTITLE}}" --links "$(cat _book/links.json)" --output "_book"

touch "_book/.nojekyll"

marimo:
desc: Start a Marimo server
cmds:
- ./bin/task build:install
- printf " ${BLUE}[INFO] Start Marimo server with {{.MARIMO_FOLDER}}...${RESET}\n"
- |
if [ ! -d "{{.MARIMO_FOLDER}}" ]; then
printf " ${YELLOW}[WARN] Marimo folder '{{.MARIMO_FOLDER}}' not found, skipping start${RESET}\n"
else
./bin/uv sync --all-extras
./bin/uv run marimo edit "{{.MARIMO_FOLDER}}"
fi
Loading