Class: Shells::ShellBase
- Inherits:
-
Object
- Object
- Shells::ShellBase
- Defined in:
- lib/shells/shell_base.rb
Overview
Provides a base interface for all shells to build on.
Instantiating this class will raise an error. All shell sessions should inherit this class.
Direct Known Subclasses
Defined Under Namespace
Classes: QuitNow
Instance Attribute Summary collapse
-
#last_exit_code ⇒ Object
Gets the exit code from the last command if it was retrieved.
-
#options ⇒ Object
readonly
The options provided to this shell.
Class Method Summary collapse
-
.after_init(proc = nil, &block) ⇒ Object
Adds code to be run after the shell is fully initialized but before the session code executes.
-
.after_term(proc = nil, &block) ⇒ Object
Adds code to be run after the shell session is terminated but before closing the shell session.
-
.before_init(proc = nil, &block) ⇒ Object
Adds code to be run before the shell is fully initialized.
-
.before_term(proc = nil, &block) ⇒ Object
Adds code to be run before the shell is terminated immediately after executing the session code.
-
.on_debug(proc = nil, &block) ⇒ Object
Sets the code to be run when debug messages are processed.
-
.on_exception(proc = nil, &block) ⇒ Object
Adds code to be run when an exception occurs.
Instance Method Summary collapse
-
#combined_output ⇒ Object
Gets both the standard output and error output from the session.
-
#exec(command, options = {}, &block) ⇒ Object
Executes a command during the shell session.
-
#exec_for_code(command, options = {}, &block) ⇒ Object
Executes a command specifically for the exit code.
-
#exec_ignore_code(command, options = {}, &block) ⇒ Object
Executes a command ignoring any exit code.
-
#initialize(options = {}, &block) ⇒ ShellBase
constructor
Initializes the shell with the supplied options.
-
#line_ending ⇒ Object
Defines the line ending used to terminate commands sent to the shell.
-
#read_file(path) ⇒ Object
Reads from a file on the device.
-
#session_complete? ⇒ Boolean
Has the session been completed?.
-
#stderr ⇒ Object
Gets the error output from the session.
-
#stdout ⇒ Object
Gets the standard output from the session.
-
#write_file(path, data) ⇒ Object
Writes to a file on the device.
Constructor Details
#initialize(options = {}, &block) ⇒ ShellBase
Initializes the shell with the supplied options.
These options are common to all shells.
prompt-
Defaults to “~~#”. Most special characters will be stripped.
retrieve_exit_code-
Defaults to false. Can also be true.
on_non_zero_exit_code-
Defaults to :ignore. Can also be :raise.
silence_timeout-
Defaults to 0. If greater than zero, will raise an error after waiting this many seconds for a prompt.
command_timeout-
Defaults to 0. If greater than zero, will raise an error after a command runs for this long without finishing.
Please check the documentation for each session class for specific shell options.
Once the shell is initialized, the shell is yielded to the provided code block which can then interact with the shell. Once the code block completes, the shell is closed and the session object is returned.
After completion the session object can only be used to review the stdout, stderr, and combined_output properties. The exec method and any other methods that interact with the shell will no longer be functional.
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 |
# File 'lib/shells/shell_base.rb', line 50 def initialize( = {}, &block) # cannot instantiate a ShellBase raise NotImplementedError if self.class == Shells::ShellBase raise ArgumentError, 'A code block is required.' unless block_given? raise ArgumentError, '\'options\' must be a hash.' unless .is_a?(Hash) = { prompt: '~~#', retrieve_exit_code: false, on_non_zero_exit_code: :ignore, silence_timeout: 0, command_timeout: 0 }.merge( .inject({}){ |m,(k,v)| m[k.to_sym] = v; m } ) [:prompt] = [:prompt] .to_s.strip .gsub('!', '#') .gsub('$', '#') .gsub('\\', '.') .gsub('/', '.') .gsub('"', '-') .gsub('\'', '-') [:prompt] = '~~#' if [:prompt] == '' raise Shells::InvalidOption, ':on_non_zero_exit_code must be :ignore, :raise, or nil.' unless [:ignore, :raise].include?([:on_non_zero_exit_code]) .freeze # no more changes to options now. @session_complete = false @last_input = Time.now debug 'Calling "exec_shell"...' exec_shell do begin debug 'Running "before_init" hooks...' run_hook :before_init debug 'Calling "exec_prompt"...' exec_prompt do begin debug 'Running "after_init" hooks...' run_hook :after_init debug 'Executing code block...' block.call self ensure debug 'Running "before_term" hooks...' run_hook :before_term end end rescue QuitNow debug 'Received QuitNow signal.' nil rescue Exception => ex unless run_hook(:on_exception, ex) raise end ensure debug 'Running "after_term" hooks...' run_hook :after_term end end @session_complete = true end |
Instance Attribute Details
#last_exit_code ⇒ Object
Gets the exit code from the last command if it was retrieved.
24 25 26 |
# File 'lib/shells/shell_base.rb', line 24 def last_exit_code @last_exit_code end |
#options ⇒ Object (readonly)
The options provided to this shell.
20 21 22 |
# File 'lib/shells/shell_base.rb', line 20 def end |
Class Method Details
.after_init(proc = nil, &block) ⇒ Object
Adds code to be run after the shell is fully initialized but before the session code executes.
after_init do |shell|
...
end
You can also pass the name of a static method.
def self.some_init_function(shell)
...
end
after_init :some_init_function
176 177 178 |
# File 'lib/shells/shell_base.rb', line 176 def self.after_init(proc = nil, &block) add_hook :after_init, proc, &block end |
.after_term(proc = nil, &block) ⇒ Object
Adds code to be run after the shell session is terminated but before closing the shell session.
This code might also be used to navigate a menu or clean up an environment. This method allows you to define that behavior without rewriting the connection code.
This code is guaranteed to be called if the shell connects correctly. That means if an error is raised in the session code or shell initialization code, this will still fire before closing the shell session.
after_term do |shell|
...
end
You can also pass the name of a static method.
def self.some_term_function(shell)
...
end
after_term :some_term_function
228 229 230 |
# File 'lib/shells/shell_base.rb', line 228 def self.after_term(proc = nil, &block) add_hook :after_term, proc, &block end |
.before_init(proc = nil, &block) ⇒ Object
Adds code to be run before the shell is fully initialized.
This code would normally be used to navigate a menu or setup an environment. This method allows you to define that behavior without rewriting the connection code.
before_init do |shell|
...
end
You can also pass the name of a static method.
def self.some_init_function(shell)
...
end
before_init :some_init_function
157 158 159 |
# File 'lib/shells/shell_base.rb', line 157 def self.before_init(proc = nil, &block) add_hook :before_init, proc, &block end |
.before_term(proc = nil, &block) ⇒ Object
Adds code to be run before the shell is terminated immediately after executing the session code.
This code might also be used to navigate a menu or clean up an environment. This method allows you to define that behavior without rewriting the connection code.
This code is guaranteed to be called if the shell initializes correctly. That means if an error is raised in the session code, this will still fire before handling the error.
before_term do |shell|
...
end
You can also pass the name of a static method.
def self.some_term_function(shell)
...
end
before_term :some_term_function
202 203 204 |
# File 'lib/shells/shell_base.rb', line 202 def self.before_term(proc = nil, &block) add_hook :before_term, proc, &block end |
.on_debug(proc = nil, &block) ⇒ Object
Sets the code to be run when debug messages are processed.
The code will receive the debug message as an argument.
on_debug do |msg|
puts msg
end
126 127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/shells/shell_base.rb', line 126 def self.on_debug(proc = nil, &block) @on_debug = if proc.respond_to?(:call) proc elsif proc && respond_to?(proc.to_s, true) method(proc.to_s.to_sym) elsif block block else nil end end |
.on_exception(proc = nil, &block) ⇒ Object
Adds code to be run when an exception occurs.
This code will receive the shell as the first argument and the exception as the second. If it handles the exception it should return true, otherwise nil or false.
on_exception do |shell, ex|
if ex.is_a?(MyExceptionType)
...
true
else
false
end
end
You can also pass the name of a static method.
def self.some_exception_handler(shell, ex)
...
end
on_exception :some_exception_handler
256 257 258 |
# File 'lib/shells/shell_base.rb', line 256 def self.on_exception(proc = nil, &block) add_hook :on_exception, proc, &block end |
Instance Method Details
#combined_output ⇒ Object
Gets both the standard output and error output from the session.
The prompts will be included in the combined output. There is no attempt to differentiate error output from standard output.
This is essentially the definitive log for the session.
All line endings are converted to LF characters, so you will not encounter or need to search for CRLF or CR sequences.
308 309 310 |
# File 'lib/shells/shell_base.rb', line 308 def combined_output @stdcomb ||= '' end |
#exec(command, options = {}, &block) ⇒ Object
Executes a command during the shell session.
If called outside of the new block, this will raise an error.
The command is the command to execute in the shell.
The options can be used to override the exit code behavior. In all cases, the :default option is the same as not providing the option and will cause exec to inherit the option from the shell’s options.
retrieve_exit_code-
This can be one of :default, true, or false.
on_non_zero_exit_code-
This can be on ot :default, :ignore, or :raise.
silence_timeout-
This can be :default or the number of seconds to wait in silence before timing out.
command_timeout-
This can be :default or the maximum number of seconds to wait for a command to finish before timing out.
If provided, the block is a chunk of code that will be processed every time the shell receives output from the program. If the block returns a string, the string will be sent to the shell. This can be used to monitor processes or monitor and interact with processes. The block is optional.
shell.exec('sudo -p "password:" nginx restart') do |data,type|
return 'super-secret' if /password:$/.match(data)
nil
end
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 |
# File 'lib/shells/shell_base.rb', line 342 def exec(command, = {}, &block) raise Shells::SessionCompleted if session_complete? ||= {} = { timeout_error: true }.merge() = self..merge(.inject({}) { |m,(k,v)| m[k.to_sym] = v; m }) [:retrieve_exit_code] = self.[:retrieve_exit_code] if [:retrieve_exit_code] == :default [:on_non_zero_exit_code] = self.[:on_non_zero_exit_code] unless [:raise, :ignore].include?([:on_non_zero_exit_code]) [:silence_timeout] = self.[:silence_timeout] if [:silence_timeout] == :default [:command_timeout] = self.[:command_timeout] if [:command_timeout] == :default [:command_is_echoed] = true if [:command_is_echoed].nil? ret = '' begin push_buffer # store the current buffer and start a fresh buffer # buffer while also passing data to the supplied block. if block_given? buffer_input(&block) end # send the command and wait for the prompt to return. debug 'Sending command: ' + command send_data command + line_ending if wait_for_prompt([:silence_timeout], [:command_timeout], [:timeout_error]) # get the output of the command, minus the trailing prompt. debug 'Reading output of command...' ret = command_output command, [:command_is_echoed] if [:retrieve_exit_code] self.last_exit_code = get_exit_code if [:on_non_zero_exit_code] == :raise raise NonZeroExitCode.new(last_exit_code) unless last_exit_code == 0 end else self.last_exit_code = nil end else # A timeout occurred and timeout_error was set to false. debug 'Command timed out...' self.last_exit_code = :timeout ret = combined_output end ensure # return buffering to normal. if block_given? buffer_input end # restore the original buffer and merge the output from the command. pop_merge_buffer end ret end |
#exec_for_code(command, options = {}, &block) ⇒ Object
Executes a command specifically for the exit code.
Does not return the output of the command, only the exit code.
402 403 404 405 406 |
# File 'lib/shells/shell_base.rb', line 402 def exec_for_code(command, = {}, &block) = ( || {}).merge(retrieve_exit_code: true, on_non_zero_exit_code: :ignore) exec command, , &block last_exit_code end |
#exec_ignore_code(command, options = {}, &block) ⇒ Object
Executes a command ignoring any exit code.
Returns the output of the command and does not even retrieve the exit code.
412 413 414 415 |
# File 'lib/shells/shell_base.rb', line 412 def exec_ignore_code(command, = {}, &block) = ( || {}).merge(retrieve_exit_code: false, on_non_zero_exit_code: :ignore) exec command, , &block end |
#line_ending ⇒ Object
Defines the line ending used to terminate commands sent to the shell.
The default is “n”. If you need “rn”, “r”, or some other value, simply override this function.
264 265 266 |
# File 'lib/shells/shell_base.rb', line 264 def line_ending "\n" end |
#read_file(path) ⇒ Object
Reads from a file on the device.
419 420 421 |
# File 'lib/shells/shell_base.rb', line 419 def read_file(path) raise ::NotImplementedError end |
#session_complete? ⇒ Boolean
Has the session been completed?
270 271 272 |
# File 'lib/shells/shell_base.rb', line 270 def session_complete? @session_complete end |
#stderr ⇒ Object
Gets the error output from the session.
All line endings are converted to LF characters, so you will not encounter or need to search for CRLF or CR sequences.
293 294 295 |
# File 'lib/shells/shell_base.rb', line 293 def stderr @stderr ||= '' end |
#stdout ⇒ Object
Gets the standard output from the session.
The prompts are stripped from the standard ouput as they are encountered. So this will be a list of commands with their output.
All line endings are converted to LF characters, so you will not encounter or need to search for CRLF or CR sequences.
283 284 285 |
# File 'lib/shells/shell_base.rb', line 283 def stdout @stdout ||= '' end |
#write_file(path, data) ⇒ Object
Writes to a file on the device.
425 426 427 |
# File 'lib/shells/shell_base.rb', line 425 def write_file(path, data) raise ::NotImplementedError end |