Class: Jekyll::Tags::IncludeTag

Inherits:
Liquid::Tag
  • Object
show all
Defined in:
lib/jekyll/tags/include.rb

Direct Known Subclasses

IncludeRelativeTag

Constant Summary collapse

VALID_SYNTAX =
/([\w-]+)\s*=\s*(?:"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|([\w\.-]+))/
VARIABLE_SYNTAX =
/(?<variable>[^{]*\{\{\s*(?<name>[\w\-\.]+)\s*(\|.*)?\}\}[^\s}]*)(?<params>.*)/

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(tag_name, markup, tokens) ⇒ IncludeTag

Returns a new instance of IncludeTag.



21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/jekyll/tags/include.rb', line 21

def initialize(tag_name, markup, tokens)
  super
  matched = markup.strip.match(VARIABLE_SYNTAX)
  if matched
    @file = matched['variable'].strip
    @params = matched['params'].strip
  else
    @file, @params = markup.strip.split(' ', 2);
  end
  validate_params if @params
  @tag_name = tag_name
end

Instance Attribute Details

#includes_dirObject (readonly)

Returns the value of attribute includes_dir.



16
17
18
# File 'lib/jekyll/tags/include.rb', line 16

def includes_dir
  @includes_dir
end

Instance Method Details

#file_read_opts(context) ⇒ Object

Grab file read opts in the context



90
91
92
# File 'lib/jekyll/tags/include.rb', line 90

def file_read_opts(context)
  context.registers[:site].file_read_opts
end

#parse_params(context) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/jekyll/tags/include.rb', line 38

def parse_params(context)
  params = {}
  markup = @params

  while match = VALID_SYNTAX.match(markup) do
    markup = markup[match.end(0)..-1]

    value = if match[2]
      match[2].gsub(/\\"/, '"')
    elsif match[3]
      match[3].gsub(/\\'/, "'")
    elsif match[4]
      context[match[4]]
    end

    params[match[1]] = value
  end
  params
end

#path_relative_to_source(dir, path) ⇒ Object



149
150
151
# File 'lib/jekyll/tags/include.rb', line 149

def path_relative_to_source(dir, path)
  File.join(@includes_dir, path.sub(Regexp.new("^#{dir}"), ""))
end

#read_file(file, context) ⇒ Object

This method allows to modify the file content by inheriting from the class.



158
159
160
# File 'lib/jekyll/tags/include.rb', line 158

def read_file(file, context)
  File.read(file, file_read_opts(context))
end

#realpath_prefixed_with?(path, dir) ⇒ Boolean

Returns:

  • (Boolean)


153
154
155
# File 'lib/jekyll/tags/include.rb', line 153

def realpath_prefixed_with?(path, dir)
  File.exist?(path) && File.realpath(path).start_with?(dir)
end

#render(context) ⇒ Object



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/jekyll/tags/include.rb', line 106

def render(context)
  site = context.registers[:site]
  @includes_dir = tag_includes_dir(context)
  dir = resolved_includes_dir(context)

  file = render_variable(context) || @file
  validate_file_name(file)

  path = File.join(dir, file)
  validate_path(path, dir, site.safe)

  # Add include to dependency tree
  if context.registers[:page] and context.registers[:page].has_key? "path"
    site.regenerator.add_dependency(
      site.in_source_dir(context.registers[:page]["path"]),
      path
    )
  end

  begin
    partial = site.liquid_renderer.file(path).parse(read_file(path, context))

    context.stack do
      context['include'] = parse_params(context) if @params
      partial.render!(context)
    end
  rescue => e
    raise IncludeTagError.new e.message, File.join(@includes_dir, @file)
  end
end

#render_variable(context) ⇒ Object

Render the variable if required



95
96
97
98
99
100
# File 'lib/jekyll/tags/include.rb', line 95

def render_variable(context)
  if @file.match(VARIABLE_SYNTAX)
    partial = context.registers[:site].liquid_renderer.file("(variable)").parse(@file)
    partial.render!(context)
  end
end

#resolved_includes_dir(context) ⇒ Object



137
138
139
# File 'lib/jekyll/tags/include.rb', line 137

def resolved_includes_dir(context)
  context.registers[:site].in_source_dir(@includes_dir)
end

#syntax_exampleObject



34
35
36
# File 'lib/jekyll/tags/include.rb', line 34

def syntax_example
  "{% #{@tag_name} file.ext param='value' param2='value' %}"
end

#tag_includes_dir(context) ⇒ Object



102
103
104
# File 'lib/jekyll/tags/include.rb', line 102

def tag_includes_dir(context)
  context.registers[:site].config['includes_dir'].freeze
end

#validate_file_name(file) ⇒ Object



58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/jekyll/tags/include.rb', line 58

def validate_file_name(file)
  if file !~ /^[a-zA-Z0-9_\/\.-]+$/ || file =~ /\.\// || file =~ /\/\./
      raise ArgumentError.new "Invalid syntax for include tag. File contains invalid characters or sequences:\n\n  \#{file}\n\nValid syntax:\n\n  \#{syntax_example}\n\n"
  end
end

#validate_paramsObject



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/jekyll/tags/include.rb', line 73

def validate_params
  full_valid_syntax = Regexp.compile('\A\s*(?:' + VALID_SYNTAX.to_s + '(?=\s|\z)\s*)*\z')
  unless @params =~ full_valid_syntax
    raise ArgumentError.new "Invalid syntax for include tag:\n\n  \#{@params}\n\nValid syntax:\n\n  \#{syntax_example}\n\n"
  end
end

#validate_path(path, dir, safe) ⇒ Object



141
142
143
144
145
146
147
# File 'lib/jekyll/tags/include.rb', line 141

def validate_path(path, dir, safe)
  if safe && !realpath_prefixed_with?(path, dir)
    raise IOError.new "The included file '#{path}' should exist and should not be a symlink"
  elsif !File.exist?(path)
    raise IOError.new "Included file '#{path_relative_to_source(dir, path)}' not found"
  end
end