yap-shell 0.7.1 → 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +9 -24
  3. data/Gemfile +1 -5
  4. data/LICENSE.txt +17 -18
  5. data/README.md +28 -14
  6. data/Rakefile +4 -1
  7. data/bin/yap +1 -3
  8. data/lib/.gitkeep +0 -0
  9. data/yap-shell.gemspec +12 -11
  10. metadata +19 -184
  11. data/.rspec +0 -2
  12. data/.travis.yml +0 -11
  13. data/DESIGN.md +0 -87
  14. data/Gemfile.travis +0 -8
  15. data/Gemfile.travis.lock +0 -104
  16. data/WISHLIST.md +0 -54
  17. data/bin/yap-dev +0 -45
  18. data/lib/tasks/gem.rake +0 -62
  19. data/lib/yap.rb +0 -52
  20. data/lib/yap/addon.rb +0 -24
  21. data/lib/yap/addon/base.rb +0 -52
  22. data/lib/yap/addon/export_as.rb +0 -12
  23. data/lib/yap/addon/loader.rb +0 -84
  24. data/lib/yap/addon/path.rb +0 -56
  25. data/lib/yap/addon/rc_file.rb +0 -21
  26. data/lib/yap/addon/reference.rb +0 -22
  27. data/lib/yap/cli.rb +0 -4
  28. data/lib/yap/cli/commands.rb +0 -6
  29. data/lib/yap/cli/commands/addon.rb +0 -14
  30. data/lib/yap/cli/commands/addon/disable.rb +0 -35
  31. data/lib/yap/cli/commands/addon/enable.rb +0 -35
  32. data/lib/yap/cli/commands/addon/list.rb +0 -37
  33. data/lib/yap/cli/commands/addon/search.rb +0 -99
  34. data/lib/yap/cli/commands/generate.rb +0 -13
  35. data/lib/yap/cli/commands/generate/addon.rb +0 -258
  36. data/lib/yap/cli/commands/generate/addonrb.template +0 -22
  37. data/lib/yap/cli/commands/generate/gemspec.template +0 -25
  38. data/lib/yap/cli/commands/generate/license.template +0 -21
  39. data/lib/yap/cli/commands/generate/rakefile.template +0 -6
  40. data/lib/yap/cli/commands/generate/readme.template +0 -40
  41. data/lib/yap/cli/options.rb +0 -162
  42. data/lib/yap/cli/options/addon.rb +0 -64
  43. data/lib/yap/cli/options/addon/disable.rb +0 -62
  44. data/lib/yap/cli/options/addon/enable.rb +0 -63
  45. data/lib/yap/cli/options/addon/list.rb +0 -65
  46. data/lib/yap/cli/options/addon/search.rb +0 -76
  47. data/lib/yap/cli/options/generate.rb +0 -59
  48. data/lib/yap/cli/options/generate/addon.rb +0 -63
  49. data/lib/yap/configuration.rb +0 -74
  50. data/lib/yap/gem_helper.rb +0 -195
  51. data/lib/yap/gem_tasks.rb +0 -6
  52. data/lib/yap/shell.rb +0 -116
  53. data/lib/yap/shell/aliases.rb +0 -58
  54. data/lib/yap/shell/builtins.rb +0 -18
  55. data/lib/yap/shell/builtins/alias.rb +0 -42
  56. data/lib/yap/shell/builtins/cd.rb +0 -57
  57. data/lib/yap/shell/builtins/env.rb +0 -11
  58. data/lib/yap/shell/commands.rb +0 -163
  59. data/lib/yap/shell/evaluation.rb +0 -439
  60. data/lib/yap/shell/evaluation/shell_expansions.rb +0 -99
  61. data/lib/yap/shell/event_emitter.rb +0 -18
  62. data/lib/yap/shell/execution.rb +0 -16
  63. data/lib/yap/shell/execution/builtin_command_execution.rb +0 -20
  64. data/lib/yap/shell/execution/command_execution.rb +0 -30
  65. data/lib/yap/shell/execution/context.rb +0 -128
  66. data/lib/yap/shell/execution/file_system_command_execution.rb +0 -137
  67. data/lib/yap/shell/execution/result.rb +0 -18
  68. data/lib/yap/shell/execution/ruby_command_execution.rb +0 -80
  69. data/lib/yap/shell/execution/shell_command_execution.rb +0 -30
  70. data/lib/yap/shell/prompt.rb +0 -21
  71. data/lib/yap/shell/repl.rb +0 -237
  72. data/lib/yap/shell/version.rb +0 -5
  73. data/lib/yap/world.rb +0 -286
  74. data/rcfiles/yaprc +0 -390
  75. data/scripts/4 +0 -8
  76. data/scripts/bg-vim +0 -4
  77. data/scripts/fail +0 -3
  78. data/scripts/letters +0 -8
  79. data/scripts/lots-of-output +0 -6
  80. data/scripts/pass +0 -3
  81. data/scripts/simulate-long-running +0 -4
  82. data/scripts/write-to-stderr.rb +0 -3
  83. data/scripts/write-to-stdout.rb +0 -3
  84. data/spec/features/addons/generating_an_addon_spec.rb +0 -55
  85. data/spec/features/addons/using_an_addon_spec.rb +0 -182
  86. data/spec/features/aliases_spec.rb +0 -78
  87. data/spec/features/environment_variables_spec.rb +0 -69
  88. data/spec/features/filesystem_commands_spec.rb +0 -61
  89. data/spec/features/first_time_spec.rb +0 -45
  90. data/spec/features/grouping_spec.rb +0 -81
  91. data/spec/features/line_editing_spec.rb +0 -174
  92. data/spec/features/range_spec.rb +0 -35
  93. data/spec/features/redirection_spec.rb +0 -234
  94. data/spec/features/repetition_spec.rb +0 -118
  95. data/spec/features/shell_expansions_spec.rb +0 -127
  96. data/spec/spec_helper.rb +0 -172
  97. data/spec/support/matchers/have_not_printed.rb +0 -30
  98. data/spec/support/matchers/have_printed.rb +0 -68
  99. data/spec/support/very_soon.rb +0 -9
  100. data/spec/support/yap_spec_dsl.rb +0 -258
  101. data/test.rb +0 -206
  102. data/update-rawline.sh +0 -6
@@ -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
@@ -1,11 +0,0 @@
1
- module Yap::Shell
2
- module Builtins
3
-
4
- builtin :env do |world:, args:, stdout:, **|
5
- world.env.keys.sort.each do |key|
6
- stdout.puts "#{key}=#{world.env[key]}"
7
- end
8
- end
9
-
10
- end
11
- end
@@ -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
@@ -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