Skip to content

Commit

Permalink
Implement lazy params
Browse files Browse the repository at this point in the history
  • Loading branch information
mdrachuk committed Jan 6, 2020
1 parent 29e4442 commit fe2d7eb
Show file tree
Hide file tree
Showing 10 changed files with 141 additions and 28 deletions.
2 changes: 1 addition & 1 deletion lightweight/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from .site import Site, Author
from .content import Content, feeds, atom, rss, markdown, jinja, sass
from .content import Content, feeds, atom, rss, markdown, jinja, from_ctx, sass
from .files import paths, directory
from .generation import GenPath, GenContext
from .template import template, jinja_env
Expand Down
2 changes: 1 addition & 1 deletion lightweight/content/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from .content import Content
from .feeds import atom, rss
from .jinja import jinja
from .jinja import jinja, from_ctx
from .markdown import markdown
from .sass import sass
50 changes: 46 additions & 4 deletions lightweight/content/jinja.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from dataclasses import dataclass
from pathlib import Path
from typing import Dict, Any, Union, TYPE_CHECKING
from typing import Dict, Any, Union, TYPE_CHECKING, Callable

from jinja2 import Template

Expand All @@ -29,9 +29,12 @@ def render(self, ctx):
return self.template.render(
site=ctx.site,
ctx=ctx,
source=self,
**self.params
))
content=self,
**self._evaluated_params(ctx)
)

def _evaluated_params(self, ctx) -> Dict[str, Any]:
return {key: eval_if_lazy(value, ctx) for key, value in self.params.items()}


def jinja(template_path: Union[str, Path], **params) -> JinjaPage:
Expand All @@ -45,3 +48,42 @@ def jinja(template_path: Union[str, Path], **params) -> JinjaPage:
source_path=path,
params=params,
)


class LazyContextParameter:
"""A decorator for Jinja template parameters lazily evaluated from [context][GenContext] during render. """

def __init__(self, func: Callable[[GenContext], Any]):
self.func = func

def eval(self, ctx: GenContext) -> Any:
return self.func(ctx) # type: ignore


def from_ctx(func: Callable[[GenContext], Any]):
"""Mark a function with a decorator for its result to be evaluated lazily from context at the point of render
and used as a Jinja template parameter.
@example
```python
from lightweight import jinja, from_ctx
...
def post_tasks(ctx: GenContext):
return [task for task in ctx.tasks if task.path.parts[0] == 'posts']
...
site.include('posts', jinja('posts.html', posts=from_ctx(post_tasks)))
```
"""
return LazyContextParameter(func)


def eval_if_lazy(o: Any, ctx: GenContext) -> Any:
"""If passed a [lazy parameter][LazyContextParameter] the result of its evaluation.
Otherwise, returns the provided value."""
if isinstance(o, LazyContextParameter):
return o.eval(ctx)
return o
37 changes: 17 additions & 20 deletions lightweight/content/markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from mistune import Markdown # type: ignore

from .content import Content
from .jinja import eval_if_lazy
from .lwmd import LwRenderer, TableOfContents

if TYPE_CHECKING:
Expand All @@ -32,17 +33,21 @@ class MarkdownPage(Content):
updated: Optional[datetime]
order: Optional[Union[int, float]]

front_matter: Dict[str, Any]
params: Dict[str, Any]

def write(self, path: GenPath, ctx: GenContext):
path.create(self.render_template(ctx))

def render_template(self, ctx):
# TODO:mdrachuk:06.01.2020: warn if site, ctx, source are in params or front matter!
return self.template.render(
site=ctx.site,
ctx=ctx,
content=self,
markdown=self.render_md(ctx)
markdown=self.render_md(ctx),
**self.front_matter,
**self._evaluated_params(ctx),
)

def render_md(self, ctx: GenContext):
Expand All @@ -59,7 +64,6 @@ def render_md(self, ctx: GenContext):
summary=self.summary,
created=self.created,
updated=self.updated,
options=self.options,
)

@staticmethod
Expand All @@ -78,34 +82,29 @@ def extract_preview(html):
preview_html = preview_split[0] if len(preview_split) == 2 else None
return preview_html

def write(self, path: GenPath, ctx: GenContext):
path.create(self.template.render(
site=ctx.site,
ctx=ctx,
source=self,
markdown=self.render(ctx)
))
def _evaluated_params(self, ctx) -> Dict[str, Any]:
return {key: eval_if_lazy(value, ctx) for key, value in self.params.items()}


def markdown(md_path: Union[str, Path], template: Template, *, renderer=LwRenderer, **kwargs) -> MarkdownPage:
path = Path(md_path)
with path.open() as f:
source = f.read()
post = frontmatter.loads(source)
title = post.get('title', None)
summary = post.get('summary', None)
created = post.get('created', None)
updated = post.get('updated', created)
fm = frontmatter.loads(source)
title = fm.get('title', None)
summary = fm.get('summary', None)
created = fm.get('created', None)
updated = fm.get('updated', created)
if created is not None:
assert isinstance(created, datetime), '"created" is not a valid datetime object'
created = created.replace(tzinfo=timezone.utc)
if updated is not None:
assert isinstance(updated, datetime), '"updated" is not a valid datetime object'
updated = updated.replace(tzinfo=timezone.utc)
order = post.get('order', None)
order = fm.get('order', None)
return MarkdownPage(
template=template,
source=post.content,
source=fm.content,
source_path=path,

renderer=renderer,
Expand All @@ -115,8 +114,8 @@ def markdown(md_path: Union[str, Path], template: Template, *, renderer=LwRender
created=created,
updated=updated,
order=order,

params=dict(post, **kwargs),
front_matter=fm,
params=dict(kwargs),
)


Expand All @@ -130,5 +129,3 @@ class RenderedMarkdown:
summary: Optional[str]
updated: Optional[date]
created: Optional[date]

params: Dict[str, Any]
10 changes: 10 additions & 0 deletions tests/expected/jinja/lazy.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Lazy</title>
</head>
<body>
Hello there! lazy.html
</body>
</html>
16 changes: 16 additions & 0 deletions tests/expected/md/lazy.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<html lang="en">
<head><title>This is a test</title></head>
<body>
<subtitle>Hello there! lazy.html</subtitle>
<h1 id="this-is-a-test">This is a test</h1>
<p>One.</p>
<h2 id="second-heading">Second heading</h2>
<h2 id="second-heading-part-2">Second heading part 2</h2>
<h3 id="third-heading">Third heading</h3>
<h4 id="4th-heading">4th heading</h4>
<h5 id="5th-heading">5th heading</h5>
<h6 id="6th-heading">6th heading!</h6>
<h2 id="and-back">And Back</h2>

</body>
</html>
10 changes: 10 additions & 0 deletions tests/resources/jinja/lazy.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Lazy</title>
</head>
<body>
{{ lazy }}
</body>
</html>
7 changes: 7 additions & 0 deletions tests/templates/md/lazy.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<html lang="en">
<head><title>{{ markdown.title }}</title></head>
<body>
<subtitle>{{ lazy }}</subtitle>
{{ markdown.html }}
</body>
</html>
17 changes: 16 additions & 1 deletion tests/test_include_jinja.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import pytest
from jinja2 import TemplateNotFound

from lightweight import Site, jinja, Content, directory, GenContext, GenPath
from lightweight import Site, jinja, Content, directory, GenContext, GenPath, from_ctx


def test_render_jinja(tmp_path: Path):
Expand Down Expand Up @@ -70,3 +70,18 @@ def test_resolves_sub_site_template_by_cwd(tmp_path: Path):

with open('expected/subsite/page.html') as expected:
assert (tmp_path / 'subsite' / 'page.html').read_text() == expected.read()


def test_lazy_params(tmp_path: Path):
src_location = 'resources/jinja/lazy.html'
out_location = 'lazy.html'

test_out = tmp_path / 'out'
site = Site(url='https://example.com')

site.include(out_location, jinja(src_location, lazy=from_ctx(lambda ctx: f'Hello there! {ctx.tasks[0].path}')))
site.generate(test_out)

assert (test_out / out_location).exists()
with open('expected/jinja/lazy.html') as expected:
assert (test_out / out_location).read_text() == expected.read()
18 changes: 17 additions & 1 deletion tests/test_include_markdown.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from pathlib import Path

from lightweight import Site, markdown, template, directory
from lightweight import Site, markdown, template, directory, from_ctx


def test_render_markdown(tmp_path: Path):
Expand Down Expand Up @@ -75,3 +75,19 @@ def test_resolves_sub_site_markdown_template_by_cwd(tmp_path: Path):

with open('expected/subsite/markdown.html') as expected:
assert (tmp_path / 'subsite' / 'markdown.html').read_text() == expected.read()


def test_lazy_params(tmp_path: Path):
src_location = 'resources/md/collection/post-1.md'
out_location = 'lazy.html'

test_out = tmp_path / 'out'
site = Site(url='https://example.com')

site.include(out_location, markdown(src_location, template('templates/md/lazy.html'),
lazy=from_ctx(lambda ctx: f'Hello there! {ctx.tasks[0].path}')))
site.generate(test_out)

assert (test_out / out_location).exists()
with open('expected/md/lazy.html') as expected:
assert (test_out / out_location).read_text() == expected.read()

0 comments on commit fe2d7eb

Please sign in to comment.