Skip to content

Commit 1ac362f

Browse files
committed
Add support for rendering markdown with layout
1 parent 28eef2c commit 1ac362f

7 files changed

Lines changed: 137 additions & 31 deletions

File tree

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
export Papercraft.html { |**a|
2-
html5 {
1+
export templ { |*a, **b|
2+
html {
33
head {
4-
title "My awesome Syntropy website"
4+
title 'My awesome Syntropy website'
5+
link rel: 'stylesheet', type: 'text/css', href: '/assets/css/style.css'
56
}
67
body {
7-
emit_yield **a
8+
emit_yield(*a, **b)
89
}
910
}
1011
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
layout: default
3+
---
4+
# About my site
5+
6+
Lorem ipsum my awesome site.
Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,29 @@
11
layout = import('_layout/default')
22

3-
poem_body = [
4-
[' in ten ', 'M', 'inutes']
5-
[' ', 'C', 'ome back: you will']
6-
['have taught me chi', 'N', 'ese']
7-
[' (s', 'A', 'tie).']
8-
[' shall I ret', 'U', 'rn the favor?']
9-
[' ', 'G', 'ive you']
10-
[' ot', 'H', 'er lessons']
11-
[' (', 'T', 'ing!)?']
12-
[' ', 'O', 'r would you prefer']
13-
[' sile', 'N', 'ce?']
3+
poem = [
4+
" in ten\xA0", 'M', 'inutes',
5+
' ', 'C', 'ome back: you will',
6+
'have taught me chi', 'N', 'ese',
7+
' (s', 'A', 'tie).',
8+
' shall I ret', 'U', 'rn the favor?',
9+
' ', 'G', 'ive you',
10+
' ot', 'H', 'er lessons',
11+
' (', 'T', 'ing!)?',
12+
' ', 'O', 'r would you prefer',
13+
' sile', 'N', 'ce?',
1414
]
1515

16-
template = layout.apply {
17-
article {
16+
export layout.apply {
17+
article(class: 'mesostic') {
1818
h2 'For William McN. who studied with Ezra Pound'
1919

20-
line(_for: poem_body) { |l|
21-
span(_for: l) { text it }
20+
content {
21+
span(_for: poem) { text it }
2222
}
2323

2424
author {
25-
span '-'
25+
span "-\xA0"
2626
a 'John cage', href: 'https://en.wikipedia.org/wiki/John_Cage'
2727
}
2828
}
2929
}
30-
31-
export template
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
body {
2+
font-family: sans-serif;
3+
}
4+
5+
article.mesostic {
6+
width: 600px;
7+
margin: 2em auto;
8+
font-size: 1.4em;
9+
10+
* {
11+
font-family: monospace;
12+
}
13+
14+
h2 {
15+
text-align: center;
16+
}
17+
18+
content {
19+
display: grid;
20+
margin: 2em 0;
21+
grid-template-columns: 1fr auto 1fr;
22+
23+
span {
24+
display: inline-block;
25+
}
26+
span:nth-child(3n + 1) {
27+
text-align: right;
28+
}
29+
30+
span:nth-child(3n + 2) {
31+
text-align: center;
32+
font-weight: bold;
33+
}
34+
}
35+
36+
author {
37+
display: block;
38+
text-align: right;
39+
}
40+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
layout = import('_layout/default')
2+
3+
export layout.apply {
4+
h1 'Hello from Syntropy'
5+
p {
6+
span "Here's an "
7+
a 'about', href: 'about'
8+
span ' page.'
9+
}
10+
p {
11+
span "Here's an "
12+
a 'article', href: 'articles/cage'
13+
span ' page.'
14+
}
15+
}

lib/syntropy/app.rb

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
# frozen_string_literal: true
22

3-
require 'qeweney'
43
require 'json'
4+
require 'yaml'
5+
6+
require 'qeweney'
57
require 'papercraft'
68

79
require 'syntropy/errors'
@@ -68,14 +70,15 @@ def start_file_watcher
6870
end
6971

7072
def invalidate_cache(fn)
73+
@module_loader.unload(fn)
74+
7175
invalidated_keys = []
7276
@route_cache.each do |k, v|
7377
@opts[:logger]&.call("Invalidate cache for #{k}", nil)
7478
invalidated_keys << k if v[:fn] == fn
7579
end
7680

7781
invalidated_keys.each { @route_cache.delete(it) }
78-
@module_loader.unload(fn)
7982
end
8083

8184
def calculate_relative_path_re(mount_path)
@@ -156,7 +159,7 @@ def render_entry(req, entry)
156159
when :static
157160
respond_static(req, entry)
158161
when :markdown
159-
body = render_markdown(IO.read(entry[:fn]))
162+
body = render_markdown(entry[:fn])
160163
req.respond(body, 'Content-Type' => 'text/html')
161164
when :module
162165
call_module(req, entry)
@@ -188,7 +191,7 @@ def load_module(entry)
188191
ref = entry[:fn].gsub(%r{^#{@src_path}/}, '').gsub(/\.rb$/, '')
189192
o = @module_loader.load(ref)
190193
o.is_a?(Papercraft::Template) ? wrap_template(o) : o
191-
rescue StandardError => e
194+
rescue Exception => e
192195
@opts[:logger]&.call("Error while loading module #{ref}: #{e.message}")
193196
:invalid
194197
end
@@ -200,8 +203,47 @@ def wrap_template(templ)
200203
}
201204
end
202205

203-
def render_markdown(str)
204-
Papercraft.markdown(str)
206+
def render_markdown(fn)
207+
atts, md = parse_markdown_file(fn)
208+
209+
if atts[:layout]
210+
layout = @module_loader.load("_layout/#{atts[:layout]}")
211+
html = layout.apply { emit_markdown(md) }.render
212+
else
213+
html = Papercraft.markdown(md)
214+
end
215+
html
216+
end
217+
218+
DATE_REGEXP = /(\d{4}\-\d{2}\-\d{2})/
219+
FRONT_MATTER_REGEXP = /\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)/m
220+
YAML_OPTS = {
221+
permitted_classes: [Date],
222+
symbolize_names: true
223+
}
224+
225+
# Parses the markdown file at the given path.
226+
#
227+
# @param path [String] file path
228+
# @return [Array] an tuple containing properties<Hash>, contents<String>
229+
def parse_markdown_file(path)
230+
content = IO.read(path) || ''
231+
atts = {}
232+
233+
# Parse date from file name
234+
if (m = path.match(DATE_REGEXP))
235+
atts[:date] ||= Date.parse(m[1])
236+
end
237+
238+
if (m = content.match(FRONT_MATTER_REGEXP))
239+
front_matter = m[1]
240+
content = m.post_match
241+
242+
yaml = YAML.safe_load(front_matter, **YAML_OPTS)
243+
atts = atts.merge(yaml)
244+
end
245+
246+
[atts, content]
205247
end
206248
end
207249
end

lib/syntropy/module.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ def unload(fn)
1919
ref = @fn_map[fn]
2020
return if !ref
2121

22+
p unload: ref, fn: fn
2223
@loaded.delete(ref)
2324
@fn_map.delete(fn)
2425
end
@@ -28,19 +29,22 @@ def unload(fn)
2829
def load_module(ref)
2930
fn = File.expand_path(File.join(@root, "#{ref}.rb"))
3031
@fn_map[fn] = ref
31-
raise RuntimeError, "File not found #{fn}" if !File.file?(fn)
32+
raise "File not found #{fn}" if !File.file?(fn)
3233

3334
mod_body = IO.read(fn)
3435
mod_ctx = Class.new(Syntropy::Module)
3536
mod_ctx.loader = self
36-
# mod_ctx = .new(self, @env)
3737
mod_ctx.module_eval(mod_body, fn, 1)
3838

3939
export_value = mod_ctx.__export_value__
4040

41+
wrap_module(mod_ctx, export_value)
42+
end
43+
44+
def wrap_module(mod_ctx, export_value)
4145
case export_value
4246
when nil
43-
raise RuntimeError, 'No export found'
47+
raise 'No export found'
4448
when Symbol
4549
# TODO: verify export_value denotes a valid method
4650
mod_ctx.new(@env)

0 commit comments

Comments
 (0)