yap-shell-parser 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,205 @@
1
+ module Yap::Shell
2
+ module Parser::Nodes
3
+ module Visitor
4
+ def accept(visitor, *args)
5
+ visitor.send "visit_#{self.class.name.split("::").last}", self, *args
6
+ end
7
+ end
8
+
9
+ class AssignmentNode
10
+ include Visitor
11
+
12
+ attr_reader :lvalue, :rvalue
13
+
14
+ def initialize(lvalue, rvalue)
15
+ @lvalue, @rvalue = lvalue, rvalue
16
+ end
17
+
18
+ def inspect
19
+ to_s
20
+ end
21
+
22
+ def to_s
23
+ "A(#{lvalue.inspect}=#{rvalue.inspect})"
24
+ end
25
+ end
26
+
27
+ class CommandNode
28
+ include Visitor
29
+
30
+ attr_reader :command, :args
31
+ attr_accessor :heredoc, :redirects
32
+
33
+ def initialize(command, *args, literal:false, heredoc:nil)
34
+ @command = command
35
+ @args = args.flatten
36
+ @literal = literal
37
+ @heredoc = nil
38
+ @redirects = []
39
+ end
40
+
41
+ def literal?
42
+ @literal
43
+ end
44
+
45
+ def heredoc?
46
+ @heredoc
47
+ end
48
+
49
+ def internally_evaluate?
50
+ false
51
+ end
52
+
53
+ def inspect
54
+ to_s
55
+ end
56
+
57
+ def to_s
58
+ "CommandNode(#{@command}, args: #{@args}, literal:#{literal?}, heredoc: #{heredoc?}, redirects: #{redirects})"
59
+ end
60
+ end
61
+
62
+ class EnvNode
63
+ include Visitor
64
+
65
+ attr_reader :env
66
+
67
+ def initialize(lvalue, rvalue)
68
+ @env = {}
69
+ @env[lvalue] = rvalue
70
+ end
71
+
72
+ def add_var(lvalue, rvalue)
73
+ @env[lvalue] = rvalue
74
+ end
75
+ end
76
+
77
+ class EnvWrapperNode
78
+ include Visitor
79
+
80
+ attr_reader :node
81
+
82
+ def initialize(env, node)
83
+ @env = env
84
+ @node = node
85
+ end
86
+
87
+ def env
88
+ @env.env
89
+ end
90
+ end
91
+
92
+ class StatementsNode
93
+ include Visitor
94
+
95
+ attr_reader :head, :tail
96
+
97
+ def initialize(head, tail=nil)
98
+ if head.is_a?(StatementsNode) && head.tail.nil?
99
+ @head = head.head
100
+ else
101
+ @head = head
102
+ end
103
+ @tail = tail
104
+ end
105
+
106
+ def to_s(indent:0)
107
+ <<-EOT.gsub(/^\s+\|/, '')
108
+ | #{' ' * indent}StatementsNode(
109
+ | #{' ' * indent} #{@head.to_s},
110
+ | #{' ' * indent} #{@tail.to_s})
111
+ EOT
112
+ end
113
+
114
+ def inspect
115
+ to_s
116
+ end
117
+ end
118
+
119
+ class ConditionalNode
120
+ include Visitor
121
+
122
+ attr_reader :operator, :expr1, :expr2
123
+
124
+ def initialize(operator, expr1, expr2)
125
+ @operator = operator
126
+ @expr1 = expr1
127
+ @expr2 = expr2
128
+ end
129
+
130
+ def to_s
131
+ "ConditionalNode(#{@operator}, #{@expr1}, #{@expr2.to_s})"
132
+ end
133
+ end
134
+
135
+ class InternalEvalNode
136
+ include Visitor
137
+
138
+ attr_reader :src
139
+ alias_method :command, :src
140
+
141
+ def initialize(src)
142
+ @src = src
143
+ end
144
+
145
+ def args
146
+ nil
147
+ end
148
+
149
+ def heredoc
150
+ nil
151
+ end
152
+
153
+ def internally_evaluate?
154
+ true
155
+ end
156
+
157
+ def to_s
158
+ "InternalEvalNode(#{@src.inspect})"
159
+ end
160
+ end
161
+
162
+ class PipelineNode
163
+ include Visitor
164
+
165
+ attr_reader :head, :tail
166
+
167
+ def initialize(head, tail)
168
+ @head = head
169
+ @tail = tail
170
+ end
171
+
172
+ def to_s(indent:0)
173
+ <<-EOT.gsub(/^\s+\|/, '')
174
+ | #{' ' * indent}PipelineNode(
175
+ | #{' ' * indent} #{@head.to_s},
176
+ | #{' ' * indent} #{@tail.to_s})
177
+ EOT
178
+ end
179
+
180
+ def inspect
181
+ to_s
182
+ end
183
+ end
184
+
185
+ class RedirectionNode
186
+ include Visitor
187
+
188
+ attr_reader :kind, :target
189
+
190
+ def initialize(kind, target)
191
+ @kind = kind
192
+ @target = target
193
+ end
194
+
195
+ def to_s(indent:0)
196
+ "RedirectionNode(#{@kind.to_s}, #{@target.to_s})"
197
+ end
198
+
199
+ def inspect
200
+ to_s
201
+ end
202
+ end
203
+
204
+ end
205
+ end
@@ -0,0 +1,5 @@
1
+ require 'yap/shell/parser'
2
+
3
+ class Yap::Shell::Parser
4
+ VERSION = "0.0.1"
5
+ end
@@ -0,0 +1,91 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__) + "/../lib"
2
+
3
+ # This file was generated by the `rspec --init` command. Conventionally, all
4
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
5
+ # The generated `.rspec` file contains `--require spec_helper` which will cause this
6
+ # file to always be loaded, without a need to explicitly require it in any files.
7
+ #
8
+ # Given that it is always loaded, you are encouraged to keep this file as
9
+ # light-weight as possible. Requiring heavyweight dependencies from this file
10
+ # will add to the boot time of your test suite on EVERY test run, even for an
11
+ # individual file that may not need all of that loaded. Instead, consider making
12
+ # a separate helper file that requires the additional dependencies and performs
13
+ # the additional setup, and require it from the spec files that actually need it.
14
+ #
15
+ # The `.rspec` file also contains a few flags that are not defaults but that
16
+ # users commonly want.
17
+ #
18
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
19
+ RSpec.configure do |config|
20
+ # rspec-expectations config goes here. You can use an alternate
21
+ # assertion/expectation library such as wrong or the stdlib/minitest
22
+ # assertions if you prefer.
23
+ config.expect_with :rspec do |expectations|
24
+ # This option will default to `true` in RSpec 4. It makes the `description`
25
+ # and `failure_message` of custom matchers include text for helper methods
26
+ # defined using `chain`, e.g.:
27
+ # be_bigger_than(2).and_smaller_than(4).description
28
+ # # => "be bigger than 2 and smaller than 4"
29
+ # ...rather than:
30
+ # # => "be bigger than 2"
31
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
32
+ end
33
+
34
+ # rspec-mocks config goes here. You can use an alternate test double
35
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
36
+ config.mock_with :rspec do |mocks|
37
+ # Prevents you from mocking or stubbing a method that does not exist on
38
+ # a real object. This is generally recommended, and will default to
39
+ # `true` in RSpec 4.
40
+ mocks.verify_partial_doubles = true
41
+ end
42
+
43
+ # The settings below are suggested to provide a good initial experience
44
+ # with RSpec, but feel free to customize to your heart's content.
45
+ =begin
46
+ # These two settings work together to allow you to limit a spec run
47
+ # to individual examples or groups you care about by tagging them with
48
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
49
+ # get run.
50
+ config.filter_run :focus
51
+ config.run_all_when_everything_filtered = true
52
+
53
+ # Limits the available syntax to the non-monkey patched syntax that is recommended.
54
+ # For more details, see:
55
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
56
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
57
+ # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
58
+ config.disable_monkey_patching!
59
+
60
+ # This setting enables warnings. It's recommended, but in some cases may
61
+ # be too noisy due to issues in dependencies.
62
+ config.warnings = true
63
+
64
+ # Many RSpec users commonly either run the entire suite or an individual
65
+ # file, and it's useful to allow more verbose output when running an
66
+ # individual spec file.
67
+ if config.files_to_run.one?
68
+ # Use the documentation formatter for detailed output,
69
+ # unless a formatter has already been configured
70
+ # (e.g. via a command-line flag).
71
+ config.default_formatter = 'doc'
72
+ end
73
+
74
+ # Print the 10 slowest examples and example groups at the
75
+ # end of the spec run, to help surface which specs are running
76
+ # particularly slow.
77
+ config.profile_examples = 10
78
+
79
+ # Run specs in random order to surface order dependencies. If you find an
80
+ # order dependency and want to debug it, you can fix the order by providing
81
+ # the seed, which is printed after each run.
82
+ # --seed 1234
83
+ config.order = :random
84
+
85
+ # Seed global randomization in this process using the `--seed` CLI option.
86
+ # Setting this allows you to use `--seed` to deterministically reproduce
87
+ # test failures related to randomization by passing the same `--seed` value
88
+ # as the one that triggered the failure.
89
+ Kernel.srand config.seed
90
+ =end
91
+ end
@@ -0,0 +1,697 @@
1
+ require 'spec_helper'
2
+ require 'yap/shell/parser/lexer'
3
+
4
+ describe Yap::Shell::Parser::Lexer do
5
+ subject { described_class.new.tokenize(str) }
6
+
7
+ def t(tag, val, lineno:0, attrs:{})
8
+ [tag, Yap::Shell::Parser::Lexer::Token.new(tag, val, lineno:lineno, attrs:attrs)]
9
+ end
10
+
11
+ describe "empty string" do
12
+ let(:str){ "" }
13
+ it { should eq [] }
14
+ end
15
+
16
+ describe "env variables" do
17
+ let(:str){ "foo $baz" }
18
+ it { should eq [
19
+ t(:Command, "foo", lineno:0),
20
+ t(:Argument, "$baz", lineno:0)
21
+ ]}
22
+ end
23
+
24
+ describe "commands" do
25
+ describe "can begin with periods: .core" do
26
+ let(:str){ ".core" }
27
+ it { should eq [
28
+ t(:Command, ".core", lineno:0)
29
+ ]}
30
+ end
31
+
32
+ describe "can contain periods: ag.core" do
33
+ let(:str){ "ag.core" }
34
+ it { should eq [
35
+ t(:Command, "ag.core", lineno:0)
36
+ ]}
37
+ end
38
+
39
+ describe "can end with periods: core." do
40
+ let(:str){ "core." }
41
+ it { should eq [
42
+ t(:Command, "core.", lineno:0)
43
+ ]}
44
+ end
45
+
46
+ describe "can contain a number: ab4cd" do
47
+ let(:str){ "ab4cd" }
48
+ it { should eq [
49
+ t(:Command, "ab4cd", lineno:0)
50
+ ]}
51
+ end
52
+
53
+ describe "can contain slashes with periods: foo/bar" do
54
+ let(:str){ "foo/bar" }
55
+ it { should eq [
56
+ t(:Command, "foo/bar", lineno:0)
57
+ ]}
58
+ end
59
+
60
+ describe "can contain asterisks: foo/ba*" do
61
+ let(:str){ "foo/ba*" }
62
+ it { should eq [
63
+ t(:Command, "foo/ba*", lineno:0)
64
+ ]}
65
+ end
66
+ end
67
+
68
+ describe "literal commands" do
69
+ describe "begin with the backslash escape: \\rm" do
70
+ let(:str){ '\rm' }
71
+ it { should eq [
72
+ t(:LiteralCommand, "rm", lineno: 0)
73
+ ]}
74
+ end
75
+
76
+ describe "can contain slashes with periods: \\foo/bar" do
77
+ let(:str){ "\\foo/bar" }
78
+ it { should eq [
79
+ t(:LiteralCommand, "foo/bar", lineno:0)
80
+ ]}
81
+ end
82
+ end
83
+
84
+ context "argument parsing" do
85
+ describe "commands with no args" do
86
+ let(:str){ "ls" }
87
+ it { should eq [
88
+ t(:Command, "ls", lineno:0)
89
+ ]}
90
+ end
91
+
92
+ describe "commands with a simple arg: ls foo" do
93
+ let(:str){ "ls foo" }
94
+ it { should eq [
95
+ t(:Command, "ls", lineno:0),
96
+ t(:Argument, "foo", lineno:0)
97
+ ]}
98
+ end
99
+
100
+ describe "commands with a simple arg: ls -al" do
101
+ let(:str){ "ls -al" }
102
+ it { should eq [
103
+ t(:Command, "ls", lineno:0),
104
+ t(:Argument, "-al", lineno:0)
105
+ ]}
106
+ end
107
+
108
+ describe "commands with multiple args: ls -al foo bar -baz" do
109
+ let(:str){ "ls -al foo bar -baz" }
110
+ it { should eq [
111
+ t(:Command, "ls", lineno:0),
112
+ t(:Argument, "-al", lineno:0),
113
+ t(:Argument, "foo", lineno:0),
114
+ t(:Argument, "bar", lineno:0),
115
+ t(:Argument, "-baz", lineno:0)
116
+ ]}
117
+ end
118
+
119
+ describe "can contain asterisks: ls foo* b*r" do
120
+ let(:str){ "ls foo* b*r" }
121
+ it { should eq [
122
+ t(:Command, "ls", lineno:0),
123
+ t(:Argument, "foo*", lineno:0),
124
+ t(:Argument, "b*r", lineno:0),
125
+ ]}
126
+ end
127
+
128
+ context "single quoted args" do
129
+ describe "simple args: ls 'hello there'" do
130
+ let(:str){ "ls 'hello there'" }
131
+ it { should eq [
132
+ t(:Command, "ls", lineno:0),
133
+ t(:Argument, "hello there", lineno:0)
134
+ ]}
135
+ end
136
+
137
+ describe "nested single quotes: ls 'hello \\'there\\''" do
138
+ let(:str){ "ls 'hello \\'there\\''" }
139
+ it { should eq [
140
+ t(:Command, "ls", lineno:0),
141
+ t(:Argument, "hello 'there'", lineno:0)
142
+ ]}
143
+ end
144
+
145
+ describe "nested double quotes: ls 'hello \"there\"'" do
146
+ let(:str){ %|ls 'hello "there"'| }
147
+ it { should eq [
148
+ t(:Command, "ls", lineno:0),
149
+ t(:Argument, 'hello "there"', lineno:0)
150
+ ]}
151
+ end
152
+
153
+ describe "multiple levels of nested single quotes: ls 'hello \\'there \\\\'guy\\\\' \\''" do
154
+ let(:str){ "ls 'hello \\'there \\\\'guy\\\\' \\''" }
155
+ it { should eq [
156
+ t(:Command, "ls", lineno:0),
157
+ t(:Argument, "hello 'there \\'guy\\' '", lineno:0)
158
+ ]}
159
+ end
160
+
161
+ describe "multiple single quoted args: ls 'hello \\'there \\'' 'how are \\'you\\'?'" do
162
+ let(:str){ "ls 'hello \\'there\\'' 'how are \\'you\\'?'" }
163
+ it { should eq [
164
+ t(:Command, "ls", lineno:0),
165
+ t(:Argument, "hello 'there'", lineno:0),
166
+ t(:Argument, "how are 'you'?", lineno:0)
167
+ ]}
168
+ end
169
+
170
+ describe "single quotes with spaces and assignment" do
171
+ let(:str){ "alias z='echo hi'" }
172
+ it { should eq [
173
+ t(:Command, "alias", lineno:0),
174
+ t(:Argument, "z=echo hi", lineno:0),
175
+ ]}
176
+ end
177
+ end
178
+
179
+ context 'double quoted args' do
180
+ describe 'simple args: ls "hello there"' do
181
+ let(:str){ 'ls "hello there"' }
182
+ it { should eq [
183
+ t(:Command, 'ls', lineno:0),
184
+ t(:Argument, 'hello there', lineno:0)
185
+ ]}
186
+ end
187
+
188
+ describe 'nested singled quotes: ls "hello \'there\'"' do
189
+ let(:str){ %|ls "hello 'there'"| }
190
+ it { should eq [
191
+ t(:Command, 'ls', lineno:0),
192
+ t(:Argument, "hello 'there'", lineno:0)
193
+ ]}
194
+ end
195
+
196
+ describe 'nested double quotes: ls "hello \\"there\\""' do
197
+ let(:str){ 'ls "hello \\"there\\""' }
198
+ it { should eq [
199
+ t(:Command, 'ls', lineno:0),
200
+ t(:Argument, 'hello "there"', lineno:0)
201
+ ]}
202
+ end
203
+
204
+ describe 'multiple levels of nested double quotes: ls "hello \\"there \\\\"guy\\\\" \\""' do
205
+ let(:str){ 'ls "hello \\"there \\\\"guy\\\\" \\""' }
206
+ it { should eq [
207
+ t(:Command, 'ls', lineno:0),
208
+ t(:Argument, 'hello "there \\"guy\\" "', lineno:0)
209
+ ]}
210
+ end
211
+
212
+ describe 'multiple double quoted args: ls "hello \\"there \\"" "how are \\"you\\"?"' do
213
+ let(:str){ 'ls "hello \\"there\\"" "how are \\"you\\"?"' }
214
+ it { should eq [
215
+ t(:Command, 'ls', lineno:0),
216
+ t(:Argument, 'hello "there"', lineno:0),
217
+ t(:Argument, 'how are "you"?', lineno:0)
218
+ ]}
219
+ end
220
+
221
+ describe "single quotes with spaces and assignment" do
222
+ let(:str){ "alias z=\"echo hi\"" }
223
+ it { should eq [
224
+ t(:Command, "alias", lineno:0),
225
+ t(:Argument, "z=echo hi", lineno:0),
226
+ ]}
227
+ end
228
+ end
229
+ end
230
+
231
+ context "statements" do
232
+ ["foo;baz", "foo; baz", "foo ;baz", "foo ; baz", "foo ; baz"].each do |src|
233
+ describe "are separated by a semi-colon: #{src.inspect}" do
234
+ let(:str){ src }
235
+ it { should eq [
236
+ t(:Command, "foo", lineno:0),
237
+ t(:Separator, ";", lineno:0),
238
+ t(:Command, "baz", lineno:0)
239
+ ]}
240
+ end
241
+ end
242
+
243
+ ["foo|baz", "foo| baz", "foo |baz", "foo | baz", "foo | baz"].each do |src|
244
+ describe "are separated by a semi-colon: #{src.inspect}" do
245
+ let(:str){ src }
246
+ it { should eq [
247
+ t(:Command, "foo", lineno:0),
248
+ t(:Pipe, "|", lineno:0),
249
+ t(:Command, "baz", lineno:0)
250
+ ]}
251
+ end
252
+ end
253
+
254
+ ["foo&&baz", "foo&& baz", "foo &&baz", "foo && baz", "foo && baz"].each do |src|
255
+ describe "are separated by double ampersands: #{src.inspect}" do
256
+ let(:str){ src }
257
+ it { should eq [
258
+ t(:Command, "foo", lineno:0),
259
+ t(:Conditional, "&&", lineno:0),
260
+ t(:Command, "baz", lineno:0)
261
+ ]}
262
+ end
263
+ end
264
+
265
+ ["foo||baz", "foo|| baz", "foo ||baz", "foo || baz", "foo || baz"].each do |src|
266
+ describe "are separated by double ampersands: #{src.inspect}" do
267
+ let(:str){ src }
268
+ it { should eq [
269
+ t(:Command, "foo", lineno:0),
270
+ t(:Conditional, "||", lineno:0),
271
+ t(:Command, "baz", lineno:0)
272
+ ]}
273
+ end
274
+ end
275
+ end
276
+
277
+ context "heredocs" do
278
+ describe "started with double arrows followed by a character: foo <<E" do
279
+ let(:str){ "foo <<E\nfoo\nbar\nE" }
280
+ it { should eq [
281
+ t(:Command, "foo", lineno:0),
282
+ t(:Heredoc, "foo\nbar\n", lineno:0)
283
+ ]}
284
+ end
285
+
286
+ describe "started with double arrows followed by multiple character: foo <<FOO" do
287
+ let(:str){ "foo <<FOO\nhere\nwe\ngo\nFOO"}
288
+ it { should eq [
289
+ t(:Command, "foo", lineno:0),
290
+ t(:Heredoc, "here\nwe\ngo\n", lineno:0)
291
+ ]}
292
+ end
293
+
294
+ describe "started with double arrows followed by numbers characters: foo <<12" do
295
+ let(:str){ "foo <<12\nnumbers\nman\n12" }
296
+ it { should eq [
297
+ t(:Command, "foo", lineno:0),
298
+ t(:Heredoc, "numbers\nman\n", lineno:0)
299
+ ]}
300
+ end
301
+
302
+ describe "started with double arrows followed by a mixture of alphanumeric characters: foo <<L337" do
303
+ let(:str){ "foo <<L337\nhere\nwe\ngo\nL337"}
304
+ it { should eq [
305
+ t(:Command, "foo", lineno:0),
306
+ t(:Heredoc, "here\nwe\ngo\n", lineno:0)
307
+ ]}
308
+ end
309
+
310
+ describe "started with a <<-: foo <<-L337" do
311
+ let(:str){ "foo <<-L337\nhere\nwe\ngo\nL337"}
312
+ it { should eq [
313
+ t(:Command, "foo", lineno:0),
314
+ t(:Heredoc, "here\nwe\ngo\n", lineno:0)
315
+ ]}
316
+ end
317
+
318
+ describe "the ending line can have leading whitespace which isn't consumed" do
319
+ let(:str){ "foo <<L337\nhere\nwe\ngo\n \t L337"}
320
+ it { should eq [
321
+ t(:Command, "foo", lineno:0),
322
+ t(:Heredoc, "here\nwe\ngo\n", lineno:0)
323
+ ]}
324
+ end
325
+
326
+ describe "the ending line can have trailing whitespace which isn't consumed" do
327
+ let(:str){ "foo <<L337\nhere\nwe\ngo\nL337 \t "}
328
+ it { should eq [
329
+ t(:Command, "foo", lineno:0),
330
+ t(:Heredoc, "here\nwe\ngo\n", lineno:0)
331
+ ]}
332
+ end
333
+
334
+ describe "the ending line can be followed by a newline" do
335
+ let(:str){ "foo <<L337\nhere\nwe\ngo\nL337\n"}
336
+ it { should eq [
337
+ t(:Command, "foo", lineno:0),
338
+ t(:Heredoc, "here\nwe\ngo\n", lineno:0)
339
+ ]}
340
+ end
341
+
342
+ describe "the ending line cannot have non-whitespace characters beyond the delimiter" do
343
+ let(:str){ "foo <<L337\nhere\nwe\ngo\n this is bad L337"}
344
+ it "raises an error" do
345
+ expect{ subject }.to raise_error
346
+ end
347
+ end
348
+ end
349
+
350
+ context "internal evaluations" do
351
+ describe "started by a exclamation point: !to_s" do
352
+ let(:str){ "!to_s" }
353
+ it { should eq [
354
+ t(:InternalEval, "to_s", lineno:0)
355
+ ]}
356
+ end
357
+
358
+ describe "keeps strings" do
359
+ let(:str){ "!Dir.chdir('..')" }
360
+ it { should eq [
361
+ t(:InternalEval, "Dir.chdir('..')", lineno:0)
362
+ ]}
363
+ end
364
+
365
+ describe "can handle quoted strings: !a + 'b' + \"c\" + d" do
366
+ let(:str){ "!a + 'b' + \"c\" + d" }
367
+ it { should eq [
368
+ t(:InternalEval, "a + 'b' + \"c\" + d", lineno:0)
369
+ ]}
370
+ end
371
+
372
+ describe "can handle {/} blocks: !foo.map{ |bar| bar + 1 }" do
373
+ let(:str){ "!foo.map{ |bar| bar + 1 }" }
374
+ it { should eq [
375
+ t(:InternalEval, "foo.map{ |bar| bar + 1 }", lineno:0)
376
+ ]}
377
+ end
378
+
379
+ describe "can handle parentheses in method calls: !foo.map(&:bar)" do
380
+ let(:str){ "!foo.map(&:bar)" }
381
+ it { should eq [
382
+ t(:InternalEval, "foo.map(&:bar)", lineno:0)
383
+ ]}
384
+ end
385
+
386
+ describe "doesn't consume beyond a semi-colon terminator into other commands: !foo.map(&:bar) ; grep fox" do
387
+ let(:str){ "!foo.map(&:bar) ; grep fox" }
388
+ it { should eq [
389
+ t(:InternalEval, "foo.map(&:bar)", lineno:0),
390
+ t(:Separator, ";", lineno:0),
391
+ t(:Command, "grep", lineno:0),
392
+ t(:Argument, "fox", lineno:0)
393
+ ]}
394
+ end
395
+
396
+ describe "doesn't consume beyond a pipe terminator into other commands: !foo.map(&:bar) | grep fox" do
397
+ let(:str){ "!foo.map(&:bar) | grep fox" }
398
+ it { should eq [
399
+ t(:InternalEval, "foo.map(&:bar)", lineno:0),
400
+ t(:Pipe, "|", lineno:0),
401
+ t(:Command, "grep", lineno:0),
402
+ t(:Argument, "fox", lineno:0)
403
+ ]}
404
+ end
405
+
406
+ describe "doesn't consume beyond a pipe terminator into other commands: !downcase | sleep 4" do
407
+ let(:str){ "!downcase | sleep 4" }
408
+ it { should eq [
409
+ t(:InternalEval, "downcase", lineno:0),
410
+ t(:Pipe, "|", lineno:0),
411
+ t(:Command, "sleep", lineno:0),
412
+ t(:Argument, "4", lineno:0)
413
+ ]}
414
+ end
415
+
416
+ describe "doesn't consume beyond a double-ampersand terminator into other commands: !foo.map(&:bar) && grep fox" do
417
+ let(:str){ "!foo.map(&:bar) && grep fox" }
418
+ it { should eq [
419
+ t(:InternalEval, "foo.map(&:bar)", lineno:0),
420
+ t(:Conditional, "&&", lineno:0),
421
+ t(:Command, "grep", lineno:0),
422
+ t(:Argument, "fox", lineno:0)
423
+ ]}
424
+ end
425
+
426
+ describe "doesn't consume beyond a double-pipe terminator into other commands: !foo.map(&:bar) || grep fox" do
427
+ let(:str){ "!foo.map(&:bar) || grep fox" }
428
+ it { should eq [
429
+ t(:InternalEval, "foo.map(&:bar)", lineno:0),
430
+ t(:Conditional, "||", lineno:0),
431
+ t(:Command, "grep", lineno:0),
432
+ t(:Argument, "fox", lineno:0)
433
+ ]}
434
+ end
435
+ end
436
+
437
+ describe 'grouping statements' do
438
+ describe 'simple one command: (bar)' do
439
+ let(:str){ "(bar)" }
440
+ it { should eq [
441
+ t('(', '(', lineno:0),
442
+ t(:Command, "bar", lineno:0),
443
+ t(')', ')', lineno:0)
444
+ ]}
445
+ end
446
+
447
+ describe 'simple interval eval: (!bar)' do
448
+ let(:str){ "(!bar)" }
449
+ it { should eq [
450
+ t('(', '(', lineno:0),
451
+ t(:InternalEval, "bar", lineno:0),
452
+ t(')', ')', lineno:0)
453
+ ]}
454
+ end
455
+
456
+ describe 'complicated interval eval: (!foo.map(&:bar).map{ |fasdf| baz })' do
457
+ let(:str){ "(!foo.map(&:bar).map{ |fasdf| baz })" }
458
+ it { should eq [
459
+ t('(', '(', lineno:0),
460
+ t(:InternalEval, "foo.map(&:bar).map{ |fasdf| baz }", lineno:0),
461
+ t(')', ')', lineno:0)
462
+ ]}
463
+ end
464
+
465
+ describe 'multiple commands: (bar ; baz && foo | yep' do
466
+ let(:str){ '(bar ; baz && foo | yep)' }
467
+ it { should eq [
468
+ t('(', '(', lineno:0),
469
+ t(:Command, "bar", lineno:0),
470
+ t(:Separator, ";", lineno:0),
471
+ t(:Command, "baz", lineno:0),
472
+ t(:Conditional, "&&", lineno:0),
473
+ t(:Command, "foo", lineno:0),
474
+ t(:Pipe, "|", lineno:0),
475
+ t(:Command, "yep", lineno:0),
476
+ t(')', ')', lineno:0)
477
+ ]}
478
+ end
479
+ end
480
+
481
+ describe "redirections" do
482
+ describe "stdin" do
483
+ describe "can come after the command with no spaces: foo<bar.txt" do
484
+ let(:str){ "foo<bar.txt" }
485
+ it { should eq [
486
+ t(:Command, "foo", lineno: 0),
487
+ t(:Redirection, "<", lineno: 0, attrs: { target: "bar.txt" }),
488
+ ]}
489
+ end
490
+
491
+ describe "can come after the command with spaces after the command: foo <bar.txt" do
492
+ let(:str){ "foo <bar.txt" }
493
+ it { should eq [
494
+ t(:Command, "foo", lineno: 0),
495
+ t(:Redirection, "<", lineno: 0, attrs: { target: "bar.txt" }),
496
+ ]}
497
+ end
498
+
499
+ describe "can come after the command with spaces after the redirect: foo < /path/to/bar.txt" do
500
+ let(:str){ "foo < /path/to/bar.txt" }
501
+ it { should eq [
502
+ t(:Command, "foo", lineno: 0),
503
+ t(:Redirection, "<", lineno: 0, attrs: { target: "/path/to/bar.txt" }),
504
+ ]}
505
+ end
506
+
507
+ describe "can come after command arguments: ls -al < a.txt" do
508
+ let(:str){ "ls -al < a.txt" }
509
+ it { should eq [
510
+ t(:Command, "ls", lineno: 0),
511
+ t(:Argument, "-al", lineno: 0),
512
+ t(:Redirection, "<", lineno: 0, attrs: { target: "a.txt" }),
513
+ ]}
514
+ end
515
+ end
516
+
517
+ describe "stdout" do
518
+ describe "can come after the command with no spaces" do
519
+ let(:str){ "foo>bar.txt" }
520
+ it { should eq [
521
+ t(:Command, "foo", lineno: 0),
522
+ t(:Redirection, ">", lineno: 0, attrs: { target: "bar.txt" }),
523
+ ]}
524
+ end
525
+
526
+ describe "can come after the command with spaces after the command" do
527
+ let(:str){ "foo >bar.txt" }
528
+ it { should eq [
529
+ t(:Command, "foo", lineno: 0),
530
+ t(:Redirection, ">", lineno: 0, attrs: { target: "bar.txt" }),
531
+ ]}
532
+ end
533
+
534
+ describe "can come after the command with spaces after the redirect" do
535
+ let(:str){ "foo > bar.txt" }
536
+ it { should eq [
537
+ t(:Command, "foo", lineno: 0),
538
+ t(:Redirection, ">", lineno: 0, attrs: { target: "bar.txt" }),
539
+ ]}
540
+ end
541
+
542
+ describe "can be specified numerically: 1>" do
543
+ let(:str){ "foo 1> bar.txt" }
544
+ it { should eq [
545
+ t(:Command, "foo", lineno: 0),
546
+ t(:Redirection, "1>", lineno: 0, attrs: { target: "bar.txt" }),
547
+ ]}
548
+ end
549
+
550
+ describe "can come after command arguments" do
551
+ let(:str){ "ls -al > a.txt" }
552
+ it { should eq [
553
+ t(:Command, "ls", lineno: 0),
554
+ t(:Argument, "-al", lineno: 0),
555
+ t(:Redirection, ">", lineno: 0, attrs: { target: "a.txt" }),
556
+ ]}
557
+ end
558
+ end
559
+
560
+ describe "stderr" do
561
+ describe "without spaces after the command it cannot be redirected" do
562
+ let(:str){ "foo2>bar.txt" }
563
+ it { should eq [
564
+ t(:Command, "foo2", lineno: 0),
565
+ t(:Redirection, ">", lineno: 0, attrs: { target: "bar.txt" }),
566
+ ]}
567
+ end
568
+
569
+ describe "it comes after the command with spaces after the command" do
570
+ let(:str){ "foo 2>bar.txt" }
571
+ it { should eq [
572
+ t(:Command, "foo", lineno: 0),
573
+ t(:Redirection, "2>", lineno: 0, attrs: { target: "bar.txt" }),
574
+ ]}
575
+ end
576
+
577
+ describe "it comes after the command with spaces after the redirect" do
578
+ let(:str){ "foo 2> bar.txt" }
579
+ it { should eq [
580
+ t(:Command, "foo", lineno: 0),
581
+ t(:Redirection, "2>", lineno: 0, attrs: { target: "bar.txt" }),
582
+ ]}
583
+ end
584
+ end
585
+
586
+ describe "stdout / stderr" do
587
+ describe "stdout redirecting to stderr: foo 1>&2" do
588
+ let(:str){ "foo 1>&2" }
589
+ it { should eq [
590
+ t(:Command, "foo", lineno: 0),
591
+ t(:Redirection, "1>&2", lineno: 0, attrs: { target: nil }),
592
+ ]}
593
+ end
594
+
595
+ describe "stderr redirecting to stdout: foo 2>&1" do
596
+ let(:str){ "foo 2>&1" }
597
+ it { should eq [
598
+ t(:Command, "foo", lineno: 0),
599
+ t(:Redirection, "2>&1", lineno: 0, attrs: { target: nil }),
600
+ ]}
601
+ end
602
+
603
+ describe "stdout redirecting to stderr with a file: foo 1>&2 bar.txt (bash incompatible)" do
604
+ let(:str){ "foo 1>&2 bar.txt" }
605
+ it { should eq [
606
+ t(:Command, "foo", lineno: 0),
607
+ # TODO: This is bash incompatible. Keep it?
608
+ t(:Redirection, "1>&2", lineno: 0, attrs: { target: "bar.txt" })
609
+ ]}
610
+ end
611
+
612
+ describe "stderr redirecting to stdout with a file: foo 2>&1 bar.txt (bash incompatible)" do
613
+ let(:str){ "foo 2>&1 bar.txt" }
614
+ it { should eq [
615
+ t(:Command, "foo", lineno: 0),
616
+ # TODO: This is bash incompatible. Keep it?
617
+ t(:Redirection, "2>&1", lineno: 0, attrs: { target: "bar.txt" })
618
+ ]}
619
+ end
620
+
621
+ describe "stdout and stderr redirecting to a file together: foo &> /dev/null" do
622
+ let(:str){ "foo &> /dev/null" }
623
+ it { should eq [
624
+ t(:Command, "foo", lineno: 0),
625
+ t(:Redirection, "&>", lineno: 0, attrs: { target: "/dev/null" })
626
+ ]}
627
+ end
628
+
629
+ describe "stdout and sdterr redirecting separately: foo 2> err.txt 1> out.txt" do
630
+ let(:str){ "foo 2> err.txt 1> out.txt" }
631
+ it { should eq [
632
+ t(:Command, "foo", lineno: 0),
633
+ t(:Redirection, "2>", lineno: 0, attrs: { target: "err.txt" }),
634
+ t(:Redirection, "1>", lineno: 0, attrs: { target: "out.txt" })
635
+ ]}
636
+ end
637
+
638
+ describe "stdout and sdterr redirecting separately: du -sh 1>&2 1>hey.txt" do
639
+ let(:str){ "du -sh 2>&1 1>hey.txt" }
640
+ it { should eq [
641
+ t(:Command, "du", lineno: 0),
642
+ t(:Argument, "-sh", lineno: 0),
643
+ t(:Redirection, "2>&1", lineno: 0, attrs: { target: nil }),
644
+ t(:Redirection, "1>", lineno: 0, attrs: { target: "hey.txt" })
645
+ ]}
646
+ end
647
+ end
648
+ end
649
+
650
+ describe "variables" do
651
+ describe "one can be assigned its their own: FOO=123" do
652
+ let(:str){ "FOO=123" }
653
+ it { should eq [
654
+ t(:LValue, "FOO", lineno: 0),
655
+ t(:RValue, "123", lineno: 0)
656
+ ]}
657
+ end
658
+
659
+ describe "many can be assigned on their own: FOO=123 BAR=a_c BAZ=4-5:6" do
660
+ let(:str){ "FOO=123 BAR=a_c BAZ=4-5:6" }
661
+ it { should eq [
662
+ t(:LValue, "FOO", lineno: 0),
663
+ t(:RValue, "123", lineno: 0),
664
+ t(:LValue, "BAR", lineno: 0),
665
+ t(:RValue, "a_c", lineno: 0),
666
+ t(:LValue, "BAZ", lineno: 0),
667
+ t(:RValue, "4-5:6", lineno: 0)
668
+ ]}
669
+ end
670
+
671
+ describe "can be assigned before a command: FOO=123 echo $FOO" do
672
+ let(:str){ "FOO=123 echo $FOO" }
673
+ it { should eq [
674
+ t(:LValue, "FOO", lineno: 0),
675
+ t(:RValue, "123", lineno: 0),
676
+ t(:Command, "echo", lineno: 0),
677
+ t(:Argument, "$FOO", lineno: 0)
678
+ ]}
679
+ end
680
+
681
+ describe "can be assigned before a command: FOO=abc BAR='hello world' ls -l" do
682
+ let(:str){ "FOO=abc BAR='hello world' ls -l" }
683
+ it { should eq [
684
+ t(:LValue, "FOO", lineno: 0),
685
+ t(:RValue, "abc", lineno: 0),
686
+ t(:LValue, "BAR", lineno: 0),
687
+ t(:RValue, "hello world", lineno: 0),
688
+ t(:Command, "ls", lineno: 0),
689
+ t(:Argument, "-l", lineno: 0)
690
+ ]}
691
+ end
692
+
693
+
694
+
695
+ end
696
+
697
+ end