Metadata-Version: 2.4
Name: topmark
Version: 0.9.0
Summary: A Python CLI to inspect, validate, and manage license and copyright headers.
Author-email: Olivier Biot <olivier.biot@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/shutterfreak/topmark
Project-URL: Documentation, https://topmark.readthedocs.io/en/latest/
Project-URL: Source, https://github.com/shutterfreak/topmark
Project-URL: Changelog, https://github.com/shutterfreak/topmark/releases
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Operating System :: OS Independent
Requires-Python: <3.14,>=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pathspec
Requires-Dist: tomlkit
Requires-Dist: toml
Requires-Dist: yachalk
Requires-Dist: click
Provides-Extra: dev
Requires-Dist: ruff; extra == "dev"
Requires-Dist: pydoclint; extra == "dev"
Requires-Dist: pylint; extra == "dev"
Requires-Dist: pyright; extra == "dev"
Requires-Dist: mdformat; extra == "dev"
Requires-Dist: mdformat-tables; extra == "dev"
Requires-Dist: taplo; extra == "dev"
Requires-Dist: pip-tools>=7.4; extra == "dev"
Requires-Dist: pre-commit; extra == "dev"
Requires-Dist: tox>=4.0; extra == "dev"
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"
Provides-Extra: docs
Requires-Dist: mkdocs>=1.6.0; extra == "docs"
Requires-Dist: mkdocs-material>=9.5.19; extra == "docs"
Requires-Dist: mkdocstrings[python]>=0.29.0; extra == "docs"
Requires-Dist: mkdocs-autorefs>=0.6.0; extra == "docs"
Requires-Dist: mkdocs-include-markdown-plugin>=7.2.0; extra == "docs"
Requires-Dist: pymdown-extensions>=10.16; extra == "docs"
Requires-Dist: mkdocs-gen-files<0.7,>=0.5; extra == "docs"
Requires-Dist: mkdocs-simple-hooks>=0.1.5; extra == "docs"
Requires-Dist: ruff>=0.5.0; extra == "docs"
Provides-Extra: test
Requires-Dist: pytest>=8.0; extra == "test"
Requires-Dist: pytest-xdist; extra == "test"
Requires-Dist: hypothesis>=6.0; extra == "test"
Provides-Extra: typing
Requires-Dist: types-toml; extra == "typing"
Dynamic: license-file

<!--
topmark:header:start

  project      : TopMark
  file         : README.md
  file_relpath : README.md
  license      : MIT
  copyright    : (c) 2025 Olivier Biot

topmark:header:end
-->

# TopMark

[![PyPI version](https://img.shields.io/pypi/v/topmark.svg)](https://pypi.org/project/topmark/)
[![Documentation Status](https://readthedocs.org/projects/topmark/badge/?version=latest)](https://topmark.readthedocs.io/en/latest/?badge=latest)
[![Downloads](https://static.pepy.tech/badge/topmark)](https://pepy.tech/project/topmark)
[![GitHub release](https://img.shields.io/github/v/release/shutterfreak/topmark)](https://github.com/shutterfreak/topmark/releases)

**TopMark** is a command-line tool to inspect, validate, and manage file headers in diverse
codebases. It helps maintain consistent header metadata across projects by supporting per-file-type
header formats, customizable fields, inclusion/exclusion rules, and dry-run safety.

## 📚 Documentation

Full documentation is available on Read the Docs: <https://topmark.readthedocs.io>

This README is the canonical, complete introduction for GitHub/PyPI. The docs site provides a
concise landing page and deep links into topics (install, usage, CI, API, etc.).

## 📝 Example headers

TopMark inserts headers that are tailored to the comment style and conventions of each file type
(line comments like `#`/`//`, and block comments like `/* … */`). Here are examples of how TopMark
headers appear in different file formats:

```bash
#!/bin/bash

# topmark:header:start
#
#   project   : TopMark
#   file      : script.sh
#   license   : MIT
#   copyright : (c) 2025 Olivier Biot

# topmark:header:end

echo "Hello, World!"
```

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!--
topmark:header:start

  project   : TopMark
  file      : config.xml
  license   : MIT
  copyright : (c) 2025 Olivier Biot

topmark:header:end
-->

<configuration>
    <!-- XML content here -->
</configuration>
```

```javascript
// topmark:header:start
//
//   project   : TopMark
//   file      : app.js
//   license   : MIT
//   copyright : (c) 2025 Olivier Biot
//
// topmark:header:end

console.log("Hello, World!");
```

```css
/*
 * topmark:header:start
 *
 *   project   : TopMark
 *   file      : styles.css
 *   license   : MIT
 *   copyright : (c) 2025 Olivier Biot
 *
 * topmark:header:end
 */

body { margin: 0; }
```

## ✨ Features

- File header detection, insertion, and replacement
- Supports multiple file types (Python, Makefile, Markdown, `.env`, ...)
- Configurable comment styles and header fields
- Inclusion and exclusion logic (via CLI, globs, stdin, or config)
- Dry-run by default; safe for CI/CD integration
- Configuration via `pyproject.toml` or `topmark.toml`
- Shell completion for enum-based options
- Colorized CLI output (via `yachalk`)
- Python ≥3.10
- Integrated pre-commit hooks for automated checks
- Formatting and linting support via Makefile targets
- CI-friendly design for safe automated use
- Strict static typing with mypy and Pyright, using PEP 604 union syntax
- Google-style docstrings without redundant type declarations
- Full header removal (`topmark strip`)
- Preserves original newline style (LF/CRLF/CR) and BOM
- Idempotent updates (re-running does not change already-correct files)
- Structured diagnostics with severity levels (`info`, `warning`, `error`) and aggregate counts

## 🚀 Installation

```bash
git clone https://github.com/shutterfreak/topmark.git
cd topmark
make setup  # creates virtualenv, installs dependencies and tools
```

Or install into an existing virtualenv:

```bash
pip install -e .
```

Or install the latest release from PyPI:

```bash
pip install topmark
```

## ⚙️ Usage

> **Note**: In dry-run mode, TopMark shows summaries ending with `- previewed`. When run with
> `--apply`, summaries end with terminal statuses such as `- inserted`, `- replaced`, or
> `- removed`.

```bash
topmark [SUBCOMMAND] [OPTIONS] [PATHS]...
```

The `check` and `strip` commands performs a dry‑run by default and will only apply changes when
`--apply` is provided.

Logging verbosity is controlled globally:

CLI verbosity has two layers:

- **Program output** (user-facing):
  - `-v`, `--verbose`: Show more detail, including per-line diagnostics in summaries.
  - `-q`, `--quiet`: Suppress most output (overrides `-v`).
- **Internal logging** (developer/machine):
  - Also mapped by `-v`/`-q`, controls log level (`DEBUG` / `INFO` / `WARNING` / `ERROR`).

All other options are specific to individual subcommands.

______________________________________________________________________

### Subcommands

| Command         | Description                                        |
| --------------- | -------------------------------------------------- |
| `check`         | Add / Update TopMark headers in files              |
| `dump-config`   | Show the resolved configuration in TOML format     |
| `filetypes`     | List supported file types and their comment styles |
| `processors`    | List registered header processors and file types   |
| `strip`         | Remove TopMark headers from files (destructive)    |
| `version`       | Print TopMark version                              |
| `show-defaults` | Show default config (without merging)              |
| `init-config`   | Output a starter configuration file                |

______________________________________________________________________

### Examples

```bash
# Check Python files in the src/ directory
topmark check --file-type python src/

# Use exclusion patterns, and compute relative paths from src
topmark check --file-type python --exclude .venv --relative-to src src/

# Add one verbosity level to topmark, use exclusion patterns from .gitignore
topmark -v check --file-type python --exclude-from .gitignore src/

# Read a list of files from STDIN, generate summary
find . -name "*.py" | topmark check --file-type python --files-from -  --summary

# Process all files that are either staged, or untracked but not ignored in a Git repo (needs dedulication with `sort -u`)
git ls-files -c -o --exclude-standard | sort -u | topmark check --files-from - --apply

# Read a single file's *content* from STDIN and render the resultng unified diff
cat README.md | topmark check - --stdin-filename README.md --diff

# Read a single file's *content* from STDIN and print the updated content to stdout
cat README.md | topmark check - --stdin-filename README.md --apply

# Dump the merged configuration (after loading all applicable config layers)
topmark dump-config --file-type python --exclude .venv --exclude-from .gitignore

# Display the default configuration without any merging
topmark show-defaults

# Output a starter configuration to stdout
topmark init-config

# Output TopMark version (PEP 440) in JSON format
topmark version --format json

# Output TopMark version (SemVer) to stdout
topmark version --semver

# Apply changes to files in-place
topmark check --apply src/

# Remove headers from files (dry-run)
topmark strip src/

# Remove headers from files and apply changes
topmark strip --apply src/

# CI-friendly summary: only show issues; ignore unsupported types
topmark check --skip-compliant --skip-unsupported src/

# Apply fixes, don't report unsupported files; default output is terse (pre-commit friendly)
topmark check --apply --skip-unsupported

# Show supported file types in MarkDown format, providing detailed output:
topmark filetypes --format markdown --long

# Show registered header processors (brief)
topmark processors

# Detailed Markdown listing of processors and their file types
topmark processors --format markdown --long
```

______________________________________________________________________

## 🔌 Public API (stable)

TopMark exposes a small, stable Python API for integrations. Import from `topmark.api` for core
operations, and use the registry facade for read-only discovery.

### Quickstart

```python
from pathlib import Path
from topmark import api

# Check (dry-run by default)
result = api.check([Path("src")])
print(result.summary)           # {"unchanged": N, "error": M, ...}
print(result.had_errors)        # True/False

# Apply changes explicitly
applied = api.check([Path("src")], apply=True)

# Remove headers
stripped = api.strip([Path("src")], apply=False)
```

### Registry facade (read-only)

```python
from topmark.registry import Registry

fts = Registry.filetypes()      # Mapping[str, FileType]
procs = Registry.processors()   # Mapping[str, HeaderProcessor]
for b in Registry.bindings():   # (filetype, optional processor)
    print(b.filetype.name, bool(b.processor))
```

> **Note:** `Registry` is **not** re-exported from `topmark.api`; import it from `topmark.registry`.

### Version helper

```python
from topmark import api
print(api.version())  # e.g. "1.2.3"
```

### Stability

- `topmark.api` and the `Registry` facade are covered by semantic versioning.
- Low-level registries (`FileTypeRegistry`, `HeaderProcessorRegistry`) remain available for
  plugins/tests but are **advanced** and may change between minor versions.

### Adding & updating headers

Use the `topmark check` command to insert or update headers. It’s **dry‑run by default**; add
`--apply` to write changes. Placement follows file‑type policy (shebang/encoding for Python, XML
declaration for XML/HTML, etc.). Newline style and BOM are preserved; runs are idempotent.

#### Quick examples

```bash
# Preview (exit code 2 when changes are pending)
topmark check src/

# Apply in place
topmark check --apply src/
```

> For full details, options, examples, and exit codes, see the dedicated guide:
> **[Adding & updating headers with `topmark`](docs/usage/commands/check.md)**

### Removing headers with `strip`

Use `topmark strip` to remove the entire TopMark header block. The command is **dry-run by
default**; add `--apply` to write changes. It preserves newline style and BOM, and keeps XML
declarations and Markdown code fences intact.

#### Quick examples

```bash
# Preview (exit code 2 when removals are pending)
topmark strip src/

# Apply in place
topmark strip --apply src/
```

> For full details, options, examples, and exit codes, see the dedicated guide:
> **[Removing headers with `topmark strip`](docs/usage/commands/strip.md)**

### Dumping the merged configuration

Use `topmark dump-config` to print the **effective** configuration (defaults → discovered config →
`--config` files → CLI flags) as TOML. This command is **file‑agnostic**: positional paths and
`--files-from` are ignored; filter flags (`--include/--exclude`, `--include-from/--exclude-from`)
are honored.

```bash
# Dump merged configuration
topmark dump-config

# Include/exclude filters still apply
topmark dump-config --exclude .venv --exclude-from .gitignore

# Read patterns from STDIN
printf "*.py\n" | topmark dump-config --include-from -
```

> For details and notes, see:
> **[Dumping the merged configuration with `dump-config`](docs/usage/commands/dump_config.md)**

### Listing supported file types

Use `topmark filetypes` to see the identifiers you can pass to `--file-type` and the associated
comment styles TopMark will use when rendering headers.

```bash
# Show available file type identifiers
topmark filetypes

# Then restrict processing to one or more types
topmark check --file-type python src/
topmark check --file-type markdown --file-type env docs/ config/
```

> For details, see:
> **[Listing supported file types with `filetypes`](docs/usage/commands/filetypes.md)**

## 📐 Header placement rules

TopMark is comment-aware and places the header block according to the file type and its policy.

The complete header placement rules are documented in the usage guide:

- [Header placement rules](docs/usage/header-placement.md)

## 🧩 Supported file types

See the full list and resolver behavior in the dedicated guide:

- [Supported file types](docs/usage/filetypes.md)

## 🪝 Pre-commit integration

TopMark ships with pre-commit hooks to validate or update file headers.

- **`topmark-check`** — runs automatically at `pre-commit` / `pre-push`, fails if headers need
  changes.
- **`topmark-apply`** — manual only; applies fixes and may modify files.

For configuration examples, hook policies, and troubleshooting, see the dedicated guide:

- [Using TopMark with pre-commit](docs/usage/pre-commit.md)

## 🛠 Configuration

TopMark supports **layered configuration discovery** with clear precedence and path semantics.
Configuration can come from multiple sources and is merged in the following order:

1. **Built‑in defaults** (`topmark-default.toml`)

1. **User config** (`~/.config/topmark/topmark.toml` or `~/.topmark.toml`)

1. **Project chain** discovered upward from the **discovery anchor**: (first input path; its parent
   if it’s a file; otherwise CWD when no inputs)

   - `pyproject.toml` (`[tool.topmark]` table)
   - `topmark.toml` (tool‑specific file)
   - same‑directory precedence: `pyproject.toml` → `topmark.toml`
   - stops when `root = true` is found

1. **Explicit config files** via `--config` (you can specify one or more `--config` files)

1. **CLI flags and options** (highest precedence)

Each layer is merged into a single effective configuration, which can be inspected with
`topmark dump-config`.

TopMark resolves paths relative to **where they are defined**:

- **Config‑declared globs** are resolved relative to the **config file’s directory**.
- **CLI‑declared globs** are resolved relative to the **current working directory**.
- **Path‑to‑file settings** (e.g., `exclude_from`, `files_from`) are resolved relative to the
  **declaring source** (config dir or CWD for CLI).
- `relative_to` affects only header metadata (e.g., `file_relpath`), not discovery.

> For complete details and examples, see\
> [`Configuration: Discovery & Precedence`](docs/configuration/discovery.md).

### Example (`topmark.toml`)

```toml
[fields]
project = "TopMark"
license = "MIT"
copyright = "(c) 2025 Olivier Biot"

[header]
fields = [ "file", "file_relpath", "project", "license", "copyright",]

[formatting]
align_fields = true
raw_header = false

[files]
file_types = [ "python", "markdown", "env" ]
include = []
include_from = []
exclude = []
exclude_from = [ ".gitignore" ]
relative_to = "."
```

### Notes

- `formatting.align_fields = true` vertically aligns field names for readability.
- File-type–specific behavior (shebang handling, XML prolog, blank-line policies) is driven by the
  internal **FileTypeHeaderPolicy** defaults.
- Use `topmark dump-config` to see the **effective merged configuration** after all layers and
  overrides are applied.

The `EnumParam` class enables shell completion for enum-based CLI options.

## 🧪 Development Setup

For development setup and contribution guidelines, see [CONTRIBUTING.md](CONTRIBUTING.md).

To verify compatibility across supported Python versions (3.10–3.13), use `tox` to run tests and
type checks in each environment.

## 📄 License

MIT License © 2025 Olivier Biot

> **[LICENSE (MIT)](LICENSE)**
