Skip to content

Commit

Permalink
docs: init cmd and func
Browse files Browse the repository at this point in the history
  • Loading branch information
antazoey committed Sep 5, 2024
1 parent c9b19f2 commit 60477ad
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 59 deletions.
25 changes: 14 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,28 @@ The purpose of this plugin to share code for generating documentation across all

- [python3](https://www.python.org/downloads) version 3.9 up to 3.12.

## Quick Usage
## Install

To use this sphinx plugin, create a `docs/` folder in your Python package.
Inside this folder, create a `conf.py` with the following content:
Install using `pip` or `uv` by either cloning this repo or accessing from pypi, e.g.:

```txt
extensions = ["sphinx_ape"]
```sh
pip install sphinx-ape
```

Then, create an `index.rst` file with the following content:
Try `sphinx-ape --help` to check if it's installed.

## Quick Usage

To use this sphinx plugin, first generate the docs structure (ran from your project directory):

```txt
.. dynamic-toc-tree::
```sh
sphinx-ape init
```

You don't have to configure anything else; it will just work.
It will have generated a `docs/` folder with some necessary config file in it, along with a quick-start that links to your `README.md`.

Now, you can begin writing your documentation.
There are three directories you can create:
Now, you can begin writing your Sphinx documentation.
There are three directories you can place documentation sources in:

1. `userguides/` - a directory containing how-to guides for how to use your package.
2. `commands/` - `.rst` files for the `sphinx-click` plugin for CLI-based references.
Expand Down
11 changes: 11 additions & 0 deletions sphinx_ape/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@ def package_name_option():
)


@cli.command()
@click.argument("base_path", type=Path)
def init():
"""
Initialize documentation structure
"""
# For this command, force the user to be in that dir.
builder = DocumentationBuilder(base_path=Path.cwd())
builder.init()


@cli.command()
@click.argument("base_path", type=Path)
@build_mode_option()
Expand Down
78 changes: 75 additions & 3 deletions sphinx_ape/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,20 @@ class DocumentationBuilder:
"""

def __init__(
self, mode: BuildMode, base_path: Optional[Path] = None, name: Optional[str] = None
self,
mode: Optional[BuildMode] = None,
base_path: Optional[Path] = None,
name: Optional[str] = None,
) -> None:
self.mode = mode
self.mode = BuildMode.LATEST if mode is None else mode
self._base_path = base_path or Path.cwd()
self._name = name or get_package_name()

@cached_property
def docs_path(self) -> Path:
path = self._base_path / "docs"
if not path.is_dir():
raise ApeDocsBuildError("No `docs/` folder found.")
raise ApeDocsBuildError(f"No `docs/` folder found (checked {path}.")

return path

Expand All @@ -75,6 +78,26 @@ def latest_path(self) -> Path:
def stable_path(self) -> Path:
return self.build_path / "stable"

@property
def userguides_path(self) -> Path:
return self.docs_path / "userguides"

@property
def commands_path(self) -> Path:
return self.docs_path / "commands"

@property
def methoddocs_path(self) -> Path:
return self.docs_path / "methoddocs"

def init(self):
if not self.docs_path.is_dir():
self.docs_path.mkdir()

self._ensure_quickstart_exists()
self._ensure_conf_exists()
self._ensure_index_exists()

def build(self):
if self.mode is BuildMode.LATEST:
# TRIGGER: Push to 'main' branch. Only builds latest.
Expand Down Expand Up @@ -115,6 +138,24 @@ def build_release(self):
for path in (self.stable_path, self.latest_path):
replace_tree(build_dir, path)

@property
def userguide_names(self) -> list[str]:
guides = self._get_filenames(self.userguides_path)
quickstart_name = "userguides/quickstart"
if quickstart_name in guides:
# Make sure quick start is first.
guides = [quickstart_name, *[g for g in guides if g != quickstart_name]]

return guides

@property
def cli_reference_names(self) -> list[str]:
return self._get_filenames(self.commands_path)

@property
def methoddoc_names(self) -> list[str]:
return self._get_filenames(self.methoddocs_path)

def _setup_redirect(self):
self.build_path.mkdir(exist_ok=True, parents=True)

Expand All @@ -128,3 +169,34 @@ def _setup_redirect(self):

def _sphinx_build(self, dst_path):
sphinx_build(dst_path, self.docs_path)

def _get_filenames(self, path: Path) -> list[str]:
if not path.is_dir():
return []

return sorted([g.stem for g in path.iterdir() if g.suffix in (".md", ".rst")])

def _ensure_conf_exists(self):
conf_file = self.docs_path / "conf.py"
if conf_file.is_file():
return

content = 'extensions = ["sphinx_ape"]\n'
conf_file.write_text(content)

def _ensure_index_exists(self):
index_file = self.docs_path / "index.rst"
if index_file.is_file():
return

content = ".. dynamic-toc-tree::\n"
index_file.write_text(content)

def _ensure_quickstart_exists(self):
quickstart_path = self.userguides_path / "quickstart.md"
if quickstart_path.is_file():
# Already exists.
return

self.userguides_path.mkdir(exist_ok=True)
quickstart_path.write_text("```{include} ../../README.md\n```\n")
10 changes: 10 additions & 0 deletions sphinx_ape/docs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from pathlib import Path


class Documentation:
"""
A class abstracting access to all the documentation.
"""

def __init__(self, base_path: Path) -> None:
self._base_path = base_path
34 changes: 7 additions & 27 deletions sphinx_ape/sphinx_ext/directives.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from docutils.parsers.rst import directives
from sphinx.util.docutils import SphinxDirective

from sphinx_ape.build import DocumentationBuilder


class DynamicTocTree(SphinxDirective):
"""
Expand Down Expand Up @@ -38,16 +40,8 @@ def _title_rst(self) -> str:
return f"{title}\n{bar}"

@property
def userguides_path(self) -> Path:
return self._base_path / "userguides"

@property
def commands_path(self) -> Path:
return self._base_path / "commands"

@property
def methoddocs_path(self) -> Path:
return self._base_path / "methoddocs"
def builder(self) -> DocumentationBuilder:
return DocumentationBuilder(base_path=self._base_path.parent)

def run(self):
userguides = self._get_userguides()
Expand Down Expand Up @@ -83,24 +77,10 @@ def run(self):
return self.parse_text_to_nodes(restructured_text)

def _get_userguides(self) -> list[str]:
guides = self._get_doc_entries(self.userguides_path)
quickstart_name = "userguides/quickstart"
if quickstart_name in guides:
# Make sure quick start is first.
guides = [quickstart_name, *[g for g in guides if g != quickstart_name]]

return guides
return [f"userguides/{n}" for n in self.builder.userguide_names]

def _get_cli_references(self) -> list[str]:
return self._get_doc_entries(self.commands_path)
return [f"commands/{n}" for n in self.builder.cli_reference_names]

def _get_methoddocs(self) -> list[str]:
return self._get_doc_entries(self.methoddocs_path)

def _get_doc_entries(self, path: Path) -> list[str]:
if not path.is_dir():
return []

return sorted(
[f"{path.name}/{g.stem}" for g in path.iterdir() if g.suffix in (".md", ".rst")]
)
return [f"methoddocs/{n}" for n in self.builder.methoddoc_names]
26 changes: 8 additions & 18 deletions sphinx_ape/sphinx_ext/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,21 @@
import sys
from pathlib import Path

from sphinx.util import logging
from sphinx.application import Sphinx
from sphinx.util import logging

from sphinx_ape.build import DocumentationBuilder
from sphinx_ape.sphinx_ext.directives import DynamicTocTree
from sphinx_ape.utils import get_package_name


logger = logging.getLogger(__name__)


def ensure_quickstart_exists(app, cfg):
"""
This will generate the quickstart guide if needbe.
I recommend committing the files it generates.
"""

userguides_path = Path(app.srcdir) / "userguides"
quickstart_path = userguides_path / "quickstart.md"
if quickstart_path.is_file():
# Already exists.
return

logger.info("Generating userguides/quickstart.md")
userguides_path.mkdir(exist_ok=True)
quickstart_path.write_text("```{include} ../../README.md\n```\n")
def config_init_hook(app, cfg):
base_path = Path(app.srcdir.parent)
builder = DocumentationBuilder(base_path)
# Ensure the necessary files exist.
builder.init()


def setup(app: Sphinx):
Expand All @@ -37,7 +27,7 @@ def setup(app: Sphinx):
sys.path.insert(0, os.path.abspath(".."))

# Register the hook that generates the quickstart file if needbe.
app.connect("config-inited", ensure_quickstart_exists)
app.connect("config-inited", config_init_hook)

# Configure project and other one-off items.
package_name = get_package_name()
Expand Down

0 comments on commit 60477ad

Please sign in to comment.