yap-shell 0.5.2 → 0.6.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.
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