Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add filename filter for performance gain #26

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
187 changes: 102 additions & 85 deletions mkdocs_swagger_ui_tag/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,16 @@
from mkdocs.config import config_options
from mkdocs.plugins import BasePlugin

log = logging.getLogger(__name__)
try:
# mkdocs logging utility for mkdocs 1.5+
# https://www.mkdocs.org/dev-guide/plugins/#logging-in-plugins
from mkdocs.plugins import get_plugin_logger

log = get_plugin_logger(__name__)
except ImportError:
# compat
log = logging.getLogger(f"mkdocs.plugins.{__name__}") # type: ignore

base_path = os.path.dirname(os.path.abspath(__file__))


Expand Down Expand Up @@ -56,6 +65,10 @@ class SwaggerUIPlugin(BasePlugin):
("validatorUrl", config_options.Type(str, default="none")),
("extra_css", config_options.Type(list, default=[])),
("dark_scheme_name", config_options.Type(str, default="slate")),
(
"filter_files",
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I renamed the option to filter_files. I think using the file path is less ambiguous.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about marking files on the markdown frontmatter? (see here) The frontmatter data is already available in the page event hooks. I didn't though about that before, but it feels more ergonomic in general.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm good with either way

config_options.ListOfItems(config_options.Type(str), default=[]),
),
)

def on_pre_page(self, page, config, files, **kwargs):
Expand Down Expand Up @@ -107,94 +120,106 @@ def on_post_page(self, output, page, config, **kwargs):
Add javascript code to update iframe height
Create a html with Swagger UI for iframe
"""
# Using file filter for performance
# https://github.com/blueswen/mkdocs-swagger-ui-tag/issues/25
if filter_list := self.config["filter_files"]:
filter_list = [os.path.normpath(f) for f in filter_list]
if os.path.normpath(page.file.src_path) not in filter_list:
return output

soup = BeautifulSoup(output, "html.parser")
swagger_ui_list = soup.find_all("swagger-ui")

# No tags found, we can return earlier
if len(swagger_ui_list) == 0:
return output
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The whole block down this change is just un-indented.
The diff is a bit weird on that, sorry.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM


# Regular processing
iframe_id_list = []
grouped_list = []

if len(swagger_ui_list) > 0:
css_dir = utils.get_relative_url(
utils.normalize_url("assets/stylesheets/"), page.url
)
js_dir = utils.get_relative_url(
utils.normalize_url("assets/javascripts/"), page.url
log.info(f"Processing file '{page.file.src_uri}'")
css_dir = utils.get_relative_url(
utils.normalize_url("assets/stylesheets/"), page.url
)
js_dir = utils.get_relative_url(
utils.normalize_url("assets/javascripts/"), page.url
)
default_oauth2_redirect_file = utils.get_relative_url(
utils.normalize_url("assets/swagger-ui/oauth2-redirect.html"), page.url
)
env = Environment(
loader=FileSystemLoader(os.path.join(base_path, "swagger-ui"))
)
template = env.get_template("swagger.html")
extra_css_files = list(
map(
lambda f: utils.get_relative_url(utils.normalize_url(f), page.url),
self.config["extra_css"],
)
default_oauth2_redirect_file = utils.get_relative_url(
utils.normalize_url("assets/swagger-ui/oauth2-redirect.html"), page.url
)

page_dir = os.path.dirname(
os.path.join(config["site_dir"], urlunquote(page.url))
)
if not os.path.exists(page_dir):
os.makedirs(page_dir)

def render_template(openapi_spec_url, swagger_ui_ele):
cur_options = self.process_options(config, swagger_ui_ele)
cur_oath2_prop = self.process_oath2_prop(swagger_ui_ele)
oauth2_redirect_url = cur_options.pop("oauth2RedirectUrl", "")
if not oauth2_redirect_url:
oauth2_redirect_url = default_oauth2_redirect_file

template_output = template.render(
css_dir=css_dir,
extra_css_files=extra_css_files,
js_dir=js_dir,
background=self.config["background"],
id="{{ID_PLACEHOLDER}}", # ID is unknown yet - it's the hash of the content.
openapi_spec_url=openapi_spec_url,
oauth2_redirect_url=oauth2_redirect_url,
validatorUrl=self.config["validatorUrl"],
options_str=json.dumps(cur_options, indent=4)[1:-1],
oath2_prop_str=json.dumps(cur_oath2_prop),
)
env = Environment(
loader=FileSystemLoader(os.path.join(base_path, "swagger-ui"))
cur_id = hashlib.sha256(template_output.encode()).hexdigest()[:8]
iframe_filename = f"swagger-{cur_id}.html"
template_output = template_output.replace("{{ID_PLACEHOLDER}}", cur_id)
with open(os.path.join(page_dir, iframe_filename), "w") as f:
f.write(template_output)
self.replace_with_iframe(soup, swagger_ui_ele, cur_id, iframe_filename)
return cur_id

for swagger_ui_ele in swagger_ui_list:
if swagger_ui_ele.has_attr("grouped"):
grouped_list.append(swagger_ui_ele)
continue

openapi_spec_url = self.path_to_url(
page.file, swagger_ui_ele.get("src", "")
)
template = env.get_template("swagger.html")
extra_css_files = list(
map(
lambda f: utils.get_relative_url(utils.normalize_url(f), page.url),
self.config["extra_css"],
iframe_id_list.append(
render_template(
openapi_spec_url=openapi_spec_url, swagger_ui_ele=swagger_ui_ele
)
)

page_dir = os.path.dirname(
os.path.join(config["site_dir"], urlunquote(page.url))
)
if not os.path.exists(page_dir):
os.makedirs(page_dir)

def render_template(openapi_spec_url, swagger_ui_ele):
cur_options = self.process_options(config, swagger_ui_ele)
cur_oath2_prop = self.process_oath2_prop(swagger_ui_ele)
oauth2_redirect_url = cur_options.pop("oauth2RedirectUrl", "")
if not oauth2_redirect_url:
oauth2_redirect_url = default_oauth2_redirect_file

template_output = template.render(
css_dir=css_dir,
extra_css_files=extra_css_files,
js_dir=js_dir,
background=self.config["background"],
id="{{ID_PLACEHOLDER}}", # ID is unknown yet - it's the hash of the content.
openapi_spec_url=openapi_spec_url,
oauth2_redirect_url=oauth2_redirect_url,
validatorUrl=self.config["validatorUrl"],
options_str=json.dumps(cur_options, indent=4)[1:-1],
oath2_prop_str=json.dumps(cur_oath2_prop),
)
cur_id = hashlib.sha256(template_output.encode()).hexdigest()[:8]
iframe_filename = f"swagger-{cur_id}.html"
template_output = template_output.replace("{{ID_PLACEHOLDER}}", cur_id)
with open(os.path.join(page_dir, iframe_filename), "w") as f:
f.write(template_output)
self.replace_with_iframe(soup, swagger_ui_ele, cur_id, iframe_filename)
return cur_id

for swagger_ui_ele in swagger_ui_list:
if swagger_ui_ele.has_attr("grouped"):
grouped_list.append(swagger_ui_ele)
continue

openapi_spec_url = self.path_to_url(
page.file, swagger_ui_ele.get("src", "")
)
iframe_id_list.append(
render_template(
openapi_spec_url=openapi_spec_url, swagger_ui_ele=swagger_ui_ele
)
)
if grouped_list:
openapi_spec_url = []
for swagger_ui_ele in grouped_list:
cur_url = self.path_to_url(page.file, swagger_ui_ele.get("src", ""))
cur_name = swagger_ui_ele.get("name", swagger_ui_ele.get("src", ""))
openapi_spec_url.append({"url": cur_url, "name": cur_name})

if grouped_list:
openapi_spec_url = []
for swagger_ui_ele in grouped_list:
cur_url = self.path_to_url(page.file, swagger_ui_ele.get("src", ""))
cur_name = swagger_ui_ele.get("name", swagger_ui_ele.get("src", ""))
openapi_spec_url.append({"url": cur_url, "name": cur_name})

# only use options from first grouped swagger ui tag
render_template(
openapi_spec_url=openapi_spec_url, swagger_ui_ele=grouped_list[0]
)
# only keep first grouped swagger ui tag
for rest_swagger_ui_ele in grouped_list[1:]:
rest_swagger_ui_ele.extract()
# only use options from first grouped swagger ui tag
render_template(
openapi_spec_url=openapi_spec_url, swagger_ui_ele=grouped_list[0]
)
# only keep first grouped swagger ui tag
for rest_swagger_ui_ele in grouped_list[1:]:
rest_swagger_ui_ele.extract()

js_code = soup.new_tag("script")
# trigger from iframe body ResizeObserver
Expand All @@ -215,16 +240,7 @@ def render_template(openapi_spec_url, swagger_ui_ele):
for (var i = 0; i < iframes.length; i++) {
iframe_id_list.push(iframes[i].getAttribute("id"))
}
"""
if len(iframe_id_list) == 0:
js_code.string += """
let ticking = true;
"""
else:
js_code.string += """
let ticking = false;
"""
js_code.string += """
document.addEventListener('scroll', function(e) {
if (!ticking) {
window.requestAnimationFrame(()=> {
Expand Down Expand Up @@ -371,6 +387,7 @@ def process_oath2_prop(self, swagger_ui_ele):
def on_post_build(self, config, **kwargs):
"""Copy Swagger UI css and js files to assets directory"""

log.info("Copying swagger ui assets.")
output_base_path = os.path.join(config["site_dir"], "assets")
css_path = os.path.join(output_base_path, "stylesheets")
for file_name in os.listdir(
Expand Down
8 changes: 8 additions & 0 deletions tests/fixtures/mkdocs-filter-files.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
site_name: test mkdocs_swagger_ui_tag
use_directory_urls: true

plugins:
- swagger-ui-tag:
filter_files:
- index.md
- sub_dir/page_in_sub_dir.md
48 changes: 47 additions & 1 deletion tests/test_builds.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import os
import re
import shutil
from unittest.mock import MagicMock

# other 3rd party
from bs4 import BeautifulSoup
Expand Down Expand Up @@ -526,7 +527,7 @@ def test_empty(tmp_path):
file = testproject_path / "site/empty/index.html"
contents = file.read_text(encoding="utf8")

validate_additional_script_code(contents, exists=True)
validate_additional_script_code(contents, exists=False)
blueswen marked this conversation as resolved.
Show resolved Hide resolved


def test_error(tmp_path):
Expand All @@ -548,3 +549,48 @@ def test_template(tmp_path):

iframe_content_list = validate_iframe(contents, file.parent)
assert len(iframe_content_list) == 2


def test_filter_files(tmp_path):
mkdocs_file = "mkdocs-filter-files.yml"
testproject_path = validate_mkdocs_file(
tmp_path,
f"tests/fixtures/{mkdocs_file}",
)
file = testproject_path / "site/index.html"
contents = file.read_text(encoding="utf8")
iframe_content_list = validate_iframe(contents, file.parent)
assert len(iframe_content_list) == 1

file = testproject_path / "site/sub_dir/page_in_sub_dir/index.html"
contents = file.read_text(encoding="utf8")
iframe_content_list = validate_iframe(contents, file.parent)
assert len(iframe_content_list) == 1

file = testproject_path / "site/empty/index.html"
contents = file.read_text(encoding="utf8")
validate_additional_script_code(contents, exists=False)

file = testproject_path / "site/multiple/index.html"
contents = file.read_text(encoding="utf8")
validate_additional_script_code(contents, exists=False)


def test_import_error(monkeypatch):
# Simulate ImportError for 'get_plugin_logger'
with monkeypatch.context() as m:
m.setattr(
"mkdocs.plugins.get_plugin_logger", MagicMock(side_effect=ImportError)
)

# Reload the module to apply the monkeypatch
import importlib

from mkdocs_swagger_ui_tag import plugin

importlib.reload(plugin)
# Test that the fallback logger is used
assert plugin.log.name == f"mkdocs.plugins.{plugin.__name__}"

# Ensure the logger works without raising errors
plugin.log.info("Test message")