Skip to content

Commit

Permalink
Merge pull request #16 from pydsigner/markdown_dependency_fix
Browse files Browse the repository at this point in the history
Markdown support enhancement (#11)
  • Loading branch information
pydsigner authored Jul 17, 2023
2 parents 747724a + 9d89ca3 commit fcb9fd1
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 49 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ dynamic = ["version"]

[project.optional-dependencies]
jinja = ["Jinja2>=3.1.2"]
markdown = ["anchovy[jinja]", "commonmark>=0.9.1"]
markdown = ["anchovy[jinja]", "markdown_it_py>=3.0.0"]
css = ["tinycss2>=1.1.1"]
pretty = ["rich>=12.5.1"]
pillow = ["Pillow>=9.2.0"]
Expand Down
8 changes: 7 additions & 1 deletion src/anchovy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
from .core import Context, InputBuildSettings, Matcher, PathCalc, Rule, Step
from .css import AnchovyCSSStep
from .dependencies import Dependency, import_install_check, which_install_check
from .dependencies import (
Dependency,
import_install_check,
pip_dependency,
web_exec_dependency,
which_install_check,
)
from .images import CWebPStep, ImageMagickStep, IMThumbnailStep, PillowStep, OptipngStep
from .jinja import JinjaMarkdownStep, JinjaRenderStep
from .paths import DirPathCalc, OutputDirPathCalc, REMatcher, WorkingDirPathCalc
Expand Down
2 changes: 1 addition & 1 deletion src/anchovy/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def pprint_step(step: t.Type[Step]):
Prettily display dependency information for the given Step class.
"""
missing = [
d.name for d in step.get_dependencies()
str(d) for d in step.get_dependencies()
if d.needed and not d.satisfied

]
Expand Down
32 changes: 29 additions & 3 deletions src/anchovy/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def import_install_check(dependency: Dependency):
An install checker which tries to import a Python module.
"""
try:
importlib.import_module(dependency.name)
importlib.import_module(dependency.check_name)
except ImportError:
return False
return True
Expand All @@ -38,7 +38,22 @@ def which_install_check(dependency: Dependency):
"""
An install checker using `shutil.which()` to look for an executable.
"""
return bool(shutil.which(dependency.name))
return bool(shutil.which(dependency.check_name))


def pip_dependency(name: str, source: str | None = None, check_name: str | None = None):
"""
A shortcut function for creating typical pip-based Dependencys.
"""
return Dependency(name, 'pip', import_install_check, source, check_name)


def web_exec_dependency(name: str, source: str | None = None, check_name: str | None = None):
"""
A shortcut function for creating typical Dependencys for general
internet-sourced executables.
"""
return Dependency(name, 'web', which_install_check, source, check_name)


class Dependency:
Expand All @@ -49,11 +64,13 @@ def __init__(self,
name: str,
type: str,
install_check: t.Callable[[Dependency], bool],
source: str | None = None):
source: str | None = None,
check_name: str | None = None):
self.name = name
self.type = type
self.install_check = install_check
self.source = source or name
self.check_name = check_name or name

@property
def satisfied(self):
Expand All @@ -79,6 +96,9 @@ def install_hint(self):
def __repr__(self):
return f'Dependency(name={self.name}, needed={self.needed}, satisfied={self.satisfied})'

def __str__(self):
return self.name

def __or__(self, other: Dependency):
return _OrDependency(self, other)

Expand All @@ -94,6 +114,9 @@ def __init__(self, left: Dependency, right: Dependency):
def __repr__(self):
return f'{self.left} | {self.right}'

def __str__(self):
return repr(self)

@property
def satisfied(self):
"""
Expand Down Expand Up @@ -129,6 +152,9 @@ def __init__(self, left: Dependency, right: Dependency):
def __repr__(self):
return f'{self.left} & {self.right}'

def __str__(self):
return repr(self)

@property
def satisfied(self):
"""
Expand Down
36 changes: 14 additions & 22 deletions src/anchovy/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from pathlib import Path

from .core import Step
from .dependencies import Dependency, import_install_check, which_install_check
from .dependencies import pip_dependency, web_exec_dependency
from .simple import BaseCommandStep

if t.TYPE_CHECKING:
Expand Down Expand Up @@ -33,14 +33,9 @@ def __init__(self,
self.options.extend(options)

@classmethod
def get_dependencies(cls) -> set[Dependency]:
def get_dependencies(cls):
return super().get_dependencies() | {
Dependency(
'cwebp',
'web',
which_install_check,
'https://developers.google.com/speed/webp/download'
),
web_exec_dependency('cwebp', 'https://developers.google.com/speed/webp/download'),
}

def get_command(self, input_path: Path, output_path: Path) -> list[StrOrBytesPath]:
Expand All @@ -60,13 +55,12 @@ def __init__(self, options: t.Iterable[str] = ()):
self.options = options

@classmethod
def get_dependencies(cls) -> set[Dependency]:
def get_dependencies(cls):
return super().get_dependencies() | {
Dependency(
'magick',
'web',
which_install_check,
'https://imagemagick.org/script/download.php'
web_exec_dependency(
'imagemagick',
'https://imagemagick.org/script/download.php',
'magick'
),
}

Expand Down Expand Up @@ -109,13 +103,11 @@ def __init__(self, thumbnail: tuple[int, int] | None = None):
self.thumbnail = thumbnail

@classmethod
def get_dependencies(cls) -> set[Dependency]:
def get_dependencies(cls):
return super().get_dependencies() | {
Dependency(
'PIL',
'pip',
import_install_check,
'Pillow'
pip_dependency(
'Pillow',
check_name='PIL'
),
}

Expand Down Expand Up @@ -148,9 +140,9 @@ def __init__(self,
self.options.extend(extra_options)

@classmethod
def get_dependencies(cls) -> set[Dependency]:
def get_dependencies(cls):
return super().get_dependencies() | {
Dependency('optipng', 'web', which_install_check, 'http://optipng.sourceforge.net'),
web_exec_dependency('optipng', 'http://optipng.sourceforge.net'),
}

def get_command(self, input_path: Path, output_path: Path) -> list[StrOrBytesPath]:
Expand Down
75 changes: 54 additions & 21 deletions src/anchovy/jinja.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,31 @@

import shutil
import typing as t
from functools import reduce
from pathlib import Path

from .core import Context, Step
from .dependencies import Dependency, import_install_check
from .dependencies import pip_dependency, Dependency

if t.TYPE_CHECKING:
import commonmark
import commonmark.render.renderer
from jinja2 import Environment


MDProcessor = t.Callable[[str], str]


class JinjaRenderStep(Step):
"""
Abstract base class for Steps using Jinja rendering.
"""
env: Environment

@classmethod
def get_dependencies(cls) -> set[Dependency]:
def get_dependencies(cls):
return super().get_dependencies() | {
Dependency('jinja2', 'pip', import_install_check),
pip_dependency('jinja2'),
}

def __init__(self,
Expand Down Expand Up @@ -73,35 +77,64 @@ class JinjaMarkdownStep(JinjaRenderStep):
encoding = 'utf-8'

@classmethod
def get_dependencies(cls) -> set[Dependency]:
return super().get_dependencies() | {
Dependency('commonmark', 'pip', import_install_check),
}
def _build_markdownit(cls):
import markdown_it
processor = markdown_it.MarkdownIt()

def convert(s: str) -> str:
return processor.render(s)

return convert

@classmethod
def _build_commonmark(cls):
import commonmark
parser = commonmark.Parser()
renderer = commonmark.HtmlRenderer()

def convert(s: str) -> str:
return renderer.render(parser.parse(s))

return convert

@classmethod
def get_options(cls):
return [
(pip_dependency('markdown-it-py', None, 'markdown_it'), cls._build_markdownit),
(pip_dependency('commonmark'), cls._build_commonmark),
]

@classmethod
def get_dependencies(cls):
deps = [option[0] for option in cls.get_options()]
dep_set = {reduce(lambda x, y: x | y, deps)} if deps else set[Dependency]()

return super().get_dependencies() | dep_set

def __init__(self,
default_template: str | None = None,
md_parser: commonmark.Parser | None = None,
md_renderer: commonmark.render.renderer.Renderer | None = None,
md_processor: MDProcessor | None = None,
jinja_env: Environment | None = None,
jinja_globals: dict[str, t.Any] | None = None):
super().__init__(jinja_env, jinja_globals)
self.default_template = default_template
self._md_processor = md_processor

@property
def md_processor(self):
if not self._md_processor:
for dep, factory in self.get_options():
if dep.satisfied:
self._md_processor = factory()
break
else:
raise RuntimeError('Markdown processor could not be initialized!')
return self._md_processor

if md_parser:
self.md_parser = md_parser
else:
import commonmark
self.md_parser = commonmark.Parser()
if md_renderer:
self.md_renderer = md_renderer
else:
import commonmark
self.md_renderer = commonmark.HtmlRenderer()

def __call__(self, path: Path, output_paths: list[Path]):
meta, content = self.extract_metadata(path.read_text(self.encoding))
ast = self.md_parser.parse(content.strip())
meta |= {'rendered_markdown': self.md_renderer.render(ast).strip()}
meta |= {'rendered_markdown': self.md_processor(content.strip()).strip()}

self.render_template(
meta.get('template', self.default_template),
Expand Down

0 comments on commit fcb9fd1

Please sign in to comment.