kapusta 0.1.4 → 0.2.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/README.md +13 -0
- data/bin/compile-examples +24 -0
- data/examples/bank-account.kap +21 -0
- data/examples/baseball-game.kap +11 -0
- data/examples/climbing-stairs.kap +13 -0
- data/examples/doto-hygiene.kap +5 -0
- data/examples/happy-number.kap +20 -0
- data/examples/length-of-last-word.kap +7 -0
- data/examples/maximum-subarray.kap +12 -0
- data/examples/move-zeroes.kap +13 -0
- data/examples/stack.kap +27 -10
- data/examples/two-sum-hash.kap +17 -0
- data/examples/use_bank_account.rb +13 -0
- data/examples/valid-parentheses-1.kap +19 -0
- data/examples/valid-parentheses-2.kap +8 -0
- data/lib/kapusta/ast.rb +33 -3
- data/lib/kapusta/compiler/emitter/bindings.rb +135 -22
- data/lib/kapusta/compiler/emitter/control_flow.rb +4 -4
- data/lib/kapusta/compiler/emitter/expressions.rb +30 -14
- data/lib/kapusta/compiler/emitter/interop.rb +108 -41
- data/lib/kapusta/compiler/emitter/patterns.rb +14 -11
- data/lib/kapusta/compiler/emitter/support.rb +29 -14
- data/lib/kapusta/compiler/emitter.rb +1 -5
- data/lib/kapusta/compiler/normalizer.rb +42 -18
- data/lib/kapusta/compiler/runtime.rb +5 -156
- data/lib/kapusta/env.rb +31 -8
- data/lib/kapusta/formatter.rb +9 -10
- data/lib/kapusta/reader.rb +30 -6
- data/lib/kapusta/version.rb +1 -1
- data/lib/kapusta.rb +62 -1
- data/spec/cli_spec.rb +25 -2
- data/spec/examples_spec.rb +234 -87
- data/spec/reader_spec.rb +26 -0
- metadata +17 -7
|
@@ -4,175 +4,21 @@ module Kapusta
|
|
|
4
4
|
module Compiler
|
|
5
5
|
module Runtime
|
|
6
6
|
HELPER_DEPENDENCIES = {
|
|
7
|
-
print_values: %i[stringify],
|
|
8
|
-
concat: %i[stringify],
|
|
9
|
-
method_path_value: %i[kebab_to_snake],
|
|
10
|
-
set_method_path: %i[kebab_to_snake],
|
|
11
|
-
get_ivar: %i[kebab_to_snake],
|
|
12
|
-
set_ivar: %i[kebab_to_snake],
|
|
13
|
-
get_cvar: %i[current_class_scope kebab_to_snake],
|
|
14
|
-
set_cvar: %i[current_class_scope kebab_to_snake],
|
|
15
|
-
get_gvar: %i[kebab_to_snake],
|
|
16
|
-
set_gvar: %i[kebab_to_snake],
|
|
17
7
|
destructure: %i[destructure_into],
|
|
18
8
|
match_pattern: %i[match_pattern_into]
|
|
19
9
|
}.freeze
|
|
20
10
|
|
|
21
11
|
HELPER_SOURCES = {
|
|
22
|
-
kebab_to_snake: <<~RUBY.chomp,
|
|
23
|
-
def kap_kebab_to_snake(name)
|
|
24
|
-
name.tr('-', '_')
|
|
25
|
-
end
|
|
26
|
-
RUBY
|
|
27
|
-
call: <<~'RUBY'.chomp,
|
|
28
|
-
def kap_call(callee, positional, kwargs = nil, block = nil)
|
|
29
|
-
raise "not callable: #{callee.inspect}" unless callee.respond_to?(:call)
|
|
30
|
-
|
|
31
|
-
if block
|
|
32
|
-
kwargs ? callee.call(*positional, **kwargs, &block) : callee.call(*positional, &block)
|
|
33
|
-
else
|
|
34
|
-
kwargs ? callee.call(*positional, **kwargs) : callee.call(*positional)
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
RUBY
|
|
38
|
-
send_call: <<~RUBY.chomp,
|
|
39
|
-
def kap_send_call(receiver, method_name, positional, kwargs = nil, block = nil)
|
|
40
|
-
if block
|
|
41
|
-
if kwargs
|
|
42
|
-
receiver.public_send(method_name, *positional, **kwargs, &block)
|
|
43
|
-
else
|
|
44
|
-
receiver.public_send(method_name, *positional, &block)
|
|
45
|
-
end
|
|
46
|
-
elsif kwargs
|
|
47
|
-
receiver.public_send(method_name, *positional, **kwargs)
|
|
48
|
-
else
|
|
49
|
-
receiver.public_send(method_name, *positional)
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
RUBY
|
|
53
|
-
invoke_self: <<~RUBY.chomp,
|
|
54
|
-
def kap_invoke_self(receiver, method_name, positional, kwargs = nil, block = nil)
|
|
55
|
-
if block
|
|
56
|
-
if kwargs
|
|
57
|
-
receiver.send(method_name, *positional, **kwargs, &block)
|
|
58
|
-
else
|
|
59
|
-
receiver.send(method_name, *positional, &block)
|
|
60
|
-
end
|
|
61
|
-
else
|
|
62
|
-
kwargs ? receiver.send(method_name, *positional, **kwargs) : receiver.send(method_name, *positional)
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
RUBY
|
|
66
|
-
stringify: <<~'RUBY'.chomp,
|
|
67
|
-
def kap_stringify(value)
|
|
68
|
-
render = nil
|
|
69
|
-
render = lambda do |item|
|
|
70
|
-
case item
|
|
71
|
-
when nil then 'nil'
|
|
72
|
-
when true then 'true'
|
|
73
|
-
when false then 'false'
|
|
74
|
-
when String, Symbol then item.inspect
|
|
75
|
-
when Array
|
|
76
|
-
"[#{item.map { |child| render.call(child) }.join(', ')}]"
|
|
77
|
-
when Hash
|
|
78
|
-
"{#{item.map { |key, child| "#{render.call(key)}=>#{render.call(child)}" }.join(', ')}}"
|
|
79
|
-
else
|
|
80
|
-
item.inspect
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
case value
|
|
85
|
-
when nil then 'nil'
|
|
86
|
-
when true then 'true'
|
|
87
|
-
when false then 'false'
|
|
88
|
-
when Array, Hash then render.call(value)
|
|
89
|
-
else value.to_s
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
RUBY
|
|
93
|
-
print_values: <<~'RUBY'.chomp,
|
|
94
|
-
def kap_print_values(*values)
|
|
95
|
-
$stdout.puts(values.map { |value| kap_stringify(value) }.join("\t"))
|
|
96
|
-
nil
|
|
97
|
-
end
|
|
98
|
-
RUBY
|
|
99
|
-
concat: <<~RUBY.chomp,
|
|
100
|
-
def kap_concat(values)
|
|
101
|
-
values.map { |value| kap_stringify(value) }.join
|
|
102
|
-
end
|
|
103
|
-
RUBY
|
|
104
|
-
get_path: <<~RUBY.chomp,
|
|
105
|
-
def kap_get_path(obj, keys)
|
|
106
|
-
keys.reduce(obj) { |acc, key| acc[key] }
|
|
107
|
-
end
|
|
108
|
-
RUBY
|
|
109
12
|
qget_path: <<~RUBY.chomp,
|
|
110
13
|
def kap_qget_path(obj, keys)
|
|
111
14
|
keys.each do |key|
|
|
112
|
-
return
|
|
15
|
+
return if obj.nil?
|
|
113
16
|
|
|
114
17
|
obj = obj[key]
|
|
115
18
|
end
|
|
116
19
|
obj
|
|
117
20
|
end
|
|
118
21
|
RUBY
|
|
119
|
-
set_path: <<~RUBY.chomp,
|
|
120
|
-
def kap_set_path(obj, keys, value)
|
|
121
|
-
target = obj
|
|
122
|
-
keys[0...-1].each { |key| target = target[key] }
|
|
123
|
-
target[keys.last] = value
|
|
124
|
-
end
|
|
125
|
-
RUBY
|
|
126
|
-
method_path_value: <<~RUBY.chomp,
|
|
127
|
-
def kap_method_path_value(base, segments)
|
|
128
|
-
segments.reduce(base) { |obj, segment| obj.public_send(kap_kebab_to_snake(segment).to_sym) }
|
|
129
|
-
end
|
|
130
|
-
RUBY
|
|
131
|
-
set_method_path: <<~'RUBY'.chomp,
|
|
132
|
-
def kap_set_method_path(base, segments, value)
|
|
133
|
-
target = base
|
|
134
|
-
segments[0...-1].each do |segment|
|
|
135
|
-
target = target.public_send(kap_kebab_to_snake(segment).to_sym)
|
|
136
|
-
end
|
|
137
|
-
setter = "#{kap_kebab_to_snake(segments.last)}="
|
|
138
|
-
target.public_send(setter.to_sym, value)
|
|
139
|
-
end
|
|
140
|
-
RUBY
|
|
141
|
-
current_class_scope: <<~RUBY.chomp,
|
|
142
|
-
def kap_current_class_scope(receiver)
|
|
143
|
-
receiver.is_a?(Module) ? receiver : receiver.class
|
|
144
|
-
end
|
|
145
|
-
RUBY
|
|
146
|
-
get_ivar: <<~'RUBY'.chomp,
|
|
147
|
-
def kap_get_ivar(receiver, name)
|
|
148
|
-
receiver.instance_variable_get("@#{kap_kebab_to_snake(name)}")
|
|
149
|
-
end
|
|
150
|
-
RUBY
|
|
151
|
-
set_ivar: <<~'RUBY'.chomp,
|
|
152
|
-
def kap_set_ivar(receiver, name, value)
|
|
153
|
-
receiver.instance_variable_set("@#{kap_kebab_to_snake(name)}", value)
|
|
154
|
-
end
|
|
155
|
-
RUBY
|
|
156
|
-
get_cvar: <<~'RUBY'.chomp,
|
|
157
|
-
def kap_get_cvar(receiver, name)
|
|
158
|
-
kap_current_class_scope(receiver).class_variable_get("@@#{kap_kebab_to_snake(name)}")
|
|
159
|
-
end
|
|
160
|
-
RUBY
|
|
161
|
-
set_cvar: <<~'RUBY'.chomp,
|
|
162
|
-
def kap_set_cvar(receiver, name, value)
|
|
163
|
-
kap_current_class_scope(receiver).class_variable_set("@@#{kap_kebab_to_snake(name)}", value)
|
|
164
|
-
end
|
|
165
|
-
RUBY
|
|
166
|
-
get_gvar: <<~'RUBY'.chomp,
|
|
167
|
-
def kap_get_gvar(name)
|
|
168
|
-
Kernel.eval("$#{kap_kebab_to_snake(name)}", binding, __FILE__, __LINE__)
|
|
169
|
-
end
|
|
170
|
-
RUBY
|
|
171
|
-
set_gvar: <<~'RUBY'.chomp,
|
|
172
|
-
def kap_set_gvar(name, value)
|
|
173
|
-
Kernel.eval("$#{kap_kebab_to_snake(name)} = value", binding, __FILE__, __LINE__)
|
|
174
|
-
end
|
|
175
|
-
RUBY
|
|
176
22
|
ensure_module: <<~RUBY.chomp,
|
|
177
23
|
def kap_ensure_module(holder, path)
|
|
178
24
|
segments = path.split('.')
|
|
@@ -367,10 +213,13 @@ module Kapusta
|
|
|
367
213
|
|
|
368
214
|
HELPER_SOURCES.each_key do |name|
|
|
369
215
|
helper_method = :"kap_#{name}"
|
|
370
|
-
|
|
216
|
+
body = instance_method(helper_method)
|
|
217
|
+
define_singleton_method(helper_method, body)
|
|
218
|
+
define_singleton_method(name, body)
|
|
371
219
|
helper_methods << helper_method
|
|
372
220
|
end
|
|
373
221
|
|
|
222
|
+
private_class_method(*helper_methods)
|
|
374
223
|
send(:private, *helper_methods)
|
|
375
224
|
end
|
|
376
225
|
end
|
data/lib/kapusta/env.rb
CHANGED
|
@@ -2,18 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
module Kapusta
|
|
4
4
|
class Env
|
|
5
|
+
MethodBinding = Struct.new(:ruby_name)
|
|
6
|
+
|
|
5
7
|
def initialize(parent = nil)
|
|
6
8
|
@parent = parent
|
|
7
9
|
@vars = {}
|
|
8
10
|
end
|
|
9
11
|
|
|
10
12
|
def define(name, value)
|
|
11
|
-
@vars[name] = value
|
|
13
|
+
@vars[binding_key(name)] = value
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
def lookup(name)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
key = binding_key(name)
|
|
18
|
+
if @vars.key?(key)
|
|
19
|
+
@vars[key]
|
|
17
20
|
elsif @parent
|
|
18
21
|
@parent.lookup(name)
|
|
19
22
|
else
|
|
@@ -21,21 +24,31 @@ module Kapusta
|
|
|
21
24
|
end
|
|
22
25
|
end
|
|
23
26
|
|
|
27
|
+
def lookup_if_defined(name)
|
|
28
|
+
key = binding_key(name)
|
|
29
|
+
if @vars.key?(key)
|
|
30
|
+
@vars[key]
|
|
31
|
+
else
|
|
32
|
+
@parent&.lookup_if_defined(name)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
24
36
|
def defined?(name)
|
|
25
|
-
@vars.key?(name) || @parent&.defined?(name)
|
|
37
|
+
@vars.key?(binding_key(name)) || @parent&.defined?(name)
|
|
26
38
|
end
|
|
27
39
|
|
|
28
40
|
def ruby_name_defined?(name)
|
|
29
|
-
@vars.value
|
|
41
|
+
@vars.any? { |_source_name, value| binding_ruby_name(value) == name } || @parent&.ruby_name_defined?(name)
|
|
30
42
|
end
|
|
31
43
|
|
|
32
44
|
def local_ruby_name_defined?(name)
|
|
33
|
-
@vars.value
|
|
45
|
+
@vars.any? { |_source_name, value| binding_ruby_name(value) == name }
|
|
34
46
|
end
|
|
35
47
|
|
|
36
48
|
def set_existing!(name, value)
|
|
37
|
-
|
|
38
|
-
|
|
49
|
+
key = binding_key(name)
|
|
50
|
+
if @vars.key?(key)
|
|
51
|
+
@vars[key] = value
|
|
39
52
|
elsif @parent
|
|
40
53
|
@parent.set_existing!(name, value)
|
|
41
54
|
else
|
|
@@ -46,5 +59,15 @@ module Kapusta
|
|
|
46
59
|
def child
|
|
47
60
|
Env.new(self)
|
|
48
61
|
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def binding_key(name)
|
|
66
|
+
name.respond_to?(:binding_key) ? name.binding_key : name
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def binding_ruby_name(value)
|
|
70
|
+
value.respond_to?(:ruby_name) ? value.ruby_name : value
|
|
71
|
+
end
|
|
49
72
|
end
|
|
50
73
|
end
|
data/lib/kapusta/formatter.rb
CHANGED
|
@@ -177,15 +177,15 @@ module Kapusta
|
|
|
177
177
|
when Sym
|
|
178
178
|
form.name
|
|
179
179
|
when Vec
|
|
180
|
-
return
|
|
180
|
+
return if contains_comments?(form.items)
|
|
181
181
|
|
|
182
182
|
"[#{form.items.map { |item| flat_render(item) }.join(' ')}]"
|
|
183
183
|
when HashLit
|
|
184
|
-
return
|
|
184
|
+
return if contains_comments?(form.entries)
|
|
185
185
|
|
|
186
186
|
"{#{form.pairs.map { |key, value| flat_hash_pair(key, value) }.join(' ')}}"
|
|
187
187
|
when List
|
|
188
|
-
return
|
|
188
|
+
return if contains_comments?(form.items)
|
|
189
189
|
return "##{flat_render(semantic_items(form.items)[1])}" if hashfn_literal?(form)
|
|
190
190
|
|
|
191
191
|
"(#{form.items.map { |item| flat_render(item) }.join(' ')})"
|
|
@@ -548,13 +548,12 @@ module Kapusta
|
|
|
548
548
|
|
|
549
549
|
def render_hanging_pairwise_vec(vec)
|
|
550
550
|
pairs = vec.items.each_slice(2).to_a
|
|
551
|
-
|
|
552
|
-
left, right = pair
|
|
553
|
-
return nil unless pair.length == 2
|
|
551
|
+
return unless pairs.all? { |pair| pair.length == 2 }
|
|
554
552
|
|
|
553
|
+
rendered_pairs = pairs.map do |left, right|
|
|
555
554
|
render_binding_pair(left, right)
|
|
556
555
|
end
|
|
557
|
-
return
|
|
556
|
+
return if rendered_pairs.any?(&:nil?)
|
|
558
557
|
|
|
559
558
|
lines = ["[#{rendered_pairs.first}"]
|
|
560
559
|
continuation = ' ' * '(let ['.length
|
|
@@ -609,7 +608,7 @@ module Kapusta
|
|
|
609
608
|
def render_pair(left, right, indent)
|
|
610
609
|
left_rendered = flat_render(left) || render(left, indent)
|
|
611
610
|
right_rendered = flat_render(right) || render(right, indent)
|
|
612
|
-
return
|
|
611
|
+
return unless single_line?(left_rendered) && single_line?(right_rendered)
|
|
613
612
|
|
|
614
613
|
pair = "#{left_rendered} #{right_rendered}"
|
|
615
614
|
fits?(pair, indent) ? pair : nil
|
|
@@ -617,12 +616,12 @@ module Kapusta
|
|
|
617
616
|
|
|
618
617
|
def render_binding_pair(left, right)
|
|
619
618
|
left_rendered = flat_render(left)
|
|
620
|
-
return
|
|
619
|
+
return unless left_rendered
|
|
621
620
|
|
|
622
621
|
right_rendered = render(right, '(let ['.length + left_rendered.length + 1)
|
|
623
622
|
first_line, *rest = right_rendered.lines(chomp: true)
|
|
624
623
|
pair = "#{left_rendered} #{first_line}"
|
|
625
|
-
return
|
|
624
|
+
return unless pair.length <= MAX_WIDTH
|
|
626
625
|
|
|
627
626
|
return pair if rest.empty?
|
|
628
627
|
|
data/lib/kapusta/reader.rb
CHANGED
|
@@ -8,6 +8,7 @@ module Kapusta
|
|
|
8
8
|
|
|
9
9
|
WHITESPACE = [' ', "\t", "\n", "\r", "\f", "\v", ','].freeze
|
|
10
10
|
DELIMS = ['(', ')', '[', ']', '{', '}', '"', ';'].freeze
|
|
11
|
+
CLOSING_DELIMS = [')', ']', '}'].freeze
|
|
11
12
|
|
|
12
13
|
def self.read_all(source, preserve_comments: false)
|
|
13
14
|
new(source, preserve_comments:).read_all
|
|
@@ -85,6 +86,7 @@ module Kapusta
|
|
|
85
86
|
when '{' then read_hash
|
|
86
87
|
when '"' then read_string
|
|
87
88
|
when '#' then read_hashfn
|
|
89
|
+
when *CLOSING_DELIMS then raise unexpected_closing_delim(peek)
|
|
88
90
|
else
|
|
89
91
|
read_atom
|
|
90
92
|
end
|
|
@@ -93,11 +95,12 @@ module Kapusta
|
|
|
93
95
|
end
|
|
94
96
|
|
|
95
97
|
def read_list
|
|
98
|
+
opening_position = source_position
|
|
96
99
|
advance
|
|
97
100
|
items = []
|
|
98
101
|
loop do
|
|
99
102
|
skip_ws
|
|
100
|
-
raise
|
|
103
|
+
raise unclosed_opening_delim('(', opening_position) if eof?
|
|
101
104
|
break if peek == ')'
|
|
102
105
|
|
|
103
106
|
items << read_next_item
|
|
@@ -107,11 +110,12 @@ module Kapusta
|
|
|
107
110
|
end
|
|
108
111
|
|
|
109
112
|
def read_vec
|
|
113
|
+
opening_position = source_position
|
|
110
114
|
advance
|
|
111
115
|
items = []
|
|
112
116
|
loop do
|
|
113
117
|
skip_ws
|
|
114
|
-
raise
|
|
118
|
+
raise unclosed_opening_delim('[', opening_position) if eof?
|
|
115
119
|
break if peek == ']'
|
|
116
120
|
|
|
117
121
|
items << read_next_item
|
|
@@ -121,12 +125,13 @@ module Kapusta
|
|
|
121
125
|
end
|
|
122
126
|
|
|
123
127
|
def read_hash
|
|
128
|
+
opening_position = source_position
|
|
124
129
|
advance
|
|
125
130
|
entries = []
|
|
126
131
|
pending = []
|
|
127
132
|
loop do
|
|
128
133
|
skip_ws
|
|
129
|
-
raise
|
|
134
|
+
raise unclosed_opening_delim('{', opening_position) if eof?
|
|
130
135
|
break if peek == '}'
|
|
131
136
|
|
|
132
137
|
item = read_next_item
|
|
@@ -218,12 +223,31 @@ module Kapusta
|
|
|
218
223
|
parse_atom(token)
|
|
219
224
|
end
|
|
220
225
|
|
|
226
|
+
def unexpected_closing_delim(char)
|
|
227
|
+
line, column = source_position
|
|
228
|
+
Error.new("unexpected closing delimiter '#{char}' at line #{line}, column #{column}")
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def unclosed_opening_delim(char, position)
|
|
232
|
+
line, column = position
|
|
233
|
+
Error.new("unclosed opening delimiter '#{char}' at line #{line}, column #{column}")
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def source_position
|
|
237
|
+
prefix = @src[0...@pos]
|
|
238
|
+
line = prefix.count("\n") + 1
|
|
239
|
+
last_newline = prefix.rindex("\n")
|
|
240
|
+
column = last_newline ? prefix.length - last_newline : prefix.length + 1
|
|
241
|
+
|
|
242
|
+
[line, column]
|
|
243
|
+
end
|
|
244
|
+
|
|
221
245
|
def parse_atom(token)
|
|
222
246
|
return true if token == 'true'
|
|
223
247
|
return false if token == 'false'
|
|
224
|
-
return
|
|
225
|
-
return token
|
|
226
|
-
return token
|
|
248
|
+
return if token == 'nil'
|
|
249
|
+
return Integer(token, 10) if token.match?(/\A-?\d+\z/)
|
|
250
|
+
return Float(token) if token.match?(/\A-?\d+\.\d+\z/)
|
|
227
251
|
|
|
228
252
|
if token.start_with?(':') && token.length > 1
|
|
229
253
|
Kapusta.kebab_to_snake(token[1..]).to_sym
|
data/lib/kapusta/version.rb
CHANGED
data/lib/kapusta.rb
CHANGED
|
@@ -9,6 +9,8 @@ require_relative 'kapusta/env'
|
|
|
9
9
|
require_relative 'kapusta/compiler'
|
|
10
10
|
|
|
11
11
|
module Kapusta
|
|
12
|
+
@loaded_kapusta_features = {}
|
|
13
|
+
|
|
12
14
|
def self.eval(source, path: '(eval)', **_opts)
|
|
13
15
|
Compiler.run(source, path:)
|
|
14
16
|
end
|
|
@@ -22,10 +24,69 @@ module Kapusta
|
|
|
22
24
|
Compiler.compile(source, path:)
|
|
23
25
|
end
|
|
24
26
|
|
|
27
|
+
def self.require(feature, relative_to: nil)
|
|
28
|
+
feature = feature.to_s
|
|
29
|
+
local_path = resolve_require_path(feature, relative_to:)
|
|
30
|
+
|
|
31
|
+
return require_kapusta_file(local_path) if local_path&.end_with?('.kap')
|
|
32
|
+
return Kernel.require(local_path) if local_path
|
|
33
|
+
|
|
34
|
+
Kernel.require(feature)
|
|
35
|
+
end
|
|
36
|
+
|
|
25
37
|
def self.install!
|
|
26
38
|
@install ||= begin
|
|
27
|
-
require 'rubygems'
|
|
39
|
+
Kernel.require 'rubygems'
|
|
28
40
|
true
|
|
29
41
|
end
|
|
30
42
|
end
|
|
43
|
+
|
|
44
|
+
def self.resolve_require_path(feature, relative_to:)
|
|
45
|
+
return unless local_feature?(feature)
|
|
46
|
+
|
|
47
|
+
path =
|
|
48
|
+
if File.absolute_path?(feature)
|
|
49
|
+
feature
|
|
50
|
+
else
|
|
51
|
+
File.expand_path(feature, require_base_dir(relative_to))
|
|
52
|
+
end
|
|
53
|
+
existing_feature_path(path)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def self.local_feature?(feature)
|
|
57
|
+
feature.start_with?('./', '../') || File.absolute_path?(feature)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self.require_base_dir(relative_to)
|
|
61
|
+
return Dir.pwd if relative_to.nil? || relative_to.start_with?('(')
|
|
62
|
+
|
|
63
|
+
File.dirname(File.expand_path(relative_to))
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def self.existing_feature_path(path)
|
|
67
|
+
candidates =
|
|
68
|
+
if File.extname(path).empty?
|
|
69
|
+
[path, "#{path}.kap", "#{path}.rb"]
|
|
70
|
+
else
|
|
71
|
+
[path]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
candidates.find { |candidate| File.file?(candidate) }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def self.require_kapusta_file(path)
|
|
78
|
+
expanded = File.realpath(path)
|
|
79
|
+
return false if @loaded_kapusta_features[expanded]
|
|
80
|
+
|
|
81
|
+
@loaded_kapusta_features[expanded] = true
|
|
82
|
+
dofile(expanded)
|
|
83
|
+
$LOADED_FEATURES << expanded unless $LOADED_FEATURES.include?(expanded)
|
|
84
|
+
true
|
|
85
|
+
rescue StandardError, ScriptError
|
|
86
|
+
@loaded_kapusta_features.delete(expanded) if expanded
|
|
87
|
+
raise
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private_class_method :resolve_require_path, :local_feature?, :require_base_dir,
|
|
91
|
+
:existing_feature_path, :require_kapusta_file
|
|
31
92
|
end
|
data/spec/cli_spec.rb
CHANGED
|
@@ -39,7 +39,28 @@ RSpec.describe Kapusta::CLI do
|
|
|
39
39
|
|
|
40
40
|
stdout, stderr, status = Open3.capture3(RbConfig.ruby, output_path)
|
|
41
41
|
|
|
42
|
-
expected =
|
|
42
|
+
expected = <<~OUT
|
|
43
|
+
1
|
|
44
|
+
2
|
|
45
|
+
"Fizz"
|
|
46
|
+
4
|
|
47
|
+
"Buzz"
|
|
48
|
+
"Fizz"
|
|
49
|
+
7
|
|
50
|
+
8
|
|
51
|
+
"Fizz"
|
|
52
|
+
"Buzz"
|
|
53
|
+
11
|
|
54
|
+
"Fizz"
|
|
55
|
+
13
|
|
56
|
+
14
|
|
57
|
+
"FizzBuzz"
|
|
58
|
+
16
|
|
59
|
+
17
|
|
60
|
+
"Fizz"
|
|
61
|
+
19
|
|
62
|
+
"Buzz"
|
|
63
|
+
OUT
|
|
43
64
|
expect(status.success?).to eq(true), stderr
|
|
44
65
|
expect(stdout).to eq(expected)
|
|
45
66
|
end
|
|
@@ -63,7 +84,9 @@ RSpec.describe Kapusta::CLI do
|
|
|
63
84
|
described_class.start([path, 'Ada'])
|
|
64
85
|
end
|
|
65
86
|
|
|
66
|
-
expect(output).to eq(
|
|
87
|
+
expect(output).to eq(<<~OUT)
|
|
88
|
+
"Hello, Ada!"
|
|
89
|
+
OUT
|
|
67
90
|
end
|
|
68
91
|
|
|
69
92
|
it 'prints the version with -v' do
|