jekyll-liquid-plus 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +19 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +345 -0
- data/Rakefile +1 -0
- data/jekyll-liquid-plus.gemspec +24 -0
- data/lib/jekyll-liquid-plus.rb +20 -0
- data/lib/jekyll-liquid-plus/helpers/conditional.rb +43 -0
- data/lib/jekyll-liquid-plus/helpers/include.rb +46 -0
- data/lib/jekyll-liquid-plus/helpers/path.rb +73 -0
- data/lib/jekyll-liquid-plus/helpers/var.rb +47 -0
- data/lib/jekyll-liquid-plus/tags/assign.rb +19 -0
- data/lib/jekyll-liquid-plus/tags/capture.rb +34 -0
- data/lib/jekyll-liquid-plus/tags/include.rb +16 -0
- data/lib/jekyll-liquid-plus/tags/render.rb +133 -0
- data/lib/jekyll-liquid-plus/tags/return.rb +20 -0
- data/lib/jekyll-liquid-plus/tags/wrap.rb +36 -0
- data/lib/jekyll-liquid-plus/version.rb +3 -0
- data/test/_config.yml +4 -0
- data/test/source/.gitignore +1 -0
- data/test/source/_includes/file.html +1 -0
- data/test/source/_includes/file2.html +1 -0
- data/test/source/_layouts/default.html +11 -0
- data/test/source/_plugins/liquid-plus.rb +2 -0
- data/test/source/assign.html +30 -0
- data/test/source/capture.html +29 -0
- data/test/source/f/file.html +1 -0
- data/test/source/f/file2.html +1 -0
- data/test/source/f/file3.md +6 -0
- data/test/source/include.html +31 -0
- data/test/source/render.html +36 -0
- data/test/source/return.html +24 -0
- data/test/source/wrap.html +29 -0
- data/test/test.rb +44 -0
- metadata +135 -0
@@ -0,0 +1,43 @@
|
|
1
|
+
module LiquidPlus
|
2
|
+
class Conditional
|
3
|
+
EXPRESSION = /(.+?)\s+(unless|if)\s+(.+)/i
|
4
|
+
TERNARY = /(.*?)\(\s*(.+?)\s+\?\s+(.+?)\s+:\s+(.+?)\s*\)(.+)?/
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def parse(markup, context)
|
8
|
+
strip_expression(markup, context) if evaluate_expression markup, context
|
9
|
+
end
|
10
|
+
|
11
|
+
def strip_expression(markup, context = false)
|
12
|
+
if markup =~ TERNARY
|
13
|
+
# Pick winner from ternary statement
|
14
|
+
result = evaluate_ternary($2, $3, $4, context)
|
15
|
+
markup = "#{$1} #{result} #{$5}"
|
16
|
+
end
|
17
|
+
markup =~ EXPRESSION ? $1 : markup
|
18
|
+
end
|
19
|
+
|
20
|
+
def evaluate_ternary(expression, if_true, if_false, context)
|
21
|
+
evaluate('if', expression, context) ? if_true : if_false
|
22
|
+
end
|
23
|
+
|
24
|
+
def evaluate_expression(markup, context)
|
25
|
+
if markup =~ EXPRESSION
|
26
|
+
evaluate($2, $3.gsub(/ \|\| /, ' or '), context)
|
27
|
+
else
|
28
|
+
true
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def evaluate(type, expression, context)
|
33
|
+
tag = if type == 'if'
|
34
|
+
Liquid::If.new('if', expression, ["true","{% endif %}"])
|
35
|
+
elsif type == 'unless'
|
36
|
+
Liquid::Unless.new('unless', expression, ["true","{% endunless %}"])
|
37
|
+
end
|
38
|
+
tag.render(context) != ''
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require File.join File.expand_path('../../', __FILE__), 'helpers/path'
|
2
|
+
|
3
|
+
module LiquidPlus
|
4
|
+
class Include
|
5
|
+
LOCALS = /(.*?)(([\w-]+)\s*=\s*(?:"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|([\w\.-]+)))(.*)?/
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def render(markup, context)
|
10
|
+
|
11
|
+
markup, params = split_params markup
|
12
|
+
file = Path.parse(markup, context, '_includes')
|
13
|
+
|
14
|
+
if file
|
15
|
+
if File.exist? Path.expand(File.join('_includes', file), context)
|
16
|
+
markup = file
|
17
|
+
markup += params if params
|
18
|
+
tag = Jekyll::Tags::IncludeTag.new('', markup, [])
|
19
|
+
tag.render(context)
|
20
|
+
else
|
21
|
+
dir = '_includes'
|
22
|
+
dir = File.join dir, File.dirname(file) unless File.dirname(file) == '.'
|
23
|
+
|
24
|
+
msg = "From #{context.registers[:page]['path']}: "
|
25
|
+
msg += "File '#{file}' not found"
|
26
|
+
msg += " in '#{dir}/' directory"
|
27
|
+
|
28
|
+
puts msg.red
|
29
|
+
return msg
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def split_params(markup)
|
35
|
+
params = nil
|
36
|
+
if markup =~ LOCALS
|
37
|
+
params = ' ' + $2
|
38
|
+
markup = $1 + $7
|
39
|
+
end
|
40
|
+
[markup, params]
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require File.join File.expand_path('../../', __FILE__), 'helpers/conditional'
|
2
|
+
|
3
|
+
module LiquidPlus
|
4
|
+
class Path
|
5
|
+
class << self
|
6
|
+
|
7
|
+
#
|
8
|
+
def parse(markup, context, path=nil)
|
9
|
+
files = Conditional.parse(markup, context)
|
10
|
+
if files
|
11
|
+
select(files, context, path)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Allow paths to begin from the directory of the context page
|
16
|
+
#
|
17
|
+
# Input:
|
18
|
+
# - file: "file.html"
|
19
|
+
# - context: A Jekyll context object
|
20
|
+
#
|
21
|
+
# Returns the full path to a file
|
22
|
+
#
|
23
|
+
def expand(file, context)
|
24
|
+
root = context.registers[:site].source
|
25
|
+
if file =~ /^\.\/(.+)/
|
26
|
+
local_dir = File.dirname context.registers[:page]['path']
|
27
|
+
File.join root, local_dir, $1
|
28
|
+
else
|
29
|
+
File.join root, file
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Find the first file which exists
|
34
|
+
#
|
35
|
+
# Input:
|
36
|
+
# - file: "file.html || some_var || path/to/file.md" - a list of files
|
37
|
+
# - context: A Jekyll context object
|
38
|
+
# - path: e.g. "_includes" - a directory to prepend to the path
|
39
|
+
#
|
40
|
+
# Return the first file path which exists, or the last if none exist
|
41
|
+
# If the last path is 'none' this returns false
|
42
|
+
#
|
43
|
+
def select(files, context, path)
|
44
|
+
files = get_paths(files, context)
|
45
|
+
files.each_with_index do |f, i|
|
46
|
+
file = path ? File.join(path, f) : f
|
47
|
+
if File.exist? expand(file, context)
|
48
|
+
return f
|
49
|
+
# If "file.html || none" is passed, fail gracefully
|
50
|
+
elsif i == files.size - 1
|
51
|
+
return f.downcase == 'none' ? false : f
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Read file paths from context variables if necessary
|
57
|
+
#
|
58
|
+
# Input:
|
59
|
+
# - file: file.html || some_var || path/to/file.md
|
60
|
+
# - context: A Jekyll context object
|
61
|
+
#
|
62
|
+
# Returns a list of file paths
|
63
|
+
#
|
64
|
+
def get_paths(files, context)
|
65
|
+
files = files.split(/ (\|\||or) /).map do |file|
|
66
|
+
file = file.strip
|
67
|
+
context[file].nil? ? file : context[file]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require File.join File.expand_path('../../', __FILE__), 'helpers/conditional'
|
2
|
+
|
3
|
+
module LiquidPlus
|
4
|
+
class Var
|
5
|
+
SYNTAX = /(#{Liquid::VariableSignature}+)\s*(=|\+=|\|\|=)\s*(.*)\s*/o
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def parse(markup, context)
|
9
|
+
if markup = Conditional.parse(markup, context)
|
10
|
+
if markup =~ SYNTAX
|
11
|
+
@to = $1
|
12
|
+
@operator = $2
|
13
|
+
@from = $3
|
14
|
+
|
15
|
+
value = get_value(@from, context)
|
16
|
+
|
17
|
+
if @operator == '+=' and !context.scopes.last[@to].nil?
|
18
|
+
context.scopes.last[@to] += value
|
19
|
+
elsif @operator == '=' or (@operator == '||=' and context.scopes.last[@to].nil?)
|
20
|
+
context.scopes.last[@to] = value
|
21
|
+
end
|
22
|
+
context
|
23
|
+
else
|
24
|
+
raise SyntaxError.new("Syntax Error in 'assign' - Valid syntax: assign [var] = [source]")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def determine_value(vars, context)
|
30
|
+
vars.each do |var|
|
31
|
+
rendered = var.render(context)
|
32
|
+
return rendered unless rendered.nil?
|
33
|
+
end
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def get_value(vars, context)
|
38
|
+
vars = vars.gsub(/ or /, ' || ')
|
39
|
+
vars = vars.strip.split(/ \|\| /).map do |v|
|
40
|
+
Liquid::Variable.new(v.strip)
|
41
|
+
end
|
42
|
+
determine_value(vars, context)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require File.join File.expand_path('../../', __FILE__), 'helpers/var'
|
2
|
+
|
3
|
+
module LiquidPlus
|
4
|
+
class AssignTag < Liquid::Tag
|
5
|
+
|
6
|
+
def initialize(tag_name, markup, tokens)
|
7
|
+
@markup = markup
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def render(context)
|
12
|
+
if c = Var.parse(@markup, context)
|
13
|
+
context = c
|
14
|
+
end
|
15
|
+
''
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require File.join File.expand_path('../../', __FILE__), 'helpers/conditional'
|
2
|
+
|
3
|
+
module LiquidPlus
|
4
|
+
class CaptureTag < Liquid::Block
|
5
|
+
WORD_REGEX = '[[:word:]]'
|
6
|
+
SYNTAX = /(#{WORD_REGEX}+)\s*(\+=|\|\|=)?/o
|
7
|
+
|
8
|
+
def initialize(tag_name, markup, tokens)
|
9
|
+
@markup = markup
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def render(context)
|
14
|
+
if @markup = Conditional.parse(@markup, context)
|
15
|
+
if @markup.strip =~ SYNTAX
|
16
|
+
@to = $1
|
17
|
+
@operator = $2
|
18
|
+
|
19
|
+
output = super
|
20
|
+
if @operator == '+=' and !context.scopes.last[@to].nil?
|
21
|
+
context.scopes.last[@to] += output.strip
|
22
|
+
elsif @operator.nil? or (@operator == '||=' and context.scopes.last[@to].nil?)
|
23
|
+
context.scopes.last[@to] = output.strip
|
24
|
+
end
|
25
|
+
|
26
|
+
else
|
27
|
+
raise SyntaxError.new("Syntax Error in 'capture' - Valid syntax: capture [var]")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
''
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require File.join File.expand_path('../../', __FILE__), 'helpers/include'
|
2
|
+
|
3
|
+
module LiquidPlus
|
4
|
+
class IncludeTag < Liquid::Tag
|
5
|
+
|
6
|
+
def initialize(tag_name, markup, tokens)
|
7
|
+
@markup = markup.strip
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def render(context)
|
12
|
+
Include.render(@markup, context)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# Title: Render Partial Tag for Jekyll
|
2
|
+
# Author: Brandon Mathis
|
3
|
+
# Description: Import files on your filesystem into any blog post and render them inline.
|
4
|
+
# Note: Paths are relative to the source directory, if you import a file with yaml front matter, the yaml will be stripped out.
|
5
|
+
#
|
6
|
+
# Syntax {% render_partial path/to/file %}
|
7
|
+
#
|
8
|
+
# Example 1:
|
9
|
+
# {% render_partial about/_bio.markdown %}
|
10
|
+
#
|
11
|
+
# This will import source/about/_bio.markdown and render it inline.
|
12
|
+
# In this example I used an underscore at the beginning of the filename to prevent Jekyll
|
13
|
+
# from generating an about/bio.html (Jekyll doesn't convert files beginning with underscores)
|
14
|
+
#
|
15
|
+
# Example 2:
|
16
|
+
# {% render_partial ../README.markdown %}
|
17
|
+
#
|
18
|
+
# You can use relative pathnames, to include files outside of the source directory.
|
19
|
+
# This might be useful if you want to have a page for a project's README without having
|
20
|
+
# to duplicated the contents
|
21
|
+
#
|
22
|
+
#
|
23
|
+
|
24
|
+
require File.join File.expand_path('../../', __FILE__), 'helpers/path'
|
25
|
+
require File.join File.expand_path('../../', __FILE__), 'helpers/include'
|
26
|
+
require 'jekyll'
|
27
|
+
require 'yaml'
|
28
|
+
require 'pathname'
|
29
|
+
|
30
|
+
module Jekyll
|
31
|
+
|
32
|
+
# Create a new page class to allow partials to trigger Jekyll Page Hooks.
|
33
|
+
class ConvertiblePage
|
34
|
+
include Convertible
|
35
|
+
|
36
|
+
attr_accessor :name, :content, :site, :ext, :output, :data
|
37
|
+
|
38
|
+
def initialize(site, name, content)
|
39
|
+
@site = site
|
40
|
+
@name = name
|
41
|
+
@ext = File.extname(name)
|
42
|
+
@content = content
|
43
|
+
@data = { layout: "no_layout" } # hack
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
def render(payload, info)
|
48
|
+
do_layout(payload, { no_layout: nil })
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
module LiquidPlus
|
54
|
+
class RenderTag < Liquid::Tag
|
55
|
+
def initialize(tag_name, markup, tokens)
|
56
|
+
@markup = markup.strip
|
57
|
+
super
|
58
|
+
end
|
59
|
+
|
60
|
+
def parse_markup
|
61
|
+
# If raw, strip from the markup as not to confuse the Path syntax parsing
|
62
|
+
if @markup =~ /^(\s*raw\s)?(.+?)(\sraw\s*)?$/
|
63
|
+
@markup = $2.strip
|
64
|
+
@raw = true unless $1.nil? and $3.nil?
|
65
|
+
end
|
66
|
+
|
67
|
+
# Separate params from markup
|
68
|
+
@markup, @params = Include.split_params(@markup)
|
69
|
+
end
|
70
|
+
|
71
|
+
def get_path(context)
|
72
|
+
if file = Path.parse(@markup, context)
|
73
|
+
path = Pathname.new Path.expand(file, context)
|
74
|
+
@markup = file
|
75
|
+
path
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def render(context)
|
80
|
+
parse_markup
|
81
|
+
path = get_path(context)
|
82
|
+
if path and File.exist? path
|
83
|
+
|
84
|
+
Dir.chdir(File.dirname(path)) do
|
85
|
+
content = path.read
|
86
|
+
|
87
|
+
#Strip out yaml header from partials
|
88
|
+
if content =~ /\A-{3}(.+[^\A])-{3}\n(.+)/m
|
89
|
+
@local_vars = YAML.safe_load($1.strip)
|
90
|
+
content = $2.strip
|
91
|
+
end
|
92
|
+
|
93
|
+
if @raw
|
94
|
+
content
|
95
|
+
else
|
96
|
+
content = parse_params(content, context) if @params or @local_vars
|
97
|
+
|
98
|
+
p = Jekyll::ConvertiblePage.new(context.registers[:site], path, content)
|
99
|
+
payload = { 'page' => context.registers[:page], 'site' => context.registers[:site] }
|
100
|
+
p.render(payload, { registers: context.registers })
|
101
|
+
p.output.strip
|
102
|
+
end
|
103
|
+
end
|
104
|
+
elsif path
|
105
|
+
name = File.basename(path)
|
106
|
+
dir = path.to_s.sub(context.registers[:site].source + '/', '')
|
107
|
+
|
108
|
+
msg = "From #{context.registers[:page]['path']}: "
|
109
|
+
msg += "File '#{name}' not found"
|
110
|
+
msg += " in '#{dir}' directory" unless name == dir
|
111
|
+
|
112
|
+
puts msg.red
|
113
|
+
return msg
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def parse_params(content, context)
|
118
|
+
if @params
|
119
|
+
markup = @markup + @params
|
120
|
+
end
|
121
|
+
partial = Liquid::Template.parse(content)
|
122
|
+
|
123
|
+
context.stack do
|
124
|
+
c = context
|
125
|
+
c['render'] = Jekyll::Tags::IncludeTag.new('', markup, []).parse_params(context) if @params
|
126
|
+
c['page'] = c['page'].deep_merge(@local_vars) if @local_vars and @local_vars.keys.size > 0
|
127
|
+
content = partial.render(c)
|
128
|
+
end
|
129
|
+
content
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.join File.expand_path('../../', __FILE__), 'helpers/var'
|
2
|
+
|
3
|
+
module LiquidPlus
|
4
|
+
class ReturnTag < Liquid::Tag
|
5
|
+
|
6
|
+
def initialize(tag_name, markup, tokens)
|
7
|
+
@markup = markup.strip
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def render(context)
|
12
|
+
if @markup = Conditional.parse(@markup, context)
|
13
|
+
Var.get_value(@markup, context)
|
14
|
+
else
|
15
|
+
''
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require File.join File.expand_path('../../', __FILE__), 'helpers/include'
|
2
|
+
require File.join File.expand_path('../../', __FILE__), 'tags/render'
|
3
|
+
|
4
|
+
module LiquidPlus
|
5
|
+
class WrapTag < Liquid::Block
|
6
|
+
HAS_CONTENT = /(.*?)({=\s*yield\s*})(.*)/im
|
7
|
+
|
8
|
+
def initialize(tag_name, markup, tokens)
|
9
|
+
@tag = tag_name
|
10
|
+
@markup = markup.strip
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
def render(context)
|
15
|
+
if super.strip =~ HAS_CONTENT
|
16
|
+
front = $1
|
17
|
+
back = $3
|
18
|
+
|
19
|
+
partial = if @tag == 'wrap'
|
20
|
+
RenderTag.new('', @markup, []).render(context)
|
21
|
+
elsif @tag == 'wrap_include'
|
22
|
+
Include.render(@markup, context)
|
23
|
+
end
|
24
|
+
|
25
|
+
if partial
|
26
|
+
front + partial.strip + back
|
27
|
+
else
|
28
|
+
''
|
29
|
+
end
|
30
|
+
else
|
31
|
+
raise SyntaxError.new("Syntax Error in '#{@tag}' - Valid syntax: {% wrap_include template/file.html %}\n[<div>]{= yield }[</div>]\n{% end#{@tag} %}")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|