forked from adamwiggins/rush
-
Notifications
You must be signed in to change notification settings - Fork 1
/
shell.rb
135 lines (120 loc) · 3.63 KB
/
shell.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
require 'readline'
# Rush::Shell is used to create an interactive shell. It is invoked by the rush binary.
module Rush
class Shell
# Set up the user's environment, including a pure binding into which
# env.rb and commands.rb are mixed.
def initialize
root = Rush::Dir.new('/')
home = Rush::Dir.new(ENV['HOME']) if ENV['HOME']
pwd = Rush::Dir.new(ENV['PWD']) if ENV['PWD']
@config = Rush::Config.new
@config.load_history.each do |item|
Readline::HISTORY.push(item)
end
Readline.basic_word_break_characters = ""
Readline.completion_append_character = nil
Readline.completion_proc = completion_proc
@box = Rush::Box.new
@pure_binding = @box.instance_eval "binding"
$last_res = nil
eval @config.load_env, @pure_binding
commands = @config.load_commands
Rush::Dir.class_eval commands
Array.class_eval commands
end
# Run a single command.
def execute(cmd)
res = eval(cmd, @pure_binding)
$last_res = res
eval("_ = $last_res", @pure_binding)
print_result res
rescue Rush::Exception => e
puts "Exception #{e.class} -> #{e.message}"
rescue ::Exception => e
puts "Exception #{e.class} -> #{e.message}"
e.backtrace.each do |t|
puts " #{::File.expand_path(t)}"
end
end
# Run the interactive shell using readline.
def run
loop do
cmd = Readline.readline('rush> ')
finish if cmd.nil? or cmd == 'exit'
next if cmd == ""
Readline::HISTORY.push(cmd)
execute(cmd)
end
end
# Save history to ~/.rush/history when the shell exists.
def finish
@config.save_history(Readline::HISTORY.to_a)
puts
exit
end
# Nice printing of different return types, particularly Rush::SearchResults.
def print_result(res)
if res.kind_of? String
puts res
elsif res.kind_of? Array
res.each do |item|
puts item
end
elsif res.kind_of? Rush::SearchResults
widest = res.entries.map { |k| k.full_path.length }.max
res.entries_with_lines.each do |entry, lines|
print entry.full_path
print ' ' * (widest - entry.full_path.length + 2)
print "=> "
print res.colorize(lines.first.strip.head(30))
print "..." if lines.first.strip.length > 30
if lines.size > 1
print " (plus #{lines.size - 1} more matches)"
end
print "\n"
end
puts "#{res.entries.size} matching files with #{res.lines.size} matching lines"
else
puts "=> #{res.inspect}"
end
end
def path_parts(input) # :nodoc:
input.match(/(\w+(?:\[[^\]]+\])*)\[(['"])([^\]]+)$/)
$~.to_a.slice(1, 3).push($~.pre_match)
rescue
[ nil, nil, nil, nil ]
end
# Try to do tab completion on dir square brackets accessors.
#
# Example:
#
# dir['subd # presing tab here will produce dir['subdir/ if subdir exists
#
# This isn't that cool yet, because it can't do multiple levels of subdirs.
# It does work remotely, though, which is pretty sweet.
def completion_proc
proc do |input|
possible_var, quote, partial_path, pre = path_parts(input)
if possible_var
original_var, fixed_path = possible_var, ''
if /^(.+\/)([^\/]+)$/ === partial_path
fixed_path, partial_path = $~.captures
possible_var += "['#{fixed_path}']"
end
full_path = eval("#{possible_var}.full_path", @pure_binding) rescue nil
box = eval("#{possible_var}.box", @pure_binding) rescue nil
if full_path and box
dir = Rush::Dir.new(full_path, box)
return dir.entries.select do |e|
e.name.match(/^#{Regexp.escape(partial_path)}/)
end.map do |e|
(pre || '') + original_var + '[' + quote + fixed_path + e.name + (e.dir? ? "/" : "")
end
end
end
nil
end
end
end
end