Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
ba8692e
Add scaffolding for implementation of usethis tool mkdocs
nathanjmcdougall Jun 28, 2025
bc45b5c
Add mkdocs to ALL_TOOL_COMMANDS
nathanjmcdougall Jun 30, 2025
d4f4c69
Add basic implementation of MkDocsTool class
nathanjmcdougall Jun 30, 2025
67d08a9
Add `MkDocsTool` to `ALL_TOOLS` and `SupportedToolType`
nathanjmcdougall Jun 30, 2025
25aa509
Add scaffolding for YAMLFileManager
nathanjmcdougall Jun 30, 2025
aa42592
Merge branch 'main' into 188-implement-usethis-tool-mkdocs
nathanjmcdougall Jul 12, 2025
feab37f
Merge branch 'main' into 188-implement-usethis-tool-mkdocs
nathanjmcdougall Jul 12, 2025
ad801b2
Add `use_mkdocs` to `use_tool`
nathanjmcdougall Jul 12, 2025
97b5f5d
Implement `get_config_spec` for `MkDocsTool`
nathanjmcdougall Jul 13, 2025
0e0a59d
Add `MkDocsTool` to `OTHER_TOOLS` list for `pyproject.toml`
nathanjmcdougall Jul 13, 2025
b649a02
Update usage table for `usethis list`
nathanjmcdougall Jul 13, 2025
9ec1f0e
Implement `use_mkdocs(..., remove=True)`
nathanjmcdougall Jul 15, 2025
10f4cfd
Add tests for `use_mkdocs(..., remove=True)`
nathanjmcdougall Jul 15, 2025
55a0db4
Add tests for `src\usethis\_integrations\mkdocs\core.py` and `use_mkd…
nathanjmcdougall Jul 15, 2025
4e49e92
Add tests for `MkDocsTool.print_how_to_use`
nathanjmcdougall Jul 15, 2025
8ef5bd8
Fix typo in comment in `src/usethis/_tool/impl/mkdocs.py`
nathanjmcdougall Jul 15, 2025
68d6d3f
Update README to document `usethis mkdocs`
nathanjmcdougall Jul 15, 2025
08620ef
Merge branch '188-implement-usethis-tool-mkdocs' of https://github.co…
nathanjmcdougall Jul 15, 2025
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
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,10 @@ declaring dependencies with `uv add`.
- `usethis tool coverage.py` - Use [Coverage.py](https://github.com/nedbat/coveragepy): a code coverage measurement tool.
- `usethis tool pytest` - Use the [pytest](https://github.com/pytest-dev/pytest) testing framework.

#### Documentation

- `usethis tool mkdocs` - Use [MkDocs](https://www.mkdocs.org/): project documentation sites with Markdown.

#### Configuration Files

- `usethis tool pyproject.toml` - Use a [pyproject.toml](https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#writing-your-pyproject-toml) file to configure the project.
Expand Down Expand Up @@ -497,9 +501,8 @@ If you're using Cookiecutter, then you can update to a latest version of a templ
Major features planned for later in 2025 are:

- Support for users who aren't using uv, e.g. poetry users,
- Support for automated GitHub Actions workflows ([#57](https://github.com/usethis-python/usethis-python/issues/57)),
- Support for a typechecker (likely Pyright, [#121](https://github.com/usethis-python/usethis-python/issues/121)), and
- Support for documentation pages (likely using mkdocs, [#188](https://github.com/usethis-python/usethis-python/issues/188)).
- Support for automated GitHub Actions workflows ([#57](https://github.com/usethis-python/usethis-python/issues/57)), and
- Support for a typechecker (likely Pyright, [#121](https://github.com/usethis-python/usethis-python/issues/121)).

Other features are tracked in the [GitHub Issues](https://github.com/usethis-python/usethis-python/issues) page.

Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ layers = [
"codespell | deptry | import_linter | pyproject_fmt | requirements_txt",
"ruff",
"pytest : coverage_py",
"pre_commit",
"pre_commit | mkdocs",
]
containers = [ "usethis._tool.impl" ]
exhaustive = true
Expand All @@ -249,7 +249,7 @@ name = "usethis._integrations"
type = "layers"
layers = [
"ci | pre_commit",
"uv | pytest | pydantic | sonarqube",
"uv | mkdocs | pytest | pydantic | sonarqube",
"project | python",
"file",
]
Expand Down
10 changes: 10 additions & 0 deletions src/usethis/_config_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from usethis._integrations.file.pyproject_toml.io_ import PyprojectTOMLManager
from usethis._integrations.file.setup_cfg.io_ import SetupCFGManager
from usethis._integrations.file.toml.io_ import TOMLFileManager
from usethis._integrations.file.yaml.io_ import YAMLFileManager
from usethis._integrations.uv.toml import UVTOMLManager

if TYPE_CHECKING:
Expand All @@ -24,6 +25,7 @@ def files_manager() -> Iterator[None]:
DotRuffTOMLManager(),
DotPytestINIManager(),
DotImportLinterManager(),
MkDocsYMLManager(),
PytestINIManager(),
RuffTOMLManager(),
ToxINIManager(),
Expand Down Expand Up @@ -72,6 +74,14 @@ def relative_path(self) -> Path:
return Path(".ruff.toml")


class MkDocsYMLManager(YAMLFileManager):
"""Class to manage the mkdocs.yml file."""

@property
def relative_path(self) -> Path:
return Path("mkdocs.yml")


class PytestINIManager(INIFileManager):
"""Class to manage the pytest.ini file."""

Expand Down
28 changes: 28 additions & 0 deletions src/usethis/_core/tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from usethis._console import box_print, tick_print
from usethis._integrations.ci.bitbucket.used import is_bitbucket_used
from usethis._integrations.file.pyproject_toml.valid import ensure_pyproject_validity
from usethis._integrations.mkdocs.core import add_docs_dir
from usethis._integrations.pre_commit.core import (
install_pre_commit_hooks,
remove_pre_commit_config,
Expand All @@ -28,6 +29,7 @@
from usethis._tool.impl.coverage_py import CoveragePyTool
from usethis._tool.impl.deptry import DeptryTool
from usethis._tool.impl.import_linter import ImportLinterTool
from usethis._tool.impl.mkdocs import MkDocsTool
from usethis._tool.impl.pre_commit import PreCommitTool
from usethis._tool.impl.pyproject_fmt import PyprojectFmtTool
from usethis._tool.impl.pyproject_toml import PyprojectTOMLTool
Expand Down Expand Up @@ -156,6 +158,30 @@ def use_import_linter(*, remove: bool = False, how: bool = False) -> None:
tool.remove_managed_files()


def use_mkdocs(*, remove: bool = False, how: bool = False) -> None:
tool = MkDocsTool()

if how:
tool.print_how_to_use()
return

if not remove:
ensure_pyproject_toml()
(usethis_config.cpd() / "mkdocs.yml").touch()

add_docs_dir()

tool.add_doc_deps()
tool.add_configs()

tool.print_how_to_use()
else:
# N.B. no need to remove configs because they all lie in managed files.

tool.remove_doc_deps()
tool.remove_managed_files()


def use_pre_commit(*, remove: bool = False, how: bool = False) -> None:
tool = PreCommitTool()

Expand Down Expand Up @@ -498,6 +524,8 @@ def use_tool(
use_deptry(remove=remove, how=how)
elif isinstance(tool, ImportLinterTool):
use_import_linter(remove=remove, how=how)
elif isinstance(tool, MkDocsTool):
use_mkdocs(remove=remove, how=how)
elif isinstance(tool, PreCommitTool):
use_pre_commit(remove=remove, how=how)
elif isinstance(tool, PyprojectFmtTool):
Expand Down
Empty file.
25 changes: 25 additions & 0 deletions src/usethis/_integrations/mkdocs/core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from usethis._config import usethis_config
from usethis._console import tick_print
from usethis._integrations.project.name import get_project_name


def add_docs_dir() -> None:
"""Create the `docs` directory and an `docs/index.md` file if they do not exist."""
docs_dir = usethis_config.cpd() / "docs"
if not docs_dir.exists():
tick_print("Creating '/docs'.")
docs_dir.mkdir()
write_index = True
elif not (docs_dir / "index.md").exists():
tick_print("Writing '/docs/index.md'.")
write_index = True
else:
write_index = False
if write_index:
(docs_dir / "index.md").write_text(
f"""\
# {get_project_name()}

Welcome to the documentation for {get_project_name()}.
"""
)
24 changes: 24 additions & 0 deletions src/usethis/_interface/tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,29 @@ def import_linter(
_run_tool(use_import_linter, remove=remove, how=how)


@app.command(
name="mkdocs",
help="Use MkDocs: project documentation sites with Markdown.",
rich_help_panel="Documentation",
)
def mkdocs(
remove: bool = remove_opt,
how: bool = how_opt,
offline: bool = offline_opt,
quiet: bool = quiet_opt,
frozen: bool = frozen_opt,
) -> None:
from usethis._config import usethis_config
from usethis._config_file import files_manager
from usethis._core.tool import use_mkdocs

with (
usethis_config.set(offline=offline, quiet=quiet, frozen=frozen),
files_manager(),
):
_run_tool(use_mkdocs, remove=remove, how=how)


@app.command(
name="pre-commit",
help="Use the pre-commit framework to manage and maintain pre-commit hooks.",
Expand Down Expand Up @@ -275,6 +298,7 @@ def _run_tool(caller: UseToolFunc, *, remove: bool, how: bool, **kwargs: Any):
"coverage.py",
"deptry",
"import-linter",
"mkdocs",
"pre-commit",
"pyproject.toml",
"pyproject-fmt",
Expand Down
3 changes: 3 additions & 0 deletions src/usethis/_tool/all_.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from usethis._tool.impl.coverage_py import CoveragePyTool
from usethis._tool.impl.deptry import DeptryTool
from usethis._tool.impl.import_linter import ImportLinterTool
from usethis._tool.impl.mkdocs import MkDocsTool
from usethis._tool.impl.pre_commit import PreCommitTool
from usethis._tool.impl.pyproject_fmt import PyprojectFmtTool
from usethis._tool.impl.pyproject_toml import PyprojectTOMLTool
Expand All @@ -18,6 +19,7 @@
| CoveragePyTool
| DeptryTool
| ImportLinterTool
| MkDocsTool
| PreCommitTool
| PyprojectFmtTool
| PyprojectTOMLTool
Expand All @@ -31,6 +33,7 @@
CoveragePyTool(),
DeptryTool(),
ImportLinterTool(),
MkDocsTool(),
PreCommitTool(),
PyprojectFmtTool(),
PyprojectTOMLTool(),
Expand Down
23 changes: 23 additions & 0 deletions src/usethis/_tool/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,17 @@ def get_test_deps(self, *, unconditional: bool = False) -> list[Dependency]:
"""
return []

def get_doc_deps(self, *, unconditional: bool = False) -> list[Dependency]:
"""The tool's documentation dependencies.

These should all be considered characteristic of this particular tool.

Args:
unconditional: Whether to return all possible dependencies regardless of
whether they are relevant to the current project.
"""
return []

def get_config_spec(self) -> ConfigSpec:
"""Get the configuration specification for this tool.

Expand Down Expand Up @@ -165,6 +176,12 @@ def is_declared_as_dep(self) -> bool:
for dep in self.get_test_deps(unconditional=True)
)

if not _is_declared:
_is_declared = any(
is_dep_in_any_group(dep)
for dep in self.get_doc_deps(unconditional=True)
)

return _is_declared

def add_dev_deps(self) -> None:
Expand All @@ -179,6 +196,12 @@ def add_test_deps(self) -> None:
def remove_test_deps(self) -> None:
remove_deps_from_group(self.get_test_deps(unconditional=True), "test")

def add_doc_deps(self) -> None:
add_deps_to_group(self.get_doc_deps(), "doc")

def remove_doc_deps(self) -> None:
remove_deps_from_group(self.get_doc_deps(unconditional=True), "doc")

def get_pre_commit_repos(self) -> list[LocalRepo | UriRepo]:
"""Get the pre-commit repository definitions for the tool."""
return [c.repo for c in self.get_pre_commit_config().repo_configs]
Expand Down
80 changes: 80 additions & 0 deletions src/usethis/_tool/impl/mkdocs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING

from usethis._config_file import MkDocsYMLManager
from usethis._console import box_print
from usethis._integrations.project.name import get_project_name
from usethis._integrations.uv.deps import Dependency
from usethis._integrations.uv.used import is_uv_used
from usethis._tool.base import Tool
from usethis._tool.config import ConfigEntry, ConfigItem, ConfigSpec

if TYPE_CHECKING:
from usethis._io import KeyValueFileManager


class MkDocsTool(Tool):
# https://www.mkdocs.org/

@property
def name(self) -> str:
return "MkDocs"

def print_how_to_use(self) -> None:
if is_uv_used():
box_print("Run 'uv run mkdocs build' to build the documentation.")
box_print("Run 'uv run mkdocs serve' to serve the documentation locally.")
else:
box_print("Run 'mkdocs build' to build the documentation.")
box_print("Run 'mkdocs serve' to serve the documentation locally.")

def get_doc_deps(self, *, unconditional: bool = False) -> list[Dependency]:
deps = [Dependency(name="mkdocs")]

if unconditional:
deps.append(Dependency(name="mkdocs-material"))

return deps

def get_config_spec(self) -> ConfigSpec:
"""Get the configuration specification for this tool.

This includes the file managers and resolution methodology.
"""
return ConfigSpec.from_flat(
file_managers=[
MkDocsYMLManager(),
],
resolution="first_content",
config_items=[
ConfigItem(
description="Site Name",
root={
Path("mkdocs.yml"): ConfigEntry(
keys=["site_name"],
get_value=lambda: get_project_name(),
),
},
),
ConfigItem(
description="Navigation",
root={
Path("mkdocs.yml"): ConfigEntry(
keys=["nav"],
get_value=lambda: [{"Home": "index.md"}],
),
},
),
],
)

def get_managed_files(self) -> list[Path]:
"""Get (relative) paths to files managed by (solely) this tool."""
return [Path("mkdocs.yml")]

def preferred_file_manager(self) -> KeyValueFileManager:
"""If there is no currently active config file, this is the preferred one."""
# Should set the mkdocs.yml file manager as the preferred one
return MkDocsYMLManager()
2 changes: 2 additions & 0 deletions src/usethis/_tool/impl/pyproject_toml.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from usethis._tool.impl.coverage_py import CoveragePyTool
from usethis._tool.impl.deptry import DeptryTool
from usethis._tool.impl.import_linter import ImportLinterTool
from usethis._tool.impl.mkdocs import MkDocsTool
from usethis._tool.impl.pre_commit import PreCommitTool
from usethis._tool.impl.pyproject_fmt import PyprojectFmtTool
from usethis._tool.impl.pytest import PytestTool
Expand All @@ -26,6 +27,7 @@
CoveragePyTool(),
DeptryTool(),
ImportLinterTool(),
MkDocsTool(),
PreCommitTool(),
PyprojectFmtTool(),
PytestTool(),
Expand Down
Loading
Loading