Skip to content

Commit dc54c1f

Browse files
committed
v0.15.0: OpenAPI spec fixes, tail formula, etc.
1 parent c8f0542 commit dc54c1f

34 files changed

+918
-714
lines changed

README.md

+4-535
Large diffs are not rendered by default.

dactyl/config.py

+14-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ def __init__(self, cli_args):
3535
else:
3636
logger.debug("No config file specified, trying ./dactyl-config.yml")
3737
self.load_config_from_file(DEFAULT_CONFIG_FILE)
38-
self.check_consistency()
3938
self.load_filters()
4039

4140
self.page_cache = []
@@ -53,6 +52,8 @@ def load_config_from_file(self, config_file):
5352
try:
5453
with open(config_file, "r", encoding="utf-8") as f:
5554
loaded_config = yaml.load(f)
55+
if loaded_config is None:
56+
loaded_config = {}
5657
except FileNotFoundError as e:
5758
if config_file == DEFAULT_CONFIG_FILE:
5859
logger.info("Couldn't read a config file; using generic config")
@@ -75,6 +76,12 @@ def load_config_from_file(self, config_file):
7576
logger.warning("Deprecation warning: Global field pdf_template has "
7677
+"been renamed default_pdf_template")
7778

79+
if "flatten_default_html_paths" in loaded_config:
80+
if loaded_config["flatten_default_html_paths"] == True:
81+
loaded_config["default_html_names"] = "flatten"
82+
else:
83+
loaded_config["default_html_names"] = "path"
84+
7885
self.config.update(loaded_config)
7986

8087
def check_consistency(self):
@@ -96,7 +103,7 @@ def check_consistency(self):
96103

97104
# Check page list for consistency
98105
for page in self.config["pages"]:
99-
if "targets" not in page:
106+
if "targets" not in page: #CURSOR
100107
if "name" in page:
101108
logger.warning("Page %s is not part of any targets." %
102109
page["name"])
@@ -132,6 +139,11 @@ def load_pages(self):
132139
# OpenAPI specs are too much work to load at this time
133140
self.page_cache.append(OPENAPI_SPEC_PLACEHOLDER)
134141

142+
# Check consistency here instead of earlier so that we have frontmatter
143+
# from pages first. (Otherwise, we raise false alarms about pages not
144+
# being part of any target.)
145+
self.check_consistency()
146+
135147
def load_filters(self):
136148
# Figure out which filters we need
137149
filternames = set(self.config["default_filters"])

dactyl/default-config.yml

+15-4
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,21 @@ skip_preprocessor: false
5151
template_allow_undefined: true
5252
preprocessor_allow_undefined: true
5353

54-
## By default, generates HTML paths from md paths by replacing / with -
55-
## Set this to false to make default HTML paths mirror the folder structure of
56-
## the input md files.
57-
flatten_default_html_paths: true
54+
## How to generate default HTML paths from Markdown files. These can be
55+
## overwritten by the "html" field of the page definition.
56+
## In all cases, the file extension is changed from .md to .html.
57+
## Available formulas include:
58+
## "flatten": The default. Use the full path to the md file, except replace /
59+
## with - so all files end up in one output folder.
60+
## "path": Use the full path to the md file including folders. This can make
61+
## for prettier URLs but requires you to handle absolute paths correctly
62+
## "tail": Use just the filename, not the whole path. This can result in
63+
## duplicates if you have files with the same name in different folders,
64+
## for example if you have multiple "overview.md" docs. You should
65+
## change the duplicates individually using "html" parameters in the
66+
## page definitions, as needed.
67+
# Legacy parameter: flatten_default_html_paths: true
68+
default_html_names: flatten
5869

5970
## Set this to true to disable Dactyl's built-in syntax highlighting
6071
no_highlighting: false

dactyl/openapi.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ def clean_up_swag(self):
167167
schema["title"] = title
168168
if "example" in schema:
169169
try:
170-
j = json.dumps(schema["example"], indent=4, default=self.json_default)
170+
j = self.json_pp(schema["example"])
171171
schema["example"] = j
172172
except Exception as e:
173173
logger.debug("%s example isn't json: %s"%(title,j))
@@ -245,7 +245,7 @@ def get_x_example_request_body(self, path, method, endpoint):
245245
return ""
246246

247247
try:
248-
ex_pp = json.dumps(ex, indent=4, separators=(',', ': '), default=self.json_default)
248+
ex_pp = self.json_pp(ex)
249249
except TypeError as e:
250250
traceback.print_tb(e.__traceback__)
251251
logger.debug("json dumps failed on example '%s'"%ex)
@@ -387,6 +387,7 @@ def new_context(self):
387387
"debug": logger.debug,
388388
"slugify": slugify,
389389
"md_escape": self.md_escape,
390+
"json_pp": self.json_pp
390391
}
391392

392393
@staticmethod
@@ -403,6 +404,12 @@ def md_escape(text):
403404
s += c
404405
return s
405406

407+
def json_pp(self, j):
408+
"""
409+
Pretty-print function for JSON
410+
"""
411+
return json.dumps(j, indent=4, default=self.json_default)
412+
406413
@staticmethod
407414
def json_default(o):
408415
"""

dactyl/page.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,13 @@ def provide_default_filename(self):
159159

160160
logger.debug("Need to generate html filename for page %s" % self)
161161
if "md" in self.data:
162-
# TODO: support other formulas including "tail" or "pretty"
163162
new_filename = re.sub(r"[.]md$", ".html", self.data["md"])
164-
if self.config.get("flatten_default_html_paths", True):
163+
name_formula = self.config.get("default_html_names", "flatten")
164+
if name_formula == "flatten":
165165
new_filename = new_filename.replace(os.sep, "-")
166+
elif name_formula == "tail":
167+
new_filename = new_filename.split(os.sep)[-1]
168+
# the "path" formula is a no-op here.
166169
self.data["html"] = new_filename
167170
elif "name" in self.data:
168171
new_filename = slugify(self.data["name"]).lower()+".html"
@@ -173,7 +176,7 @@ def provide_default_filename(self):
173176
self.data[PROVIDED_FILENAME_KEY] = True
174177

175178
logger.debug("Generated html filename '%s' for page: %s" %
176-
(new_filename, self))
179+
(self.data["html"], self))
177180

178181
def provide_name(self):
179182
"""

dactyl/templates/breadcrumbs.html

+3-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515

1616
{% if currentpage.html != "index.html" %}
1717
{%- for page in ns.crumbs %}
18-
<li class="active breadcrumb-item"><a href="{% if "//" not in page.html %}{{ currentpage.prefix }}{% endif %}{{ page.html }}">{{ page.name }}</a></li>
18+
<li class="active breadcrumb-item"><a href="
19+
{%- if page is defined and "//" not in page.html %}{{ currentpage.prefix }}{% endif -%}
20+
{{ page.html }}">{{ page.name }}</a></li>
1921
{% endfor %}
2022
{% endif %}
2123
<li class="active breadcrumb-item">{{ currentpage.name }}</li>

dactyl/templates/landing.html

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
{% if content %}
55

66
<section class="pt-3 p-md-3">
7-
<article class="content">
7+
<article class="dactyl-content">
88
{{ content }}
99

10+
<h2>Read More</h2>
1011
{% set show_blurbs = True %}
1112
{% set depth= 1 %}
1213
{% include 'children.html' %}

dactyl/templates/template-openapi_data_type.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
{% endif -%}
4646
{% if example is defined %}- **Example:**
4747

48-
{{example|indent(8,indentfirst=False)}}
48+
{{example|indent(8,first=False)}}
4949

5050
{% endif -%}
5151

@@ -54,8 +54,9 @@ This type can contain the following fields:
5454

5555
| Field | Type | Required? | Description |
5656
|-------|------|-----------|-------------|
57+
{%- set required_fields = required if required is defined else [] -%}
5758
{%- for name,field in properties.items() %}
58-
| `{{name}}` | {{field.type|title}}{% if "items" in field.keys() and "title" in field["items"].keys() %} of [{{field["items"].title}}]({{type_link(field["items"].title)}}){% endif %} {% if field["title"] is defined %}([{{field.title}}]({{type_link(field.title)}})){% endif %} | {{"Required" if field.required else "Optional"}} | {{field.description}} |
59+
| `{{name}}` | {{field.type|title}}{% if "items" in field.keys() and "title" in field["items"].keys() %} of [{{field["items"].title}}]({{type_link(field["items"].title)}}){% endif %} {% if field["title"] is defined %}([{{field.title}}]({{type_link(field.title)}})){% endif %} | {{"Required" if name in required_fields else "Optional"}} | {{field.description}} |
5960
{%- endfor %}
6061

6162
{% if additionalProperties is defined and additionalProperties == True %}This type MUST NOT contain any additional fields.{% endif %}

dactyl/templates/template-openapi_endpoint.md

+35-13
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
11
# {{summary}}
22

3-
{{description}}
4-
5-
## Request Format
6-
73
```
84
{{method|upper}} {{path}}
9-
{%- if method in ["post","put","delete"] and requestBody is defined %}
10-
11-
{{ x_example_request_body }}
12-
{% endif %}
135
```
146

7+
{{description}}
8+
9+
## Request Format
10+
1511
{% if path_params|length %}
12+
#### Path Parameters
1613
This API method uses the following path parameters:
1714

1815
| Field | Value | Required? | Description |
@@ -24,6 +21,7 @@ This API method uses the following path parameters:
2421
{% endif %}
2522

2623
{% if query_params|length %}
24+
#### Query Parameters
2725
This API method uses the following query parameters:
2826

2927
| Field | Value | Required? | Description |
@@ -35,11 +33,28 @@ This API method uses the following query parameters:
3533
{% endif %}
3634

3735
{% if requestBody is defined %}
36+
### Request Body
3837
{{requestBody.description}}
3938

4039
{% if requestBody.content is defined %}
4140

4241
{% for mediatype,thisbody in requestBody.content.items() %}
42+
{% if thisbody.examples is defined and thisbody.examples|length > 0 %}
43+
44+
<!-- MULTICODE_BLOCK_START -->
45+
46+
{% for body_name,body_sample in thisbody.examples.items() %}
47+
_{{body_name}}_
48+
49+
```{%if mediatype == "application/json"%}json{%endif%}
50+
{{json_pp(body_sample)}}
51+
```
52+
{% endfor %}
53+
54+
<!-- MULTICODE_BLOCK_END -->
55+
56+
{% endif %}
57+
4358
{% if thisbody.schema is defined %}
4459
**Media type:** {{mediatype|replace("*","\*")}}
4560

@@ -51,8 +66,9 @@ The request uses the following fields:
5166

5267
| Field | Type | Required? | Description |
5368
|-------|------|-----------|-------------|
69+
{%- set required_fields = thisbody.schema.required if thisbody.schema.required is defined else [] -%}
5470
{%- for name,field in thisbody.schema.properties.items() %}
55-
| `{{name}}` | {{field.type|title}}{% if "items" in field.keys() and "title" in field["items"].keys() %} of [{{field["items"].title}}]({{type_link(field["items"].title)}}){% endif %} {% if field["title"] is defined %}([{{field.title}}]({{type_link(field.title)}})){% endif %} | {{"Required" if field.required else "May be omitted"}} | {% if field.description is defined %}{{field.description}}{% endif %} |
71+
| `{{name}}` | {{field.type|title}}{% if "items" in field.keys() and "title" in field["items"].keys() %} of [{{field["items"].title}}]({{type_link(field["items"].title)}}){% endif %} {% if field["title"] is defined %}([{{field.title}}]({{type_link(field.title)}})){% endif %} | {{"Required" if name in required_fields else "Optional"}} | {% if field.description is defined %}{{field.description}}{% endif %} |
5672
{%- endfor %}
5773
{% endif %}
5874

@@ -81,21 +97,27 @@ The response uses the following fields:
8197

8298
| Field | Type | Required? | Description |
8399
|-------|------|-----------|-------------|
100+
{%- set required_fields = thisbody.schema.required if thisbody.schema.required is defined else [] -%}
84101
{%- for name,field in thisbody.schema.properties.items() %}
85-
| `{{name}}` | {{field.type|title}}{% if "items" in field.keys() and "title" in field["items"].keys() %} of [{{field["items"].title}}]({{type_link(field["items"].title)}}){% endif %} {% if field["title"] is defined %}([{{field.title}}]({{type_link(field.title)}})){% endif %} | {{ "Required" if field.required else "May be omitted"}} | {% if field.description is defined %}{{field.description}}{% endif %} |
102+
| `{{name}}` | {{field.type|title}}{% if "items" in field.keys() and "title" in field["items"].keys() %} of [{{field["items"].title}}]({{type_link(field["items"].title)}}){% endif %} {% if field["title"] is defined %}([{{field.title}}]({{type_link(field.title)}})){% endif %} | {{ "Required" if name in required_fields else "Optional"}} | {% if field.description is defined %}{{field.description}}{% endif %} |
86103
{%- endfor %}
87104
{% endif %}{# TODO: handle allOf, etc. #}
88105

89106
{% if thisbody.examples is defined and thisbody.examples|length > 0 %}
90107
#### Example Response(s)
91108

92-
{% for body_name,body_sample in thisbody.examples %}
109+
<!-- MULTICODE_BLOCK_START -->
110+
111+
{% for body_name,body_sample in thisbody.examples.items() %}
93112
_{{body_name}}_
94113

95-
```
96-
{{body_sample|pprint}}
114+
```{%if mediatype == "application/json"%}json{%endif%}
115+
{{json_pp(body_sample)}}
97116
```
98117
{% endfor %}
118+
119+
<!-- MULTICODE_BLOCK_END -->
120+
99121
{% endif %}
100122

101123
{% endif %}

dactyl/version.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '0.14.5'
1+
__version__ = '0.15.0'

examples/content/extending/extend-templates.md

-11
This file was deleted.

0 commit comments

Comments
 (0)