yap-shell 0.4.0 → 0.4.1
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/.gitignore +2 -0
- data/addons/history/history.rb +2 -0
- data/addons/keyboard_macros/keyboard_macros.rb +60 -2
- data/addons/tab_completion/tab_completion.rb +13 -0
- data/bin/yap-dev +0 -4
- data/lib/tasks/gem.rake +1 -1
- data/lib/yap.rb +3 -0
- data/lib/yap/shell.rb +12 -0
- data/lib/yap/shell/aliases.rb +18 -4
- data/lib/yap/shell/builtins/alias.rb +1 -1
- data/lib/yap/shell/commands.rb +12 -0
- data/lib/yap/shell/evaluation.rb +77 -10
- data/lib/yap/shell/evaluation/shell_expansions.rb +22 -8
- data/lib/yap/shell/execution/builtin_command_execution.rb +5 -1
- data/lib/yap/shell/execution/context.rb +8 -0
- data/lib/yap/shell/execution/file_system_command_execution.rb +20 -7
- data/lib/yap/shell/execution/ruby_command_execution.rb +60 -65
- data/lib/yap/shell/execution/shell_command_execution.rb +3 -0
- data/lib/yap/shell/repl.rb +14 -5
- data/lib/yap/shell/version.rb +1 -1
- data/lib/yap/world.rb +4 -0
- data/lib/yap/world/addons.rb +56 -13
- data/rcfiles/.yaprc +7 -1
- data/yap-shell.gemspec +2 -1
- metadata +18 -4
@@ -9,24 +9,30 @@ module Yap::Shell
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def expand_aliases_in(input)
|
12
|
+
Treefell['shell'].puts "shell-expansions expand aliases in: #{input.inspect}"
|
12
13
|
head, *tail = input.split(/\s/, 2).first
|
13
|
-
if
|
14
|
+
expanded = if aliases.has_key?(head)
|
15
|
+
new_head=aliases.fetch_alias(head)
|
14
16
|
[new_head].concat(tail).join(" ")
|
15
17
|
else
|
16
18
|
input
|
17
19
|
end
|
20
|
+
expanded
|
18
21
|
end
|
19
22
|
|
20
23
|
def expand_words_in(input, escape_directory_expansions: true)
|
21
|
-
[
|
24
|
+
Treefell['shell'].puts "shell-expansions expand words in: #{input.inspect}"
|
25
|
+
expanded = [input].flatten.inject([]) do |results,str|
|
22
26
|
results << process_expansions(
|
23
27
|
word_expand(str),
|
24
28
|
escape_directory_expansions: escape_directory_expansions
|
25
29
|
)
|
26
30
|
end.flatten
|
31
|
+
expanded
|
27
32
|
end
|
28
33
|
|
29
34
|
def expand_variables_in(input)
|
35
|
+
Treefell['shell'].puts "shell-expansions expand variables in: #{input.inspect}"
|
30
36
|
env_expand(input)
|
31
37
|
end
|
32
38
|
|
@@ -35,11 +41,16 @@ module Yap::Shell
|
|
35
41
|
def env_expand(str)
|
36
42
|
str.gsub(/\$(\S+)/) do |match,*args|
|
37
43
|
var_name = match[1..-1]
|
38
|
-
|
39
|
-
|
40
|
-
|
44
|
+
if var_name == '?'
|
45
|
+
(world.last_result ? world.last_result.status_code.to_s : '0').tap do |expanded|
|
46
|
+
Treefell['shell'].puts "shell-expansions expanding env var #{match} to #{expanded}"
|
47
|
+
end
|
48
|
+
elsif world.env.has_key?(var_name)
|
49
|
+
world.env.fetch(var_name).tap do |expanded|
|
50
|
+
Treefell['shell'].puts "shell-expansions expanding env var #{match} to #{expanded}"
|
51
|
+
end
|
41
52
|
else
|
42
|
-
|
53
|
+
match
|
43
54
|
end
|
44
55
|
end
|
45
56
|
end
|
@@ -53,10 +64,13 @@ module Yap::Shell
|
|
53
64
|
# at least one comma listed. E.g. "a_{1,2}" => "a_1 a_2" whereas
|
54
65
|
# "a_{1}" => "a_{1}"
|
55
66
|
if expansions.length > 1
|
56
|
-
|
67
|
+
expanded = expansions.map { |expansion| str.sub(/\{([^\}]+)\}/, expansion) }.tap do |expanded|
|
68
|
+
Treefell['shell'].puts "shell-expansions expanding words in #{str} to #{expanded}"
|
69
|
+
end
|
70
|
+
return expanded
|
57
71
|
end
|
58
72
|
end
|
59
|
-
|
73
|
+
[str]
|
60
74
|
end
|
61
75
|
|
62
76
|
def process_expansions(expansions, escape_directory_expansions: true)
|
@@ -3,13 +3,17 @@ module Yap::Shell::Execution
|
|
3
3
|
|
4
4
|
class BuiltinCommandExecution < CommandExecution
|
5
5
|
on_execute do |command:, n:, of:, wait:|
|
6
|
+
Treefell['shell'].puts "builtin command executing: #{command}"
|
6
7
|
status_code = command.execute(stdin:@stdin, stdout:@stdout, stderr:@stderr)
|
7
8
|
if status_code == :resume
|
9
|
+
Treefell['shell'].puts "builtin command execution resumed: #{command}"
|
8
10
|
ResumeExecution.new(status_code:0, directory:Dir.pwd, n:n, of:of)
|
9
11
|
else
|
10
12
|
@stdout.close if @stdout != $stdout && !@stdout.closed?
|
11
13
|
@stderr.close if @stderr != $stderr && !@stderr.closed?
|
12
|
-
Result.new(status_code:status_code, directory:Dir.pwd, n:n, of:of)
|
14
|
+
Result.new(status_code:status_code, directory:Dir.pwd, n:n, of:of).tap do |result|
|
15
|
+
Treefell['shell'].puts "builtin command execution done with result=#{result.inspect}"
|
16
|
+
end
|
13
17
|
end
|
14
18
|
end
|
15
19
|
end
|
@@ -60,12 +60,15 @@ module Yap::Shell::Execution
|
|
60
60
|
)
|
61
61
|
|
62
62
|
@saved_tty_attrs = Termios.tcgetattr(STDIN)
|
63
|
+
Treefell['shell'].puts "firing :before_execute for #{command}"
|
63
64
|
self.class.fire :before_execute, world, command: command
|
64
65
|
|
65
66
|
begin
|
66
67
|
result = execution_context.execute(command:command, n:i, of:of, wait:wait)
|
67
68
|
rescue Exception => ex
|
68
69
|
raise(ex) if ex.is_a?(SystemExit)
|
70
|
+
|
71
|
+
Treefell['shell'].puts "rescued unexpected error=#{ex} with message=#{ex.message.inspect}"
|
69
72
|
puts <<-ERROR.gsub(/^\s*\|/, '')
|
70
73
|
|******************************
|
71
74
|
|\e[31mWhoops! An unexpected error has occurred\e[0m
|
@@ -83,6 +86,7 @@ module Yap::Shell::Execution
|
|
83
86
|
ERROR
|
84
87
|
end
|
85
88
|
|
89
|
+
Treefell['shell'].puts "firing :after_execute for #{command}"
|
86
90
|
self.class.fire :after_execute, world, command: command, result: result
|
87
91
|
|
88
92
|
results << process_execution_result(execution_context:execution_context, result:result)
|
@@ -100,15 +104,19 @@ module Yap::Shell::Execution
|
|
100
104
|
def process_execution_result(execution_context:, result:)
|
101
105
|
case result
|
102
106
|
when SuspendExecution
|
107
|
+
Treefell['shell'].puts "suspending execution context"
|
103
108
|
@suspended_execution_contexts.push execution_context
|
104
109
|
return result
|
105
110
|
|
106
111
|
when ResumeExecution
|
112
|
+
Treefell['shell'].puts "resuming suspended execution context"
|
107
113
|
execution_context = @suspended_execution_contexts.pop
|
108
114
|
if execution_context
|
109
115
|
nresult = execution_context.resume
|
116
|
+
Treefell['shell'].puts "resuming suspended execution context success"
|
110
117
|
return process_execution_result execution_context: execution_context, result: nresult
|
111
118
|
else
|
119
|
+
Treefell['shell'].puts "error: cannot resume execution when there is nothing suspended"
|
112
120
|
@stderr.puts "fg: No such job"
|
113
121
|
end
|
114
122
|
else
|
@@ -49,11 +49,13 @@ module Yap::Shell::Execution
|
|
49
49
|
ENV.replace(before)
|
50
50
|
end
|
51
51
|
end
|
52
|
+
Treefell['shell'].puts "forked child process pid=#{pid} to execute #{command}"
|
52
53
|
|
53
54
|
# Put the child process into a process group of its own
|
54
55
|
Process.setpgid pid, pid
|
55
56
|
|
56
57
|
if command.heredoc
|
58
|
+
Treefell['shell'].puts "command has heredoc, wriing to stdin"
|
57
59
|
w.write command.heredoc
|
58
60
|
w.close
|
59
61
|
end
|
@@ -67,9 +69,11 @@ module Yap::Shell::Execution
|
|
67
69
|
# is so the next command in the pipeline can complete and don't hang waiting for
|
68
70
|
# stdin after the command that's writing to its stdin has completed.
|
69
71
|
if stdout != $stdout && stdout.is_a?(IO) && !stdout.closed? then
|
72
|
+
Treefell['shell'].puts "closing stdout for child process with pid=#{pid}"
|
70
73
|
stdout.close
|
71
74
|
end
|
72
75
|
if stderr != $stderr && stderr.is_a?(IO) && !stderr.closed? then
|
76
|
+
Treefell['shell'].puts "closing stderr for child process with pid=#{pid}"
|
73
77
|
stderr.close
|
74
78
|
end
|
75
79
|
# if stdin != $stdin && !stdin.closed? then stdin.close end
|
@@ -79,17 +83,24 @@ module Yap::Shell::Execution
|
|
79
83
|
# give it back to the us so we can become the foreground process
|
80
84
|
# in the terminal
|
81
85
|
if pid == Termios.tcgetpgrp(STDIN)
|
86
|
+
Treefell['shell'].puts <<-DEBUG.gsub(/^\s*\|/, '')
|
87
|
+
|restoring process group for STDIN to yap process with pid=#{Process.pid}
|
88
|
+
DEBUG
|
82
89
|
Process.setpgid Process.pid, Process.pid
|
83
90
|
Termios.tcsetpgrp STDIN, Process.pid
|
84
91
|
end
|
85
92
|
|
86
93
|
# if the reason we stopped is from being suspended
|
87
|
-
|
88
|
-
|
94
|
+
sigtstp = Signal.list["TSTP"]
|
95
|
+
if status && status.stopsig == sigtstp
|
96
|
+
Treefell['shell'].puts "process pid=#{pid} suspended by signal=#{status.stopsig.inspect}"
|
97
|
+
Treefell['shell'].puts "$?: #{$?.inspect}"
|
89
98
|
suspended(command:command, n:n, of:of, pid: pid)
|
90
99
|
result = Yap::Shell::Execution::SuspendExecution.new(status_code:nil, directory:Dir.pwd, n:n, of:of)
|
91
100
|
else
|
92
|
-
|
101
|
+
caused_by_signal = status ? (status.termsig || status.stopsig) : nil
|
102
|
+
Treefell['shell'].puts "process pid=#{pid} stopped by signal=#{caused_by_signal.inspect}"
|
103
|
+
Treefell['shell'].puts "$?: #{$?.inspect}"
|
93
104
|
# if a signal killed or stopped the process (such as SIGINT or SIGTSTP) $? is nil.
|
94
105
|
exitstatus = $? ? $?.exitstatus : nil
|
95
106
|
result = Yap::Shell::Execution::Result.new(status_code:exitstatus, directory:Dir.pwd, n:n, of:of)
|
@@ -99,18 +110,20 @@ module Yap::Shell::Execution
|
|
99
110
|
def resume
|
100
111
|
args = @suspended
|
101
112
|
@suspended = nil
|
113
|
+
pid = args[:pid]
|
114
|
+
sigcont = Signal.list["CONT"]
|
102
115
|
|
103
|
-
puts "
|
116
|
+
Treefell['shell'].puts "resuming suspended process pid=#{pid} by sending it signal=#{sigcont}"
|
104
117
|
resume_blk = lambda do
|
105
|
-
Process.kill
|
106
|
-
|
118
|
+
Process.kill sigcont, pid
|
119
|
+
pid
|
107
120
|
end
|
108
121
|
|
109
122
|
self.instance_exec command:args[:command], n:args[:n], of:args[:of], resume_blk:resume_blk, wait:true, &self.class.on_execute
|
110
123
|
end
|
111
124
|
|
112
125
|
def suspended(command:, n:, of:, pid:)
|
113
|
-
puts "
|
126
|
+
Treefell['shell'].puts "process pid=#{pid} suspended"
|
114
127
|
@suspended = {
|
115
128
|
command: command,
|
116
129
|
n: n,
|
@@ -3,83 +3,78 @@ module Yap::Shell::Execution
|
|
3
3
|
on_execute do |command:, n:, of:, wait:|
|
4
4
|
result = nil
|
5
5
|
stdin, stdout, stderr, world = @stdin, @stdout, @stderr, @world
|
6
|
-
# t = Thread.new {
|
7
|
-
exit_code = 0
|
8
|
-
first_command = n == 1
|
9
6
|
|
10
|
-
|
11
|
-
|
12
|
-
begin
|
13
|
-
ruby_command = command.to_executable_str
|
7
|
+
exit_code = 0
|
8
|
+
first_command = n == 1
|
14
9
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
elsif stdin != $stdin
|
20
|
-
puts "READ: stdin is not $stdin: #{stdin.inspect}" if ENV["DEBUG"]
|
21
|
-
stdin.read
|
22
|
-
else
|
23
|
-
puts "READ: contents is: #{contents.inspect}" if ENV["DEBUG"]
|
24
|
-
end
|
10
|
+
f = nil
|
11
|
+
ruby_result = nil
|
12
|
+
begin
|
13
|
+
ruby_command = command.to_executable_str
|
25
14
|
|
26
|
-
|
27
|
-
|
15
|
+
Treefell['shell'].puts "ruby execution: reading stdin from #{stdin.inspect}"
|
16
|
+
contents = if stdin.is_a?(String)
|
17
|
+
f = File.open stdin
|
18
|
+
f.read
|
19
|
+
elsif stdin != $stdin
|
20
|
+
stdin.read
|
21
|
+
end
|
22
|
+
|
23
|
+
Treefell['shell'].puts "ruby execution: contents=#{contents.inspect}, setting to world.content"
|
24
|
+
world.contents = contents
|
28
25
|
|
29
|
-
|
30
|
-
|
26
|
+
method = ruby_command.scan(/^(\w+(?:[!?]|\s*=)?)/).flatten.first.gsub(/\s/, '')
|
27
|
+
Treefell['shell'].puts "ruby execution: method=#{method.inspect}"
|
31
28
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
29
|
+
obj = if first_command
|
30
|
+
world
|
31
|
+
elsif contents.respond_to?(method)
|
32
|
+
contents
|
33
|
+
else
|
34
|
+
world
|
35
|
+
end
|
39
36
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
end
|
48
|
-
rescue Exception => ex
|
49
|
-
ruby_result = <<-EOT.gsub(/^\s*\S/, '')
|
50
|
-
|Failed processing ruby: #{ruby_command}
|
51
|
-
|#{ex}
|
52
|
-
|#{ex.backtrace.join("\n")}
|
53
|
-
EOT
|
54
|
-
exit_code = 1
|
55
|
-
ensure
|
56
|
-
f.close if f && !f.closed?
|
37
|
+
if ruby_command =~ /^[A-Z0-9]|::/
|
38
|
+
Treefell['shell'].puts "ruby executing: eval(#{ruby_command.inspect})"
|
39
|
+
ruby_result = eval ruby_command
|
40
|
+
else
|
41
|
+
ruby_command = "self.#{ruby_command}"
|
42
|
+
Treefell['shell'].puts "ruby executing: #{obj.class.name} instance instance_eval(#{ruby_command.inspect})"
|
43
|
+
ruby_result = obj.instance_eval ruby_command
|
57
44
|
end
|
45
|
+
rescue Exception => ex
|
46
|
+
ruby_result = <<-EOT.gsub(/^\s*\S/, '')
|
47
|
+
|Failed processing ruby: #{ruby_command}
|
48
|
+
|#{ex}
|
49
|
+
|#{ex.backtrace.join("\n")}
|
50
|
+
EOT
|
51
|
+
exit_code = 1
|
52
|
+
ensure
|
53
|
+
f.close if f && !f.closed?
|
54
|
+
end
|
58
55
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
56
|
+
# The next line causes issues sometimes?
|
57
|
+
# puts "WRITING #{ruby_result.length} bytes" if ENV["DEBUG"]
|
58
|
+
ruby_result = ruby_result.to_s
|
59
|
+
ruby_result << "\n" unless ruby_result.end_with?("\n")
|
63
60
|
|
64
|
-
|
65
|
-
|
66
|
-
|
61
|
+
stdout.write ruby_result
|
62
|
+
stdout.flush
|
63
|
+
stderr.flush
|
67
64
|
|
68
|
-
|
69
|
-
|
65
|
+
stdout.close if stdout != $stdout && !stdout.closed?
|
66
|
+
stderr.close if stderr != $stderr && !stderr.closed?
|
70
67
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
68
|
+
# Pass current execution to give any other threads a chance
|
69
|
+
# to be scheduled before we send back our status code. This could
|
70
|
+
# probably use a more elaborate signal or message passing scheme,
|
71
|
+
# but that's for another day.
|
72
|
+
# Thread.pass
|
76
73
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
# t.join
|
82
|
-
result
|
74
|
+
# Make up an exit code
|
75
|
+
Result.new(status_code:exit_code, directory:Dir.pwd, n:n, of:of).tap do |result|
|
76
|
+
Treefell['shell'].puts "ruby execution done with result=#{result.inspect}"
|
77
|
+
end
|
83
78
|
end
|
84
79
|
end
|
85
80
|
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
module Yap::Shell::Execution
|
2
2
|
class ShellCommandExecution < CommandExecution
|
3
3
|
on_execute do |command:, n:, of:, wait:|
|
4
|
+
Treefell['shell'].puts "shell command execution: #{command}"
|
5
|
+
|
4
6
|
possible_parameters = {
|
5
7
|
command: command.str,
|
6
8
|
args: command.args,
|
@@ -17,6 +19,7 @@ module Yap::Shell::Execution
|
|
17
19
|
h
|
18
20
|
end
|
19
21
|
|
22
|
+
Treefell['shell'].puts "shell command executing with params: #{params.inspect}"
|
20
23
|
command_result = func.call(**params)
|
21
24
|
@stdout.close if @stdout != $stdout && !@stdout.closed?
|
22
25
|
@stderr.close if @stderr != $stderr && !@stderr.closed?
|
data/lib/yap/shell/repl.rb
CHANGED
@@ -12,7 +12,11 @@ module Yap::Shell
|
|
12
12
|
def initialize(world:nil)
|
13
13
|
@world = world
|
14
14
|
@editor= world.editor
|
15
|
+
|
16
|
+
Treefell['shell'].puts "installing default keybindings"
|
15
17
|
install_default_keybindings
|
18
|
+
|
19
|
+
Treefell['shell'].puts "installing default tab completion"
|
16
20
|
install_default_tab_completion_proc
|
17
21
|
end
|
18
22
|
|
@@ -20,17 +24,23 @@ module Yap::Shell
|
|
20
24
|
@blk = blk
|
21
25
|
|
22
26
|
@world.editor.on_read_line do |event|
|
27
|
+
line_read = event[:payload][:line]
|
28
|
+
Treefell['shell'].puts "editor line read: #{line_read.inspect}"
|
23
29
|
# editor.history = true?
|
24
|
-
line =
|
30
|
+
line = line_read << "\n"
|
25
31
|
begin
|
26
32
|
@blk.call(line)
|
27
33
|
@world.editor.redraw_prompt
|
28
|
-
rescue Yap::Shell::Parser::Lexer::NonterminatedString,
|
34
|
+
rescue Yap::Shell::Parser::Lexer::NonterminatedString,
|
35
|
+
Yap::Shell::Parser::Lexer::LineContinuationFound => ex
|
36
|
+
Treefell['shell'].puts "rescued #{ex}, asking user for more input"
|
29
37
|
line << read_another_line_of_input
|
30
38
|
retry
|
31
39
|
rescue ::Yap::Shell::CommandUnknownError => ex
|
40
|
+
Treefell['shell'].puts "rescued #{ex}, telling user"
|
32
41
|
puts " CommandError: #{ex.message}"
|
33
42
|
rescue ::Yap::Shell::Parser::ParseError => ex
|
43
|
+
Treefell['shell'].puts "rescued #{ex}, telling user"
|
34
44
|
puts " Parse error: #{ex.message}"
|
35
45
|
ensure
|
36
46
|
@world.editor.reset_line
|
@@ -213,13 +223,12 @@ module Yap::Shell
|
|
213
223
|
return _input
|
214
224
|
end
|
215
225
|
|
216
|
-
puts "
|
217
|
-
|
226
|
+
Treefell['shell'].puts "asking for heredoc input with @world.secondary_prompt"
|
218
227
|
loop do
|
219
228
|
str = editor.read(@world.secondary_prompt.update.text, false)
|
220
229
|
input << "#{str}\n"
|
221
230
|
if str =~ /^#{Regexp.escape(marker)}$/
|
222
|
-
puts "
|
231
|
+
Treefell['shell'].puts "done asking for heredoc input"
|
223
232
|
break
|
224
233
|
end
|
225
234
|
end
|
data/lib/yap/shell/version.rb
CHANGED
data/lib/yap/world.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'term/ansicolor'
|
2
|
+
require 'fileutils'
|
2
3
|
require 'forwardable'
|
3
4
|
require 'rawline'
|
4
5
|
require 'termios'
|
@@ -30,6 +31,9 @@ module Yap
|
|
30
31
|
@env = ENV.to_h.dup
|
31
32
|
dom = build_editor_dom
|
32
33
|
|
34
|
+
# ensure yap directory exists
|
35
|
+
FileUtils.mkdir_p configuration.yap_path
|
36
|
+
|
33
37
|
@editor = RawLine::Editor.create(dom: dom)
|
34
38
|
|
35
39
|
self.prompt = Yap::Shell::Prompt.new(text: DEFAULTS[:primary_prompt_text])
|