Metadata-Version: 2.5
Name: epythet
Version: 0.2.1
Summary: Beautiful, correct documentation from a Python package, with no boilerplate: Sphinx, README landing page, nested API tree, themes, docstring normalizer, agent-facing outputs, GitHub Pages
Project-URL: Homepage, https://github.com/i2mint/epythet
Author-email: andie.phan@analog.com
License: Apache-2.0
License-File: LICENSE
Keywords: documentation,github-pages,llms.txt,publishing,sphinx
Requires-Python: >=3.11
Requires-Dist: cw<0.2,>=0.1.1
Requires-Dist: dol
Requires-Dist: furo>=2024
Requires-Dist: myst-parser<6,>=5.1
Requires-Dist: pydata-sphinx-theme>=0.16
Requires-Dist: requests
Requires-Dist: shibuya>=2025
Requires-Dist: sphinx-autoapi<4,>=3.8
Requires-Dist: sphinx-autodoc-typehints>=3
Requires-Dist: sphinx-copybutton>=0.5
Requires-Dist: sphinx-llm<2,>=1
Requires-Dist: sphinx-markdown-builder>=0.6
Requires-Dist: sphinx<10,>=9
Requires-Dist: sphinxawesome-theme>=5
Requires-Dist: sphinxcontrib-mermaid>=1
Provides-Extra: dev
Requires-Dist: hubcap; extra == 'dev'
Requires-Dist: pandas; extra == 'dev'
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: ruff>=0.1.0; extra == 'dev'
Provides-Extra: docs
Provides-Extra: pdf
Requires-Dist: playwright>=1.40; extra == 'pdf'
Provides-Extra: themes
Requires-Dist: sphinx-book-theme>=1.1; extra == 'themes'
Requires-Dist: sphinx-rtd-theme>=3; extra == 'themes'
Description-Content-Type: text/markdown

# epythet

Beautiful, correct documentation from a Python package, with no boilerplate in the package.
Less humdrum, more automation, earlier at the pub.

[Full documentation here](https://i2mint.github.io/epythet/), generated by epythet.

```bash
pip install epythet
epythet quickstart /path/to/project --ignore tests/ scrap/ examples/
```

Open `/path/to/project/docsrc/_build/html/index.html`. You get:

- a landing page that **is your README** (badges, images, GitHub alerts and mermaid fences intact),
- a **nested API tree** built from your package layout, one page per module,
- **RST field lists and Google/NumPy sections rendered side by side**, types linked from annotations,
- a modern theme with light/dark mode and an accent colour derived from your package name,
- **agent-facing twins**: `llms.txt`, a `.md` twin of every page, a flat `<package>.md`, and `objects.inv`.

Nothing has to be added to the package. Everything is read from `pyproject.toml` (or `setup.cfg`), the README and the docstrings.

# What it fixes without touching your docstrings

Docstrings in real packages mix reStructuredText, Google sections and Markdown habits, and a few recurring slips render wrongly, often silently. epythet rewrites those at build time (the *normalizer*), so the rendered site is right even when the source is not:

| you wrote | what happened before | what epythet renders |
|---|---|---|
| a `>>>` block right after a sentence | a paragraph starting with `>>>`; never run by `sphinx.ext.doctest` | a doctest block |
| ```` ```python ```` fences | the backticks printed literally | a highlighted code block |
| `Returns: the answer` on one line | a sentence | a *Returns* section |
| `## Heading` | a literal `##` | a heading |
| `*args` / `**kwargs` in prose | an "emphasis start-string without end-string" error | escaped, as written |
| `[text](url)` | printed literally | a link |
| a wrapped list item at the bullet's indentation | "bullet list ends without a blank line" | a list item |
| `Examples:` followed by an unindented doctest | a stray "Examples:" paragraph | an *Examples* rubric |

Single backticks render as code (`default_role = "code"`), matching the Markdown habit. Code inside doctests, literal blocks and fences is never touched. The rules are pure functions in `epythet.normalizer`; you can see what one docstring becomes with `epythet.normalize_text(docstring)`.

On the `dol` package (48 modules, 23,000 lines of doctests) this took the build from 91 Sphinx warnings and 266 detected rendering artifacts to 25 and 55, with every doctest that autodoc documented still documented.

# Configuration: `[tool.epythet]`

All optional. Omit the section and you get the defaults below.

```toml
[tool.epythet]
display_name = "Dol"            # site title; default: the project name
copyright = "2024, Jane Doe"    # footer; default: no copyright line at all
theme = "auto"                  # "auto" | "furo" | "shibuya" | "pydata" | "sphinxawesome" | "book" | "alabaster" | "rtd" | any installed theme
accent = "#3661ac"              # default: derived from the package name (OKLCH, WCAG AA on white by construction)
mode = "auto"                   # "auto" | "light" | "dark"  (where the theme supports forcing it)
ignore = ["tests/", "scrap/", "examples/"]   # path substrings to skip; `--ignore` on the CLI overrides
api_generator = "autosummary"   # "autosummary" (imports the package) | "autoapi" (static parsing, no import)
agent_outputs = true            # llms.txt, .md twins, <link rel="alternate"> relations
aggregates = ["md"]             # flat single-document twins at the site root: "md", "pdf"
package_dir = "src/dol"         # default: found by convention (<name>/ or src/<name>/)
docs_dir = "docsrc"             # where the Sphinx sources are generated

[tool.epythet.theme_options]    # verbatim passthrough into Sphinx's html_theme_options; always wins
announcement = "v2 is in beta"
```

`setup.cfg` projects put the same keys under `[metadata]` (`display_name`, `copyright`) or a `[tool.epythet]` section. When both files exist, `pyproject.toml` wins.

**Themes.** `theme = "auto"` (the default) hashes the package name into a curated pool (furo, shibuya, pydata-sphinx-theme, sphinxawesome-theme) so a fleet of packages gets variety while every package keeps the same look across rebuilds. The pool's themes are installed with epythet; `sphinx-book-theme` and `sphinx_rtd_theme` come with `pip install "epythet[themes]"`. The accent is one hue per package, at a fixed perceptual lightness, so every possible colour clears WCAG AA against white and AAA on a dark background; an explicit `accent` is used as given in light mode and lifted to the same dark-mode lightness for dark mode.

**API generator.** `autosummary` (Sphinx built-in) imports your package, so aliases, `functools.partial` objects and other assigned names keep the docstring of what they point to. `autoapi` parses statically and needs no import: use it when the package cannot be imported in CI. Both give the nested tree; both run the normalizer. Under `autosummary`, `ignore` keeps the ignored modules out of the tree, but Python still imports them once while discovering the package.

**PDF aggregate.** `aggregates = ["md", "pdf"]` renders `<package>.pdf` from the Markdown aggregate with Playwright (`pip install "epythet[pdf]" && playwright install chromium`) or WeasyPrint, whichever is installed. No LaTeX.

# The generated `docsrc/`

`epythet quickstart` (or `epythet make-docsrc`) writes a `docsrc/` directory holding a two-line `conf.py`:

```python
from epythet.sphinx_conf import *  # noqa: F401,F403
```

and an `index.md` that includes your README and a hidden toctree for the API pages. That is the whole scaffold; the API pages and the agent outputs are generated at build time. You do not need to commit `docsrc/` (CI regenerates it), but if you do, the shim is the single source of truth: put project-specific Sphinx overrides below the import and they win over the generated values. A hand-written `conf.py` without the import is never overwritten.

`epythet make PROJECT_DIR [html|doctest|markdown|github|clean]` runs `sphinx-build` with the current interpreter; there is no Makefile. `github` builds HTML and copies it into `PROJECT_DIR/docs`. `doctest` is Sphinx's doctest builder, which runs examples without the module's namespace; for docstring doctests use `pytest --doctest-modules`.

# For agents

Every site also serves, next to the HTML:

- `llms.txt`: an index of every page with a one-line description,
- `<page>.html.md`: a fully rendered Markdown twin of every page, advertised from each page's `<head>` with `<link rel="alternate" type="text/markdown">`,
- `<package>.md`: the whole documentation as one Markdown file, linked from the landing page (and `<package>.pdf` when enabled),
- `objects.inv`: the Sphinx inventory, a machine-readable symbol-to-URL index (`sphobjinv convert plain objects.inv -`).

Set `agent_outputs = false` to skip the second (Markdown) build pass.

# Python API

```python
from epythet import (
    quickstart,
    make_docsrc,
    make,
    load_config,
    sphinx_settings,
    normalize_text,
)

quickstart(
    "/path/to/project", ignore=["tests/"]
)  # scaffold + build; returns the html dir
cfg = load_config("/path/to/project")  # the resolved DocsConfig
sphinx_settings(cfg)  # the conf.py namespace as a dict
```

Diagnosis and repair of docstring formatting in *source* files (missing blank lines before doctests) is unchanged: `epythet.diagnose_doctest_code_blocks`, `epythet.repair_package`.

# Publishing to GitHub Pages with GitHub Actions

## Step 1: Add the CI workflow

Add a workflow such as [.github/workflows/publish-docs.yml](https://github.com/i2mint/epythet/blob/master/.github/workflows/publish-docs.yml) to your repo and adjust the trigger. The example below runs after the "Continuous Integration" workflow completes.

```yaml
name: GitHub Pages

on:
  workflow_run:
    workflows: ["Continuous Integration"]
    types:
      - completed

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: i2mint/epythet/actions/publish-github-pages@master
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          ignore: "tests/,scrap/,examples/"
          python-version: "3.12"
```

The action installs epythet, installs your project, runs `epythet quickstart . --ignore ...` and pushes `./docsrc/_build/html/` to the `gh-pages` branch.

## Step 2: Enable GitHub Pages

After the CI runs and creates the `gh-pages` branch, you need to tell GitHub to actually serve it. There are two ways to do this:

### The clicky way (for those who enjoy navigating settings menus)

Go to your repo's **Settings > Pages**, set the source branch to `gh-pages` and the folder to `/ (root)`, then click Save.

![image](https://user-images.githubusercontent.com/22692594/212193474-80b287e2-211c-470d-aa7c-9f779bdd3866.png)

### The fast way (for those who value their time)

If you have the [`gh` CLI](https://cli.github.com/) installed:

```bash
# Check if Pages is set up correctly
epythet check-pages owner/repo

# Enable or fix Pages configuration
epythet configure-pages owner/repo
```

Or from Python:

```python
from epythet import check_pages_setup, enable_pages

# Diagnose
check_pages_setup("owner/repo")

# Fix
enable_pages("owner/repo")
```

You can also point these at a local git checkout instead of `owner/repo`:

```bash
epythet check-pages .
epythet configure-pages /path/to/my/project
```

These tools work with either the `gh` CLI (recommended) or a `GITHUB_TOKEN`
environment variable.

Under the hood, `configure-pages` is just the GitHub Pages REST API — the direct
`gh` equivalent of *Settings > Pages → Branch `gh-pages`, folder `/ (root)` →
Save* is:

```bash
# POST creates the Pages site (when Pages is not yet enabled — GitHub's default);
# use -X PUT instead to change an already-enabled Pages config.
gh api repos/owner/repo/pages -X POST -f 'source[branch]=gh-pages' -f 'source[path]=/'
```

See [CI epythet troubleshooting](https://github.com/i2mint/epythet/wiki/CI-epythet-troubleshooting).


# Upgrading from epythet 0.1.x

epythet 0.2 keeps the contract the fleet depends on and changes what is behind it:

- `epythet quickstart DIR --ignore ...` still writes HTML to `DIR/docsrc/_build/html/`; the `--ignore` flag with no values still means "use the default".
- `make_docsrc`, `make_autodocs`, `make` and `quickstart` are still importable from `epythet` (and from `epythet.setup_docsrc` / `epythet.call_make`); `make_autodocs` is now a no-op alias of `make_docsrc`, since API pages are generated at build time.
- `epythet.config_parser.parse_config` keeps its 5-tuple `(name, copyright, author, version, display_name)`, so a committed 0.1.x `docsrc/conf.py` keeps working. It now resolves the project directory whatever path it is given, so `pyproject.toml` wins over a stale `setup.cfg` (0.1.x silently preferred `setup.cfg`).
- A committed 0.1.x `docsrc/` (template `conf.py`, `index.rst`, `table_of_contents.rst`, `module_docs/`, Makefile) is recognised and replaced by the new scaffold on the next `quickstart`.
- Dropped: the `sphinx_rtd_theme` default (furo-class themes replace it), `sphinx-toggleprompt` (copybutton already strips prompts), `commonmark`, and the `Makefile`. Requires Python 3.11+, Sphinx 9, myst-parser 5.1; a project that pins `sphinx<9` or `docutils<0.22` in its own dependencies will conflict with epythet 0.2 in the same environment.
- URLs of API pages changed (`module_docs/<pkg>/<mod>.html` is now `_autosummary/<pkg>.<mod>.html`); `objects.inv` keeps every symbol resolvable across sites.

The publish action pins `epythet<0.2` until v2 is validated across the fleet; see the [v2 decision record](https://github.com/i2mint/epythet/discussions/15) and the [tracking issue](https://github.com/i2mint/epythet/issues/16).
