11# frozen_string_literal: true
22
3- require 'qeweney'
43require 'json'
4+ require 'yaml'
5+
6+ require 'qeweney'
57require 'papercraft'
68
79require '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
207249end
0 commit comments