yap-shell 0.4.0 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/addons/history/history.rb +2 -0
- data/addons/keyboard_macros/keyboard_macros.rb +60 -2
- data/addons/tab_completion/tab_completion.rb +13 -0
- data/bin/yap-dev +0 -4
- data/lib/tasks/gem.rake +1 -1
- data/lib/yap.rb +3 -0
- data/lib/yap/shell.rb +12 -0
- data/lib/yap/shell/aliases.rb +18 -4
- data/lib/yap/shell/builtins/alias.rb +1 -1
- data/lib/yap/shell/commands.rb +12 -0
- data/lib/yap/shell/evaluation.rb +77 -10
- data/lib/yap/shell/evaluation/shell_expansions.rb +22 -8
- data/lib/yap/shell/execution/builtin_command_execution.rb +5 -1
- data/lib/yap/shell/execution/context.rb +8 -0
- data/lib/yap/shell/execution/file_system_command_execution.rb +20 -7
- data/lib/yap/shell/execution/ruby_command_execution.rb +60 -65
- data/lib/yap/shell/execution/shell_command_execution.rb +3 -0
- data/lib/yap/shell/repl.rb +14 -5
- data/lib/yap/shell/version.rb +1 -1
- data/lib/yap/world.rb +4 -0
- data/lib/yap/world/addons.rb +56 -13
- data/rcfiles/.yaprc +7 -1
- data/yap-shell.gemspec +2 -1
- metadata +18 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cca1d07b231bb416a8b295be3f63df4d343de0c9
|
4
|
+
data.tar.gz: 152bf268dedcba028e1f288a781f47fc818b780f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5fcb45c7edd79541be56467df065440dd90b723d8291733407c9bf76a63b896ec46a62278cd4acdb65064bcaf6e2fb97461e97ccdd3669434d5879f866078df9
|
7
|
+
data.tar.gz: e600e1906665cf39be024ec45dd1fc88ecc397ff898e81adb129b2e1392a0d837bbd47c716b15eb75be4e394addb769c0ec8bd425a223a05ae1abb5d846f7469
|
data/addons/history/history.rb
CHANGED
@@ -22,6 +22,7 @@ class History < Addon
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def save
|
25
|
+
debug_log "saving history file=#{file.inspect}"
|
25
26
|
File.open(file, "a") do |file|
|
26
27
|
# Don't write the YAML header because we're going to append to the
|
27
28
|
# history file, not overwrite. YAML works fine without it.
|
@@ -40,6 +41,7 @@ class History < Addon
|
|
40
41
|
private
|
41
42
|
|
42
43
|
def load_history
|
44
|
+
debug_log "loading history file=#{file.inspect}"
|
43
45
|
at_exit { save }
|
44
46
|
|
45
47
|
if File.exists?(file) && File.readable?(file)
|
@@ -1,6 +1,20 @@
|
|
1
1
|
class KeyboardMacros < Addon
|
2
2
|
require 'keyboard_macros/cycle'
|
3
3
|
|
4
|
+
module PrettyPrintKey
|
5
|
+
# ppk means "pretty print key". For example, it returns \C-g if the given
|
6
|
+
# byte is 7.
|
7
|
+
def ppk(byte)
|
8
|
+
if byte && byte.ord <= 26
|
9
|
+
'\C-' + ('a'..'z').to_a[byte.ord - 1]
|
10
|
+
else
|
11
|
+
byte.inspect
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
include PrettyPrintKey
|
17
|
+
|
4
18
|
DEFAULT_TRIGGER_KEY = :ctrl_g
|
5
19
|
DEFAULT_CANCEL_KEY = " "
|
6
20
|
DEFAULT_TIMEOUT_IN_MS = 500
|
@@ -24,7 +38,29 @@ class KeyboardMacros < Addon
|
|
24
38
|
@cancel_on_unknown_sequences = false
|
25
39
|
end
|
26
40
|
|
41
|
+
def cancel_key=(key)
|
42
|
+
debug_log "setting default cancel_key key=#{ppk(key)}"
|
43
|
+
@cancel_key = key
|
44
|
+
end
|
45
|
+
|
46
|
+
def cancel_on_unknown_sequences=(true_or_false)
|
47
|
+
debug_log "setting default cancel_on_unknown_sequences=#{true_or_false}"
|
48
|
+
@cancel_on_unknown_sequences = true_or_false
|
49
|
+
end
|
50
|
+
|
51
|
+
def timeout_in_ms=(milliseconds)
|
52
|
+
debug_log "setting default timeout_in_ms milliseconds=#{milliseconds.inspect}"
|
53
|
+
@timeout_in_ms = milliseconds
|
54
|
+
end
|
55
|
+
|
56
|
+
def trigger_key=(key)
|
57
|
+
debug_log "setting default trigger_key key=#{ppk(key)}"
|
58
|
+
@trigger_key = key
|
59
|
+
end
|
60
|
+
|
27
61
|
def configure(cancel_key: nil, trigger_key: nil, &blk)
|
62
|
+
debug_log "configure cancel_key=#{ppk(cancel_key)} trigger_key=#{ppk(trigger_key)} block_given?=#{block_given?}"
|
63
|
+
|
28
64
|
cancel_key ||= @cancel_key
|
29
65
|
trigger_key ||= @trigger_key
|
30
66
|
|
@@ -45,12 +81,18 @@ class KeyboardMacros < Addon
|
|
45
81
|
|
46
82
|
world.unbind(trigger_key)
|
47
83
|
world.bind(trigger_key) do
|
84
|
+
debug_log "macro triggered key=#{ppk(trigger_key)}"
|
85
|
+
|
48
86
|
begin
|
49
87
|
@previous_result = nil
|
50
88
|
@stack << OpenStruct.new(configuration: configuration)
|
51
89
|
configuration.start.call if configuration.start
|
90
|
+
|
91
|
+
debug_log "taking over keyboard input processing from editor"
|
52
92
|
world.editor.keyboard_input_processors.push(self)
|
53
|
-
|
93
|
+
|
94
|
+
wait_timeout_in_seconds = 0.1
|
95
|
+
world.editor.input.wait_timeout_in_seconds = wait_timeout_in_seconds
|
54
96
|
ensure
|
55
97
|
queue_up_remove_input_processor(&configuration.stop)
|
56
98
|
end
|
@@ -60,6 +102,8 @@ class KeyboardMacros < Addon
|
|
60
102
|
end
|
61
103
|
|
62
104
|
def cycle(name, &cycle_thru_blk)
|
105
|
+
debug_log "defining cycle name=#{name.inspect}"
|
106
|
+
|
63
107
|
@cycles ||= {}
|
64
108
|
if block_given?
|
65
109
|
cycle = KeyboardMacros::Cycle.new(
|
@@ -126,7 +170,6 @@ class KeyboardMacros < Addon
|
|
126
170
|
|
127
171
|
def process_result(result)
|
128
172
|
if result.is_a?(String)
|
129
|
-
$z.puts "WRITING: #{result}"
|
130
173
|
@world.editor.write result, add_to_line_history: false
|
131
174
|
@previous_result = result
|
132
175
|
end
|
@@ -146,13 +189,17 @@ class KeyboardMacros < Addon
|
|
146
189
|
end
|
147
190
|
|
148
191
|
def cancel_processing
|
192
|
+
debug_log "cancel_processing"
|
149
193
|
@event_id = nil
|
150
194
|
@stack.reverse.each do |definition|
|
151
195
|
definition.configuration.stop.call if definition.configuration.stop
|
152
196
|
end
|
153
197
|
@stack.clear
|
154
198
|
if world.editor.keyboard_input_processors.last == self
|
199
|
+
debug_log "giving keyboard input processing control back"
|
155
200
|
world.editor.keyboard_input_processors.pop
|
201
|
+
|
202
|
+
debug_log "restoring default editor input timeout"
|
156
203
|
world.editor.input.restore_default_timeout
|
157
204
|
end
|
158
205
|
end
|
@@ -171,8 +218,14 @@ class KeyboardMacros < Addon
|
|
171
218
|
end
|
172
219
|
|
173
220
|
class Configuration
|
221
|
+
include PrettyPrintKey
|
222
|
+
|
174
223
|
attr_reader :cancellation, :trigger_key, :keymap
|
175
224
|
|
225
|
+
def debug_log(*args)
|
226
|
+
KeyboardMacros.debug_log(*args)
|
227
|
+
end
|
228
|
+
|
176
229
|
def initialize(cancellation: nil, editor:, keymap: {}, trigger_key: nil)
|
177
230
|
@cancellation = cancellation
|
178
231
|
@editor = editor
|
@@ -183,6 +236,8 @@ class KeyboardMacros < Addon
|
|
183
236
|
@on_stop_blk = nil
|
184
237
|
@cycles = {}
|
185
238
|
|
239
|
+
debug_log "configuring a macro trigger_key=#{ppk(trigger_key)}"
|
240
|
+
|
186
241
|
if @cancellation
|
187
242
|
define @cancellation.cancel_key, -> { @cancellation.call }
|
188
243
|
end
|
@@ -199,6 +254,8 @@ class KeyboardMacros < Addon
|
|
199
254
|
end
|
200
255
|
|
201
256
|
def cycle(name, &cycle_thru_blk)
|
257
|
+
debug_log "defining a cycle on macro name=#{name.inspect}"
|
258
|
+
|
202
259
|
if block_given?
|
203
260
|
cycle = KeyboardMacros::Cycle.new(
|
204
261
|
cycle_proc: cycle_thru_blk,
|
@@ -217,6 +274,7 @@ class KeyboardMacros < Addon
|
|
217
274
|
end
|
218
275
|
|
219
276
|
def define(sequence, result=nil, fragment: false, &blk)
|
277
|
+
debug_log "defining macro sequence=#{sequence.inspect} result=#{result.inspect} fragment=#{fragment.inspect} under macro #{ppk(trigger_key)}"
|
220
278
|
unless result.respond_to?(:call)
|
221
279
|
string_result = result
|
222
280
|
result = -> { string_result }
|
@@ -26,6 +26,8 @@ class TabCompletion < Addon
|
|
26
26
|
def to_s
|
27
27
|
@text
|
28
28
|
end
|
29
|
+
alias :to_str :to_s
|
30
|
+
alias :inspect :to_s
|
29
31
|
end
|
30
32
|
|
31
33
|
COMPLETIONS = [ BasicCompletion ]
|
@@ -69,6 +71,8 @@ class TabCompletion < Addon
|
|
69
71
|
@display_procs = DISPLAY_PROCS.dup
|
70
72
|
|
71
73
|
editor.on_word_complete do |event|
|
74
|
+
debug_log "on_word_complete event: #{event}"
|
75
|
+
|
72
76
|
sub_word = event[:payload][:sub_word]
|
73
77
|
word = event[:payload][:word]
|
74
78
|
actual_completion = event[:payload][:completion]
|
@@ -98,6 +102,8 @@ class TabCompletion < Addon
|
|
98
102
|
end
|
99
103
|
|
100
104
|
editor.on_word_complete_no_match do |event|
|
105
|
+
debug_log "on_word_complete_no_match event: #{event}"
|
106
|
+
|
101
107
|
sub_word = event[:payload][:sub_word]
|
102
108
|
word = event[:payload][:word]
|
103
109
|
editor.content_box.children = []
|
@@ -105,6 +111,8 @@ class TabCompletion < Addon
|
|
105
111
|
end
|
106
112
|
|
107
113
|
editor.on_word_complete_done do |event|
|
114
|
+
debug_log "on_word_complete_done event: #{event}"
|
115
|
+
|
108
116
|
# TODO: add a better way to clear content
|
109
117
|
editor.content_box.children = []
|
110
118
|
end
|
@@ -112,15 +120,19 @@ class TabCompletion < Addon
|
|
112
120
|
|
113
121
|
def add_completion(name, pattern, &blk)
|
114
122
|
raise ArgumentError, "Must supply block!" unless block_given?
|
123
|
+
debug_log "NO-OP add_completion for name=#{name.inspect} pattern=#{pattern.inspect} block?=#{block_given?}"
|
115
124
|
# @completions.push CustomCompletion.new(name:name, pattern:pattern, world:world, &blk)
|
116
125
|
end
|
117
126
|
|
118
127
|
def set_decoration(type, &blk)
|
119
128
|
raise ArgumentError, "Must supply block!" unless block_given?
|
129
|
+
debug_log "set_decoration for type=#{name.inspect}"
|
120
130
|
@style_procs[type] = blk
|
121
131
|
end
|
122
132
|
|
123
133
|
def complete(word, words, word_index)
|
134
|
+
debug_log "complete word=#{word.inspect} words=#{words.inspect} word_index=#{word_index.inspect}"
|
135
|
+
|
124
136
|
matches = @completions.sort_by(&:priority).reverse.map do |completion|
|
125
137
|
if completion.respond_to?(:call)
|
126
138
|
completion.call
|
@@ -135,6 +147,7 @@ class TabCompletion < Addon
|
|
135
147
|
end
|
136
148
|
end.flatten
|
137
149
|
|
150
|
+
debug_log "complete possible matches are #{matches.inspect}"
|
138
151
|
matches
|
139
152
|
end
|
140
153
|
|
data/bin/yap-dev
CHANGED
data/lib/tasks/gem.rake
CHANGED
@@ -2,7 +2,7 @@ namespace :bump do
|
|
2
2
|
namespace :version do
|
3
3
|
class ProjectVersion
|
4
4
|
FILE = File.dirname(__FILE__) + '/../yap/shell/version.rb'
|
5
|
-
PATTERN = /VERSION\s*=\s*"(\d+)\.(\d+)\.(\d+)"/m
|
5
|
+
PATTERN = /VERSION\s*=\s*['"](\d+)\.(\d+)\.(\d+)['"]/m
|
6
6
|
|
7
7
|
def initialize(file=FILE, pattern=PATTERN)
|
8
8
|
@file = file
|
data/lib/yap.rb
CHANGED
@@ -1,9 +1,12 @@
|
|
1
|
+
require 'treefell'
|
2
|
+
|
1
3
|
module Yap
|
2
4
|
require 'yap/configuration'
|
3
5
|
require 'yap/shell'
|
4
6
|
require 'yap/world'
|
5
7
|
|
6
8
|
def self.run_shell
|
9
|
+
Treefell['shell'].puts "#{self}.#{__callee__} booting shell"
|
7
10
|
addons = [
|
8
11
|
World::Addons.load_directories(configuration.addon_paths),
|
9
12
|
World::Addons.load_rcfiles(configuration.rcfiles)
|
data/lib/yap/shell.rb
CHANGED
@@ -37,6 +37,8 @@ module Yap
|
|
37
37
|
@stdout.sync = true
|
38
38
|
@stderr.sync = true
|
39
39
|
|
40
|
+
addons_str = "\n - " + addons.map(&:class).map(&:name).join("\n - ")
|
41
|
+
Treefell['shell'].puts "Constructing world instance with addons: #{addons_str}"
|
40
42
|
@world = Yap::World.instance(addons:addons)
|
41
43
|
end
|
42
44
|
|
@@ -69,8 +71,16 @@ module Yap
|
|
69
71
|
)
|
70
72
|
|
71
73
|
@world.repl.on_input do |input|
|
74
|
+
Treefell['shell'].puts "repl received input: #{input.inspect}"
|
72
75
|
evaluation = Yap::Shell::Evaluation.new(stdin:@stdin, stdout:@stdout, stderr:@stderr, world:@world)
|
73
76
|
evaluation.evaluate(input) do |command, stdin, stdout, stderr, wait|
|
77
|
+
Treefell['shell'].puts <<-DEBUG.gsub(/^\s*\|/, '')
|
78
|
+
|adding #{command} to run in context of:
|
79
|
+
| stdin: #{stdin.inspect}
|
80
|
+
| stdout: #{stdout.inspect}
|
81
|
+
| stderr: #{stderr.inspect}
|
82
|
+
| wait for child process to complete? #{wait.inspect}
|
83
|
+
DEBUG
|
74
84
|
context.clear_commands
|
75
85
|
context.add_command_to_run command, stdin:stdin, stdout:stdout, stderr:stderr, wait:wait
|
76
86
|
|
@@ -81,12 +91,14 @@ module Yap
|
|
81
91
|
end
|
82
92
|
|
83
93
|
begin
|
94
|
+
Treefell['shell'].puts "enter interactive mode"
|
84
95
|
@world.interactive!
|
85
96
|
# rescue Errno::EIO => ex
|
86
97
|
# # This happens when yap is no longer the foreground process
|
87
98
|
# # but it tries to receive input/output from the tty. I believe it
|
88
99
|
# # is a race condition when launching a child process.
|
89
100
|
rescue Interrupt
|
101
|
+
Treefell['shell'].puts "^C"
|
90
102
|
@world.editor.puts "^C"
|
91
103
|
retry
|
92
104
|
rescue Exception => ex
|
data/lib/yap/shell/aliases.rb
CHANGED
@@ -7,6 +7,7 @@ module Yap::Shell
|
|
7
7
|
def initialize
|
8
8
|
@file = ENV["HOME"] + "/.yapaliases.yml"
|
9
9
|
@aliases = begin
|
10
|
+
Treefell['shell'].puts "reading aliases from disk: #{@file}"
|
10
11
|
YAML.load_file(@file)
|
11
12
|
rescue
|
12
13
|
{}
|
@@ -18,16 +19,21 @@ module Yap::Shell
|
|
18
19
|
end
|
19
20
|
|
20
21
|
def fetch_alias(name)
|
21
|
-
@aliases[name]
|
22
|
+
@aliases[name].tap do |contents|
|
23
|
+
Treefell['shell'].puts "alias fetched name=#{name} contents=#{contents.inspect}"
|
24
|
+
end
|
22
25
|
end
|
23
26
|
|
24
|
-
def set_alias(name,
|
25
|
-
@aliases[name] =
|
26
|
-
|
27
|
+
def set_alias(name, contents)
|
28
|
+
@aliases[name] = contents
|
29
|
+
Treefell['shell'].puts "alias set name=#{name} to #{contents.inspect}"
|
30
|
+
write_to_disk
|
27
31
|
end
|
28
32
|
|
29
33
|
def unset_alias(name)
|
30
34
|
@aliases.delete(name)
|
35
|
+
Treefell['shell'].puts "alias unset name=#{name}"
|
36
|
+
write_to_disk
|
31
37
|
end
|
32
38
|
|
33
39
|
def has_key?(key)
|
@@ -40,5 +46,13 @@ module Yap::Shell
|
|
40
46
|
h
|
41
47
|
end
|
42
48
|
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def write_to_disk
|
53
|
+
File.write(@file, @aliases.to_yaml).tap do
|
54
|
+
Treefell['shell'].puts "aliases written to disk: #{@file}"
|
55
|
+
end
|
56
|
+
end
|
43
57
|
end
|
44
58
|
end
|
@@ -16,7 +16,7 @@ module Yap::Shell
|
|
16
16
|
end
|
17
17
|
output << ""
|
18
18
|
else
|
19
|
-
name_eq_value = args.
|
19
|
+
name_eq_value = args.map(&:shellsplit).join(' ')
|
20
20
|
name, command = name_eq_value.scan(/^(.*?)\s*=\s*(.*)$/).flatten
|
21
21
|
Yap::Shell::Aliases.instance.set_alias name, command
|
22
22
|
end
|
data/lib/yap/shell/commands.rb
CHANGED
@@ -33,6 +33,12 @@ module Yap::Shell
|
|
33
33
|
@line = line
|
34
34
|
end
|
35
35
|
|
36
|
+
def to_s
|
37
|
+
"#{self.class.name}(#{str.inspect})"
|
38
|
+
end
|
39
|
+
alias :to_str :to_s
|
40
|
+
alias :inspect :to_s
|
41
|
+
|
36
42
|
def to_executable_str
|
37
43
|
raise NotImplementedError, ":to_executable_str must be implemented by including object."
|
38
44
|
end
|
@@ -90,6 +96,12 @@ module Yap::Shell
|
|
90
96
|
:FileSystemCommand
|
91
97
|
end
|
92
98
|
|
99
|
+
def to_s
|
100
|
+
"#{self.class.name}(#{to_executable_str.inspect})"
|
101
|
+
end
|
102
|
+
alias :to_str :to_s
|
103
|
+
alias :inspect :to_s
|
104
|
+
|
93
105
|
def to_executable_str
|
94
106
|
[
|
95
107
|
str,
|
data/lib/yap/shell/evaluation.rb
CHANGED
@@ -14,22 +14,39 @@ module Yap::Shell
|
|
14
14
|
|
15
15
|
def evaluate(input, &blk)
|
16
16
|
@blk = blk
|
17
|
+
debug_log "evaluation begins input=#{input.inspect}"
|
17
18
|
@input = recursively_find_and_replace_command_substitutions(input)
|
19
|
+
debug_log "recursive find/replace command substitutions results in input=#{@input.inspect}"
|
18
20
|
ast = Parser.parse(@input)
|
19
|
-
ast
|
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
|
20
26
|
end
|
21
27
|
|
22
28
|
def set_last_result(result)
|
29
|
+
debug_log "evaluation setting last result=#{result.inspect}"
|
23
30
|
@world.last_result = result
|
24
31
|
end
|
25
32
|
|
26
33
|
private
|
27
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
|
+
|
28
44
|
# +recursively_find_and_replace_command_substitutions+ is responsible for recursively
|
29
45
|
# finding and expanding command substitutions, in a depth first manner.
|
30
46
|
def recursively_find_and_replace_command_substitutions(input)
|
31
47
|
input = input.dup
|
32
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}"
|
33
50
|
result = recursively_find_and_replace_command_substitutions(substitution_result.str)
|
34
51
|
position = substitution_result.position
|
35
52
|
ast = Parser.parse(result)
|
@@ -37,7 +54,19 @@ module Yap::Shell
|
|
37
54
|
r,w = IO.pipe
|
38
55
|
@stdout = w
|
39
56
|
ast.accept(self)
|
40
|
-
|
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
|
41
70
|
end
|
42
71
|
end
|
43
72
|
input
|
@@ -51,11 +80,13 @@ module Yap::Shell
|
|
51
80
|
######################################################################
|
52
81
|
|
53
82
|
def visit_CommandNode(node)
|
83
|
+
debug_visit(node)
|
54
84
|
@aliases_expanded ||= []
|
55
85
|
@command_node_args_stack ||= []
|
56
86
|
with_standard_streams do |stdin, stdout, stderr|
|
57
87
|
args = process_ArgumentNodes(node.args)
|
58
|
-
if !node.literal? && !@aliases_expanded.include?(node.command) &&
|
88
|
+
if !node.literal? && !@aliases_expanded.include?(node.command) && Aliases.instance.has_key?(node.command)
|
89
|
+
_alias=Aliases.instance.fetch_alias(node.command)
|
59
90
|
@suppress_events = true
|
60
91
|
@command_node_args_stack << args
|
61
92
|
ast = Parser.parse(_alias)
|
@@ -66,7 +97,7 @@ module Yap::Shell
|
|
66
97
|
else
|
67
98
|
cmd2execute = variable_expand(node.command)
|
68
99
|
final_args = (args + @command_node_args_stack).flatten.map(&:shellescape)
|
69
|
-
expanded_args =
|
100
|
+
expanded_args = final_args
|
70
101
|
command = CommandFactory.build_command_for(
|
71
102
|
world: world,
|
72
103
|
command: cmd2execute,
|
@@ -82,10 +113,12 @@ module Yap::Shell
|
|
82
113
|
end
|
83
114
|
|
84
115
|
def visit_CommentNode(node)
|
116
|
+
debug_visit(node)
|
85
117
|
# no-op, do nothing
|
86
118
|
end
|
87
119
|
|
88
120
|
def visit_RangeNode(node)
|
121
|
+
debug_visit(node)
|
89
122
|
range = node.head.value
|
90
123
|
if node.tail
|
91
124
|
@current_range_values = range.to_a
|
@@ -97,6 +130,7 @@ module Yap::Shell
|
|
97
130
|
end
|
98
131
|
|
99
132
|
def visit_RedirectionNode(node)
|
133
|
+
debug_visit(node)
|
100
134
|
filename = node.target
|
101
135
|
|
102
136
|
if File.directory?(filename)
|
@@ -114,6 +148,8 @@ module Yap::Shell
|
|
114
148
|
end
|
115
149
|
|
116
150
|
def visit_BlockNode(node)
|
151
|
+
debug_visit(node)
|
152
|
+
|
117
153
|
with_standard_streams do |stdin, stdout, stderr|
|
118
154
|
# Modify @stdout and @stderr for the first command
|
119
155
|
stdin, @stdout = IO.pipe
|
@@ -150,10 +186,10 @@ module Yap::Shell
|
|
150
186
|
end
|
151
187
|
end
|
152
188
|
end
|
153
|
-
|
154
189
|
end
|
155
190
|
|
156
191
|
def visit_NumericalRangeNode(node)
|
192
|
+
debug_visit(node)
|
157
193
|
node.range.each do |n|
|
158
194
|
if node.tail
|
159
195
|
if node.reference
|
@@ -169,6 +205,7 @@ module Yap::Shell
|
|
169
205
|
end
|
170
206
|
|
171
207
|
def visit_StatementsNode(node)
|
208
|
+
debug_visit(node)
|
172
209
|
Yap::Shell::Execution::Context.fire :before_statements_execute, @world unless @suppress_events
|
173
210
|
node.head.accept(self)
|
174
211
|
if node.tail
|
@@ -194,9 +231,10 @@ module Yap::Shell
|
|
194
231
|
# 5
|
195
232
|
#
|
196
233
|
def visit_EnvWrapperNode(node)
|
234
|
+
debug_visit(node)
|
197
235
|
with_env do
|
198
236
|
node.env.each_pair do |env_var_name, arg_node|
|
199
|
-
world.env[env_var_name] =
|
237
|
+
world.env[env_var_name] = process_EnvArgumentNode(arg_node)
|
200
238
|
end
|
201
239
|
node.node.accept(self)
|
202
240
|
end
|
@@ -213,13 +251,15 @@ module Yap::Shell
|
|
213
251
|
# they cleared or overridden.
|
214
252
|
#
|
215
253
|
def visit_EnvNode(node)
|
254
|
+
debug_visit(node)
|
216
255
|
node.env.each_pair do |key, arg_node|
|
217
|
-
world.env[key] =
|
256
|
+
world.env[key] = process_EnvArgumentNode(arg_node)
|
218
257
|
end
|
219
258
|
Yap::Shell::Execution::Result.new(status_code:0, directory:Dir.pwd, n:1, of:1)
|
220
259
|
end
|
221
260
|
|
222
261
|
def visit_ConditionalNode(node)
|
262
|
+
debug_visit(node)
|
223
263
|
case node.operator
|
224
264
|
when '&&'
|
225
265
|
node.expr1.accept self
|
@@ -237,6 +277,8 @@ module Yap::Shell
|
|
237
277
|
end
|
238
278
|
|
239
279
|
def visit_PipelineNode(node, options={})
|
280
|
+
debug_visit(node)
|
281
|
+
|
240
282
|
with_standard_streams do |stdin, stdout, stderr|
|
241
283
|
# Modify @stdout and @stderr for the first command
|
242
284
|
stdin, @stdout = IO.pipe
|
@@ -260,6 +302,8 @@ module Yap::Shell
|
|
260
302
|
end
|
261
303
|
|
262
304
|
def visit_InternalEvalNode(node)
|
305
|
+
debug_visit(node)
|
306
|
+
|
263
307
|
command = CommandFactory.build_command_for(
|
264
308
|
world: world,
|
265
309
|
command: node.command,
|
@@ -281,10 +325,31 @@ module Yap::Shell
|
|
281
325
|
end
|
282
326
|
|
283
327
|
def process_ArgumentNode(node)
|
284
|
-
if node.
|
285
|
-
|
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)]
|
286
334
|
else
|
287
|
-
|
335
|
+
debug_visit(node, 'unquoted argument, performing shell expansion')
|
336
|
+
shell_expand(node.lvalue, escape_directory_expansions: false)
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
# process_EnvArgumentNode is separate from process_ArgumentNode since
|
341
|
+
# it doesn't allow full shell expansion. For example, the following
|
342
|
+
# will not be expanded to "FOO=a_1 a_2", it will be stored as provided:
|
343
|
+
#
|
344
|
+
# > FOO=a_{1,2}
|
345
|
+
#
|
346
|
+
def process_EnvArgumentNode(node)
|
347
|
+
if node.single_quoted?
|
348
|
+
debug_visit(node, 'single quoted argument, performing no expansion')
|
349
|
+
node.lvalue
|
350
|
+
else
|
351
|
+
debug_visit(node, 'not singled quoted argument, performing variable expansion')
|
352
|
+
variable_expand(node.lvalue)
|
288
353
|
end
|
289
354
|
end
|
290
355
|
|
@@ -316,6 +381,8 @@ module Yap::Shell
|
|
316
381
|
end
|
317
382
|
|
318
383
|
def stream_redirections_for(node)
|
384
|
+
debug_visit(node)
|
385
|
+
|
319
386
|
stdin, stdout, stderr = @stdin, @stdout, @stderr
|
320
387
|
node.redirects.each do |redirect|
|
321
388
|
case redirect.kind
|