twig_ruby 0.0.1 → 0.0.3

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 (168) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +116 -0
  3. data/lib/tasks/twig_parity.rake +278 -0
  4. data/lib/twig/auto_hash.rb +7 -1
  5. data/lib/twig/callable.rb +28 -1
  6. data/lib/twig/compiler.rb +35 -3
  7. data/lib/twig/environment.rb +198 -41
  8. data/lib/twig/error/base.rb +81 -16
  9. data/lib/twig/error/loader.rb +8 -0
  10. data/lib/twig/error/logic.rb +8 -0
  11. data/lib/twig/error/runtime.rb +8 -0
  12. data/lib/twig/expression_parser/base.rb +30 -0
  13. data/lib/twig/expression_parser/expression_parsers.rb +57 -0
  14. data/lib/twig/expression_parser/infix/arrow.rb +31 -0
  15. data/lib/twig/expression_parser/infix/binary.rb +34 -0
  16. data/lib/twig/expression_parser/infix/conditional_ternary.rb +39 -0
  17. data/lib/twig/expression_parser/infix/dot.rb +72 -0
  18. data/lib/twig/expression_parser/infix/filter.rb +43 -0
  19. data/lib/twig/expression_parser/infix/function.rb +67 -0
  20. data/lib/twig/expression_parser/infix/is.rb +53 -0
  21. data/lib/twig/expression_parser/infix/is_not.rb +19 -0
  22. data/lib/twig/expression_parser/infix/parses_arguments.rb +84 -0
  23. data/lib/twig/expression_parser/infix/square_bracket.rb +66 -0
  24. data/lib/twig/expression_parser/infix_expression_parser.rb +34 -0
  25. data/lib/twig/expression_parser/prefix/grouping.rb +60 -0
  26. data/lib/twig/expression_parser/prefix/literal.rb +244 -0
  27. data/lib/twig/expression_parser/prefix/unary.rb +29 -0
  28. data/lib/twig/expression_parser/prefix_expression_parser.rb +18 -0
  29. data/lib/twig/extension/base.rb +26 -4
  30. data/lib/twig/extension/core.rb +1076 -48
  31. data/lib/twig/extension/debug.rb +25 -0
  32. data/lib/twig/extension/escaper.rb +73 -0
  33. data/lib/twig/extension/rails.rb +10 -57
  34. data/lib/twig/extension/string_loader.rb +19 -0
  35. data/lib/twig/extension_set.rb +117 -20
  36. data/lib/twig/file_extension_escaping_strategy.rb +35 -0
  37. data/lib/twig/lexer.rb +225 -81
  38. data/lib/twig/loader/array.rb +25 -8
  39. data/lib/twig/loader/chain.rb +93 -0
  40. data/lib/twig/loader/filesystem.rb +106 -7
  41. data/lib/twig/node/auto_escape.rb +18 -0
  42. data/lib/twig/node/base.rb +58 -2
  43. data/lib/twig/node/block.rb +2 -0
  44. data/lib/twig/node/block_reference.rb +5 -1
  45. data/lib/twig/node/body.rb +7 -0
  46. data/lib/twig/node/cache.rb +50 -0
  47. data/lib/twig/node/capture.rb +22 -0
  48. data/lib/twig/node/deprecated.rb +53 -0
  49. data/lib/twig/node/do.rb +19 -0
  50. data/lib/twig/node/embed.rb +43 -0
  51. data/lib/twig/node/expression/array.rb +29 -20
  52. data/lib/twig/node/expression/arrow_function.rb +55 -0
  53. data/lib/twig/node/expression/assign_name.rb +1 -1
  54. data/lib/twig/node/expression/binary/and.rb +17 -0
  55. data/lib/twig/node/expression/binary/base.rb +6 -4
  56. data/lib/twig/node/expression/binary/boolean.rb +24 -0
  57. data/lib/twig/node/expression/binary/concat.rb +20 -0
  58. data/lib/twig/node/expression/binary/elvis.rb +35 -0
  59. data/lib/twig/node/expression/binary/ends_with.rb +24 -0
  60. data/lib/twig/node/expression/binary/floor_div.rb +21 -0
  61. data/lib/twig/node/expression/binary/has_every.rb +20 -0
  62. data/lib/twig/node/expression/binary/has_some.rb +20 -0
  63. data/lib/twig/node/expression/binary/in.rb +20 -0
  64. data/lib/twig/node/expression/binary/matches.rb +24 -0
  65. data/lib/twig/node/expression/binary/not_in.rb +20 -0
  66. data/lib/twig/node/expression/binary/null_coalesce.rb +49 -0
  67. data/lib/twig/node/expression/binary/or.rb +15 -0
  68. data/lib/twig/node/expression/binary/starts_with.rb +24 -0
  69. data/lib/twig/node/expression/binary/xor.rb +17 -0
  70. data/lib/twig/node/expression/block_reference.rb +62 -0
  71. data/lib/twig/node/expression/call.rb +126 -6
  72. data/lib/twig/node/expression/constant.rb +3 -1
  73. data/lib/twig/node/expression/filter/default.rb +37 -0
  74. data/lib/twig/node/expression/filter/raw.rb +31 -0
  75. data/lib/twig/node/expression/filter.rb +2 -2
  76. data/lib/twig/node/expression/function.rb +37 -0
  77. data/lib/twig/node/expression/get_attribute.rb +51 -7
  78. data/lib/twig/node/expression/hash.rb +75 -0
  79. data/lib/twig/node/expression/helper_method.rb +6 -18
  80. data/lib/twig/node/expression/macro_reference.rb +43 -0
  81. data/lib/twig/node/expression/name.rb +42 -8
  82. data/lib/twig/node/expression/operator_escape.rb +13 -0
  83. data/lib/twig/node/expression/parent.rb +28 -0
  84. data/lib/twig/node/expression/support_defined_test.rb +23 -0
  85. data/lib/twig/node/expression/ternary.rb +7 -1
  86. data/lib/twig/node/expression/test/base.rb +26 -0
  87. data/lib/twig/node/expression/test/constant.rb +35 -0
  88. data/lib/twig/node/expression/test/defined.rb +33 -0
  89. data/lib/twig/node/expression/test/divisible_by.rb +23 -0
  90. data/lib/twig/node/expression/test/even.rb +21 -0
  91. data/lib/twig/node/expression/test/iterable.rb +21 -0
  92. data/lib/twig/node/expression/test/mapping.rb +21 -0
  93. data/lib/twig/node/expression/test/null.rb +21 -0
  94. data/lib/twig/node/expression/test/odd.rb +21 -0
  95. data/lib/twig/node/expression/test/same_as.rb +23 -0
  96. data/lib/twig/node/expression/test/sequence.rb +21 -0
  97. data/lib/twig/node/expression/unary/base.rb +3 -1
  98. data/lib/twig/node/expression/unary/not.rb +18 -0
  99. data/lib/twig/node/expression/unary/spread.rb +18 -0
  100. data/lib/twig/node/expression/unary/string_cast.rb +18 -0
  101. data/lib/twig/node/expression/variable/assign_template.rb +35 -0
  102. data/lib/twig/node/expression/variable/local.rb +35 -0
  103. data/lib/twig/node/expression/variable/template.rb +54 -0
  104. data/lib/twig/node/for.rb +38 -8
  105. data/lib/twig/node/for_loop.rb +0 -22
  106. data/lib/twig/node/if.rb +4 -1
  107. data/lib/twig/node/import.rb +32 -0
  108. data/lib/twig/node/include.rb +38 -8
  109. data/lib/twig/node/macro.rb +79 -0
  110. data/lib/twig/node/module.rb +278 -23
  111. data/lib/twig/node/output.rb +7 -0
  112. data/lib/twig/node/print.rb +4 -1
  113. data/lib/twig/node/set.rb +72 -0
  114. data/lib/twig/node/text.rb +4 -1
  115. data/lib/twig/node/with.rb +50 -0
  116. data/lib/twig/node/yield.rb +6 -1
  117. data/lib/twig/node_traverser.rb +50 -0
  118. data/lib/twig/node_visitor/base.rb +30 -0
  119. data/lib/twig/node_visitor/escaper.rb +165 -0
  120. data/lib/twig/node_visitor/safe_analysis.rb +127 -0
  121. data/lib/twig/node_visitor/spreader.rb +39 -0
  122. data/lib/twig/output_buffer.rb +14 -12
  123. data/lib/twig/parser.rb +281 -8
  124. data/lib/twig/rails/config.rb +33 -0
  125. data/lib/twig/rails/engine.rb +44 -0
  126. data/lib/twig/rails/renderer.rb +41 -0
  127. data/lib/twig/runtime/argument_spreader.rb +46 -0
  128. data/lib/twig/runtime/context.rb +154 -0
  129. data/lib/twig/runtime/enumerable_hash.rb +51 -0
  130. data/lib/twig/runtime/escaper.rb +155 -0
  131. data/lib/twig/runtime/loop_context.rb +81 -0
  132. data/lib/twig/runtime/loop_iterator.rb +60 -0
  133. data/lib/twig/runtime/spread.rb +21 -0
  134. data/lib/twig/runtime_loader/base.rb +12 -0
  135. data/lib/twig/runtime_loader/factory.rb +23 -0
  136. data/lib/twig/template.rb +267 -14
  137. data/lib/twig/template_wrapper.rb +42 -0
  138. data/lib/twig/token.rb +28 -2
  139. data/lib/twig/token_parser/apply.rb +48 -0
  140. data/lib/twig/token_parser/auto_escape.rb +45 -0
  141. data/lib/twig/token_parser/base.rb +26 -0
  142. data/lib/twig/token_parser/block.rb +4 -4
  143. data/lib/twig/token_parser/cache.rb +31 -0
  144. data/lib/twig/token_parser/deprecated.rb +40 -0
  145. data/lib/twig/token_parser/do.rb +19 -0
  146. data/lib/twig/token_parser/embed.rb +62 -0
  147. data/lib/twig/token_parser/extends.rb +4 -3
  148. data/lib/twig/token_parser/for.rb +14 -9
  149. data/lib/twig/token_parser/from.rb +57 -0
  150. data/lib/twig/token_parser/guard.rb +65 -0
  151. data/lib/twig/token_parser/if.rb +9 -9
  152. data/lib/twig/token_parser/import.rb +29 -0
  153. data/lib/twig/token_parser/include.rb +2 -2
  154. data/lib/twig/token_parser/macro.rb +109 -0
  155. data/lib/twig/token_parser/set.rb +76 -0
  156. data/lib/twig/token_parser/use.rb +54 -0
  157. data/lib/twig/token_parser/with.rb +36 -0
  158. data/lib/twig/token_parser/yield.rb +7 -7
  159. data/lib/twig/token_stream.rb +23 -3
  160. data/lib/twig/twig_filter.rb +20 -0
  161. data/lib/twig/twig_function.rb +37 -0
  162. data/lib/twig/twig_test.rb +31 -0
  163. data/lib/twig/util/callable_arguments_extractor.rb +227 -0
  164. data/lib/twig_ruby.rb +21 -2
  165. metadata +148 -6
  166. data/lib/twig/context.rb +0 -64
  167. data/lib/twig/expression_parser.rb +0 -517
  168. data/lib/twig/railtie.rb +0 -60
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 674b4b6d23d8a81c8dfe49d8b4c55766df7a2b110038f7dd04bb5dbed09095eb
4
- data.tar.gz: 41320e049677ff964e222b3e65eb2aefe5d8513387834bed644d17abefd6caac
3
+ metadata.gz: de30e1d0f458a7c5e2683f725036d8cf37e0fd09238bf14b7921d17af9f08998
4
+ data.tar.gz: bfdc53936b6dc8449c44269e5bb0b717c01e02597911266404550a805cbb34b8
5
5
  SHA512:
6
- metadata.gz: a9d8b98237d0c9e1955efd5bf1abf41877159bf590960c223dde2ca6a5a9c09ba6c1ce15d64c7d9beb0a29aa7eef3aa81d1166e45f572308faa249f67d106b73
7
- data.tar.gz: c30fdc12854565c6c733eecf77ae9fce7e3134d67ca1630642ee21b1c5473a6e4fd2296ac82cf95abbe880a9de946a814562ce4913a73bbe5754bbccb571db89
6
+ metadata.gz: 6554b109c0e00241e6a5dac0dfd4f8c36e78b50dcc45edff041ff9907cf02609d2abd4b5cdc326ca1ee92b12217ed83a8ba420c60afc65f790bb34567fbc9af9
7
+ data.tar.gz: 70bf08c22925adf25f6b469bc7afcad62bdd3b4ed45761dbca3cd4619b1a213dcf2a3d70c1595ae7958354b2527fde438b78763c72238281971dc35c2aeaba71
data/README.md ADDED
@@ -0,0 +1,116 @@
1
+ # twig-ruby
2
+
3
+ Implementation of [Twig](https://twig.symfony.com/) in Ruby.
4
+
5
+ ```bash
6
+ bundle add twig-ruby
7
+ ```
8
+
9
+ ## Rails
10
+
11
+ This gem includes a Railtie that will automatically add your views folder and
12
+ register a `:twig` template handler. Just simply create your views such as
13
+ `app/views/welcome/index.html.twig` and it will start rendering them.
14
+
15
+ ```twig
16
+ {# welcome/index.html.twig #}
17
+
18
+ {% extends 'base.html.twig' %}
19
+
20
+ {% block body %}
21
+ Welcome to my site!
22
+ {% endblock %}
23
+ ```
24
+
25
+ You should add `layout false` in your `ApplicationController`
26
+
27
+ ```ruby
28
+ class ApplicationController < ActionController::Base
29
+ layout false
30
+ end
31
+ ```
32
+
33
+ ## Rails Configuration
34
+
35
+ These are all the defaults. You only need this configuration if you plan to change anything.
36
+
37
+ ```ruby
38
+ Rails.application.configure do
39
+ config.twig.root = ::Rails.root, # Used for default Filesystem Loader
40
+ config.twig.paths = %w[/ app/views/], # Used for default Filesystem Loader
41
+ config.twig.debug = ::Rails.env.development?,
42
+ config.twig.allow_helper_methods = true,
43
+ config.twig.cache = ::Rails.root.join('tmp/cache/twig').to_s,
44
+ config.twig.charset = 'UTF-8',
45
+ config.twig.strict_variables = true,
46
+ config.twig.auto_reload = nil,
47
+ config.twig.loader = lambda do
48
+ ::Twig::Loader::Filesystem.new(
49
+ current.root,
50
+ current.paths
51
+ )
52
+ end
53
+ end
54
+ ```
55
+
56
+ The loader is memoized as late as possible, so if you need to actually access the loader instance, you can
57
+ use `Twig.loader` to create the instance. If you do this, you can no longer set a new loader with the config
58
+ or paths. You would need to use any available methods on the loader to alter it:
59
+
60
+ ```ruby
61
+ config.after_initialize do
62
+ Twig.loader.prepend_path('app/views/theme', 'theme')
63
+ end
64
+ ```
65
+
66
+ If you plan to create your own loader that loads templates from another source like the database, you can provide
67
+ a different lamba in the config for initializing it.
68
+
69
+ ## Additions
70
+
71
+ Twig Ruby supports symbols as Ruby does and can be used in places strings can as
72
+ hash keys, arguments, etc.
73
+
74
+ ```twig
75
+ {{ name[:first] }}
76
+ {{ user_func(:first, :second) }}
77
+ {% set hash = { key: :value } %}
78
+ ```
79
+
80
+ Since Ruby has the concept of blocks, a new tag is introduced call `yield` it
81
+ can be used with helpers like `form_with`
82
+
83
+ ```twig
84
+ {% yield form_with(url: 'login') do |f| %}
85
+ {{ f.email_field(:email) }}
86
+ {% endyield %}
87
+ ```
88
+ ### Cache Tag
89
+
90
+ The way the `cache` tag works in Rails is that it captures output from the buffer that
91
+ sends the contents of the response. Twig cannot do this prematurely since a cache might be used within
92
+ a block or other callable meant to return the string. There is a cache tag to handle this instead
93
+ that is passed the same arguments it normally would, but has extra code to capture the cache.
94
+ Using `{% yield cache() do %}` WILL NOT WORK CORRECTLY.
95
+
96
+ ```twig
97
+ {% cache(product) %}
98
+ ...
99
+ {% endyield %}
100
+ ```
101
+
102
+ Macros can also use Ruby notation for default values:
103
+
104
+ Typical Twig:
105
+ ```twig
106
+ {% macro input(name, value, type = "text", size = 20) %}
107
+ <input type="{{ type }}" name="{{ name }}" value="{{ value|e }}" size="{{ size }}"/>
108
+ {% endmacro %}
109
+ ```
110
+
111
+ Twig Ruby (Both versions work)
112
+ ```twig
113
+ {% macro input(name, value, type: "text", size: 20) %}
114
+ <input type="{{ type }}" name="{{ name }}" value="{{ value|e }}" size="{{ size }}"/>
115
+ {% endmacro %}
116
+ ```
@@ -0,0 +1,278 @@
1
+ # frozen_string_literal: true
2
+
3
+ desc 'Tests against Twig PHP fixtures.'
4
+
5
+ GIT_LOCATION = "#{__dir__}/../../tmp/twig-php".freeze
6
+ WONT_IMPLEMENT = %w[
7
+ tags/macro/super_globals.test
8
+ tags/for/non_countable.test
9
+ tags/for/generator.test
10
+ tags/for/objects.test
11
+ tags/for/iterator_aggregate.test
12
+ tags/for/objects_countable.test
13
+ expressions/matches_error_compilation.test
14
+ expressions/matches_error_runtime.test
15
+ functions/enum/invalid_dynamic_enum.test
16
+ functions/enum/invalid_enum.test
17
+ functions/enum/invalid_literal_type.test
18
+ functions/enum/valid.test
19
+ functions/enum_cases/invalid_dynamic_enum.test
20
+ functions/enum_cases/invalid_enum.test
21
+ functions/enum_cases/invalid_literal_type.test
22
+ functions/enum_cases/valid.test
23
+ functions/attribute.legacy.test
24
+ functions/attribute_with_wrong_args.legacy.test
25
+ functions/constant.test
26
+ functions/dump.test
27
+ functions/dump_array.test
28
+ tests/defined_for_attribute.legacy.test
29
+ filters/date_default_format_interval.test
30
+ filters/date_interval.test
31
+ filters/date_immutable.test
32
+ filters/date_modify.test
33
+ operators/not_precedence.test
34
+ operators/concat_vs_add_sub.test
35
+ functions/include/sandbox_disabling.test
36
+ functions/include/sandbox.test
37
+ ].freeze
38
+
39
+ class Color
40
+ def self.colorize(text, color_code)
41
+ "\e[#{color_code}m#{text}\e[0m"
42
+ end
43
+
44
+ def self.red(text)
45
+ colorize(text, 31)
46
+ end
47
+
48
+ def self.green(text)
49
+ colorize(text, 32)
50
+ end
51
+ end
52
+
53
+ task :twig_parity, [:file] do |_t, args|
54
+ require 'rspec/support'
55
+ require_relative '../twig_ruby'
56
+ require_relative '../../test/parity'
57
+
58
+ `git clone -b 4.x https://github.com/twigphp/Twig.git #{GIT_LOCATION}`
59
+
60
+ stats = { pass: 0, fail: 0, total: 0 }
61
+
62
+ fixtures = if args[:file]
63
+ [File.join(GIT_LOCATION, 'tests/Fixtures/', args[:file])]
64
+ else
65
+ Dir.glob("#{GIT_LOCATION}/tests/Fixtures/**/*.test")
66
+ end
67
+
68
+ fixtures.each do |fixture|
69
+ base_name = fixture.delete_prefix("#{GIT_LOCATION}/tests/Fixtures/")
70
+
71
+ next if WONT_IMPLEMENT.include?(base_name)
72
+
73
+ TwigFixture.new(fixture, base_name).call.each do |data|
74
+ stats[:total] += 1
75
+
76
+ if data[:status]
77
+ stats[:pass] += 1
78
+ else
79
+ stats[:fail] += 1
80
+ puts '============================='
81
+ puts "FAIL: #{data[:file].delete_prefix("#{GIT_LOCATION}/tests/Fixtures/")}"
82
+ puts data[:error]
83
+ puts "Link: #{data[:file]}:#{data[:lineno]}"
84
+ puts "Rerun: rake twig_parity[#{base_name}]"
85
+ puts "=============================\n\n"
86
+ end
87
+ end
88
+ end
89
+
90
+ puts <<~STATS
91
+
92
+ Stats:
93
+ #{Color.green("#{stats[:pass]} passed")}
94
+ #{Color.red("#{stats[:fail]} failed")}
95
+ correct: #{(stats[:pass] * 100 / stats[:total]).round(2)}%
96
+ STATS
97
+
98
+ if stats[:fail].positive?
99
+ exit 1
100
+ else
101
+ exit 0
102
+ end
103
+ end
104
+
105
+ class TwigFixture
106
+ EXCEPTION_REGEX = /
107
+ --TEST--\s*(.*?)\s*
108
+ (?:--CONDITION--\s*(.*))?\s*
109
+ (?:--DEPRECATION--\s*(.*?))?\s*
110
+ ((?:--TEMPLATE(?:\(.*?\))?--(?:.*?))+)\s*
111
+ (?:--DATA--\s*(.*))?\s*
112
+ --EXCEPTION--\s*(.*)
113
+ /mx
114
+
115
+ EXPECT_REGEX = /
116
+ --TEST--\s*(.*?)\s*
117
+ (?:--CONDITION--\s*(.*))?\s*
118
+ (?:--DEPRECATION--\s*(.*?))?\s*
119
+ ((?:--TEMPLATE(?:\(.*?\))?--(?:.*?))+)
120
+ --DATA--.*?
121
+ --EXPECT--.*
122
+ /mx
123
+
124
+ OUTPUTS_REGEX = /
125
+ --DATA--(.*?)(?:--CONFIG--(.*?))?--EXPECT--(.*?)(?=--DATA--|\z)
126
+ /mx
127
+
128
+ def initialize(file, base_name)
129
+ @file = file
130
+ @base_name = base_name
131
+ end
132
+
133
+ def call
134
+ parse
135
+
136
+ require_relative "#{__dir__}/../../test/fixtures/#{@base_name}.rb"
137
+ examples = Data.examples
138
+
139
+ examples.each.with_index.map do |example, i|
140
+ build_and_run(example[:data], example[:config], example[:gsub] || {}, outputs[i][2], i)
141
+ end
142
+ end
143
+
144
+ private
145
+
146
+ attr_accessor :message, :condition, :deprecation, :templates, :exception, :outputs
147
+
148
+ def build_and_run(data, config, replacements, expected, index)
149
+ gsub_templates = templates.transform_values do |template|
150
+ replace(template, replacements[:fixture]) + (' ' * index)
151
+ end
152
+
153
+ loader = ::Twig::Loader::Array.new(gsub_templates)
154
+ environment = ::Twig::Environment.new(loader, {
155
+ cache: false,
156
+ strict_variables: true,
157
+ auto_reload: true,
158
+ **config,
159
+ })
160
+ environment.add_global('global', 'global')
161
+ environment.add_extension(::TwigTestExtension.new)
162
+ environment.add_extension(::Twig::Extension::Debug.new)
163
+ environment.add_extension(::Twig::Extension::StringLoader.new)
164
+ environment.add_runtime_loader(::Twig::Parity::Runtime::Loader.new(environment))
165
+ expected ||= ''
166
+
167
+ # Reset timezone
168
+ ::Time.zone = 'UTC'
169
+
170
+ # Pass current environment if test is lazy
171
+ data = data.call(environment) if data.is_a?(Proc)
172
+
173
+ begin
174
+ output = environment.load('index.twig').render(data).gsub(/\A[\n ]*/, '').gsub(/[\n ]*\z/, '')
175
+ output = replace(output, replacements[:output])
176
+ expected = expected.gsub(/\A[\n ]*/, '').gsub(/[\n ]*\z/, '')
177
+ expected = replace(expected, replacements[:result])
178
+
179
+ if output == expected
180
+ {
181
+ message:,
182
+ file: @file,
183
+ status: true,
184
+ output:,
185
+ }
186
+ else
187
+ {
188
+ message:,
189
+ file: @file,
190
+ status: false,
191
+ error: ::RSpec::Support::Differ.new.diff_as_string(output, expected),
192
+ }
193
+ end
194
+ rescue ::Twig::Error::Base => e
195
+ if exception
196
+ message_only = exception.match(/Twig\\Error\\\w+: (.*)/)&.captures&.[](0)
197
+ message_only = replace(message_only, replacements[:exception]) if message_only
198
+ exception_matches = message_only == e.message
199
+ error = ::RSpec::Support::Differ.new.diff_as_string(e.message, message_only)
200
+ # error = "#{Color.red("- #{message_only}")}\n#{Color.green("+ #{e.message}")}"
201
+ else
202
+ error = Color.red(e.message)
203
+ end
204
+
205
+ {
206
+ message:,
207
+ file: @file,
208
+ status: exception_matches,
209
+ lineno: e.lineno,
210
+ error:,
211
+ }
212
+ rescue Exception => e # rubocop:disable Lint/RescueException
213
+ {
214
+ message:,
215
+ file: @file,
216
+ status: exception_matches,
217
+ lineno: -1,
218
+ error: "Full Error: #{e.message}",
219
+ }
220
+ end
221
+ end
222
+
223
+ def replace(string, replacements)
224
+ return string if replacements.nil?
225
+
226
+ replacements.each do |find, replace|
227
+ string = string.gsub(find, replace)
228
+ end
229
+
230
+ string
231
+ end
232
+
233
+ def contents
234
+ @contents ||= File.read(@file)
235
+ end
236
+
237
+ def parse
238
+ if (matches = contents.match(EXCEPTION_REGEX))
239
+ self.message = matches.captures[0]
240
+ self.condition = matches.captures[1]
241
+ self.deprecation = matches.captures[2]
242
+ self.templates = parse_templates(matches.captures[3])
243
+ self.exception = matches.captures[5]
244
+ self.outputs = [[nil, matches.captures[4], nil, '']]
245
+ elsif (matches = contents.match(EXPECT_REGEX))
246
+ self.message = matches.captures[0]
247
+ self.condition = matches.captures[1]
248
+ self.deprecation = matches.captures[2]
249
+ self.templates = parse_templates(matches.captures[3])
250
+ self.exception = false
251
+ self.outputs = contents.scan(OUTPUTS_REGEX)
252
+ end
253
+ end
254
+
255
+ def parse_templates(test)
256
+ templates = {}
257
+ test.scan(/--TEMPLATE(?:\((.*?)\))?--(.*?)(?=--TEMPLATE|\z)/mx).map do |name, contents|
258
+ templates[name || 'index.twig'] = contents.
259
+ gsub(/[\n]*\z/, '').
260
+ gsub('d/m/Y H:i:s P', '%d/%m/%Y %H:%M:%S %:z'). # Change dates to Ruby format
261
+ gsub('Twig\Tests\TwigTestFoo', 'TwigTestFoo') # Change class name to match Ruby
262
+ end
263
+
264
+ templates
265
+ end
266
+
267
+ def parse_return_value(object)
268
+ if object.is_a?(Array) && object.length == 1
269
+ return parse_return_value(object.first)
270
+ end
271
+
272
+ if object.is_a?(Hash)
273
+ return object.transform_values { |v| parse_return_value(v) }
274
+ end
275
+
276
+ object
277
+ end
278
+ end
@@ -4,7 +4,7 @@ module Twig
4
4
  class AutoHash < Hash
5
5
  def add(*values)
6
6
  values.each do |value|
7
- self[length] = value
7
+ self[next_key] = value
8
8
  end
9
9
 
10
10
  self
@@ -13,5 +13,11 @@ module Twig
13
13
  def <<(*values)
14
14
  add(*values)
15
15
  end
16
+
17
+ private
18
+
19
+ def next_key
20
+ (keys.filter { |key| key.is_a?(Integer) }.max || -1) + 1
21
+ end
16
22
  end
17
23
  end
data/lib/twig/callable.rb CHANGED
@@ -2,13 +2,15 @@
2
2
 
3
3
  module Twig
4
4
  class Callable
5
- attr_reader :name, :dynamic_name, :callable
5
+ attr_reader :callable
6
+ attr_accessor :name, :dynamic_name, :arguments
6
7
 
7
8
  # @param [String] name
8
9
  # @param [Proc|Nil] callable
9
10
  # @param [Hash] options
10
11
  def initialize(name, callable = nil, options = {})
11
12
  @name = @dynamic_name = name
13
+ @arguments = []
12
14
  @callable = callable
13
15
  @options = {
14
16
  needs_environment: false,
@@ -17,5 +19,30 @@ module Twig
17
19
  is_variadic: false,
18
20
  }.merge(options)
19
21
  end
22
+
23
+ def type
24
+ raise NotImplementedError
25
+ end
26
+
27
+ def needs_charset?
28
+ @options[:needs_charset]
29
+ end
30
+
31
+ def needs_environment?
32
+ @options[:needs_environment]
33
+ end
34
+
35
+ def needs_context?
36
+ @options[:needs_context]
37
+ end
38
+
39
+ def with_dynamic_arguments(name, dynamic_name, arguments)
40
+ new = clone
41
+ new.name = name
42
+ new.dynamic_name = dynamic_name
43
+ new.arguments = arguments
44
+
45
+ new
46
+ end
20
47
  end
21
48
  end
data/lib/twig/compiler.rb CHANGED
@@ -6,6 +6,9 @@ module Twig
6
6
  class Compiler
7
7
  attr_reader :source, :environment
8
8
 
9
+ # @return [Hash<Integer, Integer>]
10
+ attr_reader :debug_info
11
+
9
12
  # @param [Environment] environment
10
13
  def initialize(environment)
11
14
  @environment = environment
@@ -47,7 +50,19 @@ module Twig
47
50
  # @param [String] value
48
51
  # @return [Compiler]
49
52
  def string(value)
50
- @source << "%q[#{value}]"
53
+ @source << "%q[#{value.to_s.gsub(/[\[\]\\]/, '[' => '\[', ']' => '\]', '\\' => '\\\\')}]"
54
+
55
+ self
56
+ end
57
+
58
+ # @param [String, Symbol] value
59
+ # @return [Compiler]
60
+ def symbol(value)
61
+ @source << if value.is_a?(Symbol)
62
+ value.inspect
63
+ else
64
+ "%q[#{value}].to_sym"
65
+ end
51
66
 
52
67
  self
53
68
  end
@@ -73,12 +88,28 @@ module Twig
73
88
  raw(Marshal.dump(value).inspect).
74
89
  raw(')')
75
90
  when Symbol
76
- raw(":#{value}")
91
+ symbol(value)
77
92
  else
78
93
  string(value)
79
94
  end
80
95
  end
81
96
 
97
+ # @param [Node::Base] node
98
+ # @return [Compiler]
99
+ def add_debug_info(node)
100
+ if node.lineno != @last_line
101
+ write("# line #{node.lineno}\n")
102
+
103
+ @source_line += @source[@source_offset..].count("\n")
104
+ @source_offset = @source.length
105
+ @debug_info[@source_line] = node.lineno
106
+
107
+ @last_line = node.lineno
108
+ end
109
+
110
+ self
111
+ end
112
+
82
113
  # @param [Integer] step
83
114
  # @return [Compiler]
84
115
  def indent(step = 1)
@@ -97,6 +128,7 @@ module Twig
97
128
 
98
129
  # @return [String]
99
130
  def var_name
131
+ @var_name_salt += 1
100
132
  "_v#{@var_name_salt}"
101
133
  end
102
134
 
@@ -110,7 +142,7 @@ module Twig
110
142
  def reset(indentation = 0)
111
143
  @last_line = nil
112
144
  @source = +''
113
- @debug_info = []
145
+ @debug_info = {}
114
146
  @source_offset = 0
115
147
  # source code starts at 1 (as we then increment it when we encounter new lines)
116
148
  @source_line = 1