yap-shell 0.7.1 → 0.7.2
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 +9 -24
- data/Gemfile +1 -5
- data/LICENSE.txt +17 -18
- data/README.md +28 -14
- data/Rakefile +4 -1
- data/bin/yap +1 -3
- data/lib/.gitkeep +0 -0
- data/yap-shell.gemspec +12 -11
- metadata +19 -184
- data/.rspec +0 -2
- data/.travis.yml +0 -11
- data/DESIGN.md +0 -87
- data/Gemfile.travis +0 -8
- data/Gemfile.travis.lock +0 -104
- data/WISHLIST.md +0 -54
- data/bin/yap-dev +0 -45
- data/lib/tasks/gem.rake +0 -62
- data/lib/yap.rb +0 -52
- data/lib/yap/addon.rb +0 -24
- data/lib/yap/addon/base.rb +0 -52
- data/lib/yap/addon/export_as.rb +0 -12
- data/lib/yap/addon/loader.rb +0 -84
- data/lib/yap/addon/path.rb +0 -56
- data/lib/yap/addon/rc_file.rb +0 -21
- data/lib/yap/addon/reference.rb +0 -22
- data/lib/yap/cli.rb +0 -4
- data/lib/yap/cli/commands.rb +0 -6
- data/lib/yap/cli/commands/addon.rb +0 -14
- data/lib/yap/cli/commands/addon/disable.rb +0 -35
- data/lib/yap/cli/commands/addon/enable.rb +0 -35
- data/lib/yap/cli/commands/addon/list.rb +0 -37
- data/lib/yap/cli/commands/addon/search.rb +0 -99
- data/lib/yap/cli/commands/generate.rb +0 -13
- data/lib/yap/cli/commands/generate/addon.rb +0 -258
- data/lib/yap/cli/commands/generate/addonrb.template +0 -22
- data/lib/yap/cli/commands/generate/gemspec.template +0 -25
- data/lib/yap/cli/commands/generate/license.template +0 -21
- data/lib/yap/cli/commands/generate/rakefile.template +0 -6
- data/lib/yap/cli/commands/generate/readme.template +0 -40
- data/lib/yap/cli/options.rb +0 -162
- data/lib/yap/cli/options/addon.rb +0 -64
- data/lib/yap/cli/options/addon/disable.rb +0 -62
- data/lib/yap/cli/options/addon/enable.rb +0 -63
- data/lib/yap/cli/options/addon/list.rb +0 -65
- data/lib/yap/cli/options/addon/search.rb +0 -76
- data/lib/yap/cli/options/generate.rb +0 -59
- data/lib/yap/cli/options/generate/addon.rb +0 -63
- data/lib/yap/configuration.rb +0 -74
- data/lib/yap/gem_helper.rb +0 -195
- data/lib/yap/gem_tasks.rb +0 -6
- data/lib/yap/shell.rb +0 -116
- data/lib/yap/shell/aliases.rb +0 -58
- data/lib/yap/shell/builtins.rb +0 -18
- data/lib/yap/shell/builtins/alias.rb +0 -42
- data/lib/yap/shell/builtins/cd.rb +0 -57
- data/lib/yap/shell/builtins/env.rb +0 -11
- data/lib/yap/shell/commands.rb +0 -163
- data/lib/yap/shell/evaluation.rb +0 -439
- data/lib/yap/shell/evaluation/shell_expansions.rb +0 -99
- data/lib/yap/shell/event_emitter.rb +0 -18
- data/lib/yap/shell/execution.rb +0 -16
- data/lib/yap/shell/execution/builtin_command_execution.rb +0 -20
- data/lib/yap/shell/execution/command_execution.rb +0 -30
- data/lib/yap/shell/execution/context.rb +0 -128
- data/lib/yap/shell/execution/file_system_command_execution.rb +0 -137
- data/lib/yap/shell/execution/result.rb +0 -18
- data/lib/yap/shell/execution/ruby_command_execution.rb +0 -80
- data/lib/yap/shell/execution/shell_command_execution.rb +0 -30
- data/lib/yap/shell/prompt.rb +0 -21
- data/lib/yap/shell/repl.rb +0 -237
- data/lib/yap/shell/version.rb +0 -5
- data/lib/yap/world.rb +0 -286
- data/rcfiles/yaprc +0 -390
- data/scripts/4 +0 -8
- data/scripts/bg-vim +0 -4
- data/scripts/fail +0 -3
- data/scripts/letters +0 -8
- data/scripts/lots-of-output +0 -6
- data/scripts/pass +0 -3
- data/scripts/simulate-long-running +0 -4
- data/scripts/write-to-stderr.rb +0 -3
- data/scripts/write-to-stdout.rb +0 -3
- data/spec/features/addons/generating_an_addon_spec.rb +0 -55
- data/spec/features/addons/using_an_addon_spec.rb +0 -182
- data/spec/features/aliases_spec.rb +0 -78
- data/spec/features/environment_variables_spec.rb +0 -69
- data/spec/features/filesystem_commands_spec.rb +0 -61
- data/spec/features/first_time_spec.rb +0 -45
- data/spec/features/grouping_spec.rb +0 -81
- data/spec/features/line_editing_spec.rb +0 -174
- data/spec/features/range_spec.rb +0 -35
- data/spec/features/redirection_spec.rb +0 -234
- data/spec/features/repetition_spec.rb +0 -118
- data/spec/features/shell_expansions_spec.rb +0 -127
- data/spec/spec_helper.rb +0 -172
- data/spec/support/matchers/have_not_printed.rb +0 -30
- data/spec/support/matchers/have_printed.rb +0 -68
- data/spec/support/very_soon.rb +0 -9
- data/spec/support/yap_spec_dsl.rb +0 -258
- data/test.rb +0 -206
- data/update-rawline.sh +0 -6
data/lib/yap/shell/builtins.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
module Yap::Shell
|
2
|
-
require 'yap/shell/commands'
|
3
|
-
|
4
|
-
module Builtins
|
5
|
-
def self.builtin(name, &blk)
|
6
|
-
Yap::Shell::BuiltinCommand.add(name, &blk)
|
7
|
-
end
|
8
|
-
|
9
|
-
def self.execute_builtin(name, world:, args:, stdin:, stdout:, stderr:)
|
10
|
-
command = Yap::Shell::BuiltinCommand.new(world:world, str:name, args: args)
|
11
|
-
command.execute(stdin:stdin, stdout:stdout, stderr:stderr)
|
12
|
-
end
|
13
|
-
|
14
|
-
Dir[File.dirname(__FILE__) + "/builtins/**/*.rb"].each do |f|
|
15
|
-
require f
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
@@ -1,42 +0,0 @@
|
|
1
|
-
require 'shellwords'
|
2
|
-
|
3
|
-
module Yap::Shell
|
4
|
-
require 'yap/shell/aliases'
|
5
|
-
|
6
|
-
module Builtins
|
7
|
-
Color = ::Term::ANSIColor
|
8
|
-
|
9
|
-
builtin :alias do |args:, stdout:, **kwargs|
|
10
|
-
output = []
|
11
|
-
if args.empty?
|
12
|
-
Yap::Shell::Aliases.instance.to_h.each_pair do |name, value|
|
13
|
-
# Escape and wrap single quotes since we're using
|
14
|
-
# single quotes to wrap the aliased command for matching
|
15
|
-
# bash output.
|
16
|
-
escaped_value = value.gsub(/'/){ |a| "'\\#{a}'" }
|
17
|
-
output << "alias #{name.shellescape}='#{escaped_value}'"
|
18
|
-
end
|
19
|
-
output << ""
|
20
|
-
else
|
21
|
-
name_eq_value = args.map(&:shellsplit).join(' ')
|
22
|
-
name, command = name_eq_value.scan(/^(.*?)\s*=\s*(.*)$/).flatten
|
23
|
-
output << "Setting alias #{name} #{Color.green('done')}"
|
24
|
-
Yap::Shell::Aliases.instance.set_alias name, command
|
25
|
-
end
|
26
|
-
stdout.puts output.join("\n")
|
27
|
-
end
|
28
|
-
|
29
|
-
builtin :unalias do |args:, stdout:, **kwargs|
|
30
|
-
output = []
|
31
|
-
if args.empty?
|
32
|
-
output << "Usage: unalias <aliasname>"
|
33
|
-
else
|
34
|
-
args.each do |alias_name|
|
35
|
-
Yap::Shell::Aliases.instance.unset_alias alias_name
|
36
|
-
output << "Removing alias #{alias_name} #{Color.green('done')}"
|
37
|
-
end
|
38
|
-
end
|
39
|
-
stdout.puts output.join("\n")
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
@@ -1,57 +0,0 @@
|
|
1
|
-
module Yap::Shell
|
2
|
-
module Builtins
|
3
|
-
DIRECTORY_HISTORY = []
|
4
|
-
DIRECTORY_FUTURE = []
|
5
|
-
|
6
|
-
builtin :cd do |world:, args:, stderr:, **|
|
7
|
-
path = (args.first || world.env['HOME']).shellsplit.join
|
8
|
-
if Dir.exist?(path)
|
9
|
-
DIRECTORY_HISTORY << Dir.pwd
|
10
|
-
world.env["PWD"] = File.expand_path(path)
|
11
|
-
Dir.chdir(path)
|
12
|
-
exit_status = 0
|
13
|
-
else
|
14
|
-
stderr.puts "cd: #{path}: No such file or directory"
|
15
|
-
exit_status = 1
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
builtin :popd do |world:, args:, stderr:, **keyword_args|
|
20
|
-
output = []
|
21
|
-
if DIRECTORY_HISTORY.any?
|
22
|
-
path = DIRECTORY_HISTORY.pop
|
23
|
-
if Dir.exist?(path)
|
24
|
-
DIRECTORY_FUTURE << Dir.pwd
|
25
|
-
Dir.chdir(path)
|
26
|
-
world.env["PWD"] = Dir.pwd
|
27
|
-
exit_status = 0
|
28
|
-
else
|
29
|
-
stderr.puts "popd: #{path}: No such file or directory"
|
30
|
-
exit_status = 1
|
31
|
-
end
|
32
|
-
else
|
33
|
-
stderr.puts "popd: directory stack empty"
|
34
|
-
exit_status = 1
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
builtin :pushd do |world:, args:, stderr:, **keyword_args|
|
39
|
-
output = []
|
40
|
-
if DIRECTORY_FUTURE.any?
|
41
|
-
path = DIRECTORY_FUTURE.pop
|
42
|
-
if Dir.exist?(path)
|
43
|
-
DIRECTORY_HISTORY << Dir.pwd
|
44
|
-
Dir.chdir(path)
|
45
|
-
world.env["PWD"] = Dir.pwd
|
46
|
-
exit_status = 0
|
47
|
-
else
|
48
|
-
stderr.puts "pushd: #{path}: No such file or directory"
|
49
|
-
exit_status = 1
|
50
|
-
end
|
51
|
-
else
|
52
|
-
stderr.puts "pushd: there are no directories in your future"
|
53
|
-
exit_status = 1
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
data/lib/yap/shell/commands.rb
DELETED
@@ -1,163 +0,0 @@
|
|
1
|
-
require 'shellwords'
|
2
|
-
|
3
|
-
module Yap::Shell
|
4
|
-
require 'yap/shell/aliases'
|
5
|
-
require 'yap/shell/execution/result'
|
6
|
-
|
7
|
-
class CommandError < StandardError ; end
|
8
|
-
|
9
|
-
class CommandFactory
|
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
|
-
|
13
|
-
case command
|
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
|
-
else
|
18
|
-
UnknownCommand.new(world:world, str:command, args:args, heredoc:heredoc)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
class Command
|
24
|
-
attr_accessor :world, :str, :args, :line
|
25
|
-
attr_accessor :heredoc
|
26
|
-
|
27
|
-
def initialize(world:, str:, args:[], line:nil, heredoc:nil)
|
28
|
-
@world = world
|
29
|
-
@str = str
|
30
|
-
@args = args
|
31
|
-
@heredoc = heredoc
|
32
|
-
@line = line
|
33
|
-
end
|
34
|
-
|
35
|
-
def to_s
|
36
|
-
"#{self.class.name}(#{str.inspect})"
|
37
|
-
end
|
38
|
-
alias :to_str :to_s
|
39
|
-
alias :inspect :to_s
|
40
|
-
|
41
|
-
def to_executable_str
|
42
|
-
raise NotImplementedError, ":to_executable_str must be implemented by including object."
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
class BuiltinCommand < Command
|
47
|
-
def self.===(other)
|
48
|
-
self.builtins.keys.include?(other.split(' ').first.to_sym) || super
|
49
|
-
end
|
50
|
-
|
51
|
-
def self.builtins
|
52
|
-
@builtins ||= {
|
53
|
-
builtins: lambda { |stdout:, **| stdout.puts @builtins.keys.sort },
|
54
|
-
exit: lambda { |code = 0, **| exit(code.to_i) },
|
55
|
-
fg: lambda{ |**| :resume },
|
56
|
-
}
|
57
|
-
end
|
58
|
-
|
59
|
-
def self.add(command, &action)
|
60
|
-
builtins.merge!(command.to_sym => action)
|
61
|
-
end
|
62
|
-
|
63
|
-
def execute(stdin:, stdout:, stderr:)
|
64
|
-
action = self.class.builtins.fetch(str.to_sym){ raise("Missing proc for builtin: '#{builtin}' in #{str.inspect}") }
|
65
|
-
action.call world:world, args:args, stdin:stdin, stdout:stdout, stderr:stderr
|
66
|
-
end
|
67
|
-
|
68
|
-
def type
|
69
|
-
:BuiltinCommand
|
70
|
-
end
|
71
|
-
|
72
|
-
def to_executable_str
|
73
|
-
raise NotImplementedError, "#to_executable_str is not implemented on BuiltInCommand"
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
class UnknownCommand < Command
|
78
|
-
EXIT_CODE = 127
|
79
|
-
|
80
|
-
def execute(stdin:, stdout:, stderr:)
|
81
|
-
stderr.puts "yap: command not found: #{str}"
|
82
|
-
EXIT_CODE
|
83
|
-
end
|
84
|
-
|
85
|
-
def type
|
86
|
-
:BuiltinCommand
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
class FileSystemCommand < Command
|
91
|
-
def self.world
|
92
|
-
::Yap::World.instance
|
93
|
-
end
|
94
|
-
|
95
|
-
def self.===(other)
|
96
|
-
command = other.split(/\s+/).detect{ |f| !f.include?("=") }
|
97
|
-
|
98
|
-
# Check to see if the user gave us a valid path to execute
|
99
|
-
return true if File.executable?(command)
|
100
|
-
|
101
|
-
# See if the command exists anywhere on the path
|
102
|
-
world.env["PATH"].split(":").detect do |path|
|
103
|
-
File.executable?(File.join(path, command))
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
def type
|
108
|
-
:FileSystemCommand
|
109
|
-
end
|
110
|
-
|
111
|
-
def to_s
|
112
|
-
"#{self.class.name}(#{to_executable_str.inspect})"
|
113
|
-
end
|
114
|
-
alias :to_str :to_s
|
115
|
-
alias :inspect :to_s
|
116
|
-
|
117
|
-
def to_executable_str
|
118
|
-
[
|
119
|
-
str,
|
120
|
-
args.join(' ')
|
121
|
-
].join(' ')
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
class ShellCommand < Command
|
126
|
-
def self.registered_functions
|
127
|
-
(@registered_functions ||= {}).freeze
|
128
|
-
end
|
129
|
-
|
130
|
-
def self.define_shell_function(name_or_pattern, name: nil, &blk)
|
131
|
-
raise ArgumentError, "Must provide block when defining a shell function" unless blk
|
132
|
-
name_or_pattern = name_or_pattern.to_s if name_or_pattern.is_a?(Symbol)
|
133
|
-
(@registered_functions ||= {})[name_or_pattern] = blk
|
134
|
-
end
|
135
|
-
|
136
|
-
def self.===(command)
|
137
|
-
registered_functions.detect do |name_or_pattern, *_|
|
138
|
-
name_or_pattern.match(command)
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
def type
|
143
|
-
:ShellCommand
|
144
|
-
end
|
145
|
-
|
146
|
-
def to_proc
|
147
|
-
self.class.registered_functions.detect do |name_or_pattern, function_body|
|
148
|
-
return function_body if name_or_pattern.match(str)
|
149
|
-
end
|
150
|
-
raise "Shell function #{str} was not found!"
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
|
-
class RubyCommand < Command
|
155
|
-
def type
|
156
|
-
:RubyCommand
|
157
|
-
end
|
158
|
-
|
159
|
-
def to_executable_str
|
160
|
-
str
|
161
|
-
end
|
162
|
-
end
|
163
|
-
end
|
data/lib/yap/shell/evaluation.rb
DELETED
@@ -1,439 +0,0 @@
|
|
1
|
-
module Yap::Shell
|
2
|
-
require 'yap/shell/parser'
|
3
|
-
require 'yap/shell/commands'
|
4
|
-
require 'yap/shell/aliases'
|
5
|
-
require 'yap/shell/evaluation/shell_expansions'
|
6
|
-
|
7
|
-
class Evaluation
|
8
|
-
attr_reader :world
|
9
|
-
|
10
|
-
def initialize(stdin:, stdout:, stderr:, world:)
|
11
|
-
@stdin, @stdout, @stderr = stdin, stdout, stderr
|
12
|
-
@world = world
|
13
|
-
end
|
14
|
-
|
15
|
-
def evaluate(input, &blk)
|
16
|
-
@blk = blk
|
17
|
-
debug_log "evaluation begins input=#{input.inspect}"
|
18
|
-
@input = recursively_find_and_replace_command_substitutions(input)
|
19
|
-
debug_log "recursive find/replace command substitutions results in input=#{@input.inspect}"
|
20
|
-
ast = Parser.parse(@input)
|
21
|
-
debug_log "parsed input into AST: #{ast}"
|
22
|
-
debug_log "beginning to walk AST"
|
23
|
-
ast.accept(self).tap do
|
24
|
-
debug_log "done walking AST"
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
def set_last_result(result)
|
29
|
-
debug_log "evaluation setting last result=#{result.inspect}"
|
30
|
-
@world.last_result = result
|
31
|
-
end
|
32
|
-
|
33
|
-
private
|
34
|
-
|
35
|
-
def debug_log(message)
|
36
|
-
Treefell['shell'].puts message
|
37
|
-
end
|
38
|
-
|
39
|
-
def debug_visit(node, msg='')
|
40
|
-
calling_method = caller[0][/`.*'/][1..-2]
|
41
|
-
debug_log "evaluation #{calling_method} node=#{node} #{msg}"
|
42
|
-
end
|
43
|
-
|
44
|
-
# +recursively_find_and_replace_command_substitutions+ is responsible for recursively
|
45
|
-
# finding and expanding command substitutions, in a depth first manner.
|
46
|
-
def recursively_find_and_replace_command_substitutions(input)
|
47
|
-
input = input.dup
|
48
|
-
Parser.each_command_substitution_for(input) do |substitution_result, start_position, end_position|
|
49
|
-
debug_log "found command substitution at position=#{(start_position..end_position)} #{substitution_result.inspect}"
|
50
|
-
result = recursively_find_and_replace_command_substitutions(substitution_result.str)
|
51
|
-
position = substitution_result.position
|
52
|
-
ast = Parser.parse(result)
|
53
|
-
with_standard_streams do |stdin, stdout, stderr|
|
54
|
-
r,w = IO.pipe
|
55
|
-
@stdout = w
|
56
|
-
ast.accept(self)
|
57
|
-
|
58
|
-
output = r.read.chomp
|
59
|
-
|
60
|
-
# Treat consecutive newlines in output as a single space
|
61
|
-
output = output.gsub(/\n+/, ' ')
|
62
|
-
|
63
|
-
# Double quote the output and escape any double quotes already
|
64
|
-
# existing
|
65
|
-
output = %|"#{output.gsub(/"/, '\\"')}"|
|
66
|
-
|
67
|
-
# Put thd output back into the original input
|
68
|
-
debug_log "replacing command substitution at position=#{(position.min...position.max)} with #{output.inspect}"
|
69
|
-
input[position.min...position.max] = output
|
70
|
-
end
|
71
|
-
end
|
72
|
-
input
|
73
|
-
end
|
74
|
-
|
75
|
-
|
76
|
-
######################################################################
|
77
|
-
# #
|
78
|
-
# VISITOR METHODS FOR AST TREE WALKING #
|
79
|
-
# #
|
80
|
-
######################################################################
|
81
|
-
|
82
|
-
def visit_CommandNode(node)
|
83
|
-
debug_visit(node)
|
84
|
-
@aliases_expanded ||= []
|
85
|
-
@command_node_args_stack ||= []
|
86
|
-
with_standard_streams do |stdin, stdout, stderr|
|
87
|
-
args = process_ArgumentNodes(node.args)
|
88
|
-
if !node.literal? && !@aliases_expanded.include?(node.command) && Aliases.instance.has_key?(node.command)
|
89
|
-
_alias=Aliases.instance.fetch_alias(node.command)
|
90
|
-
@suppress_events = true
|
91
|
-
@command_node_args_stack << args
|
92
|
-
ast = Parser.parse(_alias)
|
93
|
-
@aliases_expanded.push(node.command)
|
94
|
-
ast.accept(self)
|
95
|
-
@aliases_expanded.pop
|
96
|
-
@suppress_events = false
|
97
|
-
else
|
98
|
-
cmd2execute = variable_expand(node.command)
|
99
|
-
final_args = (args + @command_node_args_stack).flatten.map(&:shellescape)
|
100
|
-
expanded_args = final_args
|
101
|
-
command = CommandFactory.build_command_for(
|
102
|
-
world: world,
|
103
|
-
command: cmd2execute,
|
104
|
-
args: expanded_args,
|
105
|
-
heredoc: (node.heredoc && node.heredoc.value),
|
106
|
-
internally_evaluate: node.internally_evaluate?,
|
107
|
-
line: @input)
|
108
|
-
@stdin, @stdout, @stderr = stream_redirections_for(node)
|
109
|
-
set_last_result @blk.call command, @stdin, @stdout, @stderr, pipeline_stack.empty?
|
110
|
-
@command_node_args_stack.clear
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
def visit_CommentNode(node)
|
116
|
-
debug_visit(node)
|
117
|
-
# no-op, do nothing
|
118
|
-
end
|
119
|
-
|
120
|
-
def visit_RangeNode(node)
|
121
|
-
debug_visit(node)
|
122
|
-
range = node.head.value
|
123
|
-
if node.tail
|
124
|
-
@current_range_values = range.to_a
|
125
|
-
node.tail.accept(self)
|
126
|
-
@current_range_values = nil
|
127
|
-
else
|
128
|
-
@stdout.puts range.to_a.join(' ')
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
def visit_RedirectionNode(node)
|
133
|
-
debug_visit(node)
|
134
|
-
filename = node.target
|
135
|
-
|
136
|
-
if File.directory?(filename)
|
137
|
-
puts <<-ERROR.gsub(/^\s*/m, '').lines.join(' ')
|
138
|
-
Whoops, #{filename.inspect} is a directory! Those can't be redirected to.
|
139
|
-
ERROR
|
140
|
-
set_last_result Yap::Shell::Execution::Result.new(status_code:1, directory:Dir.pwd, n:1, of:1)
|
141
|
-
elsif node.kind == ">"
|
142
|
-
File.write(filename, "")
|
143
|
-
set_last_result Yap::Shell::Execution::Result.new(status_code:0, directory:Dir.pwd, n:1, of:1)
|
144
|
-
else
|
145
|
-
puts "Sorry, #{node.kind} redirection isn't a thing, but >#{filename} is!"
|
146
|
-
set_last_result Yap::Shell::Execution::Result.new(status_code:2, directory:Dir.pwd, n:1, of:1)
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
def visit_BlockNode(node)
|
151
|
-
debug_visit(node)
|
152
|
-
|
153
|
-
with_standard_streams do |stdin, stdout, stderr|
|
154
|
-
# Modify @stdout and @stderr for the first command
|
155
|
-
stdin, @stdout = IO.pipe
|
156
|
-
|
157
|
-
# Don't modify @stdin for the first command in the pipeline.
|
158
|
-
values = []
|
159
|
-
if node.head
|
160
|
-
node.head.accept(self)
|
161
|
-
values = stdin.read.split(/\n|\0/)
|
162
|
-
else
|
163
|
-
# assume range for now
|
164
|
-
values = @current_range_values
|
165
|
-
end
|
166
|
-
|
167
|
-
evaluate_block = lambda {
|
168
|
-
with_standard_streams do |stdin2, stdout2, stderr2|
|
169
|
-
@stdout = stdout
|
170
|
-
node.tail.accept(self)
|
171
|
-
end
|
172
|
-
}
|
173
|
-
|
174
|
-
if node.params.any?
|
175
|
-
values.each_slice(node.params.length).each do |_slice|
|
176
|
-
with_env do
|
177
|
-
Hash[ node.params.zip(_slice) ].each_pair do |k,v|
|
178
|
-
world.env[k] = v.to_s
|
179
|
-
end
|
180
|
-
evaluate_block.call
|
181
|
-
end
|
182
|
-
end
|
183
|
-
else
|
184
|
-
values.each do
|
185
|
-
evaluate_block.call
|
186
|
-
end
|
187
|
-
end
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
def visit_NumericalRangeNode(node)
|
192
|
-
debug_visit(node)
|
193
|
-
node.range.each do |n|
|
194
|
-
if node.tail
|
195
|
-
if node.reference
|
196
|
-
with_env do
|
197
|
-
world.env[node.reference.value] = n.to_s
|
198
|
-
node.tail.accept(self)
|
199
|
-
end
|
200
|
-
else
|
201
|
-
node.tail.accept(self)
|
202
|
-
end
|
203
|
-
end
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
def visit_StatementsNode(node)
|
208
|
-
debug_visit(node)
|
209
|
-
Yap::Shell::Execution::Context.fire :before_statements_execute, @world unless @suppress_events
|
210
|
-
node.head.accept(self)
|
211
|
-
if node.tail
|
212
|
-
node.tail.accept(self)
|
213
|
-
end
|
214
|
-
Yap::Shell::Execution::Context.fire :after_statements_execute, @world unless @suppress_events
|
215
|
-
end
|
216
|
-
|
217
|
-
# Represents a statement that has scoped environment variables being set,
|
218
|
-
# e.g.:
|
219
|
-
#
|
220
|
-
# yap> A=5 ls
|
221
|
-
# yap> A=5 B=6 echo
|
222
|
-
#
|
223
|
-
# These environment variables are reset after the their statement, e.g.:
|
224
|
-
#
|
225
|
-
# yap> A=5
|
226
|
-
# yap> echo $A
|
227
|
-
# 5
|
228
|
-
# yap> A=b echo $A
|
229
|
-
# b
|
230
|
-
# yap> echo $
|
231
|
-
# 5
|
232
|
-
#
|
233
|
-
def visit_EnvWrapperNode(node)
|
234
|
-
debug_visit(node)
|
235
|
-
with_env do
|
236
|
-
node.env.each_pair do |env_var_name, arg_node|
|
237
|
-
world.env[env_var_name] = process_EnvArgumentNode(arg_node)
|
238
|
-
end
|
239
|
-
node.node.accept(self)
|
240
|
-
end
|
241
|
-
end
|
242
|
-
|
243
|
-
# Represents a statement that contains nothing but environment
|
244
|
-
# variables being set, e.g.:
|
245
|
-
#
|
246
|
-
# yap> A=5
|
247
|
-
# yap> A=5 B=6
|
248
|
-
# yap> A=3 && B=6
|
249
|
-
#
|
250
|
-
# The environment variables persist from statement to statement until
|
251
|
-
# they cleared or overridden.
|
252
|
-
#
|
253
|
-
def visit_EnvNode(node)
|
254
|
-
debug_visit(node)
|
255
|
-
node.env.each_pair do |key, arg_node|
|
256
|
-
world.env[key] = process_EnvArgumentNode(arg_node)
|
257
|
-
end
|
258
|
-
Yap::Shell::Execution::Result.new(status_code:0, directory:Dir.pwd, n:1, of:1)
|
259
|
-
end
|
260
|
-
|
261
|
-
def visit_ConditionalNode(node)
|
262
|
-
debug_visit(node)
|
263
|
-
case node.operator
|
264
|
-
when '&&'
|
265
|
-
node.expr1.accept self
|
266
|
-
if @world.last_result.status_code == 0
|
267
|
-
node.expr2.accept self
|
268
|
-
end
|
269
|
-
when '||'
|
270
|
-
node.expr1.accept self
|
271
|
-
if @world.last_result.status_code != 0
|
272
|
-
node.expr2.accept self
|
273
|
-
end
|
274
|
-
else
|
275
|
-
raise "Don't know how to visit conditional node: #{node.inspect}"
|
276
|
-
end
|
277
|
-
end
|
278
|
-
|
279
|
-
def visit_PipelineNode(node, options={})
|
280
|
-
debug_visit(node)
|
281
|
-
|
282
|
-
with_standard_streams do |stdin, stdout, stderr|
|
283
|
-
# Modify @stdout and @stderr for the first command
|
284
|
-
stdin, @stdout = IO.pipe
|
285
|
-
pipeline_stack.push true
|
286
|
-
# Don't modify @stdin for the first command in the pipeline.
|
287
|
-
node.head.accept(self)
|
288
|
-
|
289
|
-
# Modify @stdin starting with the second command to read from the
|
290
|
-
# read portion of our above stdout.
|
291
|
-
@stdin = stdin
|
292
|
-
|
293
|
-
# Modify @stdout,@stderr to go back to the original
|
294
|
-
@stdout, @stderr = stdout, stderr
|
295
|
-
|
296
|
-
pipeline_stack.pop
|
297
|
-
node.tail.accept(self)
|
298
|
-
|
299
|
-
# Set our @stdin back to the original
|
300
|
-
@stdin = stdin
|
301
|
-
end
|
302
|
-
end
|
303
|
-
|
304
|
-
def visit_InternalEvalNode(node)
|
305
|
-
debug_visit(node)
|
306
|
-
|
307
|
-
command = CommandFactory.build_command_for(
|
308
|
-
world: world,
|
309
|
-
command: node.command,
|
310
|
-
args: node.args,
|
311
|
-
heredoc: node.heredoc,
|
312
|
-
internally_evaluate: node.internally_evaluate?,
|
313
|
-
line: @input)
|
314
|
-
set_last_result @blk.call command, @stdin, @stdout, @stderr, pipeline_stack.empty?
|
315
|
-
end
|
316
|
-
|
317
|
-
######################################################################
|
318
|
-
# #
|
319
|
-
# HELPER / UTILITY METHODS #
|
320
|
-
# #
|
321
|
-
######################################################################
|
322
|
-
|
323
|
-
def process_ArgumentNodes(nodes)
|
324
|
-
nodes.map { |arg_node| process_ArgumentNode(arg_node) }
|
325
|
-
end
|
326
|
-
|
327
|
-
def process_ArgumentNode(node)
|
328
|
-
if node.single_quoted?
|
329
|
-
debug_visit(node, 'single quoted argument, performing no expansion')
|
330
|
-
["#{node.lvalue}"]
|
331
|
-
elsif node.double_quoted?
|
332
|
-
debug_visit(node, 'double quoted argument, performing variable expansion')
|
333
|
-
[variable_expand(node.lvalue)]
|
334
|
-
elsif node.escaped?
|
335
|
-
debug_visit(node, 'escaped argument, skipping variable expansion')
|
336
|
-
[node.lvalue]
|
337
|
-
else
|
338
|
-
debug_visit(node, 'unquoted argument, performing shell expansion')
|
339
|
-
shell_expand(node.lvalue, escape_directory_expansions: false)
|
340
|
-
end
|
341
|
-
end
|
342
|
-
|
343
|
-
# process_EnvArgumentNode is separate from process_ArgumentNode since
|
344
|
-
# it doesn't allow full shell expansion. For example, the following
|
345
|
-
# will not be expanded to "FOO=a_1 a_2", it will be stored as provided:
|
346
|
-
#
|
347
|
-
# > FOO=a_{1,2}
|
348
|
-
#
|
349
|
-
def process_EnvArgumentNode(node)
|
350
|
-
if node.single_quoted?
|
351
|
-
debug_visit(node, 'single quoted argument, performing no expansion')
|
352
|
-
node.lvalue
|
353
|
-
else
|
354
|
-
debug_visit(node, 'not singled quoted argument, performing variable expansion')
|
355
|
-
variable_expand(node.lvalue)
|
356
|
-
end
|
357
|
-
end
|
358
|
-
|
359
|
-
# +pipeline_stack+ is used to determine if we are about go inside of a
|
360
|
-
# pipeline. It will be empty when we are coming out of a pipeline node.
|
361
|
-
def pipeline_stack
|
362
|
-
@pipeline_stack ||= []
|
363
|
-
end
|
364
|
-
|
365
|
-
def alias_expand(input)
|
366
|
-
ShellExpansions.new(world: world).expand_aliases_in(input)
|
367
|
-
end
|
368
|
-
|
369
|
-
def variable_expand(input)
|
370
|
-
ShellExpansions.new(world: world).expand_variables_in(input)
|
371
|
-
end
|
372
|
-
|
373
|
-
def shell_expand(input, escape_directory_expansions: true)
|
374
|
-
ShellExpansions.new(world: world).expand_words_in(
|
375
|
-
input,
|
376
|
-
escape_directory_expansions: escape_directory_expansions
|
377
|
-
)
|
378
|
-
end
|
379
|
-
|
380
|
-
def with_standard_streams(&blk)
|
381
|
-
stdin, stdout, stderr = @stdin, @stdout, @stderr
|
382
|
-
yield stdin, stdout, stderr
|
383
|
-
@stdin, @stdout, @stderr = stdin, stdout, stderr
|
384
|
-
end
|
385
|
-
|
386
|
-
def stream_redirections_for(node)
|
387
|
-
debug_visit(node)
|
388
|
-
|
389
|
-
stdin, stdout, stderr = @stdin, @stdout, @stderr
|
390
|
-
node.redirects.each do |redirect|
|
391
|
-
case redirect.kind
|
392
|
-
when "<"
|
393
|
-
stdin = redirect.target
|
394
|
-
stdin = File.open(stdin, "rb") if stdin.is_a?(String)
|
395
|
-
when ">", "1>"
|
396
|
-
stdout = redirect.target
|
397
|
-
stdout = File.open(stdout, "wb") if stdout.is_a?(String)
|
398
|
-
when "1>&2"
|
399
|
-
stdout = redirect.target
|
400
|
-
stdout = File.open(stdout, "wb") if stdout.is_a?(String)
|
401
|
-
stderr = stdout
|
402
|
-
when "2>"
|
403
|
-
stderr = redirect.target
|
404
|
-
stderr = File.open(stderr, "wb") if stderr.is_a?(String)
|
405
|
-
when "2>&1"
|
406
|
-
stderr = redirect.target
|
407
|
-
stderr = File.open(stderr, "wb") if stderr.is_a?(String)
|
408
|
-
stdout = stderr
|
409
|
-
when "1>>", ">>"
|
410
|
-
stdout = redirect.target
|
411
|
-
stdout = File.open(stdout, "ab") if stdout.is_a?(String)
|
412
|
-
when "2>>"
|
413
|
-
stderr = redirect.target
|
414
|
-
stderr = File.open(stderr, "ab") if stderr.is_a?(String)
|
415
|
-
when "&>"
|
416
|
-
stdout = redirect.target
|
417
|
-
stdout = File.open(stdout, "wb") if stdout.is_a?(String)
|
418
|
-
stderr = stdout
|
419
|
-
when "&>>"
|
420
|
-
stdout = redirect.target
|
421
|
-
stdout = File.open(stdout, "ab") if stdout.is_a?(String)
|
422
|
-
stderr = stdout
|
423
|
-
end
|
424
|
-
end
|
425
|
-
|
426
|
-
[stdin, stdout, stderr]
|
427
|
-
end
|
428
|
-
|
429
|
-
def with_env(&blk)
|
430
|
-
env = world.env.dup
|
431
|
-
begin
|
432
|
-
yield if block_given?
|
433
|
-
ensure
|
434
|
-
world.env.clear
|
435
|
-
world.env.replace(env)
|
436
|
-
end
|
437
|
-
end
|
438
|
-
end
|
439
|
-
end
|