Class: Ruber::ExternalProgramPlugin
Overview
Base class for plugins whose main task is to run an external program and maybe (not necessarily) to display the output in a tool widget.
Basically, this class is a wrapper for KDE::Process providing a more integrated API for the most common functions. In particular, it provides the following functionality:
- automatic creation of the process
- automatic cleanup at shutdown
- automatic managing of buffered output
- nicer API to read from standard output and standard error
- simplified API to start the process
- automatic display of standard output and standard error in a tool widget (if required)
- display of the command line and of the process status in a tool widget (if required)
To start the process, you simply call the run_process method, passing it the name of the program to run, the working directory and the arguments.
When the process emits the readyReadStandardOutput() or the readyReadStandardError(), the contents of the appropriate stream are read and split into lines. The resulting array are passed to the process_standard_output or process_standard_error method. Derived classes can override these methods to do what they want with the output.
In general, you can’t be sure whether the data contained read from standard output or error contains complete lines. To deal with this issue, whenob reading from a stream, only full lines (that is, lines which end in “\n”) are passed to process_standard_output or process_standard_error. If the last line isn’t complete, it is stored in a buffer. The next time characters are read from the process, if they came from the same channel, the buffer is added at the beginning of the new stream; if they came from the other channel, they’re passed to the appropriate process_standard_* method. This behaviour can be changed by passing the appropriate arguments to the constructor.
Some methods in this class have the ability to insert data in a OutputWidget. They
will do so if the output_widget</tt> instance variable (which is created in
the constructor, if it doesn't already exist) is not *nil*. Note that those methods
don't check if <tt>
output_widget actually is an OutputWidget or not. If it
is something else, you’ll get errors. In all the following documentation it is
assumed that:
- the expression the output widget refers to the object the @output_widget instance variable refers to, unless it’s nil
- everything concerning the output widget will be ignored (without giving any error) if the output widget doesn’t exist
=Signals =process_finished(int code, QString reason) Signal emitted when the process finishes. _code_ is the exit code of the process, while _reason_ is a string which can have one of the values "killed", "crash" or be empty. If it's empty, it means that the program finished normally; "killed" means that it was killed by the user and "crash" means that the program crashed. =process_started() Signal emitted when the process has started =process_failed_to_start() Signal emitted if the process couldn't be started (for example, because the given program doesn't exist) =Slots
- slot_process_finished(int, QProcess::ExitStatus)
- stop_process()
Direct Known Subclasses
FindInFiles::FindInFilesPlugin, RubyRunner::RubyRunnerPlugin
Constant Summary
Constants inherited from Plugin
Instance Attribute Summary (collapse)
-
- (Object) process
readonly
The KDE::Process used by the plugin.
Attributes included from PluginLike
Instance Method Summary (collapse)
-
- (Object) display_exit_message(code, reason)
slot
private
Displays a message in the output widget describing the exit status of the program.
-
- (Object) do_stderr(str)
private
Pre-processes the string str then passes the resulting array to process_standard_error.
-
- (Object) do_stdout(str)
private
Pre-processes the string str then passes the resulting array to process_standard_output.
-
- (Object) failed_to_start
private
Method called if the program fails to start (in response, but not connected to, the process error(QProcess::ProcessError) signal if the argument is Qt::Process::FailedToStart).
-
- (ExternalProgramPlugin) initialize(pdf, line_buffered = true)
constructor
Creates a new ExternalProgramPlugin.
-
- (nil) process_standard_error(lines)
private
Does something with the text written to standard error by the program.
-
- (nil) process_standard_output(lines)
private
Does something with the text written to standard output by the program.
-
- (Object) run_process(prog, dir, args, title = '')
Starts the program.
-
- (Object) shutdown
Prepares the plugin to be unloaded by killing the process (no signal will be emitted from the process or the plugin from now on).
-
- (Object) slot_process_finished(code, status)
slot
private
Slot called in response to the process’ finished(int, QProcess::ExitStatus) signal.
-
- (Object) stop_process
slot
Stops the process.
Methods inherited from GuiPlugin
#action_collection, #execute_action, #register_action_handler, #setup_actions, #unload
Methods inherited from Plugin
Methods included from PluginLike
#add_extensions_to_project, #add_options_to_project, #add_widgets_to_project, #create_tool_widget, #delayed_initialize, #initialize_plugin, #load_settings, #plugin_name, #query_close, #register_options, #register_with_project, #remove_extensions_from_project, #remove_from_project, #remove_options_from_project, #remove_widgets_from_project, #restore_session, #save_settings, #session_data, #setup_action, #setup_actions, #unload, #update_project
Signal Summary
Constructor Details
- (ExternalProgramPlugin) initialize(pdf, line_buffered = true)
Creates a new ExternalProgramPlugin.
pdf is the plugin info object for the plugin. If line_buffered is false, buffering won’t be used (all the characters read from the process will be passed to process_standard_output or process_standard_error even if they don’t end in a newline).
Note: the process’ channel mode will be set to Qt::Process::SeparateChannels. You can set it to any value you want later
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
# File 'lib/ruber/external_program_plugin.rb', line 108 def initialize pdf, line_buffered = true super pdf @buffer = nil @buffer_content_channel = nil @line_buffered = line_buffered @output_widget = nil unless defined? @output_widget @process = KDE::Process.new self @process.process_channel_mode = Qt::Process::SeparateChannels @process.output_channel_mode = KDE::Process::SeparateChannels @process.connect SIGNAL(:readyReadStandardOutput) do do_stdout @process.read_all_standard_output.to_s end @process.connect SIGNAL(:readyReadStandardError) do do_stderr @process.read_all_standard_error.to_s end connect @process, SIGNAL('finished(int, QProcess::ExitStatus)'), self, SLOT('slot_process_finished(int, QProcess::ExitStatus)') connect self, SIGNAL('process_finished(int, QString)'), self, SLOT('display_exit_message(int, QString)') @process.connect SIGNAL('error(QProcess::ProcessError)') do |e| failed_to_start if e == Qt::Process::FailedToStart end connect @process, SIGNAL('started()'), self, SIGNAL('process_started()') end |
Instance Attribute Details
- (Object) process (readonly)
The KDE::Process used by the plugin
95 96 97 |
# File 'lib/ruber/external_program_plugin.rb', line 95 def process @process end |
Instance Method Details
- (Object) display_exit_message(code, reason) (private)
Displays a message in the output widget describing the exit status of the program.
The message (output type message) has the format program_name message, where message is:
- exited normally if reason is an empty string
- was killed if reason is ‘killed’
- exited with code code if reason is ‘crash’
This method is meant to be connected to the process_finished(int, QString) signal. Its arguments match those of the signal.
If you want to change the message, override this method. If you don’t want any message and the @output_widget instance variable is not nil, you can either disconnect this slot from the process_finished(int, QString) signal or override this method without calling super
379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 |
# File 'lib/ruber/external_program_plugin.rb', line 379 def code, reason return unless @output_widget mod = @output_widget.model rc = mod.row_count mod.insert_row rc idx = mod.index rc, 0 type = :message text = "Process " case reason when '' if code == 0 then text << 'exited normally' else text << "exited with code #{code}" type = :message_bad end when 'killed' then text << 'killed' when 'crash' text << "crashed with code #{code}" type = :message_bad end mod.set_data idx, Qt::Variant.new(text) @output_widget.set_output_type idx, type end |
Slot Signature:
display_exit_message(int, QString)
- (Object) do_stderr(str) (private)
Pre-processes the string str then passes the resulting array to process_standard_error.
Pre-processing the string means:
- splitting it into lines
- emptying the buffer (by passing its contents to process_standard_output if it refers to standard output or prepending it to str if it refers to standard error)
- putting the last line in the buffer if it doesn’t end in a newline.
If the plugin is not buffered, then only the first step is done.
Note: if str is empty, nothing will happen. If it consists only of newlines, it will cause the buffer to be emptied (as described above) and nothing else. Note: consecutive newlines will be treated as a single newline
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 |
# File 'lib/ruber/external_program_plugin.rb', line 234 def do_stderr str return if str.empty? if @line_buffered and @buffer buffer = @buffer channel = @buffer_content_channel @buffer = nil @buffer_content_channel = nil if channel == :stderr str = buffer + str return do_stderr str else process_standard_output [buffer] end end lines = str.split_lines if @line_buffered and !str.end_with? "\n" @buffer = lines.pop @buffer_content_channel = :stderr end return if lines.empty? process_standard_error lines end |
- (Object) do_stdout(str) (private)
Pre-processes the string str then passes the resulting array to process_standard_output.
Pre-processing the string means:
- splitting it into lines
- emptying the buffer (by passing its contents to process_standard_error if it refers to standard error or prepending it to str if it refers to standard output)
- putting the last line in the buffer if it doesn’t end in a newline.
If the plugin is not buffered, then only the first step is done.
Note: if str is empty, nothing will happen. If it consists only of newlines, it will cause the buffer to be emptied (as described above) and nothing else. Note: consecutive newlines will be treated as a single newline
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 |
# File 'lib/ruber/external_program_plugin.rb', line 196 def do_stdout str return if str.empty? if @line_buffered and @buffer buffer = @buffer channel = @buffer_content_channel @buffer = nil @buffer_content_channel = nil if channel == :stdout str = buffer + str return do_stdout str else process_standard_error [buffer] end end lines = str.split_lines if @line_buffered and !str.end_with? "\n" @buffer = lines.pop @buffer_content_channel = :stdout end return if lines.empty? process_standard_output lines end |
- (Object) failed_to_start (private)
Method called if the program fails to start (in response, but not connected to, the process error(QProcess::ProcessError) signal if the argument is Qt::Process::FailedToStart).
It emits the process_failed_to_start() signal and displays an appropriate message to the output widget (using the :error1 output type).
If @output_widget is not nil but you don’t want the message to be generated, you’ll need to override this method. If you do so, don’t forget to emit the failed_to_start() signal.
326 327 328 329 330 331 332 333 334 335 336 337 338 339 |
# File 'lib/ruber/external_program_plugin.rb', line 326 def failed_to_start emit process_failed_to_start if @output_widget mod = @output_widget.model rc = mod.row_count mod.insert_row rc cmd = @process.program cmd_line = cmd.join " " prog = cmd.shift idx = mod.index(rc, 0) mod.set_data idx, Qt::Variant.new("#{prog} failed to start. The command line was #{cmd_line}") @output_widget.set_output_type idx, :error1 end end |
- (nil) process_standard_error(lines) (private)
Does something with the text written to standard error by the program.
The base class implementation of this method inserts the text at the end of the output widget in the first column (one item per line) and sets the output type to :output. Nothing is done if the output widget doesn’t exist. Subclasses can reimplement this method to do something else (in this case, you don’t usually want to call super)
lines is an array where each entry corresponds to a line of output from the program. If buffering is on, each entry is a complete line (or should be considered such). If buffering is off, you’ll have to take care of newlines by yourself.
301 302 303 304 305 306 307 308 309 310 311 312 |
# File 'lib/ruber/external_program_plugin.rb', line 301 def process_standard_error lines return unless @output_widget mod = @output_widget.model rc = mod.row_count mod.insert_rows rc, lines.size lines.each_with_index do |l, i| idx = mod.index(i + rc, 0) mod.set_data idx, Qt::Variant.new(l) @output_widget.set_output_type idx, :error end nil end |
- (nil) process_standard_output(lines) (private)
Does something with the text written to standard output by the program.
The base class implementation of this method inserts the text at the end of the output widget in the first column (one item per line) and sets the output type to :output. Nothing is done if the output widget doesn’t exist. Subclasses can reimplement this method to do something else (in this case, you don’t usually want to call super)
lines is an array where each entry corresponds to a line of output from the program. If buffering is on, each entry is a complete line (or should be considered such). If buffering is off, you’ll have to take care of newlines by yourself.
272 273 274 275 276 277 278 279 280 281 282 283 |
# File 'lib/ruber/external_program_plugin.rb', line 272 def process_standard_output lines return unless @output_widget mod = @output_widget.model rc = mod.row_count mod.insert_rows rc, lines.size lines.each_with_index do |l, i| idx = mod.index(i + rc, 0) mod.set_data idx, Qt::Variant.new(l) @output_widget.set_output_type idx, :output end nil end |
- (Object) run_process(prog, dir, args, title = '')
Starts the program.
prog is the name of the program (you don’t need to specify the absolute path if it’s in PATH). args is an array containing the arguments. dir is the working directory.
title is the string to display in the output widget. If it is an empty string, the name of the program followed by its arguments will be used. If it is nil or false, the title won’t be set.
142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/ruber/external_program_plugin.rb', line 142 def run_process prog, dir, args, title = '' @buffer = nil @buffer_content_channel = nil @process.clear_program @process.working_directory = dir program = [prog] + args @process.program = program if @output_widget and title title = program.join ' ' if title.empty? @output_widget.title = title end @process.start end |
- (Object) shutdown
Prepares the plugin to be unloaded by killing the process (no signal will be emitted from the process or the plugin from now on).
If you reimplement this method, don’t forget to call super. If you don’t you might cause a crash when Ruber is closed
172 173 174 175 176 |
# File 'lib/ruber/external_program_plugin.rb', line 172 def shutdown @process.block_signals true @process.kill super end |
- (Object) slot_process_finished(code, status) (private)
Slot called in response to the process’ finished(int, QProcess::ExitStatus) signal.
It emits the process_finished(int, QString) signal
347 348 349 350 351 352 353 354 355 356 357 358 359 |
# File 'lib/ruber/external_program_plugin.rb', line 347 def slot_process_finished code, status str = @process.read_all_standard_output.to_s str << "\n" unless str.end_with?("\n") do_stdout str str = @process.read_all_standard_error.to_s str << "\n" unless str.end_with?("\n") do_stderr str reason = '' if status == Qt::Process::CrashExit reason = code == 0 ? 'killed' : 'crash' end emit process_finished code, reason end |
Slot Signature:
slot_process_finished(int, QProcess::ExitStatus)
- (Object) stop_process
Stops the process.
It’s a shortcut for process.kill
161 162 163 |
# File 'lib/ruber/external_program_plugin.rb', line 161 def stop_process @process.kill end |
Slot Signature:
stop_process()
Signal Details
- process_finished(int arg1, QString arg2)
- process_started
- process_failed_to_start