Skip to content

Commit

Permalink
Merged features tied to 'Goat's Horn' funding goal
Browse files Browse the repository at this point in the history
  • Loading branch information
squidfunk committed Dec 7, 2023
1 parent 560bb90 commit f855a67
Show file tree
Hide file tree
Showing 73 changed files with 2,457 additions and 221 deletions.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion material/overrides/main.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@
{% endblock %}
{% block scripts %}
{{ super() }}
<script src="{{ 'assets/javascripts/custom.fe17d8dd.min.js' | url }}"></script>
<script src="{{ 'assets/javascripts/custom.2340dcd7.min.js' | url }}"></script>
{% endblock %}
7 changes: 4 additions & 3 deletions material/plugins/blog/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def on_files(self, files, *, config):
root = posixpath.normpath(self.config.blog_dir)
site = config.site_dir

# Compute path to posts directory
# Compute and normalize path to posts directory
path = self.config.post_dir.format(blog = root)
path = posixpath.normpath(path)

Expand Down Expand Up @@ -265,10 +265,11 @@ def on_page_markdown(self, markdown, *, page, config, files):
# is not already present, so we can remove footnotes or other content
# from the excerpt without affecting the content of the excerpt
if separator not in page.markdown:
path = page.file.src_path
if self.config.post_excerpt == "required":
docs = os.path.relpath(config.docs_dir)
path = os.path.relpath(page.file.abs_src_path, docs)
raise PluginError(
f"Couldn't find '{separator}' separator in '{path}'"
f"Couldn't find '{separator}' in post '{path}' in '{docs}'"
)
else:
page.markdown += f"\n\n{separator}"
Expand Down
4 changes: 2 additions & 2 deletions material/plugins/blog/structure/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def __init__(self, file: File, config: MkDocsConfig):
self.markdown = f.read()

# Sadly, MkDocs swallows any exceptions that occur during parsing.
# As we want to provide the best possible authoring experience, we
# Since we want to provide the best possible user experience, we
# need to catch errors early and display them nicely. We decided to
# drop support for MkDocs' MultiMarkdown syntax, because it is not
# correctly implemented anyway. When using MultiMarkdown syntax, all
Expand All @@ -80,7 +80,7 @@ def __init__(self, file: File, config: MkDocsConfig):
self.markdown = self.markdown[match.end():].lstrip("\n")

# The post's metadata could not be parsed because of a syntax error,
# which we display to the user with a nice error message
# which we display to the author with a nice error message
except Exception as e:
raise PluginError(
f"Error reading metadata of post '{path}' in '{docs}':\n"
Expand Down
8 changes: 4 additions & 4 deletions material/plugins/blog/structure/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ def __init__(self, *args, **kwargs):
# Normalize the supported types for post dates to datetime
def pre_validation(self, config: Config, key_name: str):

# If the date points to a scalar value, convert it to a dictionary,
# since we want to allow the user to specify custom and arbitrary date
# values for posts. Currently, only the `created` date is mandatory,
# because it's needed to sort posts for views.
# If the date points to a scalar value, convert it to a dictionary, as
# we want to allow the author to specify custom and arbitrary dates for
# posts. Currently, only the `created` date is mandatory, because it's
# needed to sort posts for views.
if not isinstance(config[key_name], dict):
config[key_name] = { "created": config[key_name] }

Expand Down
6 changes: 3 additions & 3 deletions material/plugins/info/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def on_startup(self, *, command, dirty):
# Create a self-contained example (run earliest) - determine all files that
# are visible to MkDocs and are used to build the site, create an archive
# that contains all of them, and print a summary of the archive contents.
# The user must attach this archive to the bug report.
# The author must attach this archive to the bug report.
@event_priority(100)
def on_config(self, config):
if not self.config.enabled:
Expand Down Expand Up @@ -103,10 +103,10 @@ def on_config(self, config):
log.error("Please remove 'hooks' setting.")
self._help_on_customizations_and_exit()

# Create in-memory archive and prompt user to enter a short descriptive
# Create in-memory archive and prompt author for a short descriptive
# name for the archive, which is also used as the directory name. Note
# that the name is slugified for better readability and stripped of any
# file extension that the user might have entered.
# file extension that the author might have entered.
archive = BytesIO()
example = input("\nPlease name your bug report (2-4 words): ")
example, _ = os.path.splitext(example)
Expand Down
19 changes: 19 additions & 0 deletions material/plugins/privacy/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright (c) 2016-2023 Martin Donath <[email protected]>

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
43 changes: 43 additions & 0 deletions material/plugins/privacy/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Copyright (c) 2016-2023 Martin Donath <[email protected]>

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.

import os

from mkdocs.config.base import Config
from mkdocs.config.config_options import Deprecated, DictOfItems, Type

# -----------------------------------------------------------------------------
# Classes
# -----------------------------------------------------------------------------

# Privacy plugin configuration
class PrivacyConfig(Config):
enabled = Type(bool, default = True)
concurrency = Type(int, default = max(1, os.cpu_count() - 1))

# Settings for caching
cache = Type(bool, default = True)
cache_dir = Type(str, default = ".cache/plugin/privacy")

# Settings for external assets
assets = Type(bool, default = True)
assets_fetch = Type(bool, default = True)
assets_fetch_dir = Type(str, default = "assets/external")
assets_expr_map = DictOfItems(Type(str), default = {})
41 changes: 41 additions & 0 deletions material/plugins/privacy/parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Copyright (c) 2016-2023 Martin Donath <[email protected]>

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.

from html.parser import HTMLParser
from xml.etree.ElementTree import Element

# -----------------------------------------------------------------------------
# Classes
# -----------------------------------------------------------------------------

# Fragment parser - previously, we used lxml for fault-tolerant HTML5 parsing,
# but it blows up the size of the Docker image by 20 MB. We can't just use the
# built-in XML parser, as it doesn't handle HTML5 (because, yeah, it's not XML),
# so we use a streaming parser and construct the element ourselves.
class FragmentParser(HTMLParser):

# Initialize parser
def __init__(self):
super().__init__(convert_charrefs = True)
self.result = None

# Create element
def handle_starttag(self, tag, attrs):
self.result = Element(tag, dict(attrs))
Loading

0 comments on commit f855a67

Please sign in to comment.