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
49 changes: 49 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Release workflow for tickle-cli
# Inspired by video-sweep project

name: Release

on:
push:
tags:
- 'v*.*.*'

jobs:
test-build-publish:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Install build dependencies
run: |
python -m pip install --upgrade pip
pip install build ruff black pytest

- name: Lint with ruff
run: ruff check src/ tests/

- name: Check formatting with black
run: black --check src/ tests/

- name: Run tests
run: pytest

- name: Build package
run: python -m build

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
skip-existing: true

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
generate_release_notes: true
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

- Initial release workflow and documentation added.
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
[![Coverage](https://codecov.io/gh/colinmakerofthings/tickle-cli/branch/main/graph/badge.svg)](https://codecov.io/gh/colinmakerofthings/tickle-cli)
![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-d7ff64.svg)
![Security: ruff](https://img.shields.io/badge/security-ruff%20S-orange)
![PyPI](https://img.shields.io/pypi/v/tickle-cli)
![Downloads](https://img.shields.io/pypi/dm/tickle-cli)
![Build](https://github.com/colinmakerofthings/tickle-cli/actions/workflows/test.yml/badge.svg)
![Platforms](https://img.shields.io/badge/platforms-windows%20%7C%20linux%20%7C%20macos-blue)
<!-- badges: end -->

A lightweight, cross-platform tool that provides **hierarchical visualization** of TODOs, code comments, and markdown checkboxes across your repositories and personal notes.
Expand Down Expand Up @@ -218,6 +222,27 @@ notepad /p my_tasks.txt

Or open and print with any text editor.

## Exporting Output as PDF

You can export tickle's output to a color PDF file for easy sharing or printing. Use the `--export-pdf` flag:

```bash
tickle [options] --export-pdf output.pdf
```

- The output will be saved to `output.pdf` in color, matching the CLI's formatting.
- Only the tree format is currently supported for PDF export.
- The file will be A4, portrait, and will overwrite if it already exists.
- After export, tickle will display a confirmation message with the file path.

**Example:**

```bash
tickle --markers TODO,FIXME --export-pdf my_tasks.pdf
```

You can then open or print the PDF using any standard PDF viewer.

---

## Configuration
Expand Down
82 changes: 82 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Release Process for tickle-cli

This document describes the steps to create a new release of tickle-cli. Follow these steps to ensure a smooth and consistent release process.

## Prerequisites

- Push access to the repository
- PyPI trusted publishing is set up (no passwords needed in workflow)
- Clean working tree (no uncommitted changes)

## Local Checks

1. **Create/activate a virtual environment:**

```sh
python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
```

2. **Install dependencies:**

```sh
pip install -e .[dev]
```

3. **Lint and format:**

```sh
ruff check src/ tests/
black --check src/ tests/
```

4. **Run tests:**

```sh
pytest
```

5. **Smoke test the CLI:**

```sh
python -m tickle --help
tickle --version
```

## Release Steps

1. **Bump the version** in `pyproject.toml` (use semantic versioning).
2. **Update `CHANGELOG.md`** with notable changes.
3. **Commit and push changes:**

```sh
git add pyproject.toml CHANGELOG.md
git commit -m "Release vX.Y.Z"
git push origin main
```

4. **Tag the release and push the tag:**

```sh
git tag vX.Y.Z
git push origin vX.Y.Z
```

5. **GitHub Actions** will run the release workflow:
- Run tests, lint, and build
- Publish to PyPI
- Create a GitHub Release

## Verification

- Check the GitHub Actions workflow for success
- Verify the new release on PyPI
- Install the package from PyPI and test the CLI

## Rollback

- Never delete a PyPI release. If needed, bump the patch version and release a fix.

---

For questions, open an issue or contact a maintainer.
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ dependencies = [
"tomli>=2.0.0; python_version < '3.11'",
"colorama>=0.4.6",
"rich>=13.0.0",
"humanize>=4.0.0"
"humanize>=4.0.0",
"fpdf2>=2.7.0"
]

[project.urls]
Expand Down
Binary file added src/tickle/DejaVuSans-Bold.ttf
Binary file not shown.
Binary file added src/tickle/DejaVuSans-Oblique.ttf
Binary file not shown.
Binary file added src/tickle/DejaVuSans.ttf
Binary file not shown.
2 changes: 2 additions & 0 deletions src/tickle/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

try:
from importlib.metadata import PackageNotFoundError, version

__version__ = version("tickle-cli")
except PackageNotFoundError:
# Fallback for development when not installed - read from pyproject.toml if it exists
from pathlib import Path

_pyproject_path = Path(__file__).parent.parent.parent / "pyproject.toml"
if _pyproject_path.is_file():
try:
Expand Down
50 changes: 39 additions & 11 deletions src/tickle/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ def handle_init_command(args):
config_path = Path("tickle.toml")

if config_path.exists():
print("Error: tickle.toml already exists. Remove it first if you want to recreate it.")
print(
"Error: tickle.toml already exists. Remove it first if you want to recreate it."
)
sys.exit(1)

try:
Expand Down Expand Up @@ -56,7 +58,9 @@ def handle_scan_command(args):
# Load configuration unless --no-config is specified
config_path = None
if not args.no_config:
config_path = find_config_file(start_path=args.path, config_override=args.config)
config_path = find_config_file(
start_path=args.path, config_override=args.config
)
if args.verbose and config_path:
print(f"Using config from: {config_path}", file=sys.stderr)

Expand Down Expand Up @@ -89,13 +93,20 @@ def handle_scan_command(args):
no_color=bool(getattr(args, "export", None)),
)
output = formatter.format(tasks)
if hasattr(args, 'export') and args.export:
if hasattr(args, "export") and args.export:
export_path = Path(args.export)
overwrite = export_path.exists()
export_path.write_text(output, encoding="utf-8")
if overwrite:
print(f"[tickle] Warning: Overwrote existing file: {export_path}")
print(f"[tickle] Output exported to: {export_path}")
# PDF export
if hasattr(args, "export_pdf") and args.export_pdf:
from tickle.output import export_pdf

export_pdf(tasks, args=args, merged=merged, filename=args.export_pdf)
print(f"[tickle] Output exported to PDF: {args.export_pdf}")
return
else:
print(output)

Expand All @@ -117,7 +128,9 @@ def main():
description="Scan repositories for outstanding developer tasks (TODO, FIXME, BUG, NOTE, HACK, CHECKBOX)",
prog="tickle",
)
parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
parser.add_argument(
"--version", action="version", version=f"%(prog)s {__version__}"
)

subparsers = parser.add_subparsers(dest="command", help="Available commands")

Expand All @@ -127,12 +140,17 @@ def main():
# Subcommand: config show
config_parser = subparsers.add_parser("config", help="Configuration management")
config_subparsers = config_parser.add_subparsers(dest="config_command")
config_show_parser = config_subparsers.add_parser("show", help="Show effective configuration")
config_show_parser = config_subparsers.add_parser(
"show", help="Show effective configuration"
)
config_show_parser.add_argument(
"--config", type=str, help="Path to config file to use"
)
config_show_parser.add_argument(
"path", nargs="?", default=".", help="Path to scan (for finding project config)"
"path",
nargs="?",
default=".",
help="Path to scan (for finding project config)",
)

args = parser.parse_args()
Expand All @@ -153,7 +171,9 @@ def main():
prog="tickle",
)

parser.add_argument("path", nargs="?", default=".", help="Path to scan (default: current directory)")
parser.add_argument(
"path", nargs="?", default=".", help="Path to scan (default: current directory)"
)
parser.add_argument(
"--markers",
type=str,
Expand Down Expand Up @@ -206,7 +226,9 @@ def main():
help="Show only directory structure with counts (hide task details)",
)
parser.add_argument(
"--config", type=str, help="Path to config file to use (overrides default search)"
"--config",
type=str,
help="Path to config file to use (overrides default search)",
)
parser.add_argument(
"--no-config", action="store_true", help="Ignore all configuration files"
Expand All @@ -217,14 +239,20 @@ def main():
parser.add_argument(
"--export",
type=str,
help="Export output to a plain text file (overwrites if exists)"
help="Export output to a plain text file (overwrites if exists)",
)
parser.add_argument(
"--export-pdf",
type=str,
help="Export output to a color PDF file (A4, portrait, overwrites if exists)",
)
parser.add_argument(
"--version", action="version", version=f"%(prog)s {__version__}"
)
parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")

args = parser.parse_args()
handle_scan_command(args)


# Entry point for pyproject.toml scripts
app = main

20 changes: 15 additions & 5 deletions src/tickle/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ def get_user_config_path() -> Path | None:
return None


def find_config_file(start_path: str = ".", config_override: str | None = None) -> Path | None:
def find_config_file(
start_path: str = ".", config_override: str | None = None
) -> Path | None:
"""Find config file in precedence order.

Args:
Expand All @@ -71,7 +73,9 @@ def find_config_file(start_path: str = ".", config_override: str | None = None)
config_path = Path(config_override)
if config_path.exists():
return config_path
warnings.warn(f"Config file not found: {config_override}", UserWarning, stacklevel=2)
warnings.warn(
f"Config file not found: {config_override}", UserWarning, stacklevel=2
)
return None

start = Path(start_path).resolve()
Expand Down Expand Up @@ -119,7 +123,9 @@ def load_config(config_path: Path | None = None, validate: bool = True) -> Tickl
with open(config_path, "rb") as f:
data = tomllib.load(f)
except Exception as e:
warnings.warn(f"Failed to parse config file {config_path}: {e}", UserWarning, stacklevel=2)
warnings.warn(
f"Failed to parse config file {config_path}: {e}", UserWarning, stacklevel=2
)
return TickleConfig()

# Handle pyproject.toml [tool.tickle] section
Expand Down Expand Up @@ -282,7 +288,9 @@ def merge_config_with_args(config: TickleConfig, args) -> dict:
result["ignore_patterns"] = config.ignore
elif hasattr(args, "ignore"):
result["ignore_patterns"] = (
[p.strip() for p in args.ignore.split(",") if p.strip()] if args.ignore else []
[p.strip() for p in args.ignore.split(",") if p.strip()]
if args.ignore
else []
)
else:
result["ignore_patterns"] = []
Expand Down Expand Up @@ -378,7 +386,9 @@ def create_minimal_config() -> str:
"""


def format_config_for_display(config: TickleConfig, config_path: Path | None = None) -> str:
def format_config_for_display(
config: TickleConfig, config_path: Path | None = None
) -> str:
"""Format configuration for human-readable display.

Args:
Expand Down
Loading