Skip to content

Commit

Permalink
feat: themes support
Browse files Browse the repository at this point in the history
  • Loading branch information
adrienbrignon committed May 27, 2023
1 parent a6e4afc commit aa38c9a
Show file tree
Hide file tree
Showing 29 changed files with 385 additions and 1,004 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ RUN apt-get update \

FROM base as builder

ENV POETRY_VERSION=1.4.2 \
ENV POETRY_VERSION=1.5.0 \
PIP_NO_CACHE_DIR=1 \
PIP_DEFAULT_TIMEOUT=100 \
PIP_DISABLE_PIP_VERSION_CHECK=1
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ A highly-configurable plugin for [*MkDocs*](https://github.com/mkdocs/mkdocs) th

- 🚀 **Fast** - PDF documents are generated concurrently!
- 🎨 **Customizable** - full control over the resulting documents
- Compatible with [`mkdocs-material`](https://github.com/squidfunk/mkdocs-material)
- Cover pages (supports [`macros`](https://github.com/fralau/mkdocs_macros_plugin) plugin)
- Define custom scripts and stylesheets to customize your PDF documents
- Define "buttons" at the top of your documentation pages ([example](https://adrienbrignon.github.io/mkdocs-exporter/setup/setting-up-buttons/))
- Compatible with [`material`](https://github.com/squidfunk/mkdocs-material) and [`readthedocs`](https://www.mkdocs.org/user-guide/choosing-your-theme/#readthedocs) themes
-**Powerful** - it uses a headless browser and some awesome libraries under the hood to generate PDF files
- [*Playwright*](https://github.com/microsoft/playwright-python) to automate browsers
- [*Paged.js*](https://github.com/pagedjs/pagedjs) polyfills are included by default ([Paged Media](https://www.w3.org/TR/css-page-3/) and [Generated Content](https://www.w3.org/TR/css-gcpm-3/) CSS modules)
- [*Paged.js*](https://github.com/pagedjs/pagedjs) polyfills are included ([Paged Media](https://www.w3.org/TR/css-page-3/) and [Generated Content](https://www.w3.org/TR/css-gcpm-3/) CSS modules)
- [*Sass*](https://sass-lang.com/) support (via [`libsass`](https://github.com/sass/libsass-python)) for your stylesheets

## Prerequisites
Expand Down
6 changes: 6 additions & 0 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@ hide:

Try this out by clicking the download button at the top of this page (or you can directly head [here](./index.pdf){:target="_blank"}).

## Prerequisites

- Python `>= 3.7`
- MkDocs `>= 1.4`
- A compatible theme
- [`material`](https://github.com/squidfunk/mkdocs-material) (:material-star-shooting: *currently used by this documentation*)
- [`readthedocs`](https://www.mkdocs.org/user-guide/choosing-your-theme/#readthedocs)

## Installation

Expand Down
19 changes: 13 additions & 6 deletions docs/setup/setting-up-buttons.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
---
buttons:
- title: I'm Feeling Lucky
href: https://www.youtube.com/watch?v=dQw4w9WgXcQ
icon: material-star-outline
target: _blank
attributes:
href: https://www.youtube.com/watch?v=dQw4w9WgXcQ
target: _blank
---

# Setting up buttons
Expand Down Expand Up @@ -33,12 +34,17 @@ plugins:
- title: Download as PDF
icon: material-file-download-outline
enabled: !!python/name:mkdocs_exporter.plugins.pdf.button.enabled
href: !!python/name:mkdocs_exporter.plugins.pdf.button.href
download: !!python/name:mkdocs_exporter.plugins.pdf.button.download
attributes:
href: !!python/name:mkdocs_exporter.plugins.pdf.button.href
download: !!python/name:mkdocs_exporter.plugins.pdf.button.download
```
The functions referenced in this configuration are provided by the **MkDocs Exporter** plugin.
!!! info
Currently, icons are only available with the [MkDocs Material](https://github.com/squidfunk/mkdocs-material) theme.
### Defining a dynamic button
As you've seen in the previous example, you can use Python functions to resolve the attributes of a button dynamically.
Expand Down Expand Up @@ -80,9 +86,10 @@ Here's the configuration used by this page:
buttons:
- title: {{ button.title }}
href: {{ button.href }}
icon: {{ button.icon }}
target: {{ button.target }}
attributes:
href: {{ button.attributes.href }}
target: {{ button.attributes.target }}
---
# {{ page.title }}
Expand Down
2 changes: 1 addition & 1 deletion docs/setup/setting-up-documents.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ plugins:
**MkDocs Exporter** comes with various plugins in a single package.
This architecture was chosen to reduce code duplication and maintain a generic base that can be used
This architecture has been chosen to reduce code duplication and maintain a generic base that can be used
to export your pages to formats other than PDF (although this is the only format currently supported).
Basically, the `mkdocs/exporter` must always be registered first as it provides a common ground for
Expand Down
14 changes: 9 additions & 5 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ site_dir: dist

theme:
name: material
custom_dir: overrides
icon:
logo: material/file-document-arrow-right
repo: fontawesome/brands/github
Expand Down Expand Up @@ -51,8 +50,9 @@ plugins:
- title: Download as PDF
icon: material-file-download-outline
enabled: !!python/name:mkdocs_exporter.plugins.pdf.button.enabled
href: !!python/name:mkdocs_exporter.plugins.pdf.button.href
download: !!python/name:mkdocs_exporter.plugins.pdf.button.download
attributes:
href: !!python/name:mkdocs_exporter.plugins.pdf.button.href
download: !!python/name:mkdocs_exporter.plugins.pdf.button.download
- search:
lang: en
- awesome-pages
Expand All @@ -63,8 +63,6 @@ plugins:
- redirects:
redirect_maps:
'index.md': 'getting-started.md'
- search:
separator: '[\s\-,:!=\[\]()"`/]+|\.(?!\d)|&[lg]t;|(?!\b)(?=[A-Z][a-z])'
- minify:
minify_html: true

Expand All @@ -73,6 +71,12 @@ markdown_extensions:
- attr_list
- pymdownx.details
- pymdownx.superfences
- mdx_truly_sane_lists:
truly_sane: true
nested_indent: 2
- pymdownx.emoji:
emoji_index: !!python/name:materialx.emoji.twemoji
emoji_generator: !!python/name:materialx.emoji.to_svg

extra_css:
- assets/stylesheets/custom.css
4 changes: 4 additions & 0 deletions mkdocs_exporter/page.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import Optional
from mkdocs_exporter.theme import Theme
from mkdocs.structure.pages import Page as BasePage


Expand All @@ -15,4 +16,7 @@ def __init__(self, *args, **kwargs):
self.formats: dict[str, str]
"""The documents that have been generated for this page (format as key, path to the file as value)."""

self.theme: Theme = None
"""The theme of the page."""

super().__init__(*args, **kwargs)
46 changes: 44 additions & 2 deletions mkdocs_exporter/plugin.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,70 @@
from mkdocs.plugins import BasePlugin
from mkdocs_exporter.page import Page
from mkdocs.plugins import event_priority
from mkdocs.structure.files import File, Files
from mkdocs_exporter.preprocessor import Preprocessor
from mkdocs_exporter.themes.factory import Factory as ThemeFactory


class Plugin(BasePlugin):
"""The plugin."""


def __init__(self) -> None:
"""The constructor."""

self.files: list[File] = []


def on_config(self, config: dict) -> None:
"""Invoked when the configuration has been loaded."""

self.theme = ThemeFactory.create(config['theme'])


def on_pre_build(self, **kwargs) -> None:
"""Invoked before the build process starts."""

self.files = []


def on_pre_page(self, page: Page, **kwargs) -> None:
"""Invoked after a page has been built."""

page.html = None
page.formats = {}
page.theme = self.theme


@event_priority(-100)
def on_post_page(self, html: str, **kwargs) -> str:
def on_post_page(self, html: str, page: Page, **kwargs) -> str:
"""Invoked after a page has been built (and after all other plugins)."""

preprocessor = Preprocessor()
preprocessor = Preprocessor(theme=page.theme)

preprocessor.preprocess(html)
preprocessor.remove('*[data-decompose=true]')
preprocessor.teleport()

return preprocessor.done()


def on_files(self, files: Files, **kwargs) -> Files:
"""Invoked when files are ready to be manipulated."""

self.files.extend(files.css_files())

return files


@event_priority(100)
def on_post_build(self, **kwargs) -> None:
"""Invoked when the build process is done."""

for file in self.files:
css = None

with open(file.abs_dest_path, 'r') as reader:
css = self.theme.stylesheet(reader.read())
with open(file.abs_dest_path, 'w+') as writer:
writer.write(css)
13 changes: 2 additions & 11 deletions mkdocs_exporter/plugins/extras/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,14 @@ class ButtonConfig(BaseConfig):
enabled = c.Type((bool, Callable), default=True)
"""Is the button enabled?"""

id = c.Optional(c.Type(str, Callable))
"""The button's identifier."""

title = c.Type((str, Callable))
"""The button's title."""

icon = c.Type((str, Callable))
"""The button's icon (typically, an SVG element)."""

href = c.Type((str, Callable))
"""The button's 'href' attribute."""

download = c.Optional(c.Type((bool, str, Callable)))
"""The button's 'download' attribute."""

target = c.Optional(c.Choice(('_blank', '_self', '_parent', '_top')))
"""The button's 'target' attribute."""
attributes = c.Type((dict, Callable), default={})
"""Some extra attributes to add to the button."""


class Config(BaseConfig):
Expand Down
20 changes: 14 additions & 6 deletions mkdocs_exporter/plugins/extras/plugin.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from typing import Optional
from collections import UserDict
from mkdocs.plugins import BasePlugin
from mkdocs_exporter.page import Page
from mkdocs.plugins import event_priority
from mkdocs_exporter.preprocessor import Preprocessor
from mkdocs_exporter.plugins.extras.config import Config
from mkdocs_exporter.plugins.extras.preprocessor import Preprocessor


class Plugin(BasePlugin[Config]):
Expand All @@ -14,15 +15,22 @@ class Plugin(BasePlugin[Config]):
def on_post_page(self, html: str, page: Page, **kwargs) -> Optional[str]:
"""Invoked after a page has been built."""

def resolve(value):
return value(page) if callable(value) else value
def resolve(object):
if callable(object):
return resolve(object(page))
if isinstance(object, list):
return [resolve(v) for v in object]
if isinstance(object, (dict, UserDict)):
return {k: resolve(v) for k, v in object.items()}

preprocessor = Preprocessor()
return object

preprocessor = Preprocessor(theme=page.theme)

preprocessor.preprocess(html)

for button in [*self.config.buttons, *page.meta.get('buttons', [])]:
if 'enabled' not in button or resolve(button['enabled']):
preprocessor.button(**{k: resolve(v) for k, v in button.items()})
if resolve(button.get('enabled', True)):
preprocessor.button(**resolve(button))

return preprocessor.done()
26 changes: 0 additions & 26 deletions mkdocs_exporter/plugins/extras/preprocessor.py

This file was deleted.

3 changes: 0 additions & 3 deletions mkdocs_exporter/plugins/pdf/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,3 @@ class Config(BaseConfig):

covers = c.SubConfig(CoversConfig)
"""The document's cover pages."""

polyfills = c.Type(bool, default=True)
"""Should polyfills be imported?"""
2 changes: 1 addition & 1 deletion mkdocs_exporter/plugins/pdf/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def on_post_page(self, html: str, page: Page, config: dict) -> Optional[str]:
async def render(page: Page) -> None:
logger.info('Rendering PDF for %s...', page.file.src_path)

pdf = await self.renderer.render(page, polyfills=self.config['polyfills'])
pdf = await self.renderer.render(page)
fullpath = os.path.join(config['site_dir'], page.formats['pdf'])

with open(fullpath, 'wb+') as file:
Expand Down
10 changes: 4 additions & 6 deletions mkdocs_exporter/plugins/pdf/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,20 +46,20 @@ def cover(self, template: str) -> Renderer:
return f'<div data-decompose="true">{content}</div>' + '\n'


async def render(self, page: Page, **kwargs) -> bytes:
async def render(self, page: Page) -> bytes:
"""Renders a page as a PDF document."""

if not self.browser.launched:
await self.browser.launch()

preprocessor = Preprocessor()
preprocessor = Preprocessor(theme=page.theme)
base = os.path.dirname(page.file.abs_dest_path)
root = base.replace(unquote(page.url).rstrip('/'), '', 1).rstrip('/')

preprocessor.preprocess(page.html)
preprocessor.remove(['.md-sidebar.md-sidebar--primary', '.md-sidebar.md-sidebar--secondary', 'header.md-header', '.md-container > nav', 'nav.md-tags'])
preprocessor.remove_scripts()
preprocessor.set_attribute('details:not([open])', 'open', 'open')
page.theme.preprocess(preprocessor)

for stylesheet in self.stylesheets:
with open(stylesheet, 'r') as file:
Expand All @@ -68,9 +68,7 @@ async def render(self, page: Page, **kwargs) -> bytes:
with open(script, 'r') as file:
preprocessor.script(file.read())

if kwargs.get('polyfills', True):
preprocessor.script(importlib_resources.files(js).joinpath('pagedjs.min.js').read_text())

preprocessor.script(importlib_resources.files(js).joinpath('pagedjs.min.js').read_text())
preprocessor.teleport()
preprocessor.update_links(base, root)

Expand Down
Loading

0 comments on commit aa38c9a

Please sign in to comment.