Class: Jekyll::Tags::TemplateBlock

Inherits:
Liquid::Block
  • Object
show all
Includes:
Liquid::StandardFilters
Defined in:
lib/jekyll/jolt.rb

Constant Summary collapse

CONTEXT_NAME =
"template"
CONTEXT_CACHE_NAME =
:cached_templates
CONTEXT_DATA_NAME =
:template_data
CONTEXT_SCOPE_NAME =
:template_data_scope
CONTEXT_STORE_NAME =
:template_data_store
PROPS_NAME =
"props"
TEMPLATE_DIR =
"_templates"
LIQUID_SYNTAX_REGEXP =
/(#{Liquid::QuotedFragment}+)?/
PROPS_REGEXP =
/#{PROPS_NAME}\./
WHITESPACE_REGEXP =
%r!^\s*!m
YAML_FRONT_MATTER_REGEXP =
%r!(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)!m

Instance Method Summary collapse

Constructor Details

#initialize(tag_name, markup, tokens) ⇒ TemplateBlock

initialize Description: Extends Liquid’s default initialize method.



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/jekyll/jolt.rb', line 28

def initialize(tag_name, markup, tokens)
  super

  if markup =~ LIQUID_SYNTAX_REGEXP
    @attributes = {}
    @context = false
    @id = rand(36**8).to_s(36).freeze
    @props = {}
    @sanitize = false
    @site = false
    @template_name = $1.freeze

    @compressor = HtmlCompressor::Compressor.new({
      :remove_comments => true
    }).freeze

    # Parse parameters
    # Source: https://gist.github.com/jgatjens/8925165
    markup.scan(Liquid::TagAttributes) do |key, value|
      if (value =~ PROPS_REGEXP) != nil
        @props[key] = @attributes[key] = value
      else
        @props[key] = @attributes[key] = Liquid::Expression.parse(value)
      end
    end
  end
end

Instance Method Details

#add_template_to_dependency(path) ⇒ Object



231
232
233
234
235
236
237
238
# File 'lib/jekyll/jolt.rb', line 231

def add_template_to_dependency(path)
  if @context.registers[:page] && @context.registers[:page].key?("path")
    @site.regenerator.add_dependency(
      @site.in_source_dir(@context.registers[:page]["path"]),
      template_path(path)
    )
  end
end

#blank?Boolean

blank? Description: Override’s Liquid’s default blank checker. This allows for templates to be used without passing inner content.

Returns:

  • (Boolean)


59
60
61
# File 'lib/jekyll/jolt.rb', line 59

def blank?
  false
end

#evaluate_propsObject

evaluate_props Description: Evaluates props that are being passed into the template.



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/jekyll/jolt.rb', line 101

def evaluate_props()
  store = @context.registers[CONTEXT_STORE_NAME]
  data = store[@id]
  index = data[:index]

  if (index > 0)
    parent = store[store.keys[index - 1]]
    # Update the data scope
    @context[CONTEXT_SCOPE_NAME] = parent
    data.each do |key, value|
      if prop?(value)
        value = prop(parent, value)
        if value
          @props[key] = value
        end
      end
    end
  end
end

#get_front_matter(content) ⇒ Object

get_front_matter(content = String) Returns: A hash of data parsed from the content’s YAML



321
322
323
324
325
326
327
328
329
330
# File 'lib/jekyll/jolt.rb', line 321

def get_front_matter(content)
  # Strip leading white-spaces
  content = unindent(content)
  if content =~ YAML_FRONT_MATTER_REGEXP
    front_matter = Regexp.last_match(0)
    values = SafeYAML.load(front_matter)
  else
    Hash.new
  end
end

#load_cached_template(path) ⇒ Object

load_cached_template(path = String) source: github.com/jekyll/jekyll/blob/e509cf2139d1a7ee11090b09721344608ecf48f6/lib/jekyll/tags/include.rb Returns: Liquid template from Jekyll’s cache.



243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/jekyll/jolt.rb', line 243

def load_cached_template(path)
  @context.registers[CONTEXT_CACHE_NAME] ||= {}
  cached_templates = @context.registers[CONTEXT_CACHE_NAME]

  unless cached_templates.key?(path)
    cached_templates[path] = load_template()
  end
  template = cached_templates[path]

  update_attributes(template["data"])
  template["template"]
end

#load_templateObject

load_template() Description: Extends Liquid’s default load_template method. Also provides extra enhancements:

  • parses and sets template front-matter content

Returns: Template class



274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
# File 'lib/jekyll/jolt.rb', line 274

def load_template()
  file = @site
    .liquid_renderer
    .file(template_path(@template_name))

  content = template_content(@template_name)

  template = Hash.new
  data = get_front_matter(content)
  markup = strip_front_matter(content)

  if content
    template["data"] = data
    template["template"] = file.parse(markup)
    template
  end
end

#prop(data, value = "") ⇒ Object

prop(data = Hash, value = String) Description: Returns the props value



87
88
89
90
91
92
93
94
95
96
97
# File 'lib/jekyll/jolt.rb', line 87

def prop(data, value = "")
  index = data[:index]
  value = data[value.gsub(PROPS_REGEXP, "")]
  if value and prop?(value) and index > 0
    store = @context.registers[CONTEXT_STORE_NAME]
    previous_scope = store[store.keys[index - 1]]
    prop(previous_scope, value)
  else
    value
  end
end

#prop?(variable = "") ⇒ Boolean

prop? Description: Determines if the variable is a template.props key Return: Boolean

Returns:

  • (Boolean)


81
82
83
# File 'lib/jekyll/jolt.rb', line 81

def prop?(variable = "")
  (variable =~ PROPS_REGEXP) != nil
end

#render(context) ⇒ Object

render Description: Extends Liquid’s default render method. This method also adds additional features:

  • YAML front-matter parsing and handling

  • properly handles indentation and whitespace (resolves rendering issues)

  • ability to parse content as markdown vs. html

  • supports custom attributes to be used in template



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/jekyll/jolt.rb', line 128

def render(context)
  @context = context
  @site = @context.registers[:site]

  template_store_data(@attributes)

  # This allows for Jekyll intelligently re-render markup during
  # incremental builds.
  add_template_to_dependency(@template_name)
  # Loading the template from cache/template directory
  template = load_cached_template(@template_name)

  # Props must be evaluated before super is initialized.
  # This allows for props to be evaluated before they're parsed by Liquid.
  evaluate_props()

  content = super

  # Return the parsed/normalized content
  render_template(template, content)
end

#render_template(template, content) ⇒ Object

render_template(template = Liquid::Template, content = String) Description: Serializes the context to be rendered by Liquid. Also resets the context to ensure template data doesn’t leak from the scope. Returns: String



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/jekyll/jolt.rb', line 155

def render_template(template, content)
  # Define the default template attributes
  # Source:
  # https://github.com/Shopify/liquid/blob/9a7778e52c37965f7b47673da09cfb82856a6791/lib/liquid/tags/include.rb
  @context[CONTEXT_NAME] = Hash.new

  # Add props
  update_attributes(@props)
  # Parse and extend template's front-matter with content front-matter
  update_attributes(get_front_matter(content))
  # Update the template's store data
  template_store_data(@attributes)

  # Setting context's template attributes from @attributes
  # This allows for @attributes to be used within the template as
  # {{ template.atttribute_name }}
  if @attributes.length
    @attributes.each do |key, value|
      val = @context.evaluate(value)
      @context[CONTEXT_NAME][key] = val

      # Adjust sanitize if parse: html
      if (key == "parse") && (val == "html")
        @sanitize = true
      end
    end
  end

  # puts @attributes
  @context[CONTEXT_NAME]["content"] = sanitize(strip_front_matter(content))
  store_template_data()
  content = @compressor.compress(template.render(@context))
  reset_template_data()

  content
end

#reset_template_dataObject

reset_template_data() Description: Works with store_template_data. This is a work-around to ensure data stays in scope and isn’t leaked from child->parent template.



215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/jekyll/jolt.rb', line 215

def reset_template_data()
  @context.registers[CONTEXT_DATA_NAME] ||= {}
  store = @context.registers[CONTEXT_DATA_NAME]
  if store.keys.length
    if store.keys[0] == @id
      # Resets template data
      @context.registers[CONTEXT_DATA_NAME] = false
      @context.registers[CONTEXT_SCOPE_NAME] = false
    else
      @context[CONTEXT_NAME] = store[store.keys[0]]
    end
  end
end

#sanitize(content) ⇒ Object

sanitize(content = String) Description: Renders the content as markdown or HTML based on the “parse” attribute. Returns: Content (string).



296
297
298
299
300
301
302
303
# File 'lib/jekyll/jolt.rb', line 296

def sanitize(content)
  unless @sanitize
    converter = @site.find_converter_instance(::Jekyll::Converters::Markdown)
    converter.convert(unindent(content))
  else
    unindent(content)
  end
end

#store_template_dataObject

store_template_data() Description: Works with reset_template_data. This is a work-around to ensure data stays in scope and isn’t leaked from child->parent template.



204
205
206
207
208
209
# File 'lib/jekyll/jolt.rb', line 204

def store_template_data()
  @context.registers[CONTEXT_DATA_NAME] ||= {}
  unless @context.registers[CONTEXT_DATA_NAME].key?(@id)
    @context.registers[CONTEXT_DATA_NAME][@id] = @context[CONTEXT_NAME]
  end
end

#strip_front_matter(content) ⇒ Object

strip_front_matter(content = String) Description: Removes the YAML front-matter content. Returns: Template content, with front-matter removed.



335
336
337
338
339
340
341
342
343
344
# File 'lib/jekyll/jolt.rb', line 335

def strip_front_matter(content)
  # Strip leading white-spaces
  content = unindent(content)
  if content =~ YAML_FRONT_MATTER_REGEXP
    front_matter = Regexp.last_match(0)
    # Returns content with stripped front-matter
    content.gsub!(front_matter, "")
  end
  content
end

#template_content(template_name) ⇒ Object

template_content(template_name = String) Description: Opens, reads, and returns template content as string. Returns: Template content



265
266
267
# File 'lib/jekyll/jolt.rb', line 265

def template_content(template_name)
  File.read(template_path(template_name).strip)
end

#template_path(path) ⇒ Object

template_path(path = String) Returns: A full file path of the template



258
259
260
# File 'lib/jekyll/jolt.rb', line 258

def template_path(path)
  File.join(@site.source.to_s, TEMPLATE_DIR, path.to_s)
end

#template_store_data(data = {}) ⇒ Object

template_store_data(data = Array) Description: Stores/updates the template data in cache Returns: Hash of the template store data



66
67
68
69
70
71
72
73
74
75
76
# File 'lib/jekyll/jolt.rb', line 66

def template_store_data(data = {})
  @context.registers[CONTEXT_STORE_NAME] ||= {}
  unless @context.registers[CONTEXT_STORE_NAME].key?(@id)
    @context.registers[CONTEXT_STORE_NAME][@id] = {
      "id": @id,
      "index": @context.registers[CONTEXT_STORE_NAME].length,
      "template_name": @template_name
    }
  end
  @context.registers[CONTEXT_STORE_NAME][@id] = @context.registers[CONTEXT_STORE_NAME][@id].merge(data)
end

#unindent(content) ⇒ Object

unindent(content = String) Description: Removes initial indentation. Returns: Content (string).



308
309
310
311
312
313
314
315
316
317
# File 'lib/jekyll/jolt.rb', line 308

def unindent(content)
  # Remove initial whitespace
  content.gsub!(/\A^\s*\n/, "")
  # Remove indentations
  if content =~ WHITESPACE_REGEXP
    indentation = Regexp.last_match(0).length
    content.gsub!(/^\ {#{indentation}}/, "")
  end
  content
end

#update_attributes(data) ⇒ Object

update_attributes(data = Hash) Description: Merges data with @attributes.



194
195
196
197
198
# File 'lib/jekyll/jolt.rb', line 194

def update_attributes(data)
  if data
    @attributes.merge!(data)
  end
end