yap-shell 0.4.0 → 0.4.1
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 +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
|