template-ruby 0.2.3 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/rspec.yml +14 -0
- data/CHANGELOG.md +20 -0
- data/Gemfile +2 -1
- data/Gemfile.lock +6 -1
- data/LICENSE +7 -0
- data/README.md +98 -5
- data/bin/code +51 -0
- data/bin/template +9 -5
- data/code-ruby.gemspec +22 -0
- data/docs/euler/4.template +0 -1
- data/docs/fibonnaci.template +14 -0
- data/docs/precedence.template +6 -31
- data/lib/code/node/code.rb +7 -1
- data/lib/code/node/list.rb +7 -7
- data/lib/code/node/name.rb +6 -1
- data/lib/code/node/string.rb +11 -6
- data/lib/code/node/string_characters.rb +13 -0
- data/lib/code/node/string_component.rb +23 -0
- data/lib/code/node/string_interpolation.rb +13 -0
- data/lib/code/object/argument.rb +1 -1
- data/lib/code/object/decimal.rb +22 -1
- data/lib/code/object/dictionnary.rb +24 -0
- data/lib/code/object/function.rb +2 -1
- data/lib/code/object/integer.rb +18 -20
- data/lib/code/object/list.rb +4 -0
- data/lib/code/object/string.rb +10 -2
- data/lib/code/object.rb +7 -0
- data/lib/code/parser/and_operator.rb +4 -4
- data/lib/code/parser/call.rb +3 -3
- data/lib/code/parser/code.rb +3 -4
- data/lib/code/parser/defined.rb +2 -2
- data/lib/code/parser/dictionnary.rb +1 -1
- data/lib/code/parser/equality.rb +4 -4
- data/lib/code/parser/function.rb +3 -2
- data/lib/code/parser/group.rb +1 -1
- data/lib/code/parser/if.rb +3 -3
- data/lib/code/parser/list.rb +1 -1
- data/lib/code/parser/not_keyword.rb +2 -2
- data/lib/code/parser/statement.rb +1 -1
- data/lib/code/parser/string.rb +19 -4
- data/lib/code/parser/ternary.rb +3 -3
- data/lib/code-ruby.rb +12 -0
- data/lib/code.rb +10 -10
- data/lib/template/version.rb +3 -0
- data/lib/template-ruby.rb +3 -1
- data/lib/template.rb +21 -11
- data/spec/code/error/type_error_spec.rb +0 -2
- data/spec/code/parser/dictionnary_spec.rb +5 -32
- data/spec/code/parser/string_spec.rb +15 -16
- data/spec/code_spec.rb +13 -0
- data/spec/template_spec.rb +6 -0
- data/template-ruby.gemspec +3 -2
- metadata +15 -4
data/lib/code/object/integer.rb
CHANGED
@@ -21,13 +21,13 @@ class Code
|
|
21
21
|
|
22
22
|
if operator == "even?"
|
23
23
|
even?(arguments)
|
24
|
-
elsif operator == "to_string"
|
25
|
-
code_to_s(arguments)
|
26
24
|
elsif operator == "*"
|
27
25
|
multiplication(arguments)
|
28
26
|
elsif operator == "/"
|
29
27
|
division(arguments)
|
30
|
-
elsif
|
28
|
+
elsif operator == "+"
|
29
|
+
plus(arguments)
|
30
|
+
elsif %w[% - **].detect { |o| operator == o }
|
31
31
|
integer_or_decimal_operation(operator.to_sym, arguments)
|
32
32
|
elsif %w[> >= < <=].detect { |o| operator == o }
|
33
33
|
comparaison(operator.to_sym, arguments)
|
@@ -38,16 +38,6 @@ class Code
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
-
def +(other)
|
42
|
-
if other.is_a?(::Code::Object::Integer)
|
43
|
-
::Code::Object::Integer.new(raw + other.raw)
|
44
|
-
elsif other.is_a?(::Code::Object::Decimal)
|
45
|
-
::Code::Object::Decimal.new(raw + other.raw)
|
46
|
-
else
|
47
|
-
raise ::Code::Error::TypeError
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
41
|
def succ
|
52
42
|
::Code::Object::Integer.new(raw + 1)
|
53
43
|
end
|
@@ -67,11 +57,6 @@ class Code
|
|
67
57
|
::Code::Object::Boolean.new(raw.even?)
|
68
58
|
end
|
69
59
|
|
70
|
-
def code_to_s(arguments)
|
71
|
-
sig(arguments)
|
72
|
-
::Code::Object::String.new(raw.to_s)
|
73
|
-
end
|
74
|
-
|
75
60
|
def multiplication(arguments)
|
76
61
|
sig(arguments, [::Code::Object::Number, ::Code::Object::String])
|
77
62
|
other = arguments.first.value
|
@@ -84,6 +69,19 @@ class Code
|
|
84
69
|
end
|
85
70
|
end
|
86
71
|
|
72
|
+
def plus(arguments)
|
73
|
+
sig(arguments, ::Code::Object)
|
74
|
+
other = arguments.first.value
|
75
|
+
|
76
|
+
if other.is_a?(::Code::Object::Integer)
|
77
|
+
::Code::Object::Integer.new(raw + other.raw)
|
78
|
+
elsif other.is_a?(::Code::Object::Decimal)
|
79
|
+
::Code::Object::Decimal.new(raw + other.raw)
|
80
|
+
else
|
81
|
+
::Code::Object::String.new(to_s + other.to_s)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
87
85
|
def division(arguments)
|
88
86
|
sig(arguments, ::Code::Object::Number)
|
89
87
|
other = arguments.first.value
|
@@ -101,9 +99,9 @@ class Code
|
|
101
99
|
end
|
102
100
|
|
103
101
|
def integer_operation(operator, arguments)
|
104
|
-
sig(arguments, ::Code::Object::
|
102
|
+
sig(arguments, ::Code::Object::Number)
|
105
103
|
other = arguments.first.value
|
106
|
-
::Code::Object::Integer.new(raw.public_send(operator, other.raw))
|
104
|
+
::Code::Object::Integer.new(raw.to_i.public_send(operator, other.raw.to_i))
|
107
105
|
end
|
108
106
|
|
109
107
|
def comparaison(operator, arguments)
|
data/lib/code/object/list.rb
CHANGED
data/lib/code/object/string.rb
CHANGED
@@ -15,6 +15,8 @@ class Code
|
|
15
15
|
to_function(arguments)
|
16
16
|
elsif operator == "+"
|
17
17
|
plus(arguments)
|
18
|
+
elsif operator == "*"
|
19
|
+
multiplication(arguments)
|
18
20
|
elsif operator == "reverse"
|
19
21
|
reverse(arguments)
|
20
22
|
else
|
@@ -46,9 +48,15 @@ class Code
|
|
46
48
|
end
|
47
49
|
|
48
50
|
def plus(arguments)
|
49
|
-
sig(arguments, ::Code::Object
|
51
|
+
sig(arguments, ::Code::Object)
|
50
52
|
other = arguments.first.value
|
51
|
-
::Code::Object::String.new(raw + other.
|
53
|
+
::Code::Object::String.new(raw + other.to_s)
|
54
|
+
end
|
55
|
+
|
56
|
+
def multiplication(arguments)
|
57
|
+
sig(arguments, ::Code::Object::Number)
|
58
|
+
other = arguments.first.value
|
59
|
+
::Code::Object::String.new(raw * other.raw)
|
52
60
|
end
|
53
61
|
|
54
62
|
def reverse(arguments)
|
data/lib/code/object.rb
CHANGED
@@ -13,6 +13,8 @@ class Code
|
|
13
13
|
and_operator(arguments)
|
14
14
|
elsif operator == "||"
|
15
15
|
or_operator(arguments)
|
16
|
+
elsif operator == "to_string"
|
17
|
+
to_string(arguments)
|
16
18
|
else
|
17
19
|
raise ::Code::Error::Undefined.new(
|
18
20
|
"#{operator} not defined on #{inspect}",
|
@@ -126,5 +128,10 @@ class Code
|
|
126
128
|
other = arguments.first.value
|
127
129
|
truthy? ? self : other
|
128
130
|
end
|
131
|
+
|
132
|
+
def to_string(arguments)
|
133
|
+
sig(arguments)
|
134
|
+
::Code::Object::String.new(to_s)
|
135
|
+
end
|
129
136
|
end
|
130
137
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
class Code
|
2
2
|
class Parser
|
3
3
|
class AndOperator < Parslet::Parser
|
4
|
-
rule(:
|
4
|
+
rule(:greater_than) { ::Code::Parser::GreaterThan.new }
|
5
5
|
|
6
6
|
rule(:ampersand) { str("&") }
|
7
7
|
|
@@ -14,12 +14,12 @@ class Code
|
|
14
14
|
|
15
15
|
rule(:and_operator) do
|
16
16
|
(
|
17
|
-
|
17
|
+
greater_than.as(:first) >>
|
18
18
|
(
|
19
19
|
whitespace? >> operator.as(:operator) >> whitespace? >>
|
20
|
-
|
20
|
+
greater_than.as(:statement)
|
21
21
|
).repeat(1).as(:rest)
|
22
|
-
).as(:and_operator) |
|
22
|
+
).as(:and_operator) | greater_than
|
23
23
|
end
|
24
24
|
|
25
25
|
root(:and_operator)
|
data/lib/code/parser/call.rb
CHANGED
@@ -2,7 +2,7 @@ class Code
|
|
2
2
|
class Parser
|
3
3
|
class Call < Parslet::Parser
|
4
4
|
rule(:dictionnary) { ::Code::Parser::Dictionnary.new }
|
5
|
-
rule(:code) { ::Code::Parser::Code.new }
|
5
|
+
rule(:code) { ::Code::Parser::Code.new.present }
|
6
6
|
rule(:name) { ::Code::Parser::Name.new }
|
7
7
|
rule(:function_arguments) { ::Code::Parser::Function.new.arguments }
|
8
8
|
|
@@ -72,12 +72,12 @@ class Code
|
|
72
72
|
rule(:block) do
|
73
73
|
(
|
74
74
|
whitespace >> do_keyword >> whitespace >>
|
75
|
-
block_arguments.as(:arguments).maybe >> code.as(:body) >>
|
75
|
+
block_arguments.as(:arguments).maybe >> code.as(:body).maybe >>
|
76
76
|
end_keyword
|
77
77
|
) |
|
78
78
|
(
|
79
79
|
whitespace? >> opening_curly_bracket >> whitespace >>
|
80
|
-
block_arguments.as(:arguments).maybe >> code.as(:body) >>
|
80
|
+
block_arguments.as(:arguments).maybe >> code.as(:body).maybe >>
|
81
81
|
closing_curly_bracket
|
82
82
|
)
|
83
83
|
end
|
data/lib/code/parser/code.rb
CHANGED
@@ -8,11 +8,10 @@ class Code
|
|
8
8
|
rule(:whitespace) { (space | newline).repeat(1) }
|
9
9
|
rule(:whitespace?) { whitespace.maybe }
|
10
10
|
|
11
|
-
rule(:
|
12
|
-
(whitespace?.ignore >> statement >> whitespace?.ignore).repeat(1)
|
13
|
-
whitespace?.ignore
|
11
|
+
rule(:present) do
|
12
|
+
(whitespace?.ignore >> statement >> whitespace?.ignore).repeat(1)
|
14
13
|
end
|
15
|
-
|
14
|
+
rule(:code) { present | whitespace?.ignore }
|
16
15
|
root(:code)
|
17
16
|
end
|
18
17
|
end
|
data/lib/code/parser/defined.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
class Code
|
2
2
|
class Parser
|
3
3
|
class Defined < Parslet::Parser
|
4
|
-
rule(:
|
4
|
+
rule(:range) { ::Code::Parser::Range.new }
|
5
5
|
rule(:name) { ::Code::Parser::Name.new }
|
6
6
|
|
7
7
|
rule(:defined_keyword) { str("defined?") }
|
@@ -11,7 +11,7 @@ class Code
|
|
11
11
|
rule(:defined) do
|
12
12
|
(
|
13
13
|
defined_keyword >> opening_parenthesis >> name >> closing_parenthesis
|
14
|
-
).as(:defined) |
|
14
|
+
).as(:defined) | range
|
15
15
|
end
|
16
16
|
|
17
17
|
root(:defined)
|
@@ -2,7 +2,7 @@ class Code
|
|
2
2
|
class Parser
|
3
3
|
class Dictionnary < Parslet::Parser
|
4
4
|
rule(:list) { ::Code::Parser::List.new }
|
5
|
-
rule(:code) { ::Code::Parser::Code.new }
|
5
|
+
rule(:code) { ::Code::Parser::Code.new.present }
|
6
6
|
rule(:string) { ::Code::Parser::String.new }
|
7
7
|
|
8
8
|
rule(:opening_curly_bracket) { str("{") }
|
data/lib/code/parser/equality.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
class Code
|
2
2
|
class Parser
|
3
3
|
class Equality < Parslet::Parser
|
4
|
-
rule(:
|
4
|
+
rule(:while_parser) { ::Code::Parser::While.new }
|
5
5
|
|
6
6
|
rule(:right_caret) { str(">") }
|
7
7
|
rule(:left_caret) { str("<") }
|
@@ -22,12 +22,12 @@ class Code
|
|
22
22
|
|
23
23
|
rule(:equality) do
|
24
24
|
(
|
25
|
-
|
25
|
+
while_parser.as(:first) >>
|
26
26
|
(
|
27
27
|
whitespace? >> operator.as(:operator) >> whitespace? >>
|
28
|
-
|
28
|
+
while_parser.as(:statement)
|
29
29
|
).repeat(1).as(:rest)
|
30
|
-
).as(:equality) |
|
30
|
+
).as(:equality) | while_parser
|
31
31
|
end
|
32
32
|
|
33
33
|
root(:equality)
|
data/lib/code/parser/function.rb
CHANGED
@@ -2,6 +2,7 @@ class Code
|
|
2
2
|
class Parser
|
3
3
|
class Function < Parslet::Parser
|
4
4
|
rule(:call) { ::Code::Parser::Call.new }
|
5
|
+
rule(:code_present) { ::Code::Parser::Code.new.present }
|
5
6
|
rule(:code) { ::Code::Parser::Code.new }
|
6
7
|
rule(:name) { ::Code::Parser::Name.new }
|
7
8
|
|
@@ -22,14 +23,14 @@ class Code
|
|
22
23
|
rule(:whitespace?) { whitespace.maybe }
|
23
24
|
|
24
25
|
rule(:keyword_argument) do
|
25
|
-
name >> whitespace? >> colon >>
|
26
|
+
name >> whitespace? >> colon >> code_present.as(:default).maybe
|
26
27
|
end
|
27
28
|
|
28
29
|
rule(:regular_argument) do
|
29
30
|
ampersand.as(:block).maybe >>
|
30
31
|
(asterisk >> asterisk).as(:keyword_splat).maybe >>
|
31
32
|
asterisk.as(:splat).maybe >> name >>
|
32
|
-
(whitespace? >> equal >> whitespace? >>
|
33
|
+
(whitespace? >> equal >> whitespace? >> code_present.as(:default)).maybe
|
33
34
|
end
|
34
35
|
|
35
36
|
rule(:argument) do
|
data/lib/code/parser/group.rb
CHANGED
@@ -2,7 +2,7 @@ class Code
|
|
2
2
|
class Parser
|
3
3
|
class Group < Parslet::Parser
|
4
4
|
rule(:name) { ::Code::Parser::Name.new }
|
5
|
-
rule(:code) { ::Code::Parser::Code.new }
|
5
|
+
rule(:code) { ::Code::Parser::Code.new.present }
|
6
6
|
|
7
7
|
rule(:opening_parenthesis) { str("(") }
|
8
8
|
rule(:closing_parenthesis) { str(")") }
|
data/lib/code/parser/if.rb
CHANGED
@@ -2,7 +2,7 @@ class Code
|
|
2
2
|
class Parser
|
3
3
|
class If < Parslet::Parser
|
4
4
|
rule(:if_modifier) { ::Code::Parser::IfModifier.new }
|
5
|
-
rule(:code) { ::Code::Parser::Code.new }
|
5
|
+
rule(:code) { ::Code::Parser::Code.new.present }
|
6
6
|
|
7
7
|
rule(:if_keyword) { str("if") }
|
8
8
|
rule(:else_keyword) { str("else") }
|
@@ -16,13 +16,13 @@ class Code
|
|
16
16
|
rule(:if_rule) do
|
17
17
|
(
|
18
18
|
(if_keyword | unless_keyword).as(:if_operator) >> whitespace >>
|
19
|
-
|
19
|
+
if_modifier.as(:if_statement) >> code.as(:if_body).maybe >>
|
20
20
|
(
|
21
21
|
else_keyword >>
|
22
22
|
(
|
23
23
|
whitespace >> (if_keyword | unless_keyword).as(:operator) >>
|
24
24
|
whitespace >> if_modifier.as(:statement)
|
25
|
-
).maybe >> code.as(:body)
|
25
|
+
).maybe >> code.as(:body).maybe
|
26
26
|
).repeat(1).as(:elses).maybe >> end_keyword
|
27
27
|
).as(:if) | if_modifier
|
28
28
|
end
|
data/lib/code/parser/list.rb
CHANGED
@@ -2,7 +2,7 @@ class Code
|
|
2
2
|
class Parser
|
3
3
|
class List < Parslet::Parser
|
4
4
|
rule(:string) { ::Code::Parser::String.new }
|
5
|
-
rule(:code) { ::Code::Parser::Code.new }
|
5
|
+
rule(:code) { ::Code::Parser::Code.new.present }
|
6
6
|
|
7
7
|
rule(:opening_square_bracket) { str("[") }
|
8
8
|
rule(:closing_square_bracket) { str("]") }
|
@@ -1,7 +1,7 @@
|
|
1
1
|
class Code
|
2
2
|
class Parser
|
3
3
|
class NotKeyword < Parslet::Parser
|
4
|
-
rule(:
|
4
|
+
rule(:equal) { ::Code::Parser::Equal.new }
|
5
5
|
|
6
6
|
rule(:not_keyword) { str("not") }
|
7
7
|
|
@@ -12,7 +12,7 @@ class Code
|
|
12
12
|
rule(:whitespace) { (space | newline).repeat(1) }
|
13
13
|
|
14
14
|
rule(:not_rule) do
|
15
|
-
(not_keyword >> whitespace >> not_rule).as(:not_keyword) |
|
15
|
+
(not_keyword >> whitespace >> not_rule).as(:not_keyword) | equal
|
16
16
|
end
|
17
17
|
|
18
18
|
root(:not_rule)
|
data/lib/code/parser/string.rb
CHANGED
@@ -3,11 +3,14 @@ class Code
|
|
3
3
|
class String < Parslet::Parser
|
4
4
|
rule(:number) { ::Code::Parser::Number.new }
|
5
5
|
rule(:name) { ::Code::Parser::Name.new.name }
|
6
|
+
rule(:code) { ::Code::Parser::Code.new }
|
6
7
|
|
7
8
|
rule(:single_quote) { str("'") }
|
8
9
|
rule(:double_quote) { str('"') }
|
9
10
|
rule(:backslash) { str("\\") }
|
10
11
|
rule(:colon) { str(":") }
|
12
|
+
rule(:opening_curly_bracket) { str("{") }
|
13
|
+
rule(:closing_curly_bracket) { str("}") }
|
11
14
|
|
12
15
|
rule(:zero) { str("0") }
|
13
16
|
rule(:one) { str("1") }
|
@@ -31,6 +34,10 @@ class Code
|
|
31
34
|
rule(:t) { str("t") | str("T") }
|
32
35
|
rule(:u) { str("u") | str("U") }
|
33
36
|
|
37
|
+
rule(:interpolation) do
|
38
|
+
opening_curly_bracket >> code >> closing_curly_bracket
|
39
|
+
end
|
40
|
+
|
34
41
|
rule(:base_16_digit) do
|
35
42
|
zero | one | two | three | four | five | six | seven | eight | nine |
|
36
43
|
a | b | c | d | e | f
|
@@ -42,20 +49,28 @@ class Code
|
|
42
49
|
end
|
43
50
|
|
44
51
|
rule(:single_quoted_character) do
|
45
|
-
escaped_character | (single_quote.absent? >> any)
|
52
|
+
escaped_character | (opening_curly_bracket.absent? >> single_quote.absent? >> any)
|
46
53
|
end
|
47
54
|
|
48
55
|
rule(:double_quoted_character) do
|
49
|
-
escaped_character | (double_quote.absent? >> any)
|
56
|
+
escaped_character | (opening_curly_bracket.absent? >> double_quote.absent? >> any)
|
50
57
|
end
|
51
58
|
|
52
59
|
rule(:single_quoted_string) do
|
53
|
-
single_quote.ignore >>
|
60
|
+
single_quote.ignore >>
|
61
|
+
(
|
62
|
+
interpolation.as(:interpolation) |
|
63
|
+
single_quoted_character.repeat(1).as(:characters)
|
64
|
+
).repeat >>
|
54
65
|
single_quote.ignore
|
55
66
|
end
|
56
67
|
|
57
68
|
rule(:double_quoted_string) do
|
58
|
-
double_quote.ignore >>
|
69
|
+
double_quote.ignore >>
|
70
|
+
(
|
71
|
+
interpolation.as(:interpolation) |
|
72
|
+
double_quoted_character.repeat(1).as(:characters)
|
73
|
+
).repeat >>
|
59
74
|
double_quote.ignore
|
60
75
|
end
|
61
76
|
|
data/lib/code/parser/ternary.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
class Code
|
2
2
|
class Parser
|
3
3
|
class Ternary < Parslet::Parser
|
4
|
-
rule(:
|
4
|
+
rule(:defined) { ::Code::Parser::Defined.new }
|
5
5
|
|
6
6
|
rule(:question_mark) { str("?") }
|
7
7
|
rule(:colon) { str(":") }
|
@@ -13,10 +13,10 @@ class Code
|
|
13
13
|
|
14
14
|
rule(:ternary) do
|
15
15
|
(
|
16
|
-
|
16
|
+
defined.as(:left) >> whitespace >> question_mark >> whitespace? >>
|
17
17
|
ternary.as(:middle) >>
|
18
18
|
(whitespace? >> colon >> whitespace? >> ternary.as(:right)).maybe
|
19
|
-
).as(:ternary) |
|
19
|
+
).as(:ternary) | defined
|
20
20
|
end
|
21
21
|
|
22
22
|
root(:ternary)
|
data/lib/code-ruby.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require "parslet"
|
2
|
+
require "zeitwerk"
|
3
|
+
require "bigdecimal"
|
4
|
+
require "active_support"
|
5
|
+
require "active_support/core_ext/object/blank"
|
6
|
+
require "stringio"
|
7
|
+
require "timeout"
|
8
|
+
|
9
|
+
loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
|
10
|
+
loader.ignore(File.expand_path("lib/template-ruby.rb"))
|
11
|
+
loader.ignore(File.expand_path("lib/code-ruby.rb"))
|
12
|
+
loader.setup
|
data/lib/code.rb
CHANGED
@@ -1,30 +1,30 @@
|
|
1
1
|
class Code
|
2
|
-
|
2
|
+
DEFAULT_TIMEOUT = Template::DEFAULT_TIMEOUT
|
3
|
+
|
4
|
+
def initialize(input, io: $stdout, timeout: DEFAULT_TIMEOUT)
|
3
5
|
@input = input
|
4
|
-
@parsed = Timeout
|
5
|
-
::Code::Parser::Code.new.parse(@input)
|
6
|
-
end
|
6
|
+
@parsed = Timeout.timeout(timeout) { ::Code::Parser::Code.new.parse(@input) }
|
7
7
|
@io = io
|
8
|
-
@timeout = timeout
|
8
|
+
@timeout = timeout || DEFAULT_TIMEOUT
|
9
9
|
end
|
10
10
|
|
11
|
-
def self.evaluate(input, context = "", io: $stdout, timeout:
|
11
|
+
def self.evaluate(input, context = "", io: $stdout, timeout: DEFAULT_TIMEOUT)
|
12
12
|
new(input, io: io, timeout: timeout).evaluate(context)
|
13
13
|
end
|
14
14
|
|
15
15
|
def evaluate(context = "")
|
16
|
-
Timeout
|
16
|
+
Timeout.timeout(timeout) do
|
17
17
|
if context.present?
|
18
|
-
context = ::Code.evaluate(context, timeout:
|
18
|
+
context = ::Code.evaluate(context, timeout: timeout)
|
19
19
|
else
|
20
20
|
context = ::Code::Object::Dictionnary.new
|
21
21
|
end
|
22
22
|
|
23
|
-
::Code::Node::Code.new(parsed).evaluate(context: context, io:
|
23
|
+
::Code::Node::Code.new(parsed).evaluate(context: context, io: io)
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
27
|
private
|
28
28
|
|
29
|
-
attr_reader :input, :parsed
|
29
|
+
attr_reader :input, :parsed, :timeout, :io
|
30
30
|
end
|
data/lib/template-ruby.rb
CHANGED
@@ -3,9 +3,11 @@ 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(File.expand_path("lib/template-ruby.rb"))
|
12
|
+
loader.ignore(File.expand_path("lib/code-ruby.rb"))
|
11
13
|
loader.setup
|
data/lib/template.rb
CHANGED
@@ -1,30 +1,40 @@
|
|
1
1
|
class Template
|
2
|
-
|
2
|
+
DEFAULT_TIMEOUT = 10
|
3
3
|
|
4
|
-
def initialize(input, io: StringIO.new, timeout:
|
4
|
+
def initialize(input, io: StringIO.new, timeout: DEFAULT_TIMEOUT)
|
5
5
|
@input = input
|
6
|
-
@parsed =
|
7
|
-
|
8
|
-
|
6
|
+
@parsed =
|
7
|
+
Timeout.timeout(timeout) do
|
8
|
+
::Template::Parser::Template.new.parse(@input)
|
9
|
+
end
|
9
10
|
@io = io
|
10
|
-
@timeout = timeout
|
11
|
+
@timeout = timeout || DEFAULT_TIMEOUT
|
11
12
|
end
|
12
13
|
|
13
|
-
def self.render(input, context = "", io: StringIO.new, timeout:
|
14
|
+
def self.render(input, context = "", io: StringIO.new, timeout: DEFAULT_TIMEOUT)
|
14
15
|
new(input, io: io, timeout: timeout).render(context)
|
15
16
|
end
|
16
17
|
|
17
18
|
def render(context = "")
|
18
|
-
Timeout
|
19
|
+
Timeout.timeout(timeout) do
|
19
20
|
if context.present?
|
20
|
-
context = ::Code.evaluate(context, timeout:
|
21
|
+
context = ::Code.evaluate(context, timeout: timeout)
|
21
22
|
else
|
22
23
|
context = ::Code::Object::Dictionnary.new
|
23
24
|
end
|
24
25
|
|
25
|
-
::Template::Node::Template.new(
|
26
|
+
::Template::Node::Template.new(parsed).evaluate(
|
27
|
+
context: context,
|
28
|
+
io: io,
|
29
|
+
)
|
26
30
|
|
27
|
-
|
31
|
+
io.is_a?(StringIO) ? io.string : nil
|
28
32
|
end
|
29
33
|
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
attr_reader :parsed, :io, :timeout
|
30
38
|
end
|
39
|
+
|
40
|
+
require_relative "template/version"
|
@@ -14,7 +14,6 @@ RSpec.describe ::Code::Error::TypeError do
|
|
14
14
|
'1 / ""',
|
15
15
|
'1 ** ""',
|
16
16
|
'1 % ""',
|
17
|
-
'1 + ""',
|
18
17
|
'1 - ""',
|
19
18
|
'1 << ""',
|
20
19
|
'1 >> ""',
|
@@ -48,7 +47,6 @@ RSpec.describe ::Code::Error::TypeError do
|
|
48
47
|
'1.0 ** ""',
|
49
48
|
'1.0 * ""',
|
50
49
|
'1.0 % ""',
|
51
|
-
'1.0 + ""',
|
52
50
|
'1.0 - ""',
|
53
51
|
'1.0 > ""',
|
54
52
|
'1.0 >= ""',
|
@@ -4,42 +4,15 @@ RSpec.describe Code::Parser::Dictionnary do
|
|
4
4
|
subject { described_class.new.parse(input) }
|
5
5
|
|
6
6
|
[
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
},
|
12
|
-
],
|
13
|
-
[
|
14
|
-
'{a: true, "b": false}',
|
15
|
-
{
|
16
|
-
dictionnary: [
|
17
|
-
{ key: { name: "a" }, value: [{ boolean: "true" }] },
|
18
|
-
{ key: { string: "b" }, value: [{ boolean: "false" }] },
|
19
|
-
],
|
20
|
-
},
|
21
|
-
],
|
22
|
-
[
|
23
|
-
"{ true => 1, false => 2}",
|
24
|
-
{
|
25
|
-
dictionnary: [
|
26
|
-
{
|
27
|
-
key: [{ boolean: "true" }],
|
28
|
-
value: [{ number: { base_10: { integer: { whole: "1" } } } }],
|
29
|
-
},
|
30
|
-
{
|
31
|
-
key: [{ boolean: "false" }],
|
32
|
-
value: [{ number: { base_10: { integer: { whole: "2" } } } }],
|
33
|
-
},
|
34
|
-
],
|
35
|
-
},
|
36
|
-
],
|
37
|
-
].each do |(input, expected)|
|
7
|
+
'{name: "Dorian"}',
|
8
|
+
'{a: true, "b": false}',
|
9
|
+
"{ true => 1, false => 2}",
|
10
|
+
].each do |input|
|
38
11
|
context input.inspect do
|
39
12
|
let(:input) { input }
|
40
13
|
|
41
14
|
it "succeeds" do
|
42
|
-
expect
|
15
|
+
expect { subject }.to_not raise_error
|
43
16
|
end
|
44
17
|
end
|
45
18
|
end
|