Class: FlexibleIncludeTag::Tags::IncludeAbsoluteTag

Inherits:
Liquid::Tag
  • Object
show all
Defined in:
flexible_include.rb

Constant Summary collapse

VALID_SYNTAX =
%r!
  ([\w-]+)\s*=\s*
  (?:"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|([\w\.-]+))
!x
VARIABLE_SYNTAX =
%r!
  (?<variable>[^{]*(\{\{\s*[\w\-\.]+\s*(\|.*)?\}\}[^\s{}]*)+)
  (?<params>.*)
!mx
FULL_VALID_SYNTAX =
%r!\A\s*(?:#{VALID_SYNTAX}(?=\s|\z)\s*)*\z!
VALID_FILENAME_CHARS =
%r!^[\w/\.-]+$!

Instance Method Summary collapse

Constructor Details

#initialize(tag_name, markup, tokens) ⇒ IncludeAbsoluteTag

Returns a new instance of IncludeAbsoluteTag.



42
43
44
45
46
47
48
49
50
51
52
53
# File 'flexible_include.rb', line 42

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(%r!\s+!, 2)
  end
  validate_params if @params
  @tag_name = tag_name
end

Instance Method Details

#expand_env(str) ⇒ Object



124
125
126
127
# File 'flexible_include.rb', line 124

def expand_env(str)
  # Expands environment variable references in str
  str.gsub(/\$([a-zA-Z_][a-zA-Z0-9_]*)|\${\g<1>}|%\g<1>%/) { ENV[$1] }
end

#file_read_opts(context) ⇒ Object

Grab file read opts in the context



109
110
111
# File 'flexible_include.rb', line 109

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

#outside_site_source?(path, dir, safe) ⇒ Boolean

Returns:

  • (Boolean)


179
180
181
# File 'flexible_include.rb', line 179

def outside_site_source?(path, dir, safe)
  safe && !realpath_prefixed_with?(path, dir)
end

#parse_params(context) ⇒ Object



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'flexible_include.rb', line 59

def parse_params(context)
  params = {}
  markup = @params
  while (match = VALID_SYNTAX.match(markup))
    markup = markup[match.end(0)..-1]

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

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

#read_file(file, context) ⇒ Object

Allows modification of the file content by inheriting from the class.



190
191
192
# File 'flexible_include.rb', line 190

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

#realpath_prefixed_with?(path, dir) ⇒ Boolean

Returns:

  • (Boolean)


183
184
185
186
187
# File 'flexible_include.rb', line 183

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

#render(context) ⇒ Object



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'flexible_include.rb', line 129

def render(context)
  file = render_variable(context) || @file
  file = file.gsub(/\A'|'\Z/, '') # Strip leading and trailing quotes
  file = expand_env(file) # Expand environment variable references
  # validate_file_name(file) # TODO: uncomment and fix validate_file_name
  path = file
  case file
    when %r{^/} # Is the file absolute?
      FlexibleIncludeTag.log.info "FlexibleInclude.render path=#{path}, file=#{file}".yellow
    when /~/ # Is the file relative to user's home directory?
      FlexibleIncludeTag.log.info "FlexibleInclude.render original file=#{file}, path=#{path}".yellow
      file.slice! '~/'
      path = File.join(ENV['HOME'], file)
      FlexibleIncludeTag.log.info "FlexibleInclude.render path=#{path}, file=#{file}".yellow
    when /!/ # Is the file on the PATH?
      FlexibleIncludeTag.log.info "FlexibleInclude.render original file=#{file}, path=#{path}".yellow
      file.slice! '!'
      path = File.which(file)
      FlexibleIncludeTag.log.info "FlexibleInclude.render path=#{path}, file=#{file}".yellow
    else # The file is relative
      source = File.expand_path(context.registers[:site].config['source']).freeze # website root directory
      path = File.join(source, file)  # Fully qualified path of include file
      FlexibleIncludeTag.log.info "FlexibleInclude.render file=#{file}, path=#{path}, source=#{source}".yellow
  end
  return unless path

  begin
    escaped_contents = read_file(path, context).gsub('{', '&#123;').gsub('}', '&#125;').gsub('<', '&lt;')
    FlexibleIncludeTag.log.debug "FlexibleInclude #{escaped_contents}".yellow
    partial = Liquid::Template.parse(escaped_contents)
  rescue StandardError => e
    abort "flexible_include.rb: #{e.message}"
  end

  context.stack do
    context['include'] = parse_params(context) if @params
    begin
      partial.render!(context)
    rescue Liquid::Error => e
      e.template_name = path
      e.markup_context = 'included ' if e.markup_context.nil?
      raise e
    end
  end
end

#render_variable(context) ⇒ Object

Render the variable if required



114
115
116
117
118
119
120
121
122
# File 'flexible_include.rb', line 114

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

#syntax_exampleObject



55
56
57
# File 'flexible_include.rb', line 55

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

#valid_include_file?(path, dir, safe) ⇒ Boolean

Returns:

  • (Boolean)


175
176
177
# File 'flexible_include.rb', line 175

def valid_include_file?(path, dir, safe)
  !outside_site_source?(path, dir, safe) && File.file?(path)
end

#validate_file_name(file) ⇒ Object

TODO: allow filenames relative to home directory

Raises:

  • (ArgumentError)


79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'flexible_include.rb', line 79

def validate_file_name(file)
  return unless file !~ VALID_FILENAME_CHARS
  raise ArgumentError, <<~HEREDOC
    Invalid syntax for the flexible_ include tag. The included file contains invalid characters or sequences:

      #{file}

    Valid syntax:

      #{syntax_example}

    HEREDOC
end

#validate_paramsObject



93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'flexible_include.rb', line 93

def validate_params
  unless @params =~ FULL_VALID_SYNTAX
    raise ArgumentError, <<~HEREDOC
      Invalid syntax for the flexible_include tag:

        #{@params}

      Valid syntax:

        #{syntax_example}

      HEREDOC
  end
end