Skip to content

Commit 1a8047e

Browse files
authored
Add custom template parameter (#61)
1 parent e5fa8f2 commit 1a8047e

File tree

8 files changed

+377
-26
lines changed

8 files changed

+377
-26
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.3.0] - 2025-11-19
9+
- Add support for passing custom Jinja2 templates as an argument, by @sindrehan.
10+
811
## [1.2.1] - 2025-07-30
912

1013
- Added support for using the current working directory (CWD) as an option when

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,34 @@ oad gen-docs -s source-openapi.json -d schemas.wsd --style "PLANTUML_API"
7373

7474
_Example of PlantUML diagram generated from path items._
7575

76+
#### Using custom templates
77+
78+
You can override the default templates by providing a custom templates directory:
79+
80+
```bash
81+
oad gen-docs -s source-openapi.json -d output.md -T ./my-templates/
82+
```
83+
84+
The custom templates directory should contain template files with the same names as the built-in templates. Any template file found in the custom directory will override the corresponding default template, while non-overridden templates will use the defaults. This follows the same pattern as [MkDocs template customization](https://www.mkdocs.org/user-guide/customizing-your-theme/#overriding-template-blocks).
85+
86+
**Important:** The custom templates directory must match the output style being rendered. Each style (MKDOCS, MARKDOWN, PLANTUML_SCHEMAS, PLANTUML_API) has its own template structure. You need to provide templates appropriate for the `--style` parameter you're using.
87+
88+
**Template structure:**
89+
- `layout.html` - Main layout template
90+
- `partial/` - Directory containing reusable template components
91+
92+
**Example custom template directory structure:**
93+
```
94+
my-templates/
95+
├── layout.html # Overrides main layout
96+
└── partial/
97+
├── info.html # Overrides info section
98+
└── path-items.html # Overrides path items section
99+
```
100+
101+
All templates use [Jinja2](https://jinja.palletsprojects.com/) syntax and have access to the same filters, functions, and context variables as the built-in templates.
102+
103+
76104
### Goals
77105

78106
* Provide an API to generate OpenAPI Documentation files.

openapidocs/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
__version__ = "1.2.1"
1+
__version__ = "1.3.0"
22
VERSION = __version__

openapidocs/commands/docs.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,20 @@
3131
default="MKDOCS",
3232
show_default=True,
3333
)
34-
def generate_documents_command(source: str, destination: str, style: Union[int, str]):
34+
@click.option(
35+
"-T",
36+
"--templates",
37+
help=(
38+
"Path to a custom templates directory. "
39+
"Templates in this directory will override default templates with matching names. "
40+
"Unspecified templates will use defaults."
41+
),
42+
required=False,
43+
default=None,
44+
)
45+
def generate_documents_command(
46+
source: str, destination: str, style: Union[int, str], templates: Union[str, None]
47+
):
3548
"""
3649
Generates other kinds of documents from source OpenAPI Documentation files.
3750
@@ -48,7 +61,7 @@ def generate_documents_command(source: str, destination: str, style: Union[int,
4861
https://github.com/Neoteroi/essentials-openapi
4962
"""
5063
try:
51-
generate_document(source, destination, style)
64+
generate_document(source, destination, style, templates)
5265
except KeyboardInterrupt: # pragma: nocover
5366
logger.info("User interrupted")
5467
exit(1)

openapidocs/mk/generate.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
1+
from typing import Optional
2+
13
from openapidocs.mk.v3 import OpenAPIV3DocumentationHandler
24
from openapidocs.utils.source import read_from_source
35

46

5-
def generate_document(source: str, destination: str, style: int | str):
7+
def generate_document(
8+
source: str,
9+
destination: str,
10+
style: int | str,
11+
templates_path: Optional[str] = None,
12+
):
613
# Note: if support for more kinds of OAD versions will be added, handle a version
714
# parameter in this function
815

916
data = read_from_source(source)
10-
handler = OpenAPIV3DocumentationHandler(data, style=style, source=source)
17+
handler = OpenAPIV3DocumentationHandler(
18+
data, style=style, source=source, templates_path=templates_path
19+
)
1120

1221
html = handler.write()
1322

openapidocs/mk/jinja.py

Lines changed: 52 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
11
"""
22
This module provides a Jinja2 environment.
33
"""
4+
45
import os
56
from enum import Enum
6-
7-
from jinja2 import Environment, PackageLoader, Template, select_autoescape
7+
from pathlib import Path
8+
from typing import Optional
9+
10+
from jinja2 import (
11+
ChoiceLoader,
12+
Environment,
13+
FileSystemLoader,
14+
PackageLoader,
15+
Template,
16+
select_autoescape,
17+
)
818

919
from . import get_http_status_phrase, highlight_params, read_dict, sort_dict
1020
from .common import DocumentsWriter, is_reference
@@ -62,29 +72,50 @@ def __init__(
6272

6373

6474
def get_environment(
65-
package_name: str, views_style: OutputStyle = OutputStyle.MKDOCS
75+
package_name: str,
76+
views_style: OutputStyle = OutputStyle.MKDOCS,
77+
custom_templates_path: Optional[str] = None,
6678
) -> Environment:
6779
templates_folder = f"views_{views_style.name}".lower()
6880

81+
loaders = []
82+
83+
# If custom templates path is provided, validate and add FileSystemLoader first
84+
if custom_templates_path:
85+
custom_path = Path(custom_templates_path)
86+
if not custom_path.exists():
87+
raise ValueError(
88+
f"Custom templates path does not exist: {custom_templates_path}"
89+
)
90+
if not custom_path.is_dir():
91+
raise ValueError(
92+
f"Custom templates path is not a directory: {custom_templates_path}"
93+
)
94+
loaders.append(FileSystemLoader(str(custom_path)))
95+
96+
# Always add the package loader as fallback
6997
try:
70-
loader = PackageLoader(package_name, templates_folder)
98+
loaders.append(PackageLoader(package_name, templates_folder))
7199
except ValueError as package_loading_error: # pragma: no cover
72-
raise PackageLoadingError(
73-
views_style, templates_folder
74-
) from package_loading_error
75-
else:
76-
env = Environment(
77-
loader=loader,
78-
autoescape=select_autoescape(["html", "xml"])
79-
if os.environ.get("SELECT_AUTOESCAPE") in {"YES", "Y", "1"}
80-
else False,
81-
auto_reload=True,
82-
enable_async=False,
83-
)
84-
configure_filters(env)
85-
configure_functions(env)
100+
if not custom_templates_path:
101+
raise PackageLoadingError(
102+
views_style, templates_folder
103+
) from package_loading_error
104+
105+
loader = ChoiceLoader(loaders)
106+
107+
env = Environment(
108+
loader=loader,
109+
autoescape=select_autoescape(["html", "xml"])
110+
if os.environ.get("SELECT_AUTOESCAPE") in {"YES", "Y", "1"}
111+
else False,
112+
auto_reload=True,
113+
enable_async=False,
114+
)
115+
configure_filters(env)
116+
configure_functions(env)
86117

87-
return env
118+
return env
88119

89120

90121
class Jinja2DocumentsWriter(DocumentsWriter):
@@ -97,8 +128,9 @@ def __init__(
97128
self,
98129
package_name: str,
99130
views_style: OutputStyle = OutputStyle.MKDOCS,
131+
custom_templates_path: Optional[str] = None,
100132
) -> None:
101-
self._env = get_environment(package_name, views_style)
133+
self._env = get_environment(package_name, views_style, custom_templates_path)
102134

103135
@property
104136
def env(self) -> Environment:

openapidocs/mk/v3/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
This module provides functions to generate Markdown for OpenAPI Version 3.
33
"""
4+
45
import copy
56
import os
67
import warnings
@@ -95,11 +96,14 @@ def __init__(
9596
writer: Optional[DocumentsWriter] = None,
9697
style: Union[int, str] = 1,
9798
source: str = "",
99+
templates_path: Optional[str] = None,
98100
) -> None:
99101
self._source = source
100102
self.texts = texts or EnglishTexts()
101103
self._writer = writer or Jinja2DocumentsWriter(
102-
__name__, views_style=style_from_value(style)
104+
__name__,
105+
views_style=style_from_value(style),
106+
custom_templates_path=templates_path,
103107
)
104108
self.doc = self.normalize_data(copy.deepcopy(doc))
105109

0 commit comments

Comments
 (0)