liquid-4-0-2 4.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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