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

Define and use Configuration type #55

Merged
merged 3 commits into from
Apr 17, 2024
Merged
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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ name: CI
on:
push:
branches:
- master
- main
pull_request:
branches:
- master
- main

jobs:
test:
Expand Down
67 changes: 41 additions & 26 deletions mkdocs_drawio_exporter/exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,29 @@
import shutil
import subprocess
import sys
from typing import TypedDict


IMAGE_RE = re.compile('(<img[^>]+src=")([^">]+)("\\s*\\/?>)')


class Configuration(TypedDict):
"""Draw.io Exporter MkDocs plugin configuration.

Contains the resolved configuration values, including defaults and any
computed values (such as paths to binaries).

Seems ugly to shadow BasePlugin.config_scheme, but improves type hints and
allows us to more easily pass configuration around."""

cache_dir: str
drawio_executable: str
drawio_args: list[str]
format: str
embed_format: str
sources: str


class ConfigurationError(Exception):
"""Configuration exception.

Expand Down Expand Up @@ -45,8 +63,7 @@ def __init__(self, key, value, message):
Exception.__init__(self, self.message)

def __str__(self):
return 'drawio-exporter: value "{}" for key "{}" is invalid: {}'.format(
self.value, self.key, self.message)
return f'drawio-exporter: value "{self.value}" for key "{self.key}" is invalid: {self.message}'

def drawio_executable(value, message):
"""Raise an error for a misconfigured Draw.io path.
Expand Down Expand Up @@ -159,7 +176,7 @@ def drawio_executable_paths(self, platform):
program_files.append(os.environ['ProgramFiles(x86)'])
return [os.path.join(dir, 'draw.io', 'draw.io.exe') for dir in program_files]
else:
self.log.warning('Draw.io executable paths not known for platform "{}"'.format(platform))
self.log.warning(f'Draw.io executable paths not known for platform "{platform}"')

def prepare_cache_dir(self, cache_dir, docs_dir):
"""Ensure the cache path is set, absolute and exists.
Expand Down Expand Up @@ -191,21 +208,20 @@ def prepare_drawio_executable(self, executable, executable_names, platform_execu
for name in executable_names:
executable = shutil.which(name)
if executable:
self.log.debug('Found Draw.io executable "{}" at "{}"'.format(name, executable))
self.log.debug(f'Found Draw.io executable "{name}" at "{executable}"')
return executable

candidates = platform_executable_paths
self.log.debug('Trying paths {} for platform "{}"'.format(candidates, sys.platform))
self.log.debug(f'Trying paths {candidates} for platform "{sys.platform}"')
for candidate in candidates:
if os.path.isfile(candidate):
self.log.debug('Found Draw.io executable for platform "{}" at "{}"'.format(
sys.platform, candidate))
self.log.debug(f'Found Draw.io executable for platform "{sys.platform}" at "{candidate}"')
return candidate

raise ConfigurationError.drawio_executable(
None, 'Unable to find Draw.io executable; ensure it\'s on PATH or set drawio_executable option')

def rewrite_image_embeds(self, output_content, sources, format, embed_format):
def rewrite_image_embeds(self, output_content, config: Configuration):
"""Rewrite image embeds.

:param str output_content: Content to rewrite.
Expand All @@ -223,11 +239,11 @@ def replace(match):
filename = match.group(2)
page_index = 0

if fnmatch.fnmatch(filename, sources):
if fnmatch.fnmatch(filename, config["sources"]):
content_sources.append(Source(filename, page_index))
img_src = "{}-{}.{}".format(filename, page_index, format)
img_src = f"{filename}-{page_index}.{config["format"]}"

return embed_format.format(
return config["embed_format"].format(
img_open=match.group(1), img_close=match.group(3),
img_src=img_src)
else:
Expand All @@ -245,7 +261,7 @@ def filter_cache_files(self, files, cache_dir):
"""
return [f for f in files if not f.abs_src_path.startswith(cache_dir)]

def ensure_file_cached(self, source, source_rel, page_index, drawio_executable, drawio_args, cache_dir, format):
def ensure_file_cached(self, source, source_rel, page_index, config: Configuration):
"""Ensure cached copy of output exists.

:param str source: Source path, absolute.
Expand All @@ -257,20 +273,19 @@ def ensure_file_cached(self, source, source_rel, page_index, drawio_executable,
:param str format: Desired export format.
:return tuple(str, int): Cached export filename.
"""
cache_filename = self.make_cache_filename(source_rel, page_index, cache_dir)
cache_filename = self.make_cache_filename(source_rel, page_index, config['cache_dir'])
exit_status = None

if self.use_cached_file(source, cache_filename):
self.log.debug('Source file appears unchanged; using cached copy from "{}"'.format(cache_filename))
self.log.debug(f'Source file appears unchanged; using cached copy from "{cache_filename}"')
else:
if not drawio_executable:
self.log.warning('Skipping export of "{}" as Draw.io executable not available'.format(source))
if not config['drawio_executable']:
self.log.warning(f'Skipping export of "{source}" as Draw.io executable not available')
return (None, exit_status)

self.log.debug('Exporting "{}" to "{}"'.format(source, cache_filename))
self.log.debug(f'Exporting "{source}" to "{cache_filename}"')
exit_status = self.export_file(
source, page_index, cache_filename,
drawio_executable, drawio_args, format)
source, page_index, cache_filename, config)

return (cache_filename, exit_status)

Expand All @@ -282,8 +297,8 @@ def make_cache_filename(self, source, page_index, cache_dir):
:param str cache_dir: Export cache directory.
:return str: Resulting filename.
"""
basename = '{}-{}'.format(
hashlib.sha1(source.encode('utf-8')).hexdigest(), page_index)
filename_hash = hashlib.sha1(source.encode('utf-8')).hexdigest()
basename = f'{filename_hash}-{page_index}'
return os.path.join(cache_dir, basename)

def use_cached_file(self, source, cache_filename):
Expand All @@ -296,7 +311,7 @@ def use_cached_file(self, source, cache_filename):
return os.path.exists(cache_filename) \
and os.path.getmtime(cache_filename) >= os.path.getmtime(source)

def export_file(self, source, page_index, dest, drawio_executable, drawio_args, format):
def export_file(self, source, page_index, dest, config: Configuration):
"""Export an individual file.

:param str source: Source path, absolute.
Expand All @@ -308,16 +323,16 @@ def export_file(self, source, page_index, dest, drawio_executable, drawio_args,
:return int: The Draw.io exit status.
"""
cmd = [
drawio_executable,
config['drawio_executable'],
'--export', source,
'--page-index', str(page_index),
'--output', dest,
'--format', format,
'--format', config['format'],
]
cmd += drawio_args
cmd += config['drawio_args']

try:
self.log.debug('Using export command {}'.format(cmd))
self.log.debug(f'Using export command {cmd}')
return subprocess.call(cmd)
except:
self.log.exception('Subprocess raised exception')
23 changes: 10 additions & 13 deletions mkdocs_drawio_exporter/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from mkdocs.structure.files import Files
from mkdocs.utils import copy_file

from .exporter import ConfigurationError, DrawIoExporter, Source
from .exporter import ConfigurationError, DrawIoExporter, Configuration


log = mkdocs.plugins.log.getChild('drawio-exporter')
Expand Down Expand Up @@ -48,14 +48,13 @@ def on_config(self, config):

os.makedirs(self.config['cache_dir'], exist_ok=True)

log.debug('Using Draw.io executable "{}", arguments {} and cache directory "{}"'.format(
self.config['drawio_executable'], self.config['drawio_args'],
self.config['cache_dir']))
log.debug(f'Using Draw.io executable "{self.config['drawio_executable']}", '
f'arguments {self.config['drawio_args']} and '
f'cache directory "{self.config['cache_dir']}"')

def on_post_page(self, output_content, page, **kwargs):
output_content, content_sources = self.exporter.rewrite_image_embeds(
output_content, self.config['sources'],
self.config['format'], self.config['embed_format'])
output_content, self.config)

for source in content_sources:
source.resolve_rel_path(page.file.dest_path)
Expand All @@ -65,27 +64,25 @@ def on_post_page(self, output_content, page, **kwargs):

def on_files(self, files, config):
keep = self.exporter.filter_cache_files(files, self.config['cache_dir'])
log.debug('{} files left after excluding cache'.format(len(keep)))
log.debug(f'{len(keep)} files left after excluding cache')

return Files(keep)

def on_post_build(self, config):
sources = set(self.sources)
log.debug('Found {} unique sources in {} total embeds'.format(len(sources), len(self.sources)))
log.debug(f'Found {len(sources)} unique sources in {len(self.sources)} total embeds')
self.sources = []

for source in sources:
dest_rel_path = '{}-{}.{}'.format(
source.source_rel, source.page_index, self.config['format'])
dest_rel_path = f'{source.source_rel}-{source.page_index}.{self.config['format']}'
abs_src_path = os.path.join(config['docs_dir'], source.source_rel)
abs_dest_path = os.path.join(config['site_dir'], dest_rel_path)
cache_filename, exit_status = self.exporter.ensure_file_cached(
abs_src_path, source.source_rel, source.page_index,
self.config['drawio_executable'], self.config['drawio_args'],
self.config['cache_dir'], self.config['format'])
self.config)

if exit_status not in (None, 0):
log.error('Export failed with exit status {}; skipping copy'.format(exit_status))
log.error(f'Export failed with exit status {exit_status}; skipping copy')
continue

try:
Expand Down
Loading