yap-shell 0.5.2 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +2 -0
  3. data/.ruby-version +1 -1
  4. data/.travis.yml +11 -0
  5. data/Gemfile.travis +8 -0
  6. data/README.md +9 -11
  7. data/addons/history/history.rb +1 -0
  8. data/addons/history_search/history_search.rb +21 -17
  9. data/bin/yap +47 -55
  10. data/bin/yap-dev +0 -1
  11. data/lib/yap/configuration.rb +25 -0
  12. data/lib/yap/shell/builtins/alias.rb +16 -0
  13. data/lib/yap/shell/commands.rb +15 -3
  14. data/lib/yap/shell/evaluation/shell_expansions.rb +10 -10
  15. data/lib/yap/shell/evaluation.rb +29 -4
  16. data/lib/yap/shell/execution/context.rb +3 -2
  17. data/lib/yap/shell/execution/file_system_command_execution.rb +9 -17
  18. data/lib/yap/shell/repl.rb +2 -5
  19. data/lib/yap/shell/version.rb +1 -1
  20. data/lib/yap/shell.rb +8 -2
  21. data/lib/yap/world.rb +23 -18
  22. data/lib/yap.rb +52 -7
  23. data/spec/features/aliases_spec.rb +78 -0
  24. data/spec/features/environment_variables_spec.rb +69 -0
  25. data/spec/features/filesystem_commands_spec.rb +61 -0
  26. data/spec/features/first_time_spec.rb +45 -0
  27. data/spec/features/grouping_spec.rb +81 -0
  28. data/spec/features/line_editing_spec.rb +166 -0
  29. data/spec/features/range_spec.rb +35 -0
  30. data/spec/features/redirection_spec.rb +225 -0
  31. data/spec/features/repetition_spec.rb +118 -0
  32. data/spec/features/shell_expansions_spec.rb +127 -0
  33. data/spec/spec_helper.rb +162 -0
  34. data/spec/support/matchers/have_not_printed.rb +30 -0
  35. data/spec/support/matchers/have_printed.rb +30 -0
  36. data/spec/support/very_soon.rb +9 -0
  37. data/spec/support/yap_spec_dsl.rb +240 -0
  38. data/yap-shell.gemspec +4 -2
  39. metadata +69 -8
data/lib/yap.rb CHANGED
@@ -1,17 +1,62 @@
1
- require 'treefell'
1
+ require "term/ansicolor"
2
+ require "tins"
3
+ require "byebug"
4
+ require "pry"
5
+ require "treefell"
6
+ require 'optparse'
2
7
 
3
8
  module Yap
4
9
  require 'yap/configuration'
5
10
  require 'yap/shell'
6
11
  require 'yap/world'
7
12
 
8
- def self.run_shell
13
+ def self.parse_cli_args_for_configuration(args, configuration)
14
+ OptionParser.new do |opts|
15
+ opts.on('-h', '--help', 'Prints this help') do
16
+ puts opts
17
+ exit
18
+ end
19
+
20
+ opts.on('--skip-first-time', 'Disables creating ~/.yap directory on shell startup') do
21
+ configuration.skip_first_time = true
22
+ end
23
+
24
+ opts.on('--no-addons', 'Disables auto-loading addons on shell startup') do
25
+ configuration.use_addons = false
26
+ end
27
+
28
+ opts.on('--no-history', 'Disables auto-loading or saving history') do
29
+ configuration.use_history = false
30
+ end
31
+
32
+ opts.on('--no-rcfiles', 'Disables auto-loading rcfiles on shell startup') do
33
+ configuration.use_rcfiles = false
34
+ end
35
+ end.parse!(args)
36
+ end
37
+
38
+ def self.run_shell(argv)
9
39
  Treefell['shell'].puts "#{self}.#{__callee__} booting shell"
10
- addons = [
11
- World::Addons.load_directories(configuration.addon_paths),
12
- World::Addons.load_rcfiles(configuration.rcfiles)
13
- ].flatten
14
40
 
15
- Shell::Impl.new(addons: addons).repl
41
+ parse_cli_args_for_configuration(argv, configuration)
42
+
43
+ addons_loaded = []
44
+ if configuration.use_addons?
45
+ Treefell['shell'].puts "#{self}.#{__callee__} loading addons"
46
+ addons_loaded.concat \
47
+ World::Addons.load_directories(configuration.addon_paths)
48
+ else
49
+ Treefell['shell'].puts "#{self}.#{__callee__} skipping addons"
50
+ end
51
+
52
+ if configuration.use_rcfiles?
53
+ Treefell['shell'].puts "#{self}.#{__callee__} loading rcfiles"
54
+ addons_loaded.concat \
55
+ World::Addons.load_rcfiles(configuration.rcfiles)
56
+ else
57
+ Treefell['shell'].puts "#{self}.#{__callee__} skipping rcfiles"
58
+ end
59
+
60
+ Shell::Impl.new(addons: addons_loaded).repl
16
61
  end
17
62
  end
@@ -0,0 +1,78 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Aliases', type: :feature do
4
+ before do
5
+ type "alias foo='echo bar'"
6
+ enter
7
+ expect { output }.to have_printed(/Setting alias foo done/)
8
+ end
9
+
10
+ it 'sets them' do
11
+ type 'alias | grep foo'
12
+ enter
13
+ expect { output }.to have_printed(/alias foo='echo bar'/)
14
+ end
15
+
16
+ it 'executes them' do
17
+ type 'foo'
18
+ enter
19
+ expect { output }.to have_printed(/bar\n/)
20
+ end
21
+
22
+ it 'unsets them' do
23
+ type 'unalias foo'
24
+ enter
25
+ expect { output }.to have_printed(/Removing alias foo done\n/)
26
+
27
+ type 'alias | grep foo'
28
+ enter
29
+ expect(output).to_not match(/alias foo='echo bar'/)
30
+ end
31
+
32
+ describe 'conditionals' do
33
+ before do
34
+ type "alias fail='non-existent-command'"
35
+ enter
36
+ type "alias pass='echo pass'"
37
+ enter
38
+ clear_all_output
39
+ end
40
+
41
+ it 'supports logical AND: &&' do
42
+ type "pass && pass"
43
+ enter
44
+ expect { output }.to have_printed(/pass.*\n.*pass/m)
45
+ end
46
+
47
+ it 'supports logical OR: &&' do
48
+ type "fail || pass"
49
+ enter
50
+ expect { error_output }.to have_printed(/yap: command not found: non-existent-command/m)
51
+ expect { output }.to have_printed(/pass/m)
52
+ clear_all_output
53
+
54
+ type "echo $?"
55
+ enter
56
+ expect { output }.to have_printed(/0/m)
57
+ end
58
+
59
+ it 'can combine logical AND and OR' do
60
+ type "(pass && fail) || pass"
61
+ enter
62
+ expect { error_output }.to have_printed(/yap: command not found: non-existent-command/m)
63
+ expect { output }.to have_printed(/pass.*\n.*pass/m)
64
+ clear_all_output
65
+
66
+ type "(pass || fail) && pass"
67
+ enter
68
+ expect { error_output }.to have_not_printed(/yap: command not found: non-existent-command/m)
69
+ expect { output }.to have_printed(/pass.*\n.*pass/m)
70
+ clear_all_output
71
+
72
+ type "(fail && pass) && pass"
73
+ enter
74
+ expect { error_output }.to have_printed(/yap: command not found: non-existent-command/m)
75
+ expect { output }.to have_not_printed(/pass.*\n.*pass/m)
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Environment variables', type: :feature do
4
+ it 'sets and gets them with: NAME=value' do
5
+ type 'FOO=foobarbaz'
6
+ enter
7
+
8
+ type 'echo $FOO'
9
+ enter
10
+ expect { output }.to have_printed('foobarbaz')
11
+ end
12
+
13
+ it 'can set multiple on a single line: NAME1=value1 NAME2=value2 etc' do
14
+ type 'A=b B=c C=d'
15
+ enter
16
+ clear_all_output
17
+
18
+ type 'echo $A $B $C'
19
+ enter
20
+ expect { output }.to have_printed('b c d')
21
+ clear_all_output
22
+
23
+ type 'echo $A$B$C'
24
+ enter
25
+ expect { output }.to have_printed('bcd')
26
+ end
27
+
28
+ it 'expands env vars inside of double quotes' do
29
+ type 'BAR=foobarbaz'
30
+ enter
31
+
32
+ type %|echo "BAR is $BAR"|
33
+ enter
34
+ expect { output }.to have_printed('BAR is foobarbaz')
35
+ end
36
+
37
+ it 'does not expand env vars inside of single quotes' do
38
+ type 'BAR=foobarbaz'
39
+ enter
40
+
41
+ type %|echo 'BAR is $BAR'|
42
+ enter
43
+ expect { output }.to have_printed('BAR is $BAR')
44
+ end
45
+
46
+ describe 'setting env vars for a single statement' do
47
+ it 'makes the env var available to the statement' do
48
+ type 'BAZ=baz echo $BAZ'
49
+ enter
50
+ expect { output }.to have_printed('baz')
51
+ end
52
+
53
+ it 'does not keep the env var around for the next statement' do
54
+ type 'BAZ=baz echo $BAZ'
55
+ enter
56
+ clear_all_output
57
+
58
+ type 'echo $BAZ'
59
+ enter
60
+ expect { output }.to have_printed('$BAZ')
61
+ end
62
+
63
+ it 'can set multiple env vars for a statement' do
64
+ type %|FOO=foo BAR='is the' BAZ="fastest pineapple" echo $FOO $BAR $BAZ|
65
+ enter
66
+ expect { output }.to have_printed('foo is the fastest pineapple')
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Filesystem commands', type: :feature do
4
+ it 'runs filesystem commands' do
5
+ touch 'bar.txt'
6
+ type 'ls .'
7
+ enter
8
+
9
+ expect { output }.to have_printed("bar.txt")
10
+
11
+ type 'echo "hello there"'
12
+ enter
13
+ expect { output }.to have_printed("hello there\n")
14
+ clear_all_output
15
+ end
16
+
17
+ it 'pipes filesystem commands' do
18
+ type "echo 'food' | sed -e 's/o/_/g'"
19
+ enter
20
+ expect { output }.to have_printed(/^f__d\n/m)
21
+ end
22
+
23
+ describe 'conditionals' do
24
+ it 'supports logical AND: &&' do
25
+ type "echo foo && echo bar"
26
+ enter
27
+ expect { output }.to have_printed(/foo.*\n.*bar/m)
28
+ end
29
+
30
+ it 'supports logical OR: &&' do
31
+ type "non-existent-command || echo bar"
32
+ enter
33
+ expect { error_output }.to have_printed(/yap: command not found: non-existent-command/m)
34
+ expect { output }.to have_printed(/bar/m)
35
+ clear_all_output
36
+
37
+ type "echo $?"
38
+ enter
39
+ expect { output }.to have_printed(/0/m)
40
+ end
41
+
42
+ it 'can combine logical AND and OR' do
43
+ type "(echo foo && non-existent-command) || echo bar"
44
+ enter
45
+ expect { error_output }.to have_printed(/yap: command not found: non-existent-command/m)
46
+ expect { output }.to have_printed(/foo.*\n.*bar/m)
47
+ clear_all_output
48
+
49
+ type "(echo foo || non-existent-command) && echo bar"
50
+ enter
51
+ expect { error_output }.to have_not_printed(/yap: command not found: non-existent-command/m)
52
+ expect { output }.to have_printed(/foo.*\n.*bar/m)
53
+ clear_all_output
54
+
55
+ type "(non-existent-command && echo foo) && echo bar"
56
+ enter
57
+ expect { error_output }.to have_printed(/yap: command not found: non-existent-command/m)
58
+ expect { output }.to have_not_printed(/foo.*\n.*bar/m)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Running Yap for the first time', type: :feature, forks: true do
4
+ before do
5
+ # do not pass in --skip-first-time
6
+ set_yap_command_line_arguments \
7
+ '--no-history', '--no-addons', '--no-rcfiles'
8
+
9
+ turn_on_debug_log(debug: 'editor')
10
+ reinitialize_shell
11
+ end
12
+
13
+ it 'tells the user it has been initialized' do
14
+ home_dir = tmp_dir.to_s
15
+ message = <<-EOT.gsub(/^\s*\|/, '').chomp
16
+ |Yap directory not found: #{home_dir}/.yap
17
+ |
18
+ |Initializing yap for the first time:
19
+ |
20
+ | Creating #{home_dir}/.yap done
21
+ | Creating default #{home_dir}/.yap/yaprc done
22
+ |
23
+ |To tweak yap take a look at #{home_dir}/.yap/yaprc.
24
+ |
25
+ |Reloading shell
26
+ EOT
27
+ expect { output }.to have_printed(message)
28
+ end
29
+
30
+ it 'creates a $HOME/.yap/ directory for yap things to go' do
31
+ expect { output }.to have_printed('Reloading shell')
32
+
33
+ dot_yap_dir = tmp_dir.join('.yap').to_s
34
+ expect(Dir.exist?(dot_yap_dir)).to be(true)
35
+ end
36
+
37
+ it 'creates a default .yap/yaprc rcfile' do
38
+ expect { output }.to have_printed('Reloading shell')
39
+
40
+ yaprc = tmp_dir.join('.yap/yaprc').to_s
41
+ templaterc = yap_dir.join('rcfiles/yaprc')
42
+ expect(File.exist?(yaprc)).to be(true)
43
+ expect(IO.read(yaprc)).to eq(IO.read(templaterc))
44
+ end
45
+ end
@@ -0,0 +1,81 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Grouping commands', type: :feature do
4
+ before do
5
+ write_executable_script 'pass', <<-SCRIPT.strip_heredoc
6
+ |#!/bin/sh
7
+ |echo pass $1
8
+ |exit 0
9
+ SCRIPT
10
+
11
+ write_executable_script 'fail', <<-SCRIPT.strip_heredoc
12
+ |#!/bin/sh
13
+ |echo fail $1
14
+ |exit 1
15
+ SCRIPT
16
+ end
17
+
18
+ describe 'using parentheses' do
19
+ it 'works with: (command1 && command2) || command3' do
20
+ type '(./pass 1 && ./fail 2) || ./pass 3'
21
+ enter
22
+ expect { output }.to have_printed(/pass 1.*fail 2.*pass 3/m)
23
+ clear_all_output
24
+
25
+ type '(./fail 1 && ./pass 2) || ./pass 3'
26
+ enter
27
+ expect { output }.to have_printed(/fail 1.*pass 3/m)
28
+ expect { output }.to have_not_printed(/pass 2/m)
29
+ end
30
+
31
+ it 'works with: (command1 && command2 && command3) || command4' do
32
+ type '(./pass 1 && ./fail 2 && ./pass 3) || ./pass 4'
33
+ enter
34
+ expect { output }.to have_printed(/pass 1.*fail 2.*pass 4/m)
35
+ expect { output }.to have_not_printed(/pass 1.*fail 2.*pass 3.*pass 4/m)
36
+ clear_all_output
37
+
38
+ type '(./fail 1 && ./pass 2 && ./pass 3) || ./pass 4'
39
+ enter
40
+ expect { output }.to have_printed(/fail 1.*pass 4/m)
41
+ expect { output }.to have_not_printed(/pass 2.*pass 3/m)
42
+ end
43
+
44
+ it 'works with: (command1 || command2) && command4' do
45
+ type '(./fail 1 || ./pass 2) && ./pass 3'
46
+ enter
47
+ expect { output }.to have_printed(/fail 1.*pass 2.*pass 3/m)
48
+
49
+ type '(./pass 1 || ./fail 2) && ./pass 3'
50
+ enter
51
+ expect { output }.to have_printed(/pass 1.*pass 3/m)
52
+ expect { output }.to have_not_printed(/fail 2/m)
53
+ end
54
+
55
+ it 'works with: (command1 && command2) && command4' do
56
+ type '(./fail 1 && ./pass 2) && ./pass 3'
57
+ enter
58
+ expect { output }.to have_printed(/fail 1/m)
59
+ expect { output }.to have_not_printed(/.*pass 2.*pass 3/m)
60
+ clear_all_output
61
+
62
+ type '(./pass 1 && ./fail 2) && ./pass 3'
63
+ enter
64
+ expect { output }.to have_printed(/pass 1.*fail 2/m)
65
+ expect { output }.to have_not_printed(/pass 3/m)
66
+ end
67
+
68
+ it 'works with: (command1 || command2) || command4' do
69
+ type '(./fail 1 || ./pass 2) || ./pass 3'
70
+ enter
71
+ expect { output }.to have_printed(/fail 1.*pass 2/m)
72
+ expect { output }.to have_not_printed(/pass 3/m)
73
+
74
+ type '(./pass 1 || ./fail 2) || ./pass 3'
75
+ enter
76
+ expect { output }.to have_printed(/pass 1/m)
77
+ expect { output }.to have_not_printed(/fail 2.*pass 3/m)
78
+ end
79
+ end
80
+
81
+ end
@@ -0,0 +1,166 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Line editing', type: :feature do
4
+ let(:left_arrow) { [?\e, ?[, ?D].join }
5
+ let(:right_arrow) { [?\e, ?[, ?C].join }
6
+ let(:backspace){ [?\C-?].join }
7
+ let(:delete){ [?\e, ?[, ?3, ?~].join }
8
+
9
+ it 'Left/Right arrow keys move cursor one position left/right' do
10
+ # cursor is right the 'o'
11
+ type 'echo hli'
12
+
13
+ # let's fix this
14
+ type left_arrow
15
+ type left_arrow
16
+ type 'e'
17
+
18
+ type right_arrow
19
+ type right_arrow
20
+ type 'copter'
21
+ enter
22
+ expect { output }.to have_printed('helicopter')
23
+ end
24
+
25
+ it 'Backspace deletes one character left of the cursor, moving the cursor with it' do
26
+ type 'echo hello worfd'
27
+ type left_arrow
28
+ type backspace
29
+ type 'l'
30
+ enter
31
+ expect { output }.to have_printed('hello world')
32
+ end
33
+
34
+ it 'Delete deletes the character under the cursor, leaving the cursor where it is' do
35
+ type 'echo hello world'
36
+ 6.times { type left_arrow }
37
+ 6.times { type delete }
38
+ enter
39
+ expect { output }.to have_printed('hello')
40
+ expect { output }.to have_not_printed('world')
41
+ end
42
+
43
+ describe 'line navigation (default key bindings)' do
44
+ it 'Ctrl-a moves to beginning of line' do
45
+ type 'o hello world'
46
+ type ?\C-a
47
+ type 'ech'
48
+ enter
49
+ expect { output }.to have_printed('hello world')
50
+ end
51
+
52
+ it 'Ctrl-e moves to end of line' do
53
+ type 'o hello'
54
+ type ?\C-a
55
+ type 'ech'
56
+ type ?\C-e
57
+ type ' world'
58
+ enter
59
+ expect { output }.to have_printed('hello world')
60
+ end
61
+
62
+ it 'Ctrl-b moves backward one line at a time' do
63
+ type 'echo hello ld'
64
+ type ?\C-b
65
+ type 'wor'
66
+ enter
67
+ expect { output }.to have_printed(/^hello world/)
68
+ clear_all_output
69
+
70
+ type 'echo hello world' #, ?\C-b, ?\C-b, 'foo'].join
71
+ type ?\C-b
72
+ type ?\C-b
73
+ type 'foo '
74
+ enter
75
+ expect { output }.to have_printed(/^foo hello world/)
76
+ end
77
+
78
+ it 'Ctrl-f moves forward one word at a time' do
79
+ type 'echo hello world'
80
+ type ?\C-a
81
+ type ?\C-f
82
+ type 'bob says '
83
+ enter
84
+ expect { output }.to have_printed('bob says hello world')
85
+ clear_all_output
86
+
87
+ type 'echo hello world'
88
+ type ?\C-a
89
+ type ?\C-f
90
+ type ?\C-f
91
+ type 'foo '
92
+ enter
93
+ expect { output }.to have_printed('hello foo world')
94
+ end
95
+
96
+ it 'Ctrl-k kills text forward from the cursor position' do
97
+ type 'echo hello world'
98
+ type ?\C-b
99
+ type ?\C-k
100
+ enter
101
+ expect { output }.to have_printed('hello')
102
+ clear_all_output
103
+
104
+ type 'echo hello world'
105
+ type ?\C-b
106
+ type ?\C-b
107
+ type ?\C-k
108
+ type 'foo'
109
+ enter
110
+ expect { output }.to have_printed('foo')
111
+ end
112
+
113
+ it 'Ctrl-y does not insert when there is no killed text' do
114
+ type 'echo "hello world"'
115
+ type left_arrow
116
+ type ?\C-y
117
+ enter
118
+ expect { output }.to have_printed('hello world')
119
+ clear_all_output
120
+ end
121
+
122
+ it 'Ctrl-y inserts the last killed text where the cursor is' do
123
+ type 'echo hello world'
124
+ type ?\C-b
125
+ type ?\C-k
126
+ type 'delightful '
127
+ type ?\C-y
128
+ enter
129
+ expect { output }.to have_printed('hello delightful world')
130
+ clear_all_output
131
+ end
132
+
133
+ it 'Ctrl-w deletes a word backwards, adding it to the kill ring' do
134
+ type 'echo hello world'
135
+ type ?\C-w
136
+ enter
137
+ expect { output }.to have_printed('hello')
138
+ clear_all_output
139
+
140
+ type 'echo hello world'
141
+ type ?\C-w
142
+ type ?\C-w
143
+ type 'nope'
144
+ enter
145
+ expect { output }.to have_printed('nope')
146
+ expect { output }.to have_not_printed('hello world')
147
+
148
+ type 'echo '
149
+ type ?\C-y
150
+ enter
151
+ expect { output }.to have_printed('hello')
152
+ expect { output }.to have_not_printed('world')
153
+ end
154
+
155
+ it 'Ctrl-u deletes from the cursor to the beginning of the line, adding to the killing as ring' do
156
+ type 'echo hello world'
157
+ type ?\C-u
158
+ type 'echo '
159
+ type ?\C-y
160
+ enter
161
+ expect { output }.to have_printed('echo hello world')
162
+ end
163
+
164
+ end
165
+
166
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Ranges', type: :feature do
4
+ describe 'line repetition: (M..N): statement' do
5
+ it 'runs the line N times' do
6
+ type '(0..2) : echo foo'
7
+ enter
8
+ expect { output }.to have_printed(/(.*foo){3}/m)
9
+ end
10
+ end
11
+
12
+ describe 'line repetition w/parameter: (M..N) as n: statement' do
13
+ it 'runs the line N times supplying an index' do
14
+ type '(0..2) as n: echo foo $n'
15
+ enter
16
+ expect { output }.to have_printed(/foo 0.*\n.*foo 1.*\n.*foo 2/m)
17
+ end
18
+ end
19
+
20
+ describe 'block repetition: (M..N) { statement } ; echo bar' do
21
+ it 'runs the block N times' do
22
+ type 'echo baz; (0..2) { echo foo } ; echo bar'
23
+ enter
24
+ expect { output }.to have_printed(/baz.*\n(.*foo){3}.*\n.*bar/m)
25
+ end
26
+ end
27
+
28
+ describe 'block repetition w/parameter: (M..N) { |n| statement }' do
29
+ it 'runs the block N times supplying an index' do
30
+ type 'echo baz ; (0..2) { |n| echo foo $n } ; echo bar'
31
+ enter
32
+ expect { output }.to have_printed(/baz.*\n.*foo 0.*\n.*foo 1.*\n.*foo 2.*\n.*bar/m)
33
+ end
34
+ end
35
+ end