Class: Morpheus::Cli::Shell

Inherits:
Object
  • Object
show all
Includes:
CliCommand
Defined in:
lib/morpheus/cli/shell.rb

Instance Method Summary collapse

Methods included from CliCommand

#build_common_options, included

Constructor Details

#initializeShell

Returns a new instance of Shell.



16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/morpheus/cli/shell.rb', line 16

def initialize() 
	@appliance_name, @appliance_url = Morpheus::Cli::Remote.active_appliance	
	@auto_complete = proc do |s|
		command_list = Morpheus::Cli::CliRegistry.all.keys.reject {|k| [:shell].include?(k) }
		command_list += [:clear, :history, :flush_history, :exit]
		result = command_list.grep(/^#{Regexp.escape(s)}/)
		if result.nil? || result.empty?
			Readline::FILENAME_COMPLETION_PROC.call(s)
		else
			result
		end
	end

end

Instance Method Details

#get_promptObject



173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/morpheus/cli/shell.rb', line 173

def get_prompt
	input = ''
	while char=STDIN.getch do
		if char == '\n'
			print "\r\n"
			puts "executing..."
			break
		end
		print char
		input << char
	end
	return input
end

#handle(args) ⇒ Object



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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/morpheus/cli/shell.rb', line 31

def handle(args)
	usage = "Usage: morpheus shell"
	@command_options = {}
	optparse = OptionParser.new do|opts|
		opts.banner = usage
		opts.on('-a','--account ACCOUNT', "Account Name") do |val|
			@command_options[:account_name] = val
     end
     opts.on('-A','--account-id ID', "Account ID") do |val|
			@command_options[:account_id] = val
     end
		opts.on('-C','--nocolor', "ANSI") do
			@command_options[:nocolor] = true
       Term::ANSIColor::coloring = false
     end
     opts.on( '-h', '--help', "Prints this help" ) do
       puts opts
       exit
     end
	end
	optparse.parse(args)

	@history_logger ||= load_history_logger
	@history_logger.info "shell started" if @history_logger
	load_history_from_log_file()

	exit = false
	while !exit do
		Readline.completion_append_character = " "
		Readline.completion_proc = @auto_complete
		Readline.basic_word_break_characters = "\t\n\"\‘`@$><=;|&{( "
		input = Readline.readline("#{cyan}morpheus> #{reset}", true).to_s
		input = input.strip
		# print cyan,"morpheus > ",reset
		# input = $stdin.gets.chomp!
		if !input.empty?

			if input == 'exit'
				@history_logger.info "exit" if @history_logger
				break
			elsif input =~ /^history/
				n_commands = input.sub(/^history\s?/, '').sub(/\-n\s?/, '')
				n_commands = n_commands.empty? ? 25 : n_commands.to_i
				cmd_numbers = @history.keys.last(n_commands)
				puts "Last #{cmd_numbers.size} commands"
				cmd_numbers.each do |cmd_number|
					cmd = @history[cmd_number]
					puts "#{cmd_number.to_s.rjust(3, ' ')}  #{cmd}"
				end
				next
			elsif input == 'clear'
				print "\e[H\e[2J"
				next
			elsif input == 'flush_history'
				file_path = history_file_path
				if File.exists?(file_path)
					File.truncate(file_path, 0)
				end
				@history = {}
				@last_command_number = 0
				@history_logger = load_history_logger
				puts "history cleared!"
				next
			elsif input == '!!'
				cmd_number = @history.keys[-1]
				input = @history[cmd_number]
				if !input
					puts "There is no previous command"
					next
				end
			elsif input =~ /^\!.+/
				cmd_number = input.sub("!", "").to_i
				if cmd_number != 0
					input = @history[cmd_number]
					if !input
						puts "Command not found by number #{cmd_index}"
						next
					end
				end
			end

				begin
					argv = Shellwords.shellsplit(input)
					if @command_options[:account_name]
						argv.push "--account", @command_options[:account_name]
					end
					if @command_options[:account_id]
						argv.push "--account-id", @command_options[:account_id]
					end
					if @command_options[:nocolor]
						argv.push "--nocolor"
					end

					# set global log level to debug (print stack trace for bubbled exceptions)
					if argv.find {|arg| arg == '-V' || arg == '--debug'}
						@return_to_log_level = Morpheus::Logging.log_level
					  Morpheus::Logging.set_log_level(Morpheus::Logging::Logger::DEBUG)
					end
					argv = argv.find_all {|arg| arg != '-V' && arg != '--debug'}

					#puts "cmd: #{argv.join(' ')}"

					if argv[0] == 'shell'
						puts "You are already in a shell."
					elsif Morpheus::Cli::CliRegistry.has_command?(argv[0])
						log_history_command(input)
						Morpheus::Cli::CliRegistry.exec(argv[0], argv[1..-1])
					else
						@history_logger.warn "Unrecognized Command: #{input}" if @history_logger
						puts "Unrecognized Command."
					end
				rescue ArgumentError
					puts "Argument Syntax Error..."
				rescue Interrupt
					# nothing to do
					@history_logger.warn "shell interrupt" if @history_logger
					print "\n"
				rescue SystemExit
					# nothing to do
					print "\n"
				rescue OptionParser::InvalidOption => e
					print Term::ANSIColor.red, "\n", "#{e.message}", "", Term::ANSIColor.reset
					print "\n", "Try -h for help with this command.", "\n\n"
				rescue => e
					@history_logger.error "#{e.message}" if @history_logger
					print Term::ANSIColor.red, "\n", "Unexpected Error", "\n\n", Term::ANSIColor.reset
					if Morpheus::Logging.print_stacktrace?
						print Term::ANSIColor.red, "\n", "#{e.class}: #{e.message}", "\n", Term::ANSIColor.reset
						print e.backtrace.join("\n"), "\n\n"
					end
				end
					
				if @return_to_log_level
					Morpheus::Logging.set_log_level(@return_to_log_level)
					@return_to_log_level = nil
				end

		end
	end
end

#history_file_pathObject



187
188
189
# File 'lib/morpheus/cli/shell.rb', line 187

def history_file_path
	File.join(Dir.home, '.morpheus', "shell_history")
end

#load_history_from_log_file(n_commands = 1000) ⇒ Object



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/morpheus/cli/shell.rb', line 203

def load_history_from_log_file(n_commands=1000)
	@history ||= {}
	@last_command_number ||= 0

	if Gem.win_platform?
		return @history
	end

	begin
		file_path = history_file_path
		FileUtils.mkdir_p(File.dirname(file_path))
		# grab extra lines because not all log entries are commands
		n_lines = n_commands + 500
		history_lines = `tail -n #{n_lines} #{file_path}`.split(/\n/)
		command_lines = history_lines.select do |line| 
			line.match(/\(cmd (\d+)\) (.+)/)
		end
		command_lines = command_lines.last(n_commands)
		command_lines.each do |line|
			matches = line.match(/\(cmd (\d+)\) (.+)/)
			if matches && matches.size == 3
				cmd_number = matches[1].to_i
				cmd = matches[2]

				@last_command_number = cmd_number
				@history[@last_command_number] = cmd

				# for Ctrl+R history searching
				Readline::HISTORY << cmd
			end
		end
	rescue => e
		raise e
	end

	return @history
end

#load_history_loggerObject



191
192
193
194
195
196
197
198
199
200
201
# File 'lib/morpheus/cli/shell.rb', line 191

def load_history_logger
	file_path = history_file_path
	if !Dir.exists?(File.dirname(file_path))
		FileUtils.mkdir_p(File.dirname(file_path))
	end
	logger = Logger.new(file_path)
	# logger.formatter = proc do |severity, datetime, progname, msg|
	# 	"#{msg}\n"
	# end
	return logger
end

#log_history_command(cmd) ⇒ Object



241
242
243
244
245
246
247
248
249
# File 'lib/morpheus/cli/shell.rb', line 241

def log_history_command(cmd)
	@history ||= {}
	@last_command_number ||= 0
	@last_command_number += 1
	@history[@last_command_number] = cmd
	if @history_logger
		@history_logger.info "(cmd #{@last_command_number}) #{cmd}" 
	end
end