code-ruby 0.3.1 → 0.4.0
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/CHANGELOG.md +5 -0
- 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 +6 -0
- data/lib/code.rb +20 -6
- data/lib/template/version.rb +1 -1
- data/lib/template-ruby.rb +6 -0
- 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
@@ -11,3 +11,9 @@ loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
|
|
11
11
|
loader.ignore("#{__dir__}/template-ruby.rb")
|
12
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/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
@@ -11,3 +11,9 @@ loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
|
|
11
11
|
loader.ignore("#{__dir__}/template-ruby.rb")
|
12
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