diff --git a/.github/workflows/docs-final.yml b/.github/workflows/docs-final.yml new file mode 100644 index 00000000..f70a74a4 --- /dev/null +++ b/.github/workflows/docs-final.yml @@ -0,0 +1,30 @@ +name: Deploy final documentation + +on: + push: + branches: [ "master" ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python 3.10 + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Install dependencies + run: | + git pull --all + python -m pip install ".[docs]" + + - name: Build and deploy documentation + run: | + mkdocs gh-deploy --strict -v diff --git a/.github/workflows/docs-preview.yml b/.github/workflows/docs-preview.yml new file mode 100644 index 00000000..db4fbd19 --- /dev/null +++ b/.github/workflows/docs-preview.yml @@ -0,0 +1,38 @@ +name: Deploy PR previews + +on: + pull_request: + types: + - opened + - reopened + - synchronize + - closed + +concurrency: preview-${{ github.ref }} + +jobs: + deploy-preview: + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python 3.10 + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Install dependencies + run: | + python -m pip install ".[docs]" + + - name: Build documentation + run: | + mkdocs build --strict -v + + - name: Deploy preview + uses: rossjrw/pr-preview-action@v1 + with: + source-dir: ./site/ diff --git a/bumpversion/config/__init__.py b/bumpversion/config/__init__.py index 5fdf3a18..98718d19 100644 --- a/bumpversion/config/__init__.py +++ b/bumpversion/config/__init__.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Union +from typing import TYPE_CHECKING, Any, Union from bumpversion.config.files import read_config_file from bumpversion.config.models import Config @@ -37,7 +37,7 @@ } -def get_configuration(config_file: Union[str, Path, None] = None, **overrides) -> Config: +def get_configuration(config_file: Union[str, Path, None] = None, **overrides: Any) -> Config: """ Return the configuration based on any configuration files and overrides. diff --git a/docsrc/CHANGELOG.md b/docsrc/CHANGELOG.md new file mode 100644 index 00000000..6b23491a --- /dev/null +++ b/docsrc/CHANGELOG.md @@ -0,0 +1,6 @@ +--- +comments: true +title: "Changelog" +--- + +{% include-markdown "../CHANGELOG.md" %} diff --git a/docsrc/contributing.md b/docsrc/CONTRIBUTING.md similarity index 61% rename from docsrc/contributing.md rename to docsrc/CONTRIBUTING.md index 89e0be46..7c04a4f0 100644 --- a/docsrc/contributing.md +++ b/docsrc/CONTRIBUTING.md @@ -1 +1,6 @@ +--- +comments: true +title: "Contributing" +--- + {% include-markdown "../CONTRIBUTING.md" rewrite-relative-urls=false %} diff --git a/docsrc/_static/css/custom.css b/docsrc/_static/css/custom.css index dc455b76..2fc87410 100644 --- a/docsrc/_static/css/custom.css +++ b/docsrc/_static/css/custom.css @@ -1,70 +1,12 @@ -.class .sig-prename.descclassname { - display: none; +/* Indentation. */ +div.doc-contents:not(.first) { + padding-left: 25px; + border-left: .05rem solid var(--md-typeset-table-color); } -.subheading { - line-height: 1.25; - margin-bottom: 0; - color: #646776; -} - -.subheading + section > h1 { - margin-top: 0; -} - -.field-list p { - margin: 0; -} - -.field-list > dl { - --gap: 1rem; - --line-offset: calc(var(--gap) / 2); - --line-thickness: 1px; - --line-color: #e0e0e0; - - display: grid; - grid-template-columns: fit-content(30%) auto; - grid-gap: var(--gap); - margin-bottom: 1rem; - overflow: hidden; -} - -.field-list > dl > dt { - text-align: right; - font-weight: bold; - word-break: break-word; - position: relative; -} - -[dir=ltr] .field-list > dl > dd { - margin-top: 0; - margin-left: 0; - margin-bottom: 0; - position: relative; -} - -/* Pseudo Element Shared Styling */ -.field-list > dl > dt::before, -.field-list > dl > dt::after, -.field-list > dl > dd::before, -.field-list > dl > dd::after { - content: ''; - position: absolute; - background-color: var(--line-color); - z-index: 1; -} - -/* Row Borders */ -.field-list > dl > dt::after, -.field-list > dl > dd::after { - inline-size: 100vw; - block-size: var(--line-thickness); - inset-inline-start: 0; - inset-block-start: calc(var(--line-offset) * -1); -} - - -figure { - padding-bottom: .75rem; - padding-top: .5rem; +/* Normal size fonts on parameter tables */ +.md-typeset div.doc-contents table { + font-size: 1em; + width: 100%; + display: table; } diff --git a/docsrc/_static/css/field-list.css b/docsrc/_static/css/field-list.css new file mode 100644 index 00000000..aff82aa0 --- /dev/null +++ b/docsrc/_static/css/field-list.css @@ -0,0 +1,61 @@ +dl.field-list .doc-param-default, dl.doc-field-list .doc-param-default { + float: none; +} + +dd.doc-field-def > p:last-child { + padding-bottom: 0; + margin-bottom: 0; +} + +dl.field-list, dl.doc-field-list { + display: flex; + flex-flow: row wrap; + padding-left: 10px; +} + +dl.field-list > dt, dl.doc-field-list > dt { + flex-basis: 20%; + font-weight: bold; + word-break: break-word; + padding: 10px 0; + border-bottom: 1px solid #e5e5e5; +} + +dl.field-list > dt:after { + content: ":"; +} + +dl.field-list > dd.doc-field-def, dl.doc-field-list > dd.doc-field-def { + flex-basis: 70%; + flex-grow: 1; + margin: 0; + padding: 10px 0 10px 10px; + border-bottom: 1px solid #e5e5e5; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} diff --git a/docsrc/_static/css/mkdocstrings.css b/docsrc/_static/css/mkdocstrings.css index 4a69994f..6288f536 100644 --- a/docsrc/_static/css/mkdocstrings.css +++ b/docsrc/_static/css/mkdocstrings.css @@ -25,3 +25,15 @@ a.external:hover::after, a.autorefs-external:hover::after { background-color: var(--md-accent-fg-color); } + +.doc-param-key, .doc-field-term, .doc-section-head { + font-weight: bold; +} + +.doc.doc-heading { + text-transform: none; +} + +h5.doc-heading, h6.doc-heading { + font-size: 1em; +} diff --git a/docsrc/changelog.md b/docsrc/changelog.md deleted file mode 100644 index e137197f..00000000 --- a/docsrc/changelog.md +++ /dev/null @@ -1 +0,0 @@ -{% include-markdown "../CHANGELOG.md" %} diff --git a/docsrc/index.md b/docsrc/index.md index 2e69f552..5626cde8 100644 --- a/docsrc/index.md +++ b/docsrc/index.md @@ -1,3 +1,8 @@ +--- +comments: true +title: Bump My Version +--- + # Bump My Version {% diff --git a/docsrc/reference/index.md b/docsrc/reference/index.md index 598778f3..a74ae62a 100644 --- a/docsrc/reference/index.md +++ b/docsrc/reference/index.md @@ -6,4 +6,4 @@ - [Formatting context](formatting-context.md) - [Version parts](version-parts.md) - [Search and replace configuration](search-and-replace-config.md) -- [API](api/bumpversion.md) +- [API](api/bumpversion/index.md) diff --git a/mkdocs.yml b/mkdocs.yml index 15527dd7..8ad6ff6f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -23,6 +23,7 @@ theme: palette: - media: "(prefers-color-scheme: light)" scheme: default + primary: blue grey toggle: icon: material/toggle-switch-off-outline name: Switch to dark mode @@ -31,13 +32,13 @@ theme: toggle: icon: material/toggle-switch name: Switch to light mode -use_directory_urls: false +use_directory_urls: true markdown_extensions: - abbr - admonition - attr_list - - def_list - customblocks + - def_list - footnotes - md_in_html - mdx_truly_sane_lists @@ -48,12 +49,17 @@ markdown_extensions: emoji_generator: !!python/name:material.extensions.emoji.to_svg - pymdownx.highlight - pymdownx.snippets - - pymdownx.superfences + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_div_format - pymdownx.tabbed: alternate_style: true slugify: !!python/object/apply:pymdownx.slugs.slugify kwds: case: lower + - smarty - toc: permalink: true toc_depth: 3 @@ -64,6 +70,8 @@ plugins: - git-revision-date-localized - git-authors: show_email_address: false + exclude: + - reference/api/* - include-markdown - drawio - literate-nav: @@ -92,6 +100,7 @@ plugins: merge_init_into_class: true separate_signature: true show_docstring_parameters: true + show_root_toc_entry: true show_signature_annotations: true show_source: false show_symbol_type_heading: true @@ -105,6 +114,7 @@ extra_css: - _static/css/custom.css - _static/css/mkdocstrings.css - _static/css/cards.css + - _static/css/field-list.css #nav: # - General: "general/" diff --git a/overrides/mkdocstrings/python/material/docstring/attributes.html b/overrides/mkdocstrings/python/material/docstring/attributes.html new file mode 100644 index 00000000..ce50d654 --- /dev/null +++ b/overrides/mkdocstrings/python/material/docstring/attributes.html @@ -0,0 +1,90 @@ +{{ log.debug("Rendering attributes section") }} + +{% import "language.html" as lang with context %} + +{% if config.docstring_section_style == "table" %} + {% block table_style scoped %} +

{{ section.title or lang.t("Attributes:") }}

+ + + + + + + + + + {% for attribute in section.value %} + + + + + + {% endfor %} + +
{{ lang.t("Name") }}{{ lang.t("Type") }}{{ lang.t("Description") }}
{{ attribute.name }} + {% if attribute.annotation %} + {% with expression = attribute.annotation %} + {% include "expression.html" with context %} + {% endwith %} + {% endif %} + +
+ {{ attribute.description|convert_markdown(heading_level, html_id) }} +
+
+ {% endblock table_style %} +{% elif config.docstring_section_style == "list" %} + {% block list_style scoped %} +

{{ section.title or lang.t("Attributes:") }}

+ + {% endblock list_style %} +{% elif config.docstring_section_style == "spacy" %} + {% block spacy_style scoped %} + + + + + + + + + {% for attribute in section.value %} + + + + + {% endfor %} + +
{{ (section.title or lang.t("ATTRIBUTE")).rstrip(":").upper() }}{{ lang.t("DESCRIPTION") }}
{{ attribute.name }} +
+ {{ attribute.description|convert_markdown(heading_level, html_id) }} +
+

+ {% if attribute.annotation %} + + TYPE: + {% with expression = attribute.annotation %} + {% include "expression.html" with context %} + {% endwith %} + + {% endif %} +

+
+ {% endblock spacy_style %} +{% endif %} diff --git a/overrides/mkdocstrings/python/material/docstring/parameters.html b/overrides/mkdocstrings/python/material/docstring/parameters.html new file mode 100644 index 00000000..ab4889e8 --- /dev/null +++ b/overrides/mkdocstrings/python/material/docstring/parameters.html @@ -0,0 +1,98 @@ +{{ log.debug("Rendering parameters section") }} + +{% import "language.html" as lang with context %} + +{% if config.docstring_section_style == "table" %} + {% block table_style scoped %} +

{{ section.title or lang.t("Parameters:") }}

+ + + + + + + + + + + {% for parameter in section.value %} + + + + + + + {% endfor %} + +
{{ lang.t("Name") }}{{ lang.t("Type") }}{{ lang.t("Description") }}{{ lang.t("Default") }}
{{ parameter.name }} + {% if parameter.annotation %} + {% with expression = parameter.annotation %} + {% include "expression.html" with context %} + {% endwith %} + {% endif %} + +
+ {{ parameter.description|convert_markdown(heading_level, html_id) }} +
+
+ {% if parameter.default %} + {% with expression = parameter.default %} + {% include "expression.html" with context %} + {% endwith %} + {% else %} + {{ lang.t("required") }} + {% endif %} +
+ {% endblock table_style %} +{% elif config.docstring_section_style == "list" %} + {% block list_style scoped %} +

{{ section.title or lang.t("Parameters:") }}

+ + {% endblock list_style %} +{% elif config.docstring_section_style == "spacy" %} + {% block spacy_style scoped %} +

{{ section.title or lang.t("Parameters:") }}

+
+ {% for parameter in section.value %} +
{{ parameter.name }}
+
+
+ {{ parameter.description|convert_markdown(heading_level, html_id) }} +
+ {% if parameter.annotation %}

+ {{ lang.t("TYPE:") }} + {% with expression = parameter.annotation %} + {% include "expression.html" with context %} + {% endwith %} +

{% endif %} + {% if parameter.default %}

+ {{ lang.t("DEFAULT:") }} + {% with expression = parameter.default %} + {% include "expression.html" with context %} + {% endwith %} +

{% endif %} +
+ {% endfor %} +
+ {% endblock spacy_style %} +{% endif %} diff --git a/overrides/mkdocstrings/python/material/docstring/raises.html b/overrides/mkdocstrings/python/material/docstring/raises.html new file mode 100644 index 00000000..628e41b9 --- /dev/null +++ b/overrides/mkdocstrings/python/material/docstring/raises.html @@ -0,0 +1,72 @@ +{{ log.debug("Rendering raises section") }} + +{% import "language.html" as lang with context %} + +{% if config.docstring_section_style == "table" %} + {% block table_style scoped %} +

{{ section.title or lang.t("Raises:") }}

+ + + + + + + + + {% for raises in section.value %} + + + + + {% endfor %} + +
{{ lang.t("Type") }}{{ lang.t("Description") }}
+ {% if raises.annotation %} + {% with expression = raises.annotation %} + {% include "expression.html" with context %} + {% endwith %} + {% endif %} + +
+ {{ raises.description|convert_markdown(heading_level, html_id) }} +
+
+ {% endblock table_style %} +{% elif config.docstring_section_style == "list" %} + {% block list_style scoped %} +

{{ lang.t(section.title) or lang.t("Raises:") }}

+ + {% endblock list_style %} +{% elif config.docstring_section_style == "spacy" %} + {% block spacy_style scoped %} +

{{ (section.title or lang.t("Raises:")) }}

+
+ {% for raises in section.value %} +
+ {% with expression = raises.annotation %} + {% include "expression.html" with context %} + {% endwith %} +
+
+
+ {{ raises.description|convert_markdown(heading_level, html_id) }} +
+
+ {% endfor %} +
+ {% endblock spacy_style %} +{% endif %} diff --git a/overrides/mkdocstrings/python/material/docstring/returns.html b/overrides/mkdocstrings/python/material/docstring/returns.html new file mode 100644 index 00000000..bd608404 --- /dev/null +++ b/overrides/mkdocstrings/python/material/docstring/returns.html @@ -0,0 +1,94 @@ +{{ log.debug("Rendering returns section") }} + +{% import "language.html" as lang with context %} + +{% if config.docstring_section_style == "table" %} + {% block table_style scoped %} + {% set name_column = section.value|selectattr("name")|any %} +

{{ section.title or lang.t("Returns:") }}

+ + + + {% if name_column %}{% endif %} + + + + + + {% for returns in section.value %} + + {% if name_column %}{% endif %} + + + + {% endfor %} + +
{{ lang.t("Name") }}{{ lang.t("Type") }}{{ lang.t("Description") }}
{% if returns.name %}{{ returns.name }}{% endif %} + {% if returns.annotation %} + {% with expression = returns.annotation %} + {% include "expression.html" with context %} + {% endwith %} + {% endif %} + +
+ {{ returns.description|convert_markdown(heading_level, html_id) }} +
+
+ {% endblock table_style %} +{% elif config.docstring_section_style == "list" %} + {% block list_style scoped %} +

{{ section.title or lang.t("Returns:") }}

+ + {% endblock list_style %} +{% elif config.docstring_section_style == "spacy" %} + {% block spacy_style scoped %} +

{{ (section.title or lang.t("Returns:")) }}

+
+ {% for returns in section.value %} +
+ {% if returns.name %} + {{ returns.name }} + {% elif returns.annotation %} + + {% with expression = returns.annotation %} + {% include "expression.html" with context %} + {% endwith %} + + {% endif %} +
+
+
+ {{ returns.description|convert_markdown(heading_level, html_id) }} +
+ {% if returns.name and returns.annotation %} +

+ + {{ lang.t("TYPE:") }} + {% with expression = returns.annotation %} + {% include "expression.html" with context %} + {% endwith %} + +

+ {% endif %} +
+ {% endfor %} +
+ {% endblock spacy_style %} +{% endif %} diff --git a/overrides/partials/comments.html b/overrides/partials/comments.html new file mode 100644 index 00000000..b0d1dd37 --- /dev/null +++ b/overrides/partials/comments.html @@ -0,0 +1,51 @@ +{% if page.meta.comments %} +

{{ lang.t("meta.comments") }}

+ + + + +{% endif %} diff --git a/pyproject.toml b/pyproject.toml index 58f3f4c9..d813b517 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,10 +58,12 @@ dev = [ "pre-commit", ] docs = [ + "black", "markdown-customblocks", "mdx-truly-sane-lists", "mkdocs", "mkdocs-click", + "mkdocs-drawio", "mkdocs-gen-files", "mkdocs-git-authors-plugin", "mkdocs-git-committers-plugin", @@ -69,8 +71,8 @@ docs = [ "mkdocs-include-markdown-plugin", "mkdocs-literate-nav", "mkdocs-material", - "mkdocstrings", "mkdocstrings[python]", + "python-frontmatter", ] test = [ "coverage", diff --git a/tools/update_frontmatter.py b/tools/update_frontmatter.py new file mode 100755 index 00000000..734c0910 --- /dev/null +++ b/tools/update_frontmatter.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python +"""Update frontmatter of markdown files.""" + +import argparse +from pathlib import Path +from typing import Any, Dict, Optional + +import frontmatter + + +def extract_main_heading(markdown_content: str) -> Optional[str]: + """ + Extracts the first level 1 heading from the provided Markdown content. + + Args: + markdown_content: A string containing Markdown text. + + Returns: + The text of the first level 1 heading, or None if no such heading is found. + """ + lines = markdown_content.split("\n") + + return next((line[2:] for line in lines if line.startswith("# ")), None) + + +def calc_title(post: frontmatter.Post) -> str: + """Calculate the title of the post.""" + return extract_main_heading(post.content) or post.get("title", "") + + +def calc_comment(post: frontmatter.Post) -> bool: + """Calculate if the post has comments.""" + return bool(post.get("comments", True)) + + +def calculate_update(post: frontmatter.Post) -> dict: + """Calculate if the frontmatter needs to be updated.""" + expected_title = calc_title(post) + expected_comment = calc_comment(post) + update: Dict[str, Any] = {} + if expected_title and expected_title != post.get("title"): + update["title"] = expected_title + if expected_comment != post.get("comments"): + update["comments"] = expected_comment + return update + + +def process_file(markdown_path: Path) -> None: + """Process a single file.""" + if not (markdown_path.is_file() and markdown_path.suffix == ".md"): + return + raw_text = markdown_path.read_text() + post = frontmatter.loads(raw_text) + + update = calculate_update(post) + if update: + for key, value in update.items(): + post[key] = value + new_text = frontmatter.dumps(post) + print(f"Updating {markdown_path}") # noqa: T201 + markdown_path.write_text(new_text) + + +def parse_args() -> argparse.Namespace: + """Parse command line arguments.""" + parser = argparse.ArgumentParser(description="Update frontmatter of markdown files") + parser.add_argument("markdown_path", type=str, nargs="+", help="Path or glob to markdown files") + return parser.parse_args() + + +if __name__ == "__main__": + args = parse_args() + documents = args.markdown_path + for document in documents: + for path in Path().glob(document): + process_file(path)