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.
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