template-ruby 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -1
- data/Gemfile +3 -0
- data/Gemfile.lock +8 -8
- data/code-ruby.gemspec +0 -3
- data/lib/code/error.rb +3 -0
- data/lib/code/node/call.rb +1 -0
- data/lib/code/node/chained_call.rb +4 -2
- data/lib/code/node/list.rb +2 -3
- data/lib/code/node/name.rb +7 -34
- data/lib/code/node/statement.rb +1 -1
- data/lib/code/node/string.rb +9 -4
- data/lib/code/node/string_component.rb +6 -6
- data/lib/code/node.rb +2 -2
- data/lib/code/object/argument.rb +1 -1
- data/lib/code/object/decimal.rb +99 -27
- data/lib/code/object/dictionnary.rb +29 -13
- data/lib/code/object/function.rb +11 -9
- data/lib/code/object/global.rb +37 -0
- data/lib/code/object/integer.rb +99 -35
- data/lib/code/object/list.rb +62 -89
- data/lib/code/object/range.rb +33 -46
- data/lib/code/object/ruby_function.rb +25 -0
- data/lib/code/object/string.rb +25 -15
- data/lib/code/object.rb +51 -49
- data/lib/code/parser/function.rb +3 -1
- data/lib/code/parser/if.rb +1 -1
- data/lib/code/parser/string.rb +8 -8
- data/lib/code/ruby.rb +161 -0
- data/lib/code-ruby.rb +9 -2
- data/lib/code.rb +20 -6
- data/lib/template/version.rb +1 -1
- data/lib/template-ruby.rb +8 -2
- data/lib/template.rb +24 -9
- data/spec/code/parser/string_spec.rb +1 -1
- data/spec/code_spec.rb +63 -1
- data/spec/template_spec.rb +25 -6
- data/template-ruby.gemspec +0 -3
- metadata +5 -30
data/lib/code/object.rb
CHANGED
@@ -5,38 +5,35 @@ class Code
|
|
5
5
|
def call(**args)
|
6
6
|
operator = args.fetch(:operator, nil)
|
7
7
|
arguments = args.fetch(:arguments, [])
|
8
|
-
|
9
|
-
|
8
|
+
|
9
|
+
if operator == "=="
|
10
|
+
sig(arguments, ::Code::Object)
|
11
|
+
equal(arguments.first.value)
|
12
|
+
elsif operator == "==="
|
13
|
+
sig(arguments, ::Code::Object)
|
14
|
+
strict_equal(arguments.first.value)
|
15
|
+
elsif operator == "!="
|
16
|
+
sig(arguments, ::Code::Object)
|
17
|
+
different(arguments.first.value)
|
10
18
|
elsif operator == "<=>"
|
11
|
-
|
19
|
+
sig(arguments, ::Code::Object)
|
20
|
+
compare(arguments.first.value)
|
12
21
|
elsif operator == "&&"
|
13
|
-
|
22
|
+
sig(arguments, ::Code::Object)
|
23
|
+
and_operator(arguments.first.value)
|
14
24
|
elsif operator == "||"
|
15
|
-
|
25
|
+
sig(arguments, ::Code::Object)
|
26
|
+
or_operator(arguments.first.value)
|
16
27
|
elsif operator == "to_string"
|
17
|
-
|
28
|
+
sig(arguments)
|
29
|
+
to_string
|
18
30
|
else
|
19
|
-
raise
|
20
|
-
|
21
|
-
|
31
|
+
raise(
|
32
|
+
Code::Error::Undefined.new("#{operator} not defined on #{inspect}"),
|
33
|
+
)
|
22
34
|
end
|
23
35
|
end
|
24
36
|
|
25
|
-
def []=(key, value)
|
26
|
-
@attributes ||= {}
|
27
|
-
@attributes[key] = value
|
28
|
-
end
|
29
|
-
|
30
|
-
def [](key)
|
31
|
-
@attributes ||= {}
|
32
|
-
@attributes[key]
|
33
|
-
end
|
34
|
-
|
35
|
-
def key?(key)
|
36
|
-
@attributes ||= {}
|
37
|
-
@attributes.key?(key)
|
38
|
-
end
|
39
|
-
|
40
37
|
def truthy?
|
41
38
|
true
|
42
39
|
end
|
@@ -78,10 +75,12 @@ class Code
|
|
78
75
|
|
79
76
|
def sig(actual_arguments, *expected_arguments)
|
80
77
|
if actual_arguments.size != expected_arguments.size
|
81
|
-
raise
|
82
|
-
|
83
|
-
|
84
|
-
|
78
|
+
raise(
|
79
|
+
::Code::Error::ArgumentError.new(
|
80
|
+
"Expected #{expected_arguments.size} arguments, " \
|
81
|
+
"got #{actual_arguments.size} arguments",
|
82
|
+
),
|
83
|
+
)
|
85
84
|
end
|
86
85
|
|
87
86
|
expected_arguments.each.with_index do |expected_argument, index|
|
@@ -91,46 +90,49 @@ class Code
|
|
91
90
|
if expected_argument.none? { |expected_arg|
|
92
91
|
actual_argument.is_a?(expected_arg)
|
93
92
|
}
|
94
|
-
raise
|
95
|
-
|
96
|
-
|
93
|
+
raise(
|
94
|
+
::Code::Error::TypeError.new(
|
95
|
+
"Expected #{expected_argument}, got #{actual_argument.class}",
|
96
|
+
),
|
97
|
+
)
|
97
98
|
end
|
98
99
|
else
|
99
100
|
if !actual_argument.is_a?(expected_argument)
|
100
|
-
raise
|
101
|
-
|
102
|
-
|
101
|
+
raise(
|
102
|
+
::Code::Error::TypeError.new(
|
103
|
+
"Expected #{expected_argument}, got #{actual_argument.class}",
|
104
|
+
),
|
105
|
+
)
|
103
106
|
end
|
104
107
|
end
|
105
108
|
end
|
106
109
|
end
|
107
110
|
|
108
|
-
def
|
109
|
-
|
110
|
-
|
111
|
-
|
111
|
+
def equal(other)
|
112
|
+
::Code::Object::Boolean.new(self == other)
|
113
|
+
end
|
114
|
+
|
115
|
+
def strict_equal(other)
|
116
|
+
::Code::Object::Boolean.new(self === other)
|
117
|
+
end
|
118
|
+
|
119
|
+
def different(other)
|
120
|
+
::Code::Object::Boolean.new(self != other)
|
112
121
|
end
|
113
122
|
|
114
|
-
def compare(
|
115
|
-
sig(arguments, ::Code::Object)
|
116
|
-
other = arguments.first.value
|
123
|
+
def compare(other)
|
117
124
|
::Code::Object::Integer.new(self <=> other)
|
118
125
|
end
|
119
126
|
|
120
|
-
def and_operator(
|
121
|
-
sig(arguments, ::Code::Object)
|
122
|
-
other = arguments.first.value
|
127
|
+
def and_operator(other)
|
123
128
|
truthy? ? other : self
|
124
129
|
end
|
125
130
|
|
126
|
-
def or_operator(
|
127
|
-
sig(arguments, ::Code::Object)
|
128
|
-
other = arguments.first.value
|
131
|
+
def or_operator(other)
|
129
132
|
truthy? ? self : other
|
130
133
|
end
|
131
134
|
|
132
|
-
def to_string
|
133
|
-
sig(arguments)
|
135
|
+
def to_string
|
134
136
|
::Code::Object::String.new(to_s)
|
135
137
|
end
|
136
138
|
end
|
data/lib/code/parser/function.rb
CHANGED
@@ -30,7 +30,9 @@ class Code
|
|
30
30
|
ampersand.as(:block).maybe >>
|
31
31
|
(asterisk >> asterisk).as(:keyword_splat).maybe >>
|
32
32
|
asterisk.as(:splat).maybe >> name >>
|
33
|
-
(
|
33
|
+
(
|
34
|
+
whitespace? >> equal >> whitespace? >> code_present.as(:default)
|
35
|
+
).maybe
|
34
36
|
end
|
35
37
|
|
36
38
|
rule(:argument) do
|
data/lib/code/parser/if.rb
CHANGED
data/lib/code/parser/string.rb
CHANGED
@@ -49,29 +49,29 @@ class Code
|
|
49
49
|
end
|
50
50
|
|
51
51
|
rule(:single_quoted_character) do
|
52
|
-
escaped_character |
|
52
|
+
escaped_character |
|
53
|
+
(opening_curly_bracket.absent? >> single_quote.absent? >> any)
|
53
54
|
end
|
54
55
|
|
55
56
|
rule(:double_quoted_character) do
|
56
|
-
escaped_character |
|
57
|
+
escaped_character |
|
58
|
+
(opening_curly_bracket.absent? >> double_quote.absent? >> any)
|
57
59
|
end
|
58
60
|
|
59
61
|
rule(:single_quoted_string) do
|
60
62
|
single_quote.ignore >>
|
61
63
|
(
|
62
64
|
interpolation.as(:interpolation) |
|
63
|
-
|
64
|
-
).repeat >>
|
65
|
-
single_quote.ignore
|
65
|
+
single_quoted_character.repeat(1).as(:characters)
|
66
|
+
).repeat >> single_quote.ignore
|
66
67
|
end
|
67
68
|
|
68
69
|
rule(:double_quoted_string) do
|
69
70
|
double_quote.ignore >>
|
70
71
|
(
|
71
72
|
interpolation.as(:interpolation) |
|
72
|
-
|
73
|
-
).repeat >>
|
74
|
-
double_quote.ignore
|
73
|
+
double_quoted_character.repeat(1).as(:characters)
|
74
|
+
).repeat >> double_quote.ignore
|
75
75
|
end
|
76
76
|
|
77
77
|
rule(:symbol) { colon.ignore >> name }
|
data/lib/code/ruby.rb
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
class Code
|
2
|
+
class Ruby
|
3
|
+
def initialize(raw = {})
|
4
|
+
@raw = raw
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.to_code(raw)
|
8
|
+
new(raw).to_code
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.from_code(raw)
|
12
|
+
new(raw).from_code
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_code
|
16
|
+
if code?
|
17
|
+
raw
|
18
|
+
elsif true?
|
19
|
+
::Code::Object::Boolean.new(raw)
|
20
|
+
elsif false?
|
21
|
+
::Code::Object::Boolean.new(raw)
|
22
|
+
elsif string?
|
23
|
+
::Code::Object::String.new(raw)
|
24
|
+
elsif symbol?
|
25
|
+
::Code::Object::String.new(raw.to_s)
|
26
|
+
elsif integer?
|
27
|
+
::Code::Object::Integer.new(raw)
|
28
|
+
elsif float?
|
29
|
+
::Code::Object::Decimal.new(raw.to_s)
|
30
|
+
elsif big_decimal?
|
31
|
+
::Code::Object::Decimal.new(raw)
|
32
|
+
elsif hash?
|
33
|
+
::Code::Object::Dictionnary.new(
|
34
|
+
raw.map do |key, value|
|
35
|
+
[::Code::Ruby.to_code(key), ::Code::Ruby.to_code(value)]
|
36
|
+
end.to_h
|
37
|
+
)
|
38
|
+
elsif array?
|
39
|
+
::Code::Object::List.new(
|
40
|
+
raw.map do |element|
|
41
|
+
::Code::Ruby.to_code(key)
|
42
|
+
end
|
43
|
+
)
|
44
|
+
elsif proc?
|
45
|
+
::Code::Object::RubyFunction.new(raw)
|
46
|
+
else
|
47
|
+
raise "Unsupported class #{raw.class} for Ruby to Code conversion"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def from_code
|
52
|
+
if code?
|
53
|
+
if code_boolean?
|
54
|
+
raw.raw
|
55
|
+
elsif code_decimal?
|
56
|
+
raw.raw
|
57
|
+
elsif code_integer?
|
58
|
+
raw.raw
|
59
|
+
elsif code_nothing?
|
60
|
+
raw.raw
|
61
|
+
elsif code_range?
|
62
|
+
raw.raw
|
63
|
+
elsif code_string?
|
64
|
+
raw.raw
|
65
|
+
elsif code_dictionnary?
|
66
|
+
raw.raw.map do |key, value|
|
67
|
+
[::Code::Ruby.to_code(key), ::Code::Ruby.to_code(value)]
|
68
|
+
end.to_h
|
69
|
+
elsif code_list?
|
70
|
+
raw.raw.map do |element|
|
71
|
+
::Code::Ruby.to_code(element)
|
72
|
+
end
|
73
|
+
else
|
74
|
+
raise "Unsupported class #{raw.class} for Code to Ruby conversion"
|
75
|
+
end
|
76
|
+
else
|
77
|
+
raw
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
attr_reader :raw
|
84
|
+
|
85
|
+
def code?
|
86
|
+
raw.is_a?(::Code::Object)
|
87
|
+
end
|
88
|
+
|
89
|
+
def true?
|
90
|
+
raw.is_a?(::TrueClass)
|
91
|
+
end
|
92
|
+
|
93
|
+
def false?
|
94
|
+
raw.is_a?(::FalseClass)
|
95
|
+
end
|
96
|
+
|
97
|
+
def hash?
|
98
|
+
raw.is_a?(::Hash)
|
99
|
+
end
|
100
|
+
|
101
|
+
def array?
|
102
|
+
raw.is_a?(::Array)
|
103
|
+
end
|
104
|
+
|
105
|
+
def string?
|
106
|
+
raw.is_a?(::String)
|
107
|
+
end
|
108
|
+
|
109
|
+
def symbol?
|
110
|
+
raw.is_a?(::Symbol)
|
111
|
+
end
|
112
|
+
|
113
|
+
def integer?
|
114
|
+
raw.is_a?(::Integer)
|
115
|
+
end
|
116
|
+
|
117
|
+
def float?
|
118
|
+
raw.is_a?(::Float)
|
119
|
+
end
|
120
|
+
|
121
|
+
def big_decimal?
|
122
|
+
raw.is_a?(::BigDecimal)
|
123
|
+
end
|
124
|
+
|
125
|
+
def proc?
|
126
|
+
raw.is_a?(::Proc)
|
127
|
+
end
|
128
|
+
|
129
|
+
def code_boolean?
|
130
|
+
raw.is_a?(::Code::Object::Boolean)
|
131
|
+
end
|
132
|
+
|
133
|
+
def code_decimal?
|
134
|
+
raw.is_a?(::Code::Object::Decimal)
|
135
|
+
end
|
136
|
+
|
137
|
+
def code_integer?
|
138
|
+
raw.is_a?(::Code::Object::Integer)
|
139
|
+
end
|
140
|
+
|
141
|
+
def code_nothing?
|
142
|
+
raw.is_a?(::Code::Object::Nothing)
|
143
|
+
end
|
144
|
+
|
145
|
+
def code_range?
|
146
|
+
raw.is_a?(::Code::Object::Range)
|
147
|
+
end
|
148
|
+
|
149
|
+
def code_string?
|
150
|
+
raw.is_a?(::Code::Object::String)
|
151
|
+
end
|
152
|
+
|
153
|
+
def code_dictionnary?
|
154
|
+
raw.is_a?(::Code::Object::Dictionnary)
|
155
|
+
end
|
156
|
+
|
157
|
+
def code_list?
|
158
|
+
raw.is_a?(::Code::Object::List)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
data/lib/code-ruby.rb
CHANGED
@@ -3,10 +3,17 @@ require "zeitwerk"
|
|
3
3
|
require "bigdecimal"
|
4
4
|
require "active_support"
|
5
5
|
require "active_support/core_ext/object/blank"
|
6
|
+
require "active_support/core_ext/object/deep_dup"
|
6
7
|
require "stringio"
|
7
8
|
require "timeout"
|
8
9
|
|
9
10
|
loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
|
10
|
-
loader.ignore(
|
11
|
-
loader.ignore(
|
11
|
+
loader.ignore("#{__dir__}/template-ruby.rb")
|
12
|
+
loader.ignore("#{__dir__}/code-ruby.rb")
|
12
13
|
loader.setup
|
14
|
+
|
15
|
+
class Hash
|
16
|
+
def multi_fetch(*keys)
|
17
|
+
keys.map { |key| [key, fetch(key)] }.to_h
|
18
|
+
end
|
19
|
+
end
|
data/lib/code.rb
CHANGED
@@ -1,30 +1,44 @@
|
|
1
1
|
class Code
|
2
|
+
GLOBALS = [:io, :context, :object]
|
2
3
|
DEFAULT_TIMEOUT = Template::DEFAULT_TIMEOUT
|
3
4
|
|
4
|
-
def initialize(input, io: $stdout, timeout: DEFAULT_TIMEOUT)
|
5
|
+
def initialize(input, io: $stdout, timeout: DEFAULT_TIMEOUT, ruby: {})
|
5
6
|
@input = input
|
6
|
-
@parsed =
|
7
|
+
@parsed =
|
8
|
+
Timeout.timeout(timeout) { ::Code::Parser::Code.new.parse(@input) }
|
7
9
|
@io = io
|
8
10
|
@timeout = timeout || DEFAULT_TIMEOUT
|
11
|
+
@ruby = ::Code::Ruby.to_code(ruby || {})
|
9
12
|
end
|
10
13
|
|
11
|
-
def self.evaluate(input, context = "", io: $stdout, timeout: DEFAULT_TIMEOUT)
|
12
|
-
new(input, io: io, timeout: timeout).evaluate(context)
|
14
|
+
def self.evaluate(input, context = "", io: $stdout, timeout: DEFAULT_TIMEOUT, ruby: {})
|
15
|
+
new(input, io: io, timeout: timeout, ruby: ruby).evaluate(context)
|
13
16
|
end
|
14
17
|
|
15
18
|
def evaluate(context = "")
|
16
19
|
Timeout.timeout(timeout) do
|
17
20
|
if context.present?
|
18
|
-
context = ::Code.evaluate(
|
21
|
+
context = ::Code.evaluate(
|
22
|
+
context,
|
23
|
+
timeout: timeout,
|
24
|
+
io: io,
|
25
|
+
ruby: ruby
|
26
|
+
)
|
19
27
|
else
|
20
28
|
context = ::Code::Object::Dictionnary.new
|
21
29
|
end
|
22
30
|
|
31
|
+
if !context.is_a?(::Code::Object::Dictionnary)
|
32
|
+
raise ::Code::Error::IncompatibleContext.new("context must be a dictionnary")
|
33
|
+
end
|
34
|
+
|
35
|
+
context = context.merge(ruby)
|
36
|
+
|
23
37
|
::Code::Node::Code.new(parsed).evaluate(context: context, io: io)
|
24
38
|
end
|
25
39
|
end
|
26
40
|
|
27
41
|
private
|
28
42
|
|
29
|
-
attr_reader :input, :parsed, :timeout, :io
|
43
|
+
attr_reader :input, :parsed, :timeout, :io, :ruby
|
30
44
|
end
|
data/lib/template/version.rb
CHANGED
data/lib/template-ruby.rb
CHANGED
@@ -8,6 +8,12 @@ require "stringio"
|
|
8
8
|
require "timeout"
|
9
9
|
|
10
10
|
loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
|
11
|
-
loader.ignore(
|
12
|
-
loader.ignore(
|
11
|
+
loader.ignore("#{__dir__}/template-ruby.rb")
|
12
|
+
loader.ignore("#{__dir__}/code-ruby.rb")
|
13
13
|
loader.setup
|
14
|
+
|
15
|
+
class Hash
|
16
|
+
def multi_fetch(*keys)
|
17
|
+
keys.map { |key| [key, fetch(key)] }.to_h
|
18
|
+
end
|
19
|
+
end
|
data/lib/template.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
class Template
|
2
2
|
DEFAULT_TIMEOUT = 10
|
3
3
|
|
4
|
-
def initialize(input, io: StringIO.new, timeout: DEFAULT_TIMEOUT)
|
4
|
+
def initialize(input, io: StringIO.new, timeout: DEFAULT_TIMEOUT, ruby: {})
|
5
5
|
@input = input
|
6
6
|
@parsed =
|
7
7
|
Timeout.timeout(timeout) do
|
@@ -9,24 +9,39 @@ class Template
|
|
9
9
|
end
|
10
10
|
@io = io
|
11
11
|
@timeout = timeout || DEFAULT_TIMEOUT
|
12
|
+
@ruby = ::Code::Ruby.to_code(ruby || {})
|
12
13
|
end
|
13
14
|
|
14
|
-
def self.render(
|
15
|
-
|
15
|
+
def self.render(
|
16
|
+
input,
|
17
|
+
context = "",
|
18
|
+
io: StringIO.new,
|
19
|
+
timeout: DEFAULT_TIMEOUT,
|
20
|
+
ruby: {}
|
21
|
+
)
|
22
|
+
new(input, io: io, timeout: timeout, ruby: ruby).render(context)
|
16
23
|
end
|
17
24
|
|
18
25
|
def render(context = "")
|
19
26
|
Timeout.timeout(timeout) do
|
20
27
|
if context.present?
|
21
|
-
context = ::Code.evaluate(
|
28
|
+
context = ::Code.evaluate(
|
29
|
+
context,
|
30
|
+
timeout: timeout,
|
31
|
+
io: io,
|
32
|
+
ruby: ruby
|
33
|
+
)
|
22
34
|
else
|
23
35
|
context = ::Code::Object::Dictionnary.new
|
24
36
|
end
|
25
37
|
|
26
|
-
::
|
27
|
-
context
|
28
|
-
|
29
|
-
|
38
|
+
if !context.is_a?(::Code::Object::Dictionnary)
|
39
|
+
raise ::Code::Template::IncompatibleContext.new("context must be a dictionnary")
|
40
|
+
end
|
41
|
+
|
42
|
+
context = context.merge(ruby)
|
43
|
+
|
44
|
+
::Template::Node::Template.new(parsed).evaluate(context: context, io: io)
|
30
45
|
|
31
46
|
io.is_a?(StringIO) ? io.string : nil
|
32
47
|
end
|
@@ -34,7 +49,7 @@ class Template
|
|
34
49
|
|
35
50
|
private
|
36
51
|
|
37
|
-
attr_reader :parsed, :io, :timeout
|
52
|
+
attr_reader :parsed, :io, :timeout, :ruby
|
38
53
|
end
|
39
54
|
|
40
55
|
require_relative "template/version"
|
data/spec/code_spec.rb
CHANGED
@@ -1,7 +1,15 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
RSpec.describe Code do
|
4
|
-
|
4
|
+
let!(:input) { "" }
|
5
|
+
let!(:context) { "" }
|
6
|
+
let!(:io) { StringIO.new }
|
7
|
+
let!(:timeout) { 0.1 }
|
8
|
+
let!(:ruby) { {} }
|
9
|
+
|
10
|
+
subject do
|
11
|
+
Code.evaluate(input, context, io: io, timeout: timeout, ruby: ruby).to_s
|
12
|
+
end
|
5
13
|
|
6
14
|
[
|
7
15
|
["nothing", ""],
|
@@ -108,6 +116,7 @@ RSpec.describe Code do
|
|
108
116
|
["1.0 << 1", "2"],
|
109
117
|
["1 << 1.0", "2"],
|
110
118
|
["1.0 << 1.0", "2"],
|
119
|
+
["eval('1 + 1')", "2"],
|
111
120
|
].each do |(input, expected)|
|
112
121
|
context input.inspect do
|
113
122
|
let(:input) { input }
|
@@ -117,4 +126,57 @@ RSpec.describe Code do
|
|
117
126
|
end
|
118
127
|
end
|
119
128
|
end
|
129
|
+
|
130
|
+
context "with ruby" do
|
131
|
+
context "with a constant" do
|
132
|
+
let!(:input) { "a + a" }
|
133
|
+
let!(:ruby) { { a: 1 } }
|
134
|
+
|
135
|
+
it "can access a" do
|
136
|
+
expect(subject).to eq("2")
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
context "with a function without arguments" do
|
141
|
+
let!(:input) { "a + a" }
|
142
|
+
let!(:ruby) { { a: ->{ "hello" } } }
|
143
|
+
|
144
|
+
it "can call a" do
|
145
|
+
expect(subject).to eq("hellohello")
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
context "with a function with regular arguments" do
|
150
|
+
let!(:input) { "add(1, 2)" }
|
151
|
+
let!(:ruby) { { add: ->(a, b){ a + b } } }
|
152
|
+
|
153
|
+
it "can call add" do
|
154
|
+
expect(subject).to eq("3")
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
context "with a function with keyword arguments" do
|
159
|
+
let!(:input) { "add(a: 1, b: 2)" }
|
160
|
+
let!(:ruby) { { add: ->(a:, b:){ a + b } } }
|
161
|
+
|
162
|
+
it "can call add" do
|
163
|
+
expect(subject).to eq("3")
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
context "with a complex function" do
|
168
|
+
let!(:input) { "add(1, 1, 1, 1, c: 1, d: 1, e: 1)" }
|
169
|
+
let!(:ruby) do
|
170
|
+
{
|
171
|
+
add: ->(a, b = 1, *args, c:, d: 2, **kargs){
|
172
|
+
a + b + args.sum + c + d + kargs.values.sum
|
173
|
+
}
|
174
|
+
}
|
175
|
+
end
|
176
|
+
|
177
|
+
it "can call add" do
|
178
|
+
expect(subject).to eq("7")
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
120
182
|
end
|
data/spec/template_spec.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
require "spec_helper"
|
2
|
+
require "prime"
|
2
3
|
|
3
4
|
RSpec.describe Template do
|
4
|
-
|
5
|
+
let!(:ruby) { {} }
|
6
|
+
let!(:input_context) { "" }
|
7
|
+
let!(:timeout) { 0 }
|
8
|
+
subject { Template.render(input, input_context, ruby: ruby, timeout: timeout) }
|
5
9
|
|
6
10
|
[
|
7
11
|
["Hello World", "", "Hello World"],
|
@@ -12,13 +16,10 @@ RSpec.describe Template do
|
|
12
16
|
'{ user: { first_name: "Dorian" } }',
|
13
17
|
"Hello Dorian",
|
14
18
|
],
|
15
|
-
[
|
16
|
-
"{add(1, 2)",
|
17
|
-
'add = (a, b) => { a + b } { add: context(:add) }',
|
18
|
-
"3",
|
19
|
-
],
|
19
|
+
["{add(1, 2)", "add = (a, b) => { a + b } { add: context(:add) }", "3"],
|
20
20
|
["Hello {", "", "Hello "],
|
21
21
|
["{{a: 1}.each { |k, v| print(k) } nothing", "", "a"],
|
22
|
+
["{{a: 1}.each { |k, v| puts(k) } nothing", "", "a\n"],
|
22
23
|
["", "", ""],
|
23
24
|
].each do |(input, input_context, expected)|
|
24
25
|
context "#{input.inspect} #{input_context.inspect}" do
|
@@ -30,4 +31,22 @@ RSpec.describe Template do
|
|
30
31
|
end
|
31
32
|
end
|
32
33
|
end
|
34
|
+
|
35
|
+
context "with a function from ruby" do
|
36
|
+
let!(:ruby) { { prime: ->(n){ n.prime? } } }
|
37
|
+
let!(:input) { "{prime(19201)" }
|
38
|
+
|
39
|
+
it "calls the ruby function" do
|
40
|
+
expect(subject).to eq("false")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "with a function from ruby in an object" do
|
45
|
+
let!(:ruby) { { Prime: { prime: ->(n){ n.prime? } } } }
|
46
|
+
let!(:input) { "{Prime.prime(19201)" }
|
47
|
+
|
48
|
+
it "calls the ruby function" do
|
49
|
+
expect(subject).to eq("false")
|
50
|
+
end
|
51
|
+
end
|
33
52
|
end
|
data/template-ruby.gemspec
CHANGED