yap-shell 0.1.1 → 0.3.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/.ruby-version +1 -0
- data/Gemfile +5 -0
- data/WISHLIST.md +14 -0
- data/addons/history/Gemfile +2 -0
- data/addons/history/history.rb +101 -0
- data/addons/history/lib/history/buffer.rb +204 -0
- data/addons/history/lib/history/events.rb +13 -0
- data/addons/keyboard_macros/keyboard_macros.rb +295 -0
- data/addons/prompt/Gemfile +1 -0
- data/addons/prompt/right_prompt.rb +17 -0
- data/addons/prompt_updates/prompt_updates.rb +28 -0
- data/addons/tab_completion/Gemfile +0 -0
- data/addons/tab_completion/lib/tab_completion/completer.rb +62 -0
- data/addons/tab_completion/lib/tab_completion/custom_completion.rb +33 -0
- data/addons/tab_completion/lib/tab_completion/dsl_methods.rb +7 -0
- data/addons/tab_completion/lib/tab_completion/file_completion.rb +75 -0
- data/addons/tab_completion/tab_completion.rb +157 -0
- data/bin/yap +13 -4
- data/lib/tasks/addons.rake +51 -0
- data/lib/yap.rb +4 -55
- data/lib/yap/shell.rb +51 -10
- data/lib/yap/shell/builtins.rb +2 -2
- data/lib/yap/shell/builtins/alias.rb +2 -2
- data/lib/yap/shell/builtins/cd.rb +9 -11
- data/lib/yap/shell/builtins/env.rb +11 -0
- data/lib/yap/shell/commands.rb +29 -18
- data/lib/yap/shell/evaluation.rb +185 -68
- data/lib/yap/shell/evaluation/shell_expansions.rb +85 -0
- data/lib/yap/shell/event_emitter.rb +18 -0
- data/lib/yap/shell/execution/builtin_command_execution.rb +1 -1
- data/lib/yap/shell/execution/command_execution.rb +3 -3
- data/lib/yap/shell/execution/context.rb +32 -9
- data/lib/yap/shell/execution/file_system_command_execution.rb +12 -7
- data/lib/yap/shell/execution/ruby_command_execution.rb +6 -6
- data/lib/yap/shell/execution/shell_command_execution.rb +17 -2
- data/lib/yap/shell/prompt.rb +21 -0
- data/lib/yap/shell/repl.rb +179 -18
- data/lib/yap/shell/version.rb +1 -1
- data/lib/yap/world.rb +149 -15
- data/lib/yap/world/addons.rb +135 -0
- data/rcfiles/.yaprc +240 -10
- data/test.rb +206 -0
- data/update-rawline.sh +6 -0
- data/yap-shell.gemspec +11 -3
- metadata +101 -10
- data/addons/history.rb +0 -171
data/lib/yap.rb
CHANGED
@@ -2,62 +2,11 @@ require 'yap/shell'
|
|
2
2
|
require 'yap/world'
|
3
3
|
|
4
4
|
module Yap
|
5
|
-
module WorldAddons
|
6
|
-
def self.syntax_ok?(file)
|
7
|
-
`ruby -c #{file}`
|
8
|
-
$?.exitstatus == 0
|
9
|
-
end
|
10
|
-
|
11
|
-
def self.load_from_files(files:[])
|
12
|
-
files.map do |file|
|
13
|
-
(puts "Cannot load world addon: #{file} does not exist" and next) unless File.exist?(file)
|
14
|
-
(puts "Cannot load world addon: #{file} is not readable" and next) unless File.exist?(file)
|
15
|
-
(puts "Cannot load world addon: #{file} is a directory file" and next) if File.directory?(file)
|
16
|
-
|
17
|
-
addon = file.end_with?("rc") ? load_rcfile(file) : load_addon_file(file)
|
18
|
-
addon.load
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
class RcFile
|
23
|
-
def initialize(contents)
|
24
|
-
@contents = contents
|
25
|
-
end
|
26
|
-
|
27
|
-
def load
|
28
|
-
self
|
29
|
-
end
|
30
|
-
|
31
|
-
def initialize_world(world)
|
32
|
-
world.instance_eval @contents
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def self.load_rcfile(file)
|
37
|
-
RcFile.new IO.read(file)
|
38
|
-
end
|
39
|
-
|
40
|
-
def self.load_addon_file(file)
|
41
|
-
name = File.basename(file).sub(/\.[^\.]+$/, "").split(/[_]/).map(&:capitalize).join
|
42
|
-
klass_name = "Yap::WorldAddons::#{name}"
|
43
|
-
|
44
|
-
load file
|
45
|
-
|
46
|
-
if Yap::WorldAddons.const_defined?(name)
|
47
|
-
Yap::WorldAddons.const_get(name)
|
48
|
-
else
|
49
|
-
raise("Did not find #{klass_name} in #{file}")
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
5
|
def self.run_shell
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
]
|
6
|
+
addons = []
|
7
|
+
addons.push World::Addons.load_directories Dir["#{ENV['HOME']}/.yap-addons/*"]
|
8
|
+
addons.push World::Addons.load_rcfiles Dir["#{ENV['HOME']}/.yaprc"]
|
59
9
|
|
60
|
-
addons
|
61
|
-
Shell::Impl.new(addons: addons).repl
|
10
|
+
Shell::Impl.new(addons: addons.flatten).repl
|
62
11
|
end
|
63
12
|
end
|
data/lib/yap/shell.rb
CHANGED
@@ -2,6 +2,7 @@ require 'readline'
|
|
2
2
|
require 'yaml'
|
3
3
|
require 'yap/shell/version'
|
4
4
|
require 'yap/shell/builtins'
|
5
|
+
require 'fcntl'
|
5
6
|
|
6
7
|
module Yap
|
7
8
|
module Shell
|
@@ -22,6 +23,12 @@ module Yap
|
|
22
23
|
|
23
24
|
class Impl
|
24
25
|
def initialize(addons:)
|
26
|
+
@original_file_descriptor_flags = {
|
27
|
+
stdin: $stdin.fcntl(Fcntl::F_GETFL, 0),
|
28
|
+
stdout: $stdout.fcntl(Fcntl::F_GETFL, 0),
|
29
|
+
stderr: $stderr.fcntl(Fcntl::F_GETFL, 0)
|
30
|
+
}
|
31
|
+
|
25
32
|
@stdin = $stdin
|
26
33
|
@stdout = $stdout
|
27
34
|
@stderr = $stderr
|
@@ -29,29 +36,63 @@ module Yap
|
|
29
36
|
@stdout.sync = true
|
30
37
|
@stderr.sync = true
|
31
38
|
|
32
|
-
@
|
39
|
+
@world = Yap::World.instance(addons:addons)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Yields to the passed in block after restoring the file descriptor
|
43
|
+
# flags that Yap started in. This ensures that any changes Yap has
|
44
|
+
# made to run the shell don't interfere with child processes.
|
45
|
+
def with_original_file_descriptor_flags(&block)
|
46
|
+
current_file_descriptor_flags = {
|
47
|
+
stdin: $stdin.fcntl(Fcntl::F_GETFL, 0),
|
48
|
+
stdout: $stdout.fcntl(Fcntl::F_GETFL, 0),
|
49
|
+
stderr: $stderr.fcntl(Fcntl::F_GETFL, 0)
|
50
|
+
}
|
51
|
+
|
52
|
+
$stdin.fcntl(Fcntl::F_SETFL, @original_file_descriptor_flags[:stdin])
|
53
|
+
$stdout.fcntl(Fcntl::F_SETFL, @original_file_descriptor_flags[:stdout])
|
54
|
+
$stderr.fcntl(Fcntl::F_SETFL, @original_file_descriptor_flags[:stderr])
|
55
|
+
|
56
|
+
yield
|
57
|
+
ensure
|
58
|
+
$stdin.fcntl(Fcntl::F_SETFL, current_file_descriptor_flags[:stdin])
|
59
|
+
$stdout.fcntl(Fcntl::F_SETFL, current_file_descriptor_flags[:stdout])
|
60
|
+
$stderr.fcntl(Fcntl::F_SETFL, current_file_descriptor_flags[:stderr])
|
33
61
|
end
|
34
62
|
|
35
63
|
def repl
|
36
|
-
@world = Yap::World.new(addons:@addons)
|
37
64
|
context = Yap::Shell::Execution::Context.new(
|
38
65
|
stdin: @stdin,
|
39
66
|
stdout: @stdout,
|
40
67
|
stderr: @stderr
|
41
68
|
)
|
42
69
|
|
43
|
-
@repl
|
44
|
-
|
45
|
-
evaluation
|
46
|
-
evaluation.evaluate(input) do |command, stdin, stdout, stderr|
|
70
|
+
@world.repl.on_input do |input|
|
71
|
+
evaluation = Yap::Shell::Evaluation.new(stdin:@stdin, stdout:@stdout, stderr:@stderr, world:@world)
|
72
|
+
evaluation.evaluate(input) do |command, stdin, stdout, stderr, wait|
|
47
73
|
context.clear_commands
|
48
|
-
context.add_command_to_run command, stdin:stdin, stdout:stdout, stderr:stderr
|
49
|
-
|
74
|
+
context.add_command_to_run command, stdin:stdin, stdout:stdout, stderr:stderr, wait:wait
|
75
|
+
|
76
|
+
with_original_file_descriptor_flags do
|
77
|
+
context.execute(world:@world)
|
78
|
+
end
|
50
79
|
end
|
51
80
|
end
|
52
|
-
end
|
53
81
|
|
82
|
+
begin
|
83
|
+
@world.interactive!
|
84
|
+
# rescue Errno::EIO => ex
|
85
|
+
# # This happens when yap is no longer the foreground process
|
86
|
+
# # but it tries to receive input/output from the tty. I believe it
|
87
|
+
# # is a race condition when launching a child process.
|
88
|
+
rescue Interrupt
|
89
|
+
@world.editor.puts "^C"
|
90
|
+
retry
|
91
|
+
rescue Exception => ex
|
92
|
+
require 'pry'
|
93
|
+
binding.pry unless ex.is_a?(SystemExit)
|
94
|
+
end
|
95
|
+
end
|
54
96
|
end
|
55
|
-
|
56
97
|
end
|
57
98
|
end
|
data/lib/yap/shell/builtins.rb
CHANGED
@@ -6,8 +6,8 @@ module Yap::Shell
|
|
6
6
|
Yap::Shell::BuiltinCommand.add(name, &blk)
|
7
7
|
end
|
8
8
|
|
9
|
-
def self.execute_builtin(name, args:, stdin:, stdout:, stderr:)
|
10
|
-
command = Yap::Shell::BuiltinCommand.new(str:name, args: args)
|
9
|
+
def self.execute_builtin(name, world:, args:, stdin:, stdout:, stderr:)
|
10
|
+
command = Yap::Shell::BuiltinCommand.new(world:world, str:name, args: args)
|
11
11
|
command.execute(stdin:stdin, stdout:stdout, stderr:stderr)
|
12
12
|
end
|
13
13
|
|
@@ -3,7 +3,7 @@ require 'shellwords'
|
|
3
3
|
|
4
4
|
module Yap::Shell
|
5
5
|
module Builtins
|
6
|
-
builtin :alias do
|
6
|
+
builtin :alias do |args:, stdout:, **kwargs|
|
7
7
|
output = []
|
8
8
|
if args.empty?
|
9
9
|
Yap::Shell::Aliases.instance.to_h.each_pair do |name, value|
|
@@ -19,7 +19,7 @@ module Yap::Shell
|
|
19
19
|
name, command = name_eq_value.scan(/^(.*?)\s*=\s*(.*)$/).flatten
|
20
20
|
Yap::Shell::Aliases.instance.set_alias name, command
|
21
21
|
end
|
22
|
-
output.join("\n")
|
22
|
+
stdout.puts output.join("\n")
|
23
23
|
end
|
24
24
|
end
|
25
25
|
end
|
@@ -3,13 +3,12 @@ module Yap::Shell
|
|
3
3
|
DIRECTORY_HISTORY = []
|
4
4
|
DIRECTORY_FUTURE = []
|
5
5
|
|
6
|
-
builtin :cd do |args:, stderr:, **|
|
7
|
-
path = args.first ||
|
8
|
-
cwd = Dir.pwd
|
6
|
+
builtin :cd do |world:, args:, stderr:, **|
|
7
|
+
path = args.first || world.env['HOME']
|
9
8
|
if Dir.exist?(path)
|
10
|
-
DIRECTORY_HISTORY <<
|
9
|
+
DIRECTORY_HISTORY << Dir.pwd
|
10
|
+
world.env["PWD"] = File.expand_path(path)
|
11
11
|
Dir.chdir(path)
|
12
|
-
ENV["PWD"] = cwd
|
13
12
|
exit_status = 0
|
14
13
|
else
|
15
14
|
stderr.puts "cd: #{path}: No such file or directory"
|
@@ -17,15 +16,14 @@ module Yap::Shell
|
|
17
16
|
end
|
18
17
|
end
|
19
18
|
|
20
|
-
builtin :popd do |args:, stderr:, **keyword_args|
|
19
|
+
builtin :popd do |world:, args:, stderr:, **keyword_args|
|
21
20
|
output = []
|
22
|
-
cwd = Dir.pwd
|
23
21
|
if DIRECTORY_HISTORY.any?
|
24
22
|
path = DIRECTORY_HISTORY.pop
|
25
23
|
if Dir.exist?(path)
|
26
|
-
DIRECTORY_FUTURE <<
|
24
|
+
DIRECTORY_FUTURE << Dir.pwd
|
27
25
|
Dir.chdir(path)
|
28
|
-
|
26
|
+
world.env["PWD"] = Dir.pwd
|
29
27
|
exit_status = 0
|
30
28
|
else
|
31
29
|
stderr.puts "popd: #{path}: No such file or directory"
|
@@ -37,14 +35,14 @@ module Yap::Shell
|
|
37
35
|
end
|
38
36
|
end
|
39
37
|
|
40
|
-
builtin :pushd do |args:, stderr:, **keyword_args|
|
38
|
+
builtin :pushd do |world:, args:, stderr:, **keyword_args|
|
41
39
|
output = []
|
42
40
|
if DIRECTORY_FUTURE.any?
|
43
41
|
path = DIRECTORY_FUTURE.pop
|
44
42
|
if Dir.exist?(path)
|
45
43
|
DIRECTORY_HISTORY << Dir.pwd
|
46
44
|
Dir.chdir(path)
|
47
|
-
|
45
|
+
world.env["PWD"] = Dir.pwd
|
48
46
|
exit_status = 0
|
49
47
|
else
|
50
48
|
stderr.puts "pushd: #{path}: No such file or directory"
|
data/lib/yap/shell/commands.rb
CHANGED
@@ -7,13 +7,13 @@ module Yap::Shell
|
|
7
7
|
class CommandUnknownError < CommandError ; end
|
8
8
|
|
9
9
|
class CommandFactory
|
10
|
-
def self.build_command_for(command:, args:, heredoc:, internally_evaluate:)
|
11
|
-
return RubyCommand.new(str:command) if internally_evaluate
|
10
|
+
def self.build_command_for(world:, command:, args:, heredoc:, internally_evaluate:, line:)
|
11
|
+
return RubyCommand.new(world:world, str:command) if internally_evaluate
|
12
12
|
|
13
13
|
case command
|
14
|
-
when ShellCommand then ShellCommand.new(str:command, args:args, heredoc:heredoc)
|
15
|
-
when BuiltinCommand then BuiltinCommand.new(str:command, args:args, heredoc:heredoc)
|
16
|
-
when FileSystemCommand then FileSystemCommand.new(str:command, args:args, heredoc:heredoc)
|
14
|
+
when ShellCommand then ShellCommand.new(world:world, str:command, args:args, heredoc:heredoc, line:line)
|
15
|
+
when BuiltinCommand then BuiltinCommand.new(world:world, str:command, args:args, heredoc:heredoc)
|
16
|
+
when FileSystemCommand then FileSystemCommand.new(world:world, str:command, args:args, heredoc:heredoc)
|
17
17
|
else
|
18
18
|
raise CommandUnknownError, "Don't know how to execute command: #{command}"
|
19
19
|
end
|
@@ -21,13 +21,15 @@ module Yap::Shell
|
|
21
21
|
end
|
22
22
|
|
23
23
|
class Command
|
24
|
-
attr_accessor :str, :args
|
24
|
+
attr_accessor :world, :str, :args, :line
|
25
25
|
attr_accessor :heredoc
|
26
26
|
|
27
|
-
def initialize(str:, args:[], heredoc:nil)
|
27
|
+
def initialize(world:, str:, args:[], line:nil, heredoc:nil)
|
28
|
+
@world = world
|
28
29
|
@str = str
|
29
30
|
@args = args
|
30
31
|
@heredoc = heredoc
|
32
|
+
@line = line
|
31
33
|
end
|
32
34
|
|
33
35
|
def to_executable_str
|
@@ -54,7 +56,7 @@ module Yap::Shell
|
|
54
56
|
|
55
57
|
def execute(stdin:, stdout:, stderr:)
|
56
58
|
action = self.class.builtins.fetch(str.to_sym){ raise("Missing proc for builtin: '#{builtin}' in #{str.inspect}") }
|
57
|
-
action.call args:args, stdin:stdin, stdout:stdout, stderr:stderr
|
59
|
+
action.call world:world, args:args, stdin:stdin, stdout:stdout, stderr:stderr
|
58
60
|
end
|
59
61
|
|
60
62
|
def type
|
@@ -67,6 +69,10 @@ module Yap::Shell
|
|
67
69
|
end
|
68
70
|
|
69
71
|
class FileSystemCommand < Command
|
72
|
+
def self.world
|
73
|
+
::Yap::World.instance
|
74
|
+
end
|
75
|
+
|
70
76
|
def self.===(other)
|
71
77
|
command = other.split(/\s+/).detect{ |f| !f.include?("=") }
|
72
78
|
|
@@ -74,7 +80,7 @@ module Yap::Shell
|
|
74
80
|
return true if File.executable?(command)
|
75
81
|
|
76
82
|
# See if the command exists anywhere on the path
|
77
|
-
|
83
|
+
world.env["PATH"].split(":").detect do |path|
|
78
84
|
File.executable?(File.join(path, command))
|
79
85
|
end
|
80
86
|
end
|
@@ -86,7 +92,7 @@ module Yap::Shell
|
|
86
92
|
def to_executable_str
|
87
93
|
[
|
88
94
|
str,
|
89
|
-
args.
|
95
|
+
args.join(' ')
|
90
96
|
].join(' ')
|
91
97
|
end
|
92
98
|
end
|
@@ -96,13 +102,17 @@ module Yap::Shell
|
|
96
102
|
(@registered_functions ||= {}).freeze
|
97
103
|
end
|
98
104
|
|
99
|
-
def self.define_shell_function(
|
100
|
-
raise ArgumentError, "Must
|
101
|
-
|
105
|
+
def self.define_shell_function(name_or_pattern, &blk)
|
106
|
+
raise ArgumentError, "Must provide block when defining a shell function" unless blk
|
107
|
+
name_or_pattern = name_or_pattern.to_s if name_or_pattern.is_a?(Symbol)
|
108
|
+
name_or_pattern = /^#{Regexp.escape(name_or_pattern)}$/ if name_or_pattern.is_a?(String)
|
109
|
+
(@registered_functions ||= {})[name_or_pattern] = blk
|
102
110
|
end
|
103
111
|
|
104
|
-
def self.===(
|
105
|
-
registered_functions.
|
112
|
+
def self.===(command)
|
113
|
+
registered_functions.detect do |name_or_pattern, *_|
|
114
|
+
name_or_pattern.match(command)
|
115
|
+
end
|
106
116
|
end
|
107
117
|
|
108
118
|
def type
|
@@ -110,9 +120,10 @@ module Yap::Shell
|
|
110
120
|
end
|
111
121
|
|
112
122
|
def to_proc
|
113
|
-
self.class.registered_functions.
|
114
|
-
|
115
|
-
|
123
|
+
self.class.registered_functions.detect do |name_or_pattern, function_body|
|
124
|
+
return function_body if name_or_pattern.match(str)
|
125
|
+
end
|
126
|
+
raise "Shell function #{str} was not found!"
|
116
127
|
end
|
117
128
|
end
|
118
129
|
|
data/lib/yap/shell/evaluation.rb
CHANGED
@@ -1,32 +1,38 @@
|
|
1
1
|
require 'yap/shell/parser'
|
2
2
|
require 'yap/shell/commands'
|
3
3
|
require 'yap/shell/aliases'
|
4
|
+
require 'yap/shell/evaluation/shell_expansions'
|
4
5
|
|
5
6
|
module Yap::Shell
|
6
7
|
class Evaluation
|
7
|
-
|
8
|
+
attr_reader :world
|
9
|
+
|
10
|
+
def initialize(stdin:, stdout:, stderr:, world:)
|
8
11
|
@stdin, @stdout, @stderr = stdin, stdout, stderr
|
9
|
-
@
|
12
|
+
@world = world
|
10
13
|
end
|
11
14
|
|
12
15
|
def evaluate(input, &blk)
|
13
16
|
@blk = blk
|
14
|
-
|
15
|
-
|
16
|
-
ast = Yap::Shell::Parser.new.parse(input)
|
17
|
+
@input = recursively_find_and_replace_command_substitutions(input)
|
18
|
+
ast = Parser.parse(@input)
|
17
19
|
ast.accept(self)
|
18
20
|
end
|
19
21
|
|
22
|
+
def set_last_result(result)
|
23
|
+
@world.last_result = result
|
24
|
+
end
|
25
|
+
|
20
26
|
private
|
21
27
|
|
22
28
|
# +recursively_find_and_replace_command_substitutions+ is responsible for recursively
|
23
29
|
# finding and expanding command substitutions, in a depth first manner.
|
24
|
-
def recursively_find_and_replace_command_substitutions(
|
30
|
+
def recursively_find_and_replace_command_substitutions(input)
|
25
31
|
input = input.dup
|
26
|
-
|
27
|
-
result = recursively_find_and_replace_command_substitutions(
|
32
|
+
Parser.each_command_substitution_for(input) do |substitution_result, start_position, end_position|
|
33
|
+
result = recursively_find_and_replace_command_substitutions(substitution_result.str)
|
28
34
|
position = substitution_result.position
|
29
|
-
ast =
|
35
|
+
ast = Parser.parse(result)
|
30
36
|
with_standard_streams do |stdin, stdout, stderr|
|
31
37
|
r,w = IO.pipe
|
32
38
|
@stdout = w
|
@@ -46,65 +52,183 @@ module Yap::Shell
|
|
46
52
|
|
47
53
|
def visit_CommandNode(node)
|
48
54
|
@aliases_expanded ||= []
|
55
|
+
@command_node_args_stack ||= []
|
49
56
|
with_standard_streams do |stdin, stdout, stderr|
|
50
|
-
args = node.args.map(&:lvalue)
|
57
|
+
args = node.args.map(&:lvalue).map do |arg|
|
58
|
+
shell_expand(arg, escape_directory_expansions: false)
|
59
|
+
end
|
51
60
|
if !node.literal? && !@aliases_expanded.include?(node.command) && _alias=Aliases.instance.fetch_alias(node.command)
|
52
61
|
@suppress_events = true
|
53
|
-
|
62
|
+
@command_node_args_stack << args
|
63
|
+
ast = Parser.parse(_alias)
|
54
64
|
@aliases_expanded.push(node.command)
|
55
65
|
ast.accept(self)
|
56
66
|
@aliases_expanded.pop
|
57
67
|
@suppress_events = false
|
58
68
|
else
|
69
|
+
cmd2execute = variable_expand(node.command)
|
70
|
+
final_args = (args + @command_node_args_stack).flatten.shelljoin
|
71
|
+
expanded_args = shell_expand(final_args)
|
59
72
|
command = CommandFactory.build_command_for(
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
73
|
+
world: world,
|
74
|
+
command: cmd2execute,
|
75
|
+
args: expanded_args,
|
76
|
+
heredoc: (node.heredoc && node.heredoc.value),
|
77
|
+
internally_evaluate: node.internally_evaluate?,
|
78
|
+
line: @input)
|
64
79
|
@stdin, @stdout, @stderr = stream_redirections_for(node)
|
65
|
-
|
80
|
+
set_last_result @blk.call command, @stdin, @stdout, @stderr, pipeline_stack.empty?
|
81
|
+
@command_node_args_stack.clear
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def visit_CommentNode(node)
|
87
|
+
# no-op, do nothing
|
88
|
+
end
|
89
|
+
|
90
|
+
def visit_RangeNode(node)
|
91
|
+
range = node.head.value
|
92
|
+
if node.tail
|
93
|
+
@current_range_values = range.to_a
|
94
|
+
node.tail.accept(self)
|
95
|
+
@current_range_values = nil
|
96
|
+
else
|
97
|
+
@stdout.puts range.to_a.join(' ')
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def visit_RedirectionNode(node)
|
102
|
+
filename = node.target
|
103
|
+
|
104
|
+
if File.directory?(filename)
|
105
|
+
puts <<-ERROR.gsub(/^\s*/m, '').lines.join(' ')
|
106
|
+
Whoops, #{filename.inspect} is a directory! Those can't be redirected to.
|
107
|
+
ERROR
|
108
|
+
set_last_result Yap::Shell::Execution::Result.new(status_code:1, directory:Dir.pwd, n:1, of:1)
|
109
|
+
elsif node.kind == ">"
|
110
|
+
File.write(filename, "")
|
111
|
+
set_last_result Yap::Shell::Execution::Result.new(status_code:0, directory:Dir.pwd, n:1, of:1)
|
112
|
+
else
|
113
|
+
puts "Sorry, #{node.kind} redirection isn't a thing, but >#{filename} is!"
|
114
|
+
set_last_result Yap::Shell::Execution::Result.new(status_code:2, directory:Dir.pwd, n:1, of:1)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def visit_BlockNode(node)
|
119
|
+
with_standard_streams do |stdin, stdout, stderr|
|
120
|
+
# Modify @stdout and @stderr for the first command
|
121
|
+
stdin, @stdout = IO.pipe
|
122
|
+
|
123
|
+
# Don't modify @stdin for the first command in the pipeline.
|
124
|
+
values = []
|
125
|
+
if node.head
|
126
|
+
node.head.accept(self)
|
127
|
+
values = stdin.read.split(/\s+/)
|
128
|
+
else
|
129
|
+
# assume range for now
|
130
|
+
values = @current_range_values
|
131
|
+
end
|
132
|
+
|
133
|
+
evaluate_block = lambda {
|
134
|
+
with_standard_streams do |stdin2, stdout2, stderr2|
|
135
|
+
@stdout = stdout
|
136
|
+
node.tail.accept(self)
|
137
|
+
end
|
138
|
+
}
|
139
|
+
|
140
|
+
if node.params.any?
|
141
|
+
values.each_slice(node.params.length).each do |_slice|
|
142
|
+
with_env do
|
143
|
+
Hash[ node.params.zip(_slice) ].each_pair do |k,v|
|
144
|
+
world.env[k] = v.to_s
|
145
|
+
end
|
146
|
+
evaluate_block.call
|
147
|
+
end
|
148
|
+
end
|
149
|
+
else
|
150
|
+
values.each do
|
151
|
+
evaluate_block.call
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
|
158
|
+
def visit_NumericalRangeNode(node)
|
159
|
+
node.range.each do |n|
|
160
|
+
if node.tail
|
161
|
+
if node.reference
|
162
|
+
with_env do
|
163
|
+
world.env[node.reference.value] = n.to_s
|
164
|
+
node.tail.accept(self)
|
165
|
+
end
|
166
|
+
else
|
167
|
+
node.tail.accept(self)
|
168
|
+
end
|
66
169
|
end
|
67
170
|
end
|
68
171
|
end
|
69
172
|
|
70
173
|
def visit_StatementsNode(node)
|
71
|
-
|
72
|
-
Yap::Shell::Execution::Context.fire :before_statements_execute, self unless @suppress_events
|
174
|
+
Yap::Shell::Execution::Context.fire :before_statements_execute, @world unless @suppress_events
|
73
175
|
node.head.accept(self)
|
74
176
|
if node.tail
|
75
177
|
node.tail.accept(self)
|
76
|
-
ENV.clear
|
77
|
-
ENV.replace(env)
|
78
178
|
end
|
79
|
-
Yap::Shell::Execution::Context.fire :after_statements_execute,
|
179
|
+
Yap::Shell::Execution::Context.fire :after_statements_execute, @world unless @suppress_events
|
80
180
|
end
|
81
181
|
|
182
|
+
# Represents a statement that has scoped environment variables being set,
|
183
|
+
# e.g.:
|
184
|
+
#
|
185
|
+
# yap> A=5 ls
|
186
|
+
# yap> A=5 B=6 echo
|
187
|
+
#
|
188
|
+
# These environment variables are reset after the their statement, e.g.:
|
189
|
+
#
|
190
|
+
# yap> A=5
|
191
|
+
# yap> echo $A
|
192
|
+
# 5
|
193
|
+
# yap> A=b echo $A
|
194
|
+
# b
|
195
|
+
# yap> echo $
|
196
|
+
# 5
|
197
|
+
#
|
82
198
|
def visit_EnvWrapperNode(node)
|
83
|
-
|
84
|
-
|
85
|
-
|
199
|
+
with_env do
|
200
|
+
node.env.each_pair { |env_var_name,value| world.env[env_var_name] = variable_expand(value) }
|
201
|
+
node.node.accept(self)
|
86
202
|
end
|
87
|
-
node.node.accept(self)
|
88
|
-
ENV.clear
|
89
|
-
ENV.replace(env)
|
90
203
|
end
|
91
204
|
|
205
|
+
# Represents a statement that contains nothing but environment
|
206
|
+
# variables being set, e.g.:
|
207
|
+
#
|
208
|
+
# yap> A=5
|
209
|
+
# yap> A=5 B=6
|
210
|
+
# yap> A=3 && B=6
|
211
|
+
#
|
212
|
+
# The environment variables persist from statement to statement until
|
213
|
+
# they cleared or overridden.
|
214
|
+
#
|
92
215
|
def visit_EnvNode(node)
|
93
216
|
node.env.each_pair do |key,val|
|
94
|
-
|
217
|
+
world.env[key] = variable_expand(val)
|
95
218
|
end
|
219
|
+
Yap::Shell::Execution::Result.new(status_code:0, directory:Dir.pwd, n:1, of:1)
|
96
220
|
end
|
97
221
|
|
98
222
|
def visit_ConditionalNode(node)
|
99
223
|
case node.operator
|
100
224
|
when '&&'
|
101
225
|
node.expr1.accept self
|
102
|
-
if @last_result.status_code == 0
|
226
|
+
if @world.last_result.status_code == 0
|
103
227
|
node.expr2.accept self
|
104
228
|
end
|
105
229
|
when '||'
|
106
230
|
node.expr1.accept self
|
107
|
-
if @last_result.status_code != 0
|
231
|
+
if @world.last_result.status_code != 0
|
108
232
|
node.expr2.accept self
|
109
233
|
end
|
110
234
|
else
|
@@ -116,7 +240,7 @@ module Yap::Shell
|
|
116
240
|
with_standard_streams do |stdin, stdout, stderr|
|
117
241
|
# Modify @stdout and @stderr for the first command
|
118
242
|
stdin, @stdout = IO.pipe
|
119
|
-
|
243
|
+
pipeline_stack.push true
|
120
244
|
# Don't modify @stdin for the first command in the pipeline.
|
121
245
|
node.head.accept(self)
|
122
246
|
|
@@ -127,6 +251,7 @@ module Yap::Shell
|
|
127
251
|
# Modify @stdout,@stderr to go back to the original
|
128
252
|
@stdout, @stderr = stdout, stderr
|
129
253
|
|
254
|
+
pipeline_stack.pop
|
130
255
|
node.tail.accept(self)
|
131
256
|
|
132
257
|
# Set our @stdin back to the original
|
@@ -136,11 +261,13 @@ module Yap::Shell
|
|
136
261
|
|
137
262
|
def visit_InternalEvalNode(node)
|
138
263
|
command = CommandFactory.build_command_for(
|
264
|
+
world: world,
|
139
265
|
command: node.command,
|
140
266
|
args: node.args,
|
141
267
|
heredoc: node.heredoc,
|
142
|
-
internally_evaluate: node.internally_evaluate
|
143
|
-
|
268
|
+
internally_evaluate: node.internally_evaluate?,
|
269
|
+
line: @input)
|
270
|
+
set_last_result @blk.call command, @stdin, @stdout, @stderr, pipeline_stack.empty?
|
144
271
|
end
|
145
272
|
|
146
273
|
######################################################################
|
@@ -149,45 +276,26 @@ module Yap::Shell
|
|
149
276
|
# #
|
150
277
|
######################################################################
|
151
278
|
|
152
|
-
def alias_expand(input, aliases:Aliases.instance)
|
153
|
-
head, *tail = input.split(/\s/, 2).first
|
154
|
-
if new_head=aliases.fetch_alias(head)
|
155
|
-
[new_head].concat(tail).join(" ")
|
156
|
-
else
|
157
|
-
input
|
158
|
-
end
|
159
|
-
end
|
160
279
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
expansions.split(",").each do |expansion|
|
167
|
-
results << str.sub(/\{([^\}]+)\}/, expansion)
|
168
|
-
end
|
169
|
-
else
|
170
|
-
results << str
|
171
|
-
end
|
280
|
+
# +pipeline_stack+ is used to determine if we are about go inside of a
|
281
|
+
# pipeline. It will be empty when we are coming out of a pipeline node.
|
282
|
+
def pipeline_stack
|
283
|
+
@pipeline_stack ||= []
|
284
|
+
end
|
172
285
|
|
173
|
-
|
174
|
-
|
175
|
-
|
286
|
+
def alias_expand(input)
|
287
|
+
ShellExpansions.new(world: world).expand_aliases_in(input)
|
288
|
+
end
|
176
289
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
end
|
290
|
+
def variable_expand(input)
|
291
|
+
ShellExpansions.new(world: world).expand_variables_in(input)
|
292
|
+
end
|
181
293
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
s
|
188
|
-
end
|
189
|
-
end.flatten
|
190
|
-
end.flatten
|
294
|
+
def shell_expand(input, escape_directory_expansions: true)
|
295
|
+
ShellExpansions.new(world: world).expand_words_in(
|
296
|
+
input,
|
297
|
+
escape_directory_expansions: escape_directory_expansions
|
298
|
+
)
|
191
299
|
end
|
192
300
|
|
193
301
|
def with_standard_streams(&blk)
|
@@ -215,5 +323,14 @@ module Yap::Shell
|
|
215
323
|
[stdin, stdout, stderr]
|
216
324
|
end
|
217
325
|
|
326
|
+
def with_env(&blk)
|
327
|
+
env = world.env.dup
|
328
|
+
begin
|
329
|
+
yield if block_given?
|
330
|
+
ensure
|
331
|
+
world.env.clear
|
332
|
+
world.env.replace(env)
|
333
|
+
end
|
334
|
+
end
|
218
335
|
end
|
219
336
|
end
|