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.
- checksums.yaml +4 -4
- data/README.md +116 -0
- data/lib/tasks/twig_parity.rake +278 -0
- data/lib/twig/auto_hash.rb +7 -1
- data/lib/twig/callable.rb +28 -1
- data/lib/twig/compiler.rb +35 -3
- data/lib/twig/environment.rb +198 -41
- data/lib/twig/error/base.rb +81 -16
- data/lib/twig/error/loader.rb +8 -0
- data/lib/twig/error/logic.rb +8 -0
- data/lib/twig/error/runtime.rb +8 -0
- data/lib/twig/expression_parser/base.rb +30 -0
- data/lib/twig/expression_parser/expression_parsers.rb +57 -0
- data/lib/twig/expression_parser/infix/arrow.rb +31 -0
- data/lib/twig/expression_parser/infix/binary.rb +34 -0
- data/lib/twig/expression_parser/infix/conditional_ternary.rb +39 -0
- data/lib/twig/expression_parser/infix/dot.rb +72 -0
- data/lib/twig/expression_parser/infix/filter.rb +43 -0
- data/lib/twig/expression_parser/infix/function.rb +67 -0
- data/lib/twig/expression_parser/infix/is.rb +53 -0
- data/lib/twig/expression_parser/infix/is_not.rb +19 -0
- data/lib/twig/expression_parser/infix/parses_arguments.rb +84 -0
- data/lib/twig/expression_parser/infix/square_bracket.rb +66 -0
- data/lib/twig/expression_parser/infix_expression_parser.rb +34 -0
- data/lib/twig/expression_parser/prefix/grouping.rb +60 -0
- data/lib/twig/expression_parser/prefix/literal.rb +244 -0
- data/lib/twig/expression_parser/prefix/unary.rb +29 -0
- data/lib/twig/expression_parser/prefix_expression_parser.rb +18 -0
- data/lib/twig/extension/base.rb +26 -4
- data/lib/twig/extension/core.rb +1076 -48
- data/lib/twig/extension/debug.rb +25 -0
- data/lib/twig/extension/escaper.rb +73 -0
- data/lib/twig/extension/rails.rb +10 -57
- data/lib/twig/extension/string_loader.rb +19 -0
- data/lib/twig/extension_set.rb +117 -20
- data/lib/twig/file_extension_escaping_strategy.rb +35 -0
- data/lib/twig/lexer.rb +225 -81
- data/lib/twig/loader/array.rb +25 -8
- data/lib/twig/loader/chain.rb +93 -0
- data/lib/twig/loader/filesystem.rb +106 -7
- data/lib/twig/node/auto_escape.rb +18 -0
- data/lib/twig/node/base.rb +58 -2
- data/lib/twig/node/block.rb +2 -0
- data/lib/twig/node/block_reference.rb +5 -1
- data/lib/twig/node/body.rb +7 -0
- data/lib/twig/node/cache.rb +50 -0
- data/lib/twig/node/capture.rb +22 -0
- data/lib/twig/node/deprecated.rb +53 -0
- data/lib/twig/node/do.rb +19 -0
- data/lib/twig/node/embed.rb +43 -0
- data/lib/twig/node/expression/array.rb +29 -20
- data/lib/twig/node/expression/arrow_function.rb +55 -0
- data/lib/twig/node/expression/assign_name.rb +1 -1
- data/lib/twig/node/expression/binary/and.rb +17 -0
- data/lib/twig/node/expression/binary/base.rb +6 -4
- data/lib/twig/node/expression/binary/boolean.rb +24 -0
- data/lib/twig/node/expression/binary/concat.rb +20 -0
- data/lib/twig/node/expression/binary/elvis.rb +35 -0
- data/lib/twig/node/expression/binary/ends_with.rb +24 -0
- data/lib/twig/node/expression/binary/floor_div.rb +21 -0
- data/lib/twig/node/expression/binary/has_every.rb +20 -0
- data/lib/twig/node/expression/binary/has_some.rb +20 -0
- data/lib/twig/node/expression/binary/in.rb +20 -0
- data/lib/twig/node/expression/binary/matches.rb +24 -0
- data/lib/twig/node/expression/binary/not_in.rb +20 -0
- data/lib/twig/node/expression/binary/null_coalesce.rb +49 -0
- data/lib/twig/node/expression/binary/or.rb +15 -0
- data/lib/twig/node/expression/binary/starts_with.rb +24 -0
- data/lib/twig/node/expression/binary/xor.rb +17 -0
- data/lib/twig/node/expression/block_reference.rb +62 -0
- data/lib/twig/node/expression/call.rb +126 -6
- data/lib/twig/node/expression/constant.rb +3 -1
- data/lib/twig/node/expression/filter/default.rb +37 -0
- data/lib/twig/node/expression/filter/raw.rb +31 -0
- data/lib/twig/node/expression/filter.rb +2 -2
- data/lib/twig/node/expression/function.rb +37 -0
- data/lib/twig/node/expression/get_attribute.rb +51 -7
- data/lib/twig/node/expression/hash.rb +75 -0
- data/lib/twig/node/expression/helper_method.rb +6 -18
- data/lib/twig/node/expression/macro_reference.rb +43 -0
- data/lib/twig/node/expression/name.rb +42 -8
- data/lib/twig/node/expression/operator_escape.rb +13 -0
- data/lib/twig/node/expression/parent.rb +28 -0
- data/lib/twig/node/expression/support_defined_test.rb +23 -0
- data/lib/twig/node/expression/ternary.rb +7 -1
- data/lib/twig/node/expression/test/base.rb +26 -0
- data/lib/twig/node/expression/test/constant.rb +35 -0
- data/lib/twig/node/expression/test/defined.rb +33 -0
- data/lib/twig/node/expression/test/divisible_by.rb +23 -0
- data/lib/twig/node/expression/test/even.rb +21 -0
- data/lib/twig/node/expression/test/iterable.rb +21 -0
- data/lib/twig/node/expression/test/mapping.rb +21 -0
- data/lib/twig/node/expression/test/null.rb +21 -0
- data/lib/twig/node/expression/test/odd.rb +21 -0
- data/lib/twig/node/expression/test/same_as.rb +23 -0
- data/lib/twig/node/expression/test/sequence.rb +21 -0
- data/lib/twig/node/expression/unary/base.rb +3 -1
- data/lib/twig/node/expression/unary/not.rb +18 -0
- data/lib/twig/node/expression/unary/spread.rb +18 -0
- data/lib/twig/node/expression/unary/string_cast.rb +18 -0
- data/lib/twig/node/expression/variable/assign_template.rb +35 -0
- data/lib/twig/node/expression/variable/local.rb +35 -0
- data/lib/twig/node/expression/variable/template.rb +54 -0
- data/lib/twig/node/for.rb +38 -8
- data/lib/twig/node/for_loop.rb +0 -22
- data/lib/twig/node/if.rb +4 -1
- data/lib/twig/node/import.rb +32 -0
- data/lib/twig/node/include.rb +38 -8
- data/lib/twig/node/macro.rb +79 -0
- data/lib/twig/node/module.rb +278 -23
- data/lib/twig/node/output.rb +7 -0
- data/lib/twig/node/print.rb +4 -1
- data/lib/twig/node/set.rb +72 -0
- data/lib/twig/node/text.rb +4 -1
- data/lib/twig/node/with.rb +50 -0
- data/lib/twig/node/yield.rb +6 -1
- data/lib/twig/node_traverser.rb +50 -0
- data/lib/twig/node_visitor/base.rb +30 -0
- data/lib/twig/node_visitor/escaper.rb +165 -0
- data/lib/twig/node_visitor/safe_analysis.rb +127 -0
- data/lib/twig/node_visitor/spreader.rb +39 -0
- data/lib/twig/output_buffer.rb +14 -12
- data/lib/twig/parser.rb +281 -8
- data/lib/twig/rails/config.rb +33 -0
- data/lib/twig/rails/engine.rb +44 -0
- data/lib/twig/rails/renderer.rb +41 -0
- data/lib/twig/runtime/argument_spreader.rb +46 -0
- data/lib/twig/runtime/context.rb +154 -0
- data/lib/twig/runtime/enumerable_hash.rb +51 -0
- data/lib/twig/runtime/escaper.rb +155 -0
- data/lib/twig/runtime/loop_context.rb +81 -0
- data/lib/twig/runtime/loop_iterator.rb +60 -0
- data/lib/twig/runtime/spread.rb +21 -0
- data/lib/twig/runtime_loader/base.rb +12 -0
- data/lib/twig/runtime_loader/factory.rb +23 -0
- data/lib/twig/template.rb +267 -14
- data/lib/twig/template_wrapper.rb +42 -0
- data/lib/twig/token.rb +28 -2
- data/lib/twig/token_parser/apply.rb +48 -0
- data/lib/twig/token_parser/auto_escape.rb +45 -0
- data/lib/twig/token_parser/base.rb +26 -0
- data/lib/twig/token_parser/block.rb +4 -4
- data/lib/twig/token_parser/cache.rb +31 -0
- data/lib/twig/token_parser/deprecated.rb +40 -0
- data/lib/twig/token_parser/do.rb +19 -0
- data/lib/twig/token_parser/embed.rb +62 -0
- data/lib/twig/token_parser/extends.rb +4 -3
- data/lib/twig/token_parser/for.rb +14 -9
- data/lib/twig/token_parser/from.rb +57 -0
- data/lib/twig/token_parser/guard.rb +65 -0
- data/lib/twig/token_parser/if.rb +9 -9
- data/lib/twig/token_parser/import.rb +29 -0
- data/lib/twig/token_parser/include.rb +2 -2
- data/lib/twig/token_parser/macro.rb +109 -0
- data/lib/twig/token_parser/set.rb +76 -0
- data/lib/twig/token_parser/use.rb +54 -0
- data/lib/twig/token_parser/with.rb +36 -0
- data/lib/twig/token_parser/yield.rb +7 -7
- data/lib/twig/token_stream.rb +23 -3
- data/lib/twig/twig_filter.rb +20 -0
- data/lib/twig/twig_function.rb +37 -0
- data/lib/twig/twig_test.rb +31 -0
- data/lib/twig/util/callable_arguments_extractor.rb +227 -0
- data/lib/twig_ruby.rb +21 -2
- metadata +148 -6
- data/lib/twig/context.rb +0 -64
- data/lib/twig/expression_parser.rb +0 -517
- data/lib/twig/railtie.rb +0 -60
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: de30e1d0f458a7c5e2683f725036d8cf37e0fd09238bf14b7921d17af9f08998
|
4
|
+
data.tar.gz: bfdc53936b6dc8449c44269e5bb0b717c01e02597911266404550a805cbb34b8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/lib/twig/auto_hash.rb
CHANGED
@@ -4,7 +4,7 @@ module Twig
|
|
4
4
|
class AutoHash < Hash
|
5
5
|
def add(*values)
|
6
6
|
values.each do |value|
|
7
|
-
self[
|
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 :
|
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
|
-
|
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
|