liquid-4-0-2 4.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +7 -0
  2. data/History.md +235 -0
  3. data/LICENSE +20 -0
  4. data/README.md +108 -0
  5. data/lib/liquid.rb +80 -0
  6. data/lib/liquid/block.rb +77 -0
  7. data/lib/liquid/block_body.rb +142 -0
  8. data/lib/liquid/condition.rb +151 -0
  9. data/lib/liquid/context.rb +226 -0
  10. data/lib/liquid/document.rb +27 -0
  11. data/lib/liquid/drop.rb +78 -0
  12. data/lib/liquid/errors.rb +56 -0
  13. data/lib/liquid/expression.rb +49 -0
  14. data/lib/liquid/extensions.rb +74 -0
  15. data/lib/liquid/file_system.rb +73 -0
  16. data/lib/liquid/forloop_drop.rb +42 -0
  17. data/lib/liquid/i18n.rb +39 -0
  18. data/lib/liquid/interrupts.rb +16 -0
  19. data/lib/liquid/lexer.rb +55 -0
  20. data/lib/liquid/locales/en.yml +26 -0
  21. data/lib/liquid/parse_context.rb +38 -0
  22. data/lib/liquid/parse_tree_visitor.rb +42 -0
  23. data/lib/liquid/parser.rb +90 -0
  24. data/lib/liquid/parser_switching.rb +31 -0
  25. data/lib/liquid/profiler.rb +158 -0
  26. data/lib/liquid/profiler/hooks.rb +23 -0
  27. data/lib/liquid/range_lookup.rb +37 -0
  28. data/lib/liquid/resource_limits.rb +23 -0
  29. data/lib/liquid/standardfilters.rb +485 -0
  30. data/lib/liquid/strainer.rb +66 -0
  31. data/lib/liquid/tablerowloop_drop.rb +62 -0
  32. data/lib/liquid/tag.rb +43 -0
  33. data/lib/liquid/tags/assign.rb +59 -0
  34. data/lib/liquid/tags/break.rb +18 -0
  35. data/lib/liquid/tags/capture.rb +38 -0
  36. data/lib/liquid/tags/case.rb +94 -0
  37. data/lib/liquid/tags/comment.rb +16 -0
  38. data/lib/liquid/tags/continue.rb +18 -0
  39. data/lib/liquid/tags/cycle.rb +65 -0
  40. data/lib/liquid/tags/decrement.rb +35 -0
  41. data/lib/liquid/tags/for.rb +203 -0
  42. data/lib/liquid/tags/if.rb +122 -0
  43. data/lib/liquid/tags/ifchanged.rb +18 -0
  44. data/lib/liquid/tags/include.rb +124 -0
  45. data/lib/liquid/tags/increment.rb +31 -0
  46. data/lib/liquid/tags/raw.rb +47 -0
  47. data/lib/liquid/tags/table_row.rb +62 -0
  48. data/lib/liquid/tags/unless.rb +30 -0
  49. data/lib/liquid/template.rb +254 -0
  50. data/lib/liquid/tokenizer.rb +31 -0
  51. data/lib/liquid/utils.rb +83 -0
  52. data/lib/liquid/variable.rb +148 -0
  53. data/lib/liquid/variable_lookup.rb +88 -0
  54. data/lib/liquid/version.rb +4 -0
  55. data/test/fixtures/en_locale.yml +9 -0
  56. data/test/integration/assign_test.rb +48 -0
  57. data/test/integration/blank_test.rb +106 -0
  58. data/test/integration/block_test.rb +12 -0
  59. data/test/integration/capture_test.rb +50 -0
  60. data/test/integration/context_test.rb +32 -0
  61. data/test/integration/document_test.rb +19 -0
  62. data/test/integration/drop_test.rb +273 -0
  63. data/test/integration/error_handling_test.rb +260 -0
  64. data/test/integration/filter_test.rb +178 -0
  65. data/test/integration/hash_ordering_test.rb +23 -0
  66. data/test/integration/output_test.rb +123 -0
  67. data/test/integration/parse_tree_visitor_test.rb +247 -0
  68. data/test/integration/parsing_quirks_test.rb +122 -0
  69. data/test/integration/render_profiling_test.rb +154 -0
  70. data/test/integration/security_test.rb +80 -0
  71. data/test/integration/standard_filter_test.rb +698 -0
  72. data/test/integration/tags/break_tag_test.rb +15 -0
  73. data/test/integration/tags/continue_tag_test.rb +15 -0
  74. data/test/integration/tags/for_tag_test.rb +410 -0
  75. data/test/integration/tags/if_else_tag_test.rb +188 -0
  76. data/test/integration/tags/include_tag_test.rb +245 -0
  77. data/test/integration/tags/increment_tag_test.rb +23 -0
  78. data/test/integration/tags/raw_tag_test.rb +31 -0
  79. data/test/integration/tags/standard_tag_test.rb +296 -0
  80. data/test/integration/tags/statements_test.rb +111 -0
  81. data/test/integration/tags/table_row_test.rb +64 -0
  82. data/test/integration/tags/unless_else_tag_test.rb +26 -0
  83. data/test/integration/template_test.rb +332 -0
  84. data/test/integration/trim_mode_test.rb +529 -0
  85. data/test/integration/variable_test.rb +96 -0
  86. data/test/test_helper.rb +116 -0
  87. data/test/unit/block_unit_test.rb +58 -0
  88. data/test/unit/condition_unit_test.rb +166 -0
  89. data/test/unit/context_unit_test.rb +489 -0
  90. data/test/unit/file_system_unit_test.rb +35 -0
  91. data/test/unit/i18n_unit_test.rb +37 -0
  92. data/test/unit/lexer_unit_test.rb +51 -0
  93. data/test/unit/parser_unit_test.rb +82 -0
  94. data/test/unit/regexp_unit_test.rb +44 -0
  95. data/test/unit/strainer_unit_test.rb +164 -0
  96. data/test/unit/tag_unit_test.rb +21 -0
  97. data/test/unit/tags/case_tag_unit_test.rb +10 -0
  98. data/test/unit/tags/for_tag_unit_test.rb +13 -0
  99. data/test/unit/tags/if_tag_unit_test.rb +8 -0
  100. data/test/unit/template_unit_test.rb +78 -0
  101. data/test/unit/tokenizer_unit_test.rb +55 -0
  102. data/test/unit/variable_unit_test.rb +162 -0
  103. metadata +224 -0
@@ -0,0 +1,27 @@
1
+ module Liquid
2
+ class Document < BlockBody
3
+ def self.parse(tokens, parse_context)
4
+ doc = new
5
+ doc.parse(tokens, parse_context)
6
+ doc
7
+ end
8
+
9
+ def parse(tokens, parse_context)
10
+ super do |end_tag_name, end_tag_params|
11
+ unknown_tag(end_tag_name, parse_context) if end_tag_name
12
+ end
13
+ rescue SyntaxError => e
14
+ e.line_number ||= parse_context.line_number
15
+ raise
16
+ end
17
+
18
+ def unknown_tag(tag, parse_context)
19
+ case tag
20
+ when 'else'.freeze, 'end'.freeze
21
+ raise SyntaxError.new(parse_context.locale.t("errors.syntax.unexpected_outer_tag".freeze, tag: tag))
22
+ else
23
+ raise SyntaxError.new(parse_context.locale.t("errors.syntax.unknown_tag".freeze, tag: tag))
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,78 @@
1
+ require 'set'
2
+
3
+ module Liquid
4
+ # A drop in liquid is a class which allows you to export DOM like things to liquid.
5
+ # Methods of drops are callable.
6
+ # The main use for liquid drops is to implement lazy loaded objects.
7
+ # If you would like to make data available to the web designers which you don't want loaded unless needed then
8
+ # a drop is a great way to do that.
9
+ #
10
+ # Example:
11
+ #
12
+ # class ProductDrop < Liquid::Drop
13
+ # def top_sales
14
+ # Shop.current.products.find(:all, :order => 'sales', :limit => 10 )
15
+ # end
16
+ # end
17
+ #
18
+ # tmpl = Liquid::Template.parse( ' {% for product in product.top_sales %} {{ product.name }} {%endfor%} ' )
19
+ # tmpl.render('product' => ProductDrop.new ) # will invoke top_sales query.
20
+ #
21
+ # Your drop can either implement the methods sans any parameters
22
+ # or implement the liquid_method_missing(name) method which is a catch all.
23
+ class Drop
24
+ attr_writer :context
25
+
26
+ # Catch all for the method
27
+ def liquid_method_missing(method)
28
+ return nil unless @context && @context.strict_variables
29
+ raise Liquid::UndefinedDropMethod, "undefined method #{method}"
30
+ end
31
+
32
+ # called by liquid to invoke a drop
33
+ def invoke_drop(method_or_key)
34
+ if self.class.invokable?(method_or_key)
35
+ send(method_or_key)
36
+ else
37
+ liquid_method_missing(method_or_key)
38
+ end
39
+ end
40
+
41
+ def key?(_name)
42
+ true
43
+ end
44
+
45
+ def inspect
46
+ self.class.to_s
47
+ end
48
+
49
+ def to_liquid
50
+ self
51
+ end
52
+
53
+ def to_s
54
+ self.class.name
55
+ end
56
+
57
+ alias_method :[], :invoke_drop
58
+
59
+ # Check for method existence without invoking respond_to?, which creates symbols
60
+ def self.invokable?(method_name)
61
+ invokable_methods.include?(method_name.to_s)
62
+ end
63
+
64
+ def self.invokable_methods
65
+ @invokable_methods ||= begin
66
+ blacklist = Liquid::Drop.public_instance_methods + [:each]
67
+
68
+ if include?(Enumerable)
69
+ blacklist += Enumerable.public_instance_methods
70
+ blacklist -= [:sort, :count, :first, :min, :max, :include?]
71
+ end
72
+
73
+ whitelist = [:to_liquid] + (public_instance_methods - blacklist)
74
+ Set.new(whitelist.map(&:to_s))
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,56 @@
1
+ module Liquid
2
+ class Error < ::StandardError
3
+ attr_accessor :line_number
4
+ attr_accessor :template_name
5
+ attr_accessor :markup_context
6
+
7
+ def to_s(with_prefix = true)
8
+ str = ""
9
+ str << message_prefix if with_prefix
10
+ str << super()
11
+
12
+ if markup_context
13
+ str << " "
14
+ str << markup_context
15
+ end
16
+
17
+ str
18
+ end
19
+
20
+ private
21
+
22
+ def message_prefix
23
+ str = ""
24
+ if is_a?(SyntaxError)
25
+ str << "Liquid syntax error"
26
+ else
27
+ str << "Liquid error"
28
+ end
29
+
30
+ if line_number
31
+ str << " ("
32
+ str << template_name << " " if template_name
33
+ str << "line " << line_number.to_s << ")"
34
+ end
35
+
36
+ str << ": "
37
+ str
38
+ end
39
+ end
40
+
41
+ ArgumentError = Class.new(Error)
42
+ ContextError = Class.new(Error)
43
+ FileSystemError = Class.new(Error)
44
+ StandardError = Class.new(Error)
45
+ SyntaxError = Class.new(Error)
46
+ StackLevelError = Class.new(Error)
47
+ TaintedError = Class.new(Error)
48
+ MemoryError = Class.new(Error)
49
+ ZeroDivisionError = Class.new(Error)
50
+ FloatDomainError = Class.new(Error)
51
+ UndefinedVariable = Class.new(Error)
52
+ UndefinedDropMethod = Class.new(Error)
53
+ UndefinedFilter = Class.new(Error)
54
+ MethodOverrideError = Class.new(Error)
55
+ InternalError = Class.new(Error)
56
+ end
@@ -0,0 +1,49 @@
1
+ module Liquid
2
+ class Expression
3
+ class MethodLiteral
4
+ attr_reader :method_name, :to_s
5
+
6
+ def initialize(method_name, to_s)
7
+ @method_name = method_name
8
+ @to_s = to_s
9
+ end
10
+
11
+ def to_liquid
12
+ to_s
13
+ end
14
+ end
15
+
16
+ LITERALS = {
17
+ nil => nil, 'nil'.freeze => nil, 'null'.freeze => nil, ''.freeze => nil,
18
+ 'true'.freeze => true,
19
+ 'false'.freeze => false,
20
+ 'blank'.freeze => MethodLiteral.new(:blank?, '').freeze,
21
+ 'empty'.freeze => MethodLiteral.new(:empty?, '').freeze
22
+ }
23
+
24
+ SINGLE_QUOTED_STRING = /\A'(.*)'\z/m
25
+ DOUBLE_QUOTED_STRING = /\A"(.*)"\z/m
26
+ INTEGERS_REGEX = /\A(-?\d+)\z/
27
+ FLOATS_REGEX = /\A(-?\d[\d\.]+)\z/
28
+ RANGES_REGEX = /\A\((\S+)\.\.(\S+)\)\z/
29
+
30
+ def self.parse(markup)
31
+ if LITERALS.key?(markup)
32
+ LITERALS[markup]
33
+ else
34
+ case markup
35
+ when SINGLE_QUOTED_STRING, DOUBLE_QUOTED_STRING
36
+ $1
37
+ when INTEGERS_REGEX
38
+ $1.to_i
39
+ when RANGES_REGEX
40
+ RangeLookup.parse($1, $2)
41
+ when FLOATS_REGEX
42
+ $1.to_f
43
+ else
44
+ VariableLookup.parse(markup)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,74 @@
1
+ require 'time'
2
+ require 'date'
3
+
4
+ class String # :nodoc:
5
+ def to_liquid
6
+ self
7
+ end
8
+ end
9
+
10
+ class Symbol # :nodoc:
11
+ def to_liquid
12
+ to_s
13
+ end
14
+ end
15
+
16
+ class Array # :nodoc:
17
+ def to_liquid
18
+ self
19
+ end
20
+ end
21
+
22
+ class Hash # :nodoc:
23
+ def to_liquid
24
+ self
25
+ end
26
+ end
27
+
28
+ class Numeric # :nodoc:
29
+ def to_liquid
30
+ self
31
+ end
32
+ end
33
+
34
+ class Range # :nodoc:
35
+ def to_liquid
36
+ self
37
+ end
38
+ end
39
+
40
+ class Time # :nodoc:
41
+ def to_liquid
42
+ self
43
+ end
44
+ end
45
+
46
+ class DateTime < Date # :nodoc:
47
+ def to_liquid
48
+ self
49
+ end
50
+ end
51
+
52
+ class Date # :nodoc:
53
+ def to_liquid
54
+ self
55
+ end
56
+ end
57
+
58
+ class TrueClass
59
+ def to_liquid # :nodoc:
60
+ self
61
+ end
62
+ end
63
+
64
+ class FalseClass
65
+ def to_liquid # :nodoc:
66
+ self
67
+ end
68
+ end
69
+
70
+ class NilClass
71
+ def to_liquid # :nodoc:
72
+ self
73
+ end
74
+ end
@@ -0,0 +1,73 @@
1
+ module Liquid
2
+ # A Liquid file system is a way to let your templates retrieve other templates for use with the include tag.
3
+ #
4
+ # You can implement subclasses that retrieve templates from the database, from the file system using a different
5
+ # path structure, you can provide them as hard-coded inline strings, or any manner that you see fit.
6
+ #
7
+ # You can add additional instance variables, arguments, or methods as needed.
8
+ #
9
+ # Example:
10
+ #
11
+ # Liquid::Template.file_system = Liquid::LocalFileSystem.new(template_path)
12
+ # liquid = Liquid::Template.parse(template)
13
+ #
14
+ # This will parse the template with a LocalFileSystem implementation rooted at 'template_path'.
15
+ class BlankFileSystem
16
+ # Called by Liquid to retrieve a template file
17
+ def read_template_file(_template_path)
18
+ raise FileSystemError, "This liquid context does not allow includes."
19
+ end
20
+ end
21
+
22
+ # This implements an abstract file system which retrieves template files named in a manner similar to Rails partials,
23
+ # ie. with the template name prefixed with an underscore. The extension ".liquid" is also added.
24
+ #
25
+ # For security reasons, template paths are only allowed to contain letters, numbers, and underscore.
26
+ #
27
+ # Example:
28
+ #
29
+ # file_system = Liquid::LocalFileSystem.new("/some/path")
30
+ #
31
+ # file_system.full_path("mypartial") # => "/some/path/_mypartial.liquid"
32
+ # file_system.full_path("dir/mypartial") # => "/some/path/dir/_mypartial.liquid"
33
+ #
34
+ # Optionally in the second argument you can specify a custom pattern for template filenames.
35
+ # The Kernel::sprintf format specification is used.
36
+ # Default pattern is "_%s.liquid".
37
+ #
38
+ # Example:
39
+ #
40
+ # file_system = Liquid::LocalFileSystem.new("/some/path", "%s.html")
41
+ #
42
+ # file_system.full_path("index") # => "/some/path/index.html"
43
+ #
44
+ class LocalFileSystem
45
+ attr_accessor :root
46
+
47
+ def initialize(root, pattern = "_%s.liquid".freeze)
48
+ @root = root
49
+ @pattern = pattern
50
+ end
51
+
52
+ def read_template_file(template_path)
53
+ full_path = full_path(template_path)
54
+ raise FileSystemError, "No such template '#{template_path}'" unless File.exist?(full_path)
55
+
56
+ File.read(full_path)
57
+ end
58
+
59
+ def full_path(template_path)
60
+ raise FileSystemError, "Illegal template name '#{template_path}'" unless template_path =~ /\A[^.\/][a-zA-Z0-9_\/]+\z/
61
+
62
+ full_path = if template_path.include?('/'.freeze)
63
+ File.join(root, File.dirname(template_path), @pattern % File.basename(template_path))
64
+ else
65
+ File.join(root, @pattern % template_path)
66
+ end
67
+
68
+ raise FileSystemError, "Illegal template path '#{File.expand_path(full_path)}'" unless File.expand_path(full_path).start_with?(File.expand_path(root))
69
+
70
+ full_path
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,42 @@
1
+ module Liquid
2
+ class ForloopDrop < Drop
3
+ def initialize(name, length, parentloop)
4
+ @name = name
5
+ @length = length
6
+ @parentloop = parentloop
7
+ @index = 0
8
+ end
9
+
10
+ attr_reader :name, :length, :parentloop
11
+
12
+ def index
13
+ @index + 1
14
+ end
15
+
16
+ def index0
17
+ @index
18
+ end
19
+
20
+ def rindex
21
+ @length - @index
22
+ end
23
+
24
+ def rindex0
25
+ @length - @index - 1
26
+ end
27
+
28
+ def first
29
+ @index == 0
30
+ end
31
+
32
+ def last
33
+ @index == @length - 1
34
+ end
35
+
36
+ protected
37
+
38
+ def increment!
39
+ @index += 1
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,39 @@
1
+ require 'yaml'
2
+
3
+ module Liquid
4
+ class I18n
5
+ DEFAULT_LOCALE = File.join(File.expand_path(__dir__), "locales", "en.yml")
6
+
7
+ TranslationError = Class.new(StandardError)
8
+
9
+ attr_reader :path
10
+
11
+ def initialize(path = DEFAULT_LOCALE)
12
+ @path = path
13
+ end
14
+
15
+ def translate(name, vars = {})
16
+ interpolate(deep_fetch_translation(name), vars)
17
+ end
18
+ alias_method :t, :translate
19
+
20
+ def locale
21
+ @locale ||= YAML.load_file(@path)
22
+ end
23
+
24
+ private
25
+
26
+ def interpolate(name, vars)
27
+ name.gsub(/%\{(\w+)\}/) do
28
+ # raise TranslationError, "Undefined key #{$1} for interpolation in translation #{name}" unless vars[$1.to_sym]
29
+ "#{vars[$1.to_sym]}"
30
+ end
31
+ end
32
+
33
+ def deep_fetch_translation(name)
34
+ name.split('.'.freeze).reduce(locale) do |level, cur|
35
+ level[cur] or raise TranslationError, "Translation for #{name} does not exist in locale #{path}"
36
+ end
37
+ end
38
+ end
39
+ end