twig_ruby 0.0.1 → 0.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 (167) hide show
  1. checksums.yaml +4 -4
  2. data/lib/tasks/twig_parity.rake +278 -0
  3. data/lib/twig/auto_hash.rb +7 -1
  4. data/lib/twig/callable.rb +28 -1
  5. data/lib/twig/compiler.rb +35 -3
  6. data/lib/twig/environment.rb +198 -41
  7. data/lib/twig/error/base.rb +81 -16
  8. data/lib/twig/error/loader.rb +8 -0
  9. data/lib/twig/error/logic.rb +8 -0
  10. data/lib/twig/error/runtime.rb +8 -0
  11. data/lib/twig/expression_parser/base.rb +30 -0
  12. data/lib/twig/expression_parser/expression_parsers.rb +57 -0
  13. data/lib/twig/expression_parser/infix/arrow.rb +31 -0
  14. data/lib/twig/expression_parser/infix/binary.rb +34 -0
  15. data/lib/twig/expression_parser/infix/conditional_ternary.rb +39 -0
  16. data/lib/twig/expression_parser/infix/dot.rb +72 -0
  17. data/lib/twig/expression_parser/infix/filter.rb +43 -0
  18. data/lib/twig/expression_parser/infix/function.rb +67 -0
  19. data/lib/twig/expression_parser/infix/is.rb +53 -0
  20. data/lib/twig/expression_parser/infix/is_not.rb +19 -0
  21. data/lib/twig/expression_parser/infix/parses_arguments.rb +84 -0
  22. data/lib/twig/expression_parser/infix/square_bracket.rb +66 -0
  23. data/lib/twig/expression_parser/infix_expression_parser.rb +34 -0
  24. data/lib/twig/expression_parser/prefix/grouping.rb +60 -0
  25. data/lib/twig/expression_parser/prefix/literal.rb +244 -0
  26. data/lib/twig/expression_parser/prefix/unary.rb +29 -0
  27. data/lib/twig/expression_parser/prefix_expression_parser.rb +18 -0
  28. data/lib/twig/extension/base.rb +26 -4
  29. data/lib/twig/extension/core.rb +1076 -48
  30. data/lib/twig/extension/debug.rb +25 -0
  31. data/lib/twig/extension/escaper.rb +73 -0
  32. data/lib/twig/extension/rails.rb +10 -57
  33. data/lib/twig/extension/string_loader.rb +19 -0
  34. data/lib/twig/extension_set.rb +117 -20
  35. data/lib/twig/file_extension_escaping_strategy.rb +35 -0
  36. data/lib/twig/lexer.rb +225 -81
  37. data/lib/twig/loader/array.rb +25 -8
  38. data/lib/twig/loader/chain.rb +93 -0
  39. data/lib/twig/loader/filesystem.rb +106 -7
  40. data/lib/twig/node/auto_escape.rb +18 -0
  41. data/lib/twig/node/base.rb +58 -2
  42. data/lib/twig/node/block.rb +2 -0
  43. data/lib/twig/node/block_reference.rb +5 -1
  44. data/lib/twig/node/body.rb +7 -0
  45. data/lib/twig/node/cache.rb +50 -0
  46. data/lib/twig/node/capture.rb +22 -0
  47. data/lib/twig/node/deprecated.rb +53 -0
  48. data/lib/twig/node/do.rb +19 -0
  49. data/lib/twig/node/embed.rb +43 -0
  50. data/lib/twig/node/expression/array.rb +29 -20
  51. data/lib/twig/node/expression/arrow_function.rb +55 -0
  52. data/lib/twig/node/expression/assign_name.rb +1 -1
  53. data/lib/twig/node/expression/binary/and.rb +17 -0
  54. data/lib/twig/node/expression/binary/base.rb +6 -4
  55. data/lib/twig/node/expression/binary/boolean.rb +24 -0
  56. data/lib/twig/node/expression/binary/concat.rb +20 -0
  57. data/lib/twig/node/expression/binary/elvis.rb +35 -0
  58. data/lib/twig/node/expression/binary/ends_with.rb +24 -0
  59. data/lib/twig/node/expression/binary/floor_div.rb +21 -0
  60. data/lib/twig/node/expression/binary/has_every.rb +20 -0
  61. data/lib/twig/node/expression/binary/has_some.rb +20 -0
  62. data/lib/twig/node/expression/binary/in.rb +20 -0
  63. data/lib/twig/node/expression/binary/matches.rb +24 -0
  64. data/lib/twig/node/expression/binary/not_in.rb +20 -0
  65. data/lib/twig/node/expression/binary/null_coalesce.rb +49 -0
  66. data/lib/twig/node/expression/binary/or.rb +15 -0
  67. data/lib/twig/node/expression/binary/starts_with.rb +24 -0
  68. data/lib/twig/node/expression/binary/xor.rb +17 -0
  69. data/lib/twig/node/expression/block_reference.rb +62 -0
  70. data/lib/twig/node/expression/call.rb +126 -6
  71. data/lib/twig/node/expression/constant.rb +3 -1
  72. data/lib/twig/node/expression/filter/default.rb +37 -0
  73. data/lib/twig/node/expression/filter/raw.rb +31 -0
  74. data/lib/twig/node/expression/filter.rb +2 -2
  75. data/lib/twig/node/expression/function.rb +37 -0
  76. data/lib/twig/node/expression/get_attribute.rb +51 -7
  77. data/lib/twig/node/expression/hash.rb +75 -0
  78. data/lib/twig/node/expression/helper_method.rb +6 -18
  79. data/lib/twig/node/expression/macro_reference.rb +43 -0
  80. data/lib/twig/node/expression/name.rb +42 -8
  81. data/lib/twig/node/expression/operator_escape.rb +13 -0
  82. data/lib/twig/node/expression/parent.rb +28 -0
  83. data/lib/twig/node/expression/support_defined_test.rb +23 -0
  84. data/lib/twig/node/expression/ternary.rb +7 -1
  85. data/lib/twig/node/expression/test/base.rb +26 -0
  86. data/lib/twig/node/expression/test/constant.rb +35 -0
  87. data/lib/twig/node/expression/test/defined.rb +33 -0
  88. data/lib/twig/node/expression/test/divisible_by.rb +23 -0
  89. data/lib/twig/node/expression/test/even.rb +21 -0
  90. data/lib/twig/node/expression/test/iterable.rb +21 -0
  91. data/lib/twig/node/expression/test/mapping.rb +21 -0
  92. data/lib/twig/node/expression/test/null.rb +21 -0
  93. data/lib/twig/node/expression/test/odd.rb +21 -0
  94. data/lib/twig/node/expression/test/same_as.rb +23 -0
  95. data/lib/twig/node/expression/test/sequence.rb +21 -0
  96. data/lib/twig/node/expression/unary/base.rb +3 -1
  97. data/lib/twig/node/expression/unary/not.rb +18 -0
  98. data/lib/twig/node/expression/unary/spread.rb +18 -0
  99. data/lib/twig/node/expression/unary/string_cast.rb +18 -0
  100. data/lib/twig/node/expression/variable/assign_template.rb +35 -0
  101. data/lib/twig/node/expression/variable/local.rb +35 -0
  102. data/lib/twig/node/expression/variable/template.rb +54 -0
  103. data/lib/twig/node/for.rb +38 -8
  104. data/lib/twig/node/for_loop.rb +0 -22
  105. data/lib/twig/node/if.rb +4 -1
  106. data/lib/twig/node/import.rb +32 -0
  107. data/lib/twig/node/include.rb +38 -8
  108. data/lib/twig/node/macro.rb +79 -0
  109. data/lib/twig/node/module.rb +278 -23
  110. data/lib/twig/node/output.rb +7 -0
  111. data/lib/twig/node/print.rb +4 -1
  112. data/lib/twig/node/set.rb +72 -0
  113. data/lib/twig/node/text.rb +4 -1
  114. data/lib/twig/node/with.rb +50 -0
  115. data/lib/twig/node/yield.rb +6 -1
  116. data/lib/twig/node_traverser.rb +50 -0
  117. data/lib/twig/node_visitor/base.rb +30 -0
  118. data/lib/twig/node_visitor/escaper.rb +165 -0
  119. data/lib/twig/node_visitor/safe_analysis.rb +127 -0
  120. data/lib/twig/node_visitor/spreader.rb +39 -0
  121. data/lib/twig/output_buffer.rb +14 -12
  122. data/lib/twig/parser.rb +281 -8
  123. data/lib/twig/rails/config.rb +33 -0
  124. data/lib/twig/rails/engine.rb +44 -0
  125. data/lib/twig/rails/renderer.rb +41 -0
  126. data/lib/twig/runtime/argument_spreader.rb +46 -0
  127. data/lib/twig/runtime/context.rb +154 -0
  128. data/lib/twig/runtime/enumerable_hash.rb +51 -0
  129. data/lib/twig/runtime/escaper.rb +155 -0
  130. data/lib/twig/runtime/loop_context.rb +81 -0
  131. data/lib/twig/runtime/loop_iterator.rb +60 -0
  132. data/lib/twig/runtime/spread.rb +21 -0
  133. data/lib/twig/runtime_loader/base.rb +12 -0
  134. data/lib/twig/runtime_loader/factory.rb +23 -0
  135. data/lib/twig/template.rb +267 -14
  136. data/lib/twig/template_wrapper.rb +42 -0
  137. data/lib/twig/token.rb +28 -2
  138. data/lib/twig/token_parser/apply.rb +48 -0
  139. data/lib/twig/token_parser/auto_escape.rb +45 -0
  140. data/lib/twig/token_parser/base.rb +26 -0
  141. data/lib/twig/token_parser/block.rb +4 -4
  142. data/lib/twig/token_parser/cache.rb +31 -0
  143. data/lib/twig/token_parser/deprecated.rb +40 -0
  144. data/lib/twig/token_parser/do.rb +19 -0
  145. data/lib/twig/token_parser/embed.rb +62 -0
  146. data/lib/twig/token_parser/extends.rb +4 -3
  147. data/lib/twig/token_parser/for.rb +14 -9
  148. data/lib/twig/token_parser/from.rb +57 -0
  149. data/lib/twig/token_parser/guard.rb +65 -0
  150. data/lib/twig/token_parser/if.rb +9 -9
  151. data/lib/twig/token_parser/import.rb +29 -0
  152. data/lib/twig/token_parser/include.rb +2 -2
  153. data/lib/twig/token_parser/macro.rb +109 -0
  154. data/lib/twig/token_parser/set.rb +76 -0
  155. data/lib/twig/token_parser/use.rb +54 -0
  156. data/lib/twig/token_parser/with.rb +36 -0
  157. data/lib/twig/token_parser/yield.rb +7 -7
  158. data/lib/twig/token_stream.rb +23 -3
  159. data/lib/twig/twig_filter.rb +20 -0
  160. data/lib/twig/twig_function.rb +37 -0
  161. data/lib/twig/twig_test.rb +31 -0
  162. data/lib/twig/util/callable_arguments_extractor.rb +227 -0
  163. data/lib/twig_ruby.rb +21 -2
  164. metadata +145 -6
  165. data/lib/twig/context.rb +0 -64
  166. data/lib/twig/expression_parser.rb +0 -517
  167. data/lib/twig/railtie.rb +0 -60
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Extension
5
+ class Debug < Extension::Base
6
+ def functions
7
+ [
8
+ TwigFunction.new('dump', method(:dump), needs_environment: true, needs_context: true),
9
+ ]
10
+ end
11
+
12
+ def dump(environment, context, *vars)
13
+ return '' unless environment.debug?
14
+
15
+ begin
16
+ if vars.empty?
17
+ context
18
+ else
19
+ vars.one? ? vars.first : vars
20
+ end
21
+ end.inspect
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Extension
5
+ class Escaper < Base
6
+ def initialize(default_strategy = :html)
7
+ super()
8
+
9
+ self.default_strategy = default_strategy
10
+ end
11
+
12
+ def filters
13
+ [
14
+ TwigFilter.new('escape', runtime(Runtime::Escaper, :escape), {
15
+ is_safe_callback: static(:escape_filter_is_safe),
16
+ }),
17
+ TwigFilter.new('e', runtime(Runtime::Escaper, :escape), {
18
+ is_safe_callback: static(:escape_filter_is_safe),
19
+ }),
20
+ TwigFilter.new('raw', nil, {
21
+ is_safe: [:all], node_class: Node::Expression::Filter::Raw
22
+ }),
23
+ ]
24
+ end
25
+
26
+ def token_parsers
27
+ [
28
+ TokenParser::AutoEscape.new,
29
+ ]
30
+ end
31
+
32
+ def node_visitors
33
+ [
34
+ NodeVisitor::Escaper.new,
35
+ ]
36
+ end
37
+
38
+ # Sets the default strategy to use when not defined by the user.
39
+ #
40
+ # @param [Symbol, String, false, Proc] strategy An escaping strategy
41
+ def default_strategy=(strategy)
42
+ @default_strategy = if [:name, 'name'].include?(strategy)
43
+ ->(name) { FileExtensionEscapingStrategy.guess(name) }
44
+ else
45
+ strategy
46
+ end
47
+ end
48
+
49
+ # Gets the default strategy to use when not defined by the user.
50
+ #
51
+ # @param [String] name The template name
52
+ # @return [Symbol, false] The default strategy to use for the template
53
+ def default_strategy(name)
54
+ if @default_strategy.is_a?(Proc)
55
+ @default_strategy.call(name)
56
+ else
57
+ @default_strategy
58
+ end
59
+ end
60
+
61
+ # @param [Node::Base] filter_args
62
+ def self.escape_filter_is_safe(filter_args)
63
+ filter_args.nodes.each_value do |arg|
64
+ if arg.is_a?(Node::Expression::Constant)
65
+ return [arg.attributes[:value]]
66
+ end
67
+ end
68
+
69
+ [:html]
70
+ end
71
+ end
72
+ end
73
+ end
@@ -4,67 +4,20 @@ module Twig
4
4
  module Extension
5
5
  class Rails < Extension::Base
6
6
  def filters
7
- {}
7
+ [
8
+ TwigFilter.new('striptags', method(:strip_tags)),
9
+ ]
8
10
  end
9
11
 
10
- def helper_methods
11
- %w[
12
- render
13
-
14
- distance_of_time_in_words
15
- time_ago_in_words
16
-
17
- number_to_currency
18
- number_to_human
19
- number_to_human_size
20
- number_to_percentage
21
- number_to_phone
22
- number_with_delimiter
23
- number_with_precision
24
-
25
- excerpt
26
- pluralize
27
- truncate
28
- word_wrap
29
-
30
- button_to
31
- current_page?
32
- link_to
33
- mail_to
34
- url_for
35
-
36
- raw
37
- sanitize
38
- sanitize_css
39
- strip_links
40
- strip_tags
41
-
42
- audio_tag
43
- auto_discovery_link_tag
44
- favicon_link_tag
45
- image_tag
46
- javascript_include_tag
47
- picture_tag
48
- preload_link_tag
49
- stylesheet_link_tag
50
- video_tag
51
-
52
- escape_javascript
53
- javascript_tag
54
-
55
- benchmark
56
- cache
57
- debug
58
-
59
- tag
60
- token_list
61
-
62
- form_for
63
- form_with
64
-
65
- date_test
12
+ def token_parsers
13
+ [
14
+ TokenParser::Cache.new,
66
15
  ]
67
16
  end
17
+
18
+ def strip_tags(string, tags = [])
19
+ ApplicationController.helpers.sanitize(string, tags:)
20
+ end
68
21
  end
69
22
  end
70
23
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Extension
5
+ class StringLoader < Extension::Base
6
+ def functions
7
+ [
8
+ TwigFunction.new('template_from_string', static(:template_from_string), needs_environment: true),
9
+ ]
10
+ end
11
+
12
+ # @param [Environment] environment
13
+ # @param [String] string
14
+ def self.template_from_string(environment, string, name = nil)
15
+ environment.create_template(string, name)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -15,55 +15,152 @@ module Twig
15
15
 
16
16
  # @param [Extension::Base] extension
17
17
  def add(extension)
18
- raise "Extension #{extension.class.name} already added" if has?(extension)
18
+ raise "Extension #{extension.class} already added" if has?(extension)
19
19
 
20
- @extensions[extension.class.name] = extension
20
+ @extensions[key(extension)] = extension
21
21
  end
22
22
 
23
23
  # @param [Object, String] extension
24
24
  # @return [Boolean]
25
25
  def has?(extension)
26
- extension = extension.class.name unless extension.is_a?(String)
27
- extensions.key?(extension.to_s)
26
+ extensions.key?(key(extension))
28
27
  end
29
28
 
30
- def operators
31
- all_unary = {}
32
- all_binary = {}
29
+ # @return [Extension::Base]
30
+ def get(extension)
31
+ extensions[key(extension)]
32
+ end
33
33
 
34
- extensions.values.map(&:operators).each do |unary, binary|
35
- all_unary.merge!(unary)
36
- all_binary.merge!(binary)
34
+ def expression_parsers
35
+ @expression_parsers ||= ExpressionParser::ExpressionParsers.new.tap do |parsers|
36
+ extensions.each_value do |extension|
37
+ parsers.add(extension.expression_parsers)
38
+ end
37
39
  end
40
+ end
38
41
 
39
- [all_unary, all_binary]
42
+ # @return [Hash{String => TwigFilter}]
43
+ def filters
44
+ @filters ||= extensions.
45
+ values.
46
+ map(&:filters).
47
+ flatten.
48
+ to_h { |filter| [filter.name, filter] }
40
49
  end
41
50
 
42
- def helper_methods
43
- @helper_methods ||= extensions.
51
+ # @return [Hash{String => TwigFunction}]
52
+ def functions
53
+ @functions ||= extensions.
44
54
  values.
45
- map(&:helper_methods).
46
- reduce([], :concat).
47
- map(&:to_sym)
55
+ map(&:functions).
56
+ flatten.
57
+ to_h { |function| [function.name, function] }
48
58
  end
49
59
 
50
- def filters
51
- @filters ||= extensions.values.map(&:filters).reduce({}, :merge)
60
+ # @return [Hash{String => TwigTest}]
61
+ def tests
62
+ @tests ||= extensions.
63
+ values.
64
+ map(&:tests).
65
+ flatten.
66
+ to_h { |test| [test.name, test] }
52
67
  end
53
68
 
69
+ # @return [TwigFilter, nil]
54
70
  def filter(name)
55
- filters[name.to_sym]
71
+ return filters[name] if filters.key?(name)
72
+
73
+ dynamic_filters.each do |pattern, filter|
74
+ if (match = Regexp.new(pattern).match(name))
75
+ return filter.with_dynamic_arguments(name, filter.name, match.captures)
76
+ end
77
+ end
78
+
79
+ nil
56
80
  end
57
81
 
82
+ # @return [TwigFunction, nil]
83
+ def function(name)
84
+ return functions[name] if functions.key?(name)
85
+
86
+ dynamic_functions.each do |pattern, function|
87
+ if (match = Regexp.new(pattern).match(name))
88
+ return function.with_dynamic_arguments(name, function.name, match.captures)
89
+ end
90
+ end
91
+
92
+ nil
93
+ end
94
+
95
+ # @return [TwigTest, nil]
96
+ def test(name)
97
+ return tests[name] if tests.key?(name)
98
+
99
+ dynamic_tests.each do |pattern, test|
100
+ if (match = Regexp.new(pattern).match(name))
101
+ return test.with_dynamic_arguments(name, test.name, match.captures)
102
+ end
103
+ end
104
+
105
+ nil
106
+ end
107
+
108
+ # @return [Array<TokenParser::Base>]
58
109
  def token_parsers
59
110
  @token_parsers ||= extensions.
60
111
  values.map(&:token_parsers).reduce([], :concat).
61
112
  to_h { |token_parser| [token_parser.tag.to_sym, token_parser] }
62
113
  end
63
114
 
64
- # @return [TokenParser::Base|nil]
115
+ # @return [TokenParser::Base, nil]
65
116
  def token_parser(name)
66
117
  token_parsers[name.to_sym]
67
118
  end
119
+
120
+ # @return [Array<NodeVisitor::Base>]
121
+ def node_visitors
122
+ @node_visitors ||= extensions.
123
+ values.map(&:node_visitors).reduce([], :concat)
124
+ end
125
+
126
+ def globals
127
+ @globals ||= extensions.
128
+ values.map(&:globals).reduce({}, :merge)
129
+ end
130
+
131
+ private
132
+
133
+ def dynamic_filters
134
+ @dynamic_filters ||= filters.
135
+ select { |name, _| name.include?('*') }.
136
+ transform_keys { |name| dynamic_callable_regex(name) }
137
+ end
138
+
139
+ def dynamic_functions
140
+ @dynamic_functions ||= functions.
141
+ select { |name, _| name.include?('*') }.
142
+ transform_keys { |name| dynamic_callable_regex(name) }
143
+ end
144
+
145
+ def dynamic_tests
146
+ @dynamic_tests ||= tests.
147
+ select { |name, _| name.include?('*') }.
148
+ transform_keys { |name| dynamic_callable_regex(name) }
149
+ end
150
+
151
+ def dynamic_callable_regex(name)
152
+ "^#{Regexp.quote(name).gsub('\\*', '(.*?)')}$"
153
+ end
154
+
155
+ def key(object)
156
+ case object
157
+ when String
158
+ object
159
+ when Class
160
+ object.to_s
161
+ else
162
+ object.class.to_s
163
+ end
164
+ end
68
165
  end
69
166
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ class FileExtensionEscapingStrategy
5
+ # Guesses the best autoescaping strategy based on the file name.
6
+ #
7
+ # @param [String] name The template name
8
+ # @return [Symbol, false] The escaping strategy name to use or false to disable
9
+ def self.guess(name)
10
+ # Return html for directories
11
+ if name.end_with?('/') || name.end_with?('\\')
12
+ return :html
13
+ end
14
+
15
+ # Remove .twig extension if present
16
+ if name.end_with?('.twig')
17
+ name = name[0...-5]
18
+ end
19
+
20
+ # Get file extension
21
+ extension = File.extname(name)[1..]
22
+
23
+ case extension
24
+ when 'js', 'json'
25
+ :js
26
+ when 'css'
27
+ :css
28
+ when 'txt'
29
+ false
30
+ else
31
+ :html
32
+ end
33
+ end
34
+ end
35
+ end