Skip to content

Commit

Permalink
v0.15.0: OpenAPI spec fixes, tail formula, etc.
Browse files Browse the repository at this point in the history
  • Loading branch information
mDuo13 committed Jan 13, 2022
1 parent c8f0542 commit dc54c1f
Show file tree
Hide file tree
Showing 34 changed files with 918 additions and 714 deletions.
539 changes: 4 additions & 535 deletions README.md

Large diffs are not rendered by default.

16 changes: 14 additions & 2 deletions dactyl/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ def __init__(self, cli_args):
else:
logger.debug("No config file specified, trying ./dactyl-config.yml")
self.load_config_from_file(DEFAULT_CONFIG_FILE)
self.check_consistency()
self.load_filters()

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

if "flatten_default_html_paths" in loaded_config:
if loaded_config["flatten_default_html_paths"] == True:
loaded_config["default_html_names"] = "flatten"
else:
loaded_config["default_html_names"] = "path"

self.config.update(loaded_config)

def check_consistency(self):
Expand All @@ -96,7 +103,7 @@ def check_consistency(self):

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

# Check consistency here instead of earlier so that we have frontmatter
# from pages first. (Otherwise, we raise false alarms about pages not
# being part of any target.)
self.check_consistency()

def load_filters(self):
# Figure out which filters we need
filternames = set(self.config["default_filters"])
Expand Down
19 changes: 15 additions & 4 deletions dactyl/default-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,21 @@ skip_preprocessor: false
template_allow_undefined: true
preprocessor_allow_undefined: true

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

## Set this to true to disable Dactyl's built-in syntax highlighting
no_highlighting: false
Expand Down
11 changes: 9 additions & 2 deletions dactyl/openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ def clean_up_swag(self):
schema["title"] = title
if "example" in schema:
try:
j = json.dumps(schema["example"], indent=4, default=self.json_default)
j = self.json_pp(schema["example"])
schema["example"] = j
except Exception as e:
logger.debug("%s example isn't json: %s"%(title,j))
Expand Down Expand Up @@ -245,7 +245,7 @@ def get_x_example_request_body(self, path, method, endpoint):
return ""

try:
ex_pp = json.dumps(ex, indent=4, separators=(',', ': '), default=self.json_default)
ex_pp = self.json_pp(ex)
except TypeError as e:
traceback.print_tb(e.__traceback__)
logger.debug("json dumps failed on example '%s'"%ex)
Expand Down Expand Up @@ -387,6 +387,7 @@ def new_context(self):
"debug": logger.debug,
"slugify": slugify,
"md_escape": self.md_escape,
"json_pp": self.json_pp
}

@staticmethod
Expand All @@ -403,6 +404,12 @@ def md_escape(text):
s += c
return s

def json_pp(self, j):
"""
Pretty-print function for JSON
"""
return json.dumps(j, indent=4, default=self.json_default)

@staticmethod
def json_default(o):
"""
Expand Down
9 changes: 6 additions & 3 deletions dactyl/page.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,10 +159,13 @@ def provide_default_filename(self):

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

logger.debug("Generated html filename '%s' for page: %s" %
(new_filename, self))
(self.data["html"], self))

def provide_name(self):
"""
Expand Down
4 changes: 3 additions & 1 deletion dactyl/templates/breadcrumbs.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@

{% if currentpage.html != "index.html" %}
{%- for page in ns.crumbs %}
<li class="active breadcrumb-item"><a href="{% if "//" not in page.html %}{{ currentpage.prefix }}{% endif %}{{ page.html }}">{{ page.name }}</a></li>
<li class="active breadcrumb-item"><a href="
{%- if page is defined and "//" not in page.html %}{{ currentpage.prefix }}{% endif -%}
{{ page.html }}">{{ page.name }}</a></li>
{% endfor %}
{% endif %}
<li class="active breadcrumb-item">{{ currentpage.name }}</li>
Expand Down
3 changes: 2 additions & 1 deletion dactyl/templates/landing.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
{% if content %}

<section class="pt-3 p-md-3">
<article class="content">
<article class="dactyl-content">
{{ content }}

<h2>Read More</h2>
{% set show_blurbs = True %}
{% set depth= 1 %}
{% include 'children.html' %}
Expand Down
5 changes: 3 additions & 2 deletions dactyl/templates/template-openapi_data_type.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
{% endif -%}
{% if example is defined %}- **Example:**

{{example|indent(8,indentfirst=False)}}
{{example|indent(8,first=False)}}

{% endif -%}

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

| Field | Type | Required? | Description |
|-------|------|-----------|-------------|
{%- set required_fields = required if required is defined else [] -%}
{%- for name,field in properties.items() %}
| `{{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}} |
| `{{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}} |
{%- endfor %}

{% if additionalProperties is defined and additionalProperties == True %}This type MUST NOT contain any additional fields.{% endif %}
Expand Down
48 changes: 35 additions & 13 deletions dactyl/templates/template-openapi_endpoint.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
# {{summary}}

{{description}}

## Request Format

```
{{method|upper}} {{path}}
{%- if method in ["post","put","delete"] and requestBody is defined %}
{{ x_example_request_body }}
{% endif %}
```

{{description}}

## Request Format

{% if path_params|length %}
#### Path Parameters
This API method uses the following path parameters:

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

{% if query_params|length %}
#### Query Parameters
This API method uses the following query parameters:

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

{% if requestBody is defined %}
### Request Body
{{requestBody.description}}

{% if requestBody.content is defined %}

{% for mediatype,thisbody in requestBody.content.items() %}
{% if thisbody.examples is defined and thisbody.examples|length > 0 %}

<!-- MULTICODE_BLOCK_START -->

{% for body_name,body_sample in thisbody.examples.items() %}
_{{body_name}}_

```{%if mediatype == "application/json"%}json{%endif%}
{{json_pp(body_sample)}}
```
{% endfor %}

<!-- MULTICODE_BLOCK_END -->

{% endif %}

{% if thisbody.schema is defined %}
**Media type:** {{mediatype|replace("*","\*")}}

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

| Field | Type | Required? | Description |
|-------|------|-----------|-------------|
{%- set required_fields = thisbody.schema.required if thisbody.schema.required is defined else [] -%}
{%- for name,field in thisbody.schema.properties.items() %}
| `{{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 %} |
| `{{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 %} |
{%- endfor %}
{% endif %}

Expand Down Expand Up @@ -81,21 +97,27 @@ The response uses the following fields:

| Field | Type | Required? | Description |
|-------|------|-----------|-------------|
{%- set required_fields = thisbody.schema.required if thisbody.schema.required is defined else [] -%}
{%- for name,field in thisbody.schema.properties.items() %}
| `{{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 %} |
| `{{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 %} |
{%- endfor %}
{% endif %}{# TODO: handle allOf, etc. #}

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

{% for body_name,body_sample in thisbody.examples %}
<!-- MULTICODE_BLOCK_START -->

{% for body_name,body_sample in thisbody.examples.items() %}
_{{body_name}}_

```
{{body_sample|pprint}}
```{%if mediatype == "application/json"%}json{%endif%}
{{json_pp(body_sample)}}
```
{% endfor %}

<!-- MULTICODE_BLOCK_END -->

{% endif %}

{% endif %}
Expand Down
2 changes: 1 addition & 1 deletion dactyl/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '0.14.5'
__version__ = '0.15.0'
11 changes: 0 additions & 11 deletions examples/content/extending/extend-templates.md

This file was deleted.

Loading

0 comments on commit dc54c1f

Please sign in to comment.