Class: Ruber::RSpec::Plugin

Inherits:
Ruber::RubyRunner::RubyRunnerPlugin show all
Defined in:
plugins/rspec/rspec.rb

Overview

Plugin object for the RSpec plugin

Constant Summary

STARTING_DELIMITER =

The starting delimiter of the data written by the formatter

/^####%%%%####KRUBY_BEGIN$/
ENDING_DELIMITER =

The ending delimiter of the data written by the formatter

/^####%%%%####KRUBY_END$/
SWITCH_BEHAVIOUR =

Symbolic values associated with the rspec/switch_behaviour settings

[:new_tab, :horizontal, :vertical]

Constants inherited from Plugin

LICENSES

Instance Attribute Summary

Attributes inherited from ExternalProgramPlugin

process

Attributes included from PluginLike

plugin_description

Class Method Summary (collapse)

Instance Method Summary (collapse)

Methods inherited from Ruber::RubyRunner::RubyRunnerPlugin

#interpreter_for, #option_for, #ruby_command_for

Methods inherited from ExternalProgramPlugin

#do_stderr, #do_stdout, #failed_to_start, #run_process, #shutdown, #slot_process_finished, #stop_process

Methods inherited from GuiPlugin

#action_collection, #execute_action, #register_action_handler, #setup_actions, #unload

Methods inherited from Plugin

#about_data

Methods included from PluginLike

#add_extensions_to_project, #add_options_to_project, #add_widgets_to_project, #create_tool_widget, #initialize_plugin, #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, #shutdown, #unload, #update_project

Signal Summary

API for feature rspec

Methods API

Constructor Details

- (Plugin) initialize(psf)

the plugin

Parameters:



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'plugins/rspec/rspec.rb', line 110

def initialize psf
  super psf, :rspec, {:scope => [:global]}, nil, false
  Ruber[:autosave].register_plugin self, true
  @formatter = File.join File.dirname(__FILE__), 'ruber_rspec_formatter'
  self.connect(SIGNAL('process_finished(int, QString)')){Ruber[:main_window].set_state 'rspec_running', false}
  Ruber[:main_window].set_state 'rspec_running', false
  
  switch_prc = Proc.new{|states| states['active_project_exists'] and states['current_document']}
  register_action_handler 'rspec-switch', &switch_prc
  register_action_handler 'rspec-run_all' do |states|
    states['active_project_exists'] and !states['rspec_running']
  end
  current_prc = Proc.new do |states|
    states['active_project_exists'] and states['current_document'] and
        !states['rspec_running']
  end
  register_action_handler 'rspec-run_current', &current_prc
  register_action_handler 'rspec-run_current_line', &current_prc
  connect Ruber[:main_window], SIGNAL('current_document_changed(QObject*)'), self, SLOT('change_switch_name(QObject*)')
  Ruber[:components].connect(SIGNAL('feature_loaded(QString, QObject*)')) do |f, o|
    o.register_plugin self, true if f == 'autosave'
  end
  @output_widget = @widget
end

Class Method Details

+ (String) find_default_executable

Finds the rspec program to use by default

It looks for an executable called rspec or spec (this is to support both RSpec 1 and 2) in PATH (using the which command). if no rspec program was found

Returns:

  • (String)

    a string with the path to the rspec program or an empty string



95
96
97
98
99
100
101
# File 'plugins/rspec/rspec.rb', line 95

def self.find_default_executable
  path = Open3.popen3('which rspec'){|stdin, stdout, stderr| stdout.read}.strip
  if path.empty?
    path = Open3.popen3('which spec'){|stdin, stdout, stderr| stdout.read}.strip
  end
  path
end

Instance Method Details

- (nil) change_switch_name(doc) (private)

Changes the text of the Switch to spec action depending on whether the given document is a spec or code file

This method is usually called in response to the MainWindow#current_document_changed signal.

Parameters:

  • doc (Document, nil)

    the document according to with to change the text of the action

Returns:

  • (nil)


529
530
531
532
533
534
535
536
537
# File 'plugins/rspec/rspec.rb', line 529

def change_switch_name doc
  prj = Ruber[:world].active_project
  return unless doc and prj
  if prj.extension(:rspec).spec_file? doc.path then text = 'Switch to &Code'
  else text = 'Switch to &Spec'
  end
  action_collection.action('rspec-switch').text = i18n(text)
  nil
end

Slot Signature:

change_switch_name(QObject*)

- (Boolean?) code_file?(file, prj = Ruber[:world].active_project)

Whether or not file is a code file for a given project

It uses the rspec/spec_directory and rspec/spec_pattern options from the project to find out whether the file is a spec file or not.

Parameters:

  • file (String)

    the file to test

  • prj (Project, nil) (defaults to: Ruber[:world].active_project)

    the project file could be a spec for. If nil, the current project, if any, will be used

Returns:

  • (Boolean, nil)

    wheter or not file is a spec file for the given project or nil if no project was specified and there’s no open project



166
167
168
169
# File 'plugins/rspec/rspec.rb', line 166

def code_file? file, prj = Ruber[:world].active_project
  return nil unless prj
  prj.extension(:rspec).code_file? file
end

- (nil) delayed_initialize (private)

Override of PluginLike#delayed_initialize

It sets the text of the Switch action depending on whether the current document (if any) is or not a spec file.

This can’t be done from the #initialize method because project options haven’t already been added when that method is called.

Returns:

  • (nil)


236
237
238
239
240
# File 'plugins/rspec/rspec.rb', line 236

def delayed_initialize
  doc = Ruber[:main_window].current_document
  change_switch_name doc if doc
  nil
end

- (Object) display_exit_message(code, reason) (private)

Override of ExternalProgramPlugin#display_exit_message

It works as the base class’s method except when the program exits successfully, in which case it does nothing.

See ExternalProgramPlugin#display_exit_message for the meaning of the parameters

Returns:

  • nil



514
515
516
# File 'plugins/rspec/rspec.rb', line 514

def display_exit_message code, reason
  super unless reason.empty?
end

- (String) file_for_spec(prj, file) (private)

The name of the code file associated with a given spec file

To find out which code file is associated with the given spec file, this method takes all the project files and constructs the file names of all the specs associated to them according to the rspec/spec_pattern project option. As soon as one of the generated file names matches the given spec file, the generating file is returned.

to the settings contained in prj.

Parameters:

  • prj (Ruber::AbstractProject)

    the project containing the settings to use

  • file (String)

    the name of the spec file to find the code file for

Returns:

  • (String)

    the absolute path of a file file is a spec of, according



498
499
500
501
502
# File 'plugins/rspec/rspec.rb', line 498

def file_for_spec prj, file
  pattern = prj[:spec_pattern]
  opts = options prj
  prj.project_files.abs.find{|f| specs_for_file( opts, f).include? file}
end

- (Object) load_settings



240
241
242
243
# File 'plugins/rspec/rspec.rb', line 240

def load_settings
  super
  emit settings_changed
end

- (Hash) options(prj) (private)

Collects all the options relative to this plugin from a project

Note: never use destructive methods on the values contained in this hash. Doing so will change the options stored in the project, which most likely isn’t what you want. If you need to change the options, make duplicates of the values

between options and entries in this hash is the following:

  • :rspec/executable:spec
  • :rspec/options:spec_options
  • :rspec/spec_directory:specs_dir
  • :rspec/spec_files:filter
  • :rspec/spec_pattern:pattern
  • :rspec/full_backtraces:full_backtraces

Besides, the above entries, the hash also contains a :dir entry which contains the project directory.

Parameters:

Returns:

  • (Hash)

    an hash containing the options stored in the project. The correspondence



428
429
430
431
432
433
434
435
436
437
# File 'plugins/rspec/rspec.rb', line 428

def options prj
  res = {}
  res[:spec] = prj[:rspec, :executable]
  res[:spec_options] = prj[:rspec, :options]
  res[:specs_dir] = prj[:rspec, :spec_directory, :absolute]
  res[:filter] = prj[:rspec, :spec_files]
  res[:dir] = prj.project_directory
  res[:full_backtraces] = prj[:rspec, :full_backtraces]
  res
end

- (<Hash, String>) parse_spec_output(str) (private)

Parses the output of the spec command

This method only works if the Ruber rspec formatter is used.

The output is split according in regions between STARTING_DELIMITER and ENDING_DELIMITER. All text inside these regions is considered the YAML dump of a hash and converted back to a hash. All text which is not between a pair of delimiters, as well as the text which can’t be converted by YAML is left as is parsed by YAML is stored as a hash, while the rest is stored as strings. Order is preserved.

Parameters:

  • str (String)

    the output to parse

Returns:

  • (<Hash, String>)

    the result of the parse. The text which was successfully



287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'plugins/rspec/rspec.rb', line 287

def parse_spec_output str
  sc = StringScanner.new str
  res = []
  until sc.eos?
    match = sc.scan_until(STARTING_DELIMITER) || sc.rest
    found = match.sub!(STARTING_DELIMITER, '')
    res << match
    if found
      yaml = sc.scan_until(ENDING_DELIMITER) || sc.rest
      yaml.sub! ENDING_DELIMITER, ''
      res << (YAML.load(yaml) rescue yaml)
    else sc.terminate
    end
  end
  res
end

- (nil) process_standard_error(lines) (private)

Parameters:

  • lines (Array)

    the standard error output, split in lines

Returns:

  • (nil)


267
268
269
270
# File 'plugins/rspec/rspec.rb', line 267

def process_standard_error lines
  @widget.model.insert_lines lines, :output1, nil
  nil
end

- (nil) process_standard_output(lines) (private)

Override of ExternalProgramPlugin#process_standard_output

It parses the output from the spec program (generated with the Ruber rspec formatter) and displays the data it contains appropriately.

Parameters:

  • lines (Array)

    the output, split in lines

Returns:

  • (nil)


252
253
254
255
256
257
258
259
260
# File 'plugins/rspec/rspec.rb', line 252

def process_standard_output lines
  items = parse_spec_output lines.join "\n"
  items.each do |it|
    if it.is_a? String then @widget.model.insert_lines it, :output, nil
    else @widget.display_example it
    end
  end
  nil
end

- (nil) run_all (private)

Runs all the specs for the project.

Returns:

  • (nil)


308
309
310
311
312
313
314
315
316
317
318
# File 'plugins/rspec/rspec.rb', line 308

def run_all
  prj = Ruber[:world].active_project
  unless prj
    KDE::MessageBox.error nil, "You must have an open project to choose this entry.\nYOU SHOULD NEVER SEE THIS MESSAGE"
    return
  end
  opts = options prj
  opts[:files] = Dir.glob File.join(opts[:specs_dir], '**', opts[:filter])
  run_rspec_for prj, opts, :files => :project_files, :on_failure => :ask, :message => 'Do you want to run the tests all the same?'
  nil
end

Slot Signature:

run_all()

- (Boolean) run_current(what = :all) (private)

Runs the specs corresponding to the current file

If the current file is a code file, the corresponding spec file will be run. If it is a spec file, the file itself will be run.

To decide whether the current file is a spec or a code file, this method uses the patters stored in the rspec/spec_pattern project setting to build the names of the spec files associated with the current file, which is assumed to be a code file. If none of these files exist, then the current file is treated as a spec file, otherwise one of the spec files thus generated is run.

Note: the way this method works implies that if it is called on a code file which doesn’t have an associated spec file, rspec will be run on that file, which, most likely, will cause it to fail.

(including the case when the process was already running or autosaving failed)

Returns:

  • (Boolean)

    true if the spec program is started and false otherwise



339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
# File 'plugins/rspec/rspec.rb', line 339

def run_current what = :all
  prj = Ruber[:world].active_project
  unless prj
    KDE::MessageBox.error nil, "You must have an open project to choose this entry.\nYOU SHOULD NEVER SEE THIS MESSAGE"
    return
  end
  opts = options prj
  view = Ruber[:main_window].active_editor
  doc = view.document
  unless doc.url.local_file?
    KDE::MessageBox.sorry nil, 'You can\'t run rspec for remote files'
    return
  end
  unless doc
    KDE::MessageBox.error nil, "You must have an open editor to choose this entry.\nYOU SHOULD NEVER SEE THIS MESSAGE"
    return
  end
  files = specs_for_file opts, doc.path
  files.reject!{|f| !File.exist? f}
  opts[:files] = files.empty? ? [doc.path] : files
  if what == :current_line
    line = view.cursor_position.line + 1
    opts[:spec_options] += ["-l", line.to_s]
  end
  run_rspec_for prj, opts, :files => :documents_with_file, :on_failure => :ask,
      :message => 'Do you want to run the tests all the same?'
end

Slot Signature:

run_current()

- (Boolean) run_current_document (private)

Runs the specs corresponding to the current file

If the current file is a code file, the corresponding spec file will be run. If it is a spec file, the file itself will be run.

To decide whether the current file is a spec or a code file, this method uses the patters stored in the rspec/spec_pattern project setting to build the names of the spec files associated with the current file, which is assumed to be a code file. If none of these files exist, then the current file is treated as a spec file, otherwise one of the spec files thus generated is run.

Note: the way this method works implies that if it is called on a code file which doesn’t have an associated spec file, rspec will be run on that file, which, most likely, will cause it to fail.

(including the case when the process was already running or autosaving failed)

Returns:

  • (Boolean)

    true if the spec program is started and false otherwise



372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'plugins/rspec/rspec.rb', line 372

def run_current_document
  doc = Ruber[:world].active_document
  unless doc
    raise "It shouldn't be possible to call #{self.class}#run_current_document when there's no active document"
  end
  prj = Ruber[:world].active_project
  unless doc
    raise "It shouldn't be possible to call #{self.class}#run_current_document when there's no active project"
  end
  opts = options prj
  ext = prj.extension(:rspec)
  if doc.path.empty?
    KDE::MessageBox.sorry nil, KDE.i18n("You must save the document to a file before running rspec on it")
    return
  elsif ext.spec_file? doc.path
    opts[:files] = [doc.path]
  elsif ext.code_file?(doc.path) 
    opts[:files] = ext.specs_for_code doc.path
  end
  run_rspec_for prj, opts, :files => :documents_with_file, :on_failure => :ask,
      :message => 'Do you want to run the tests all the same?'
end

Slot Signature:

run_current_document()

- (Boolean) run_current_line (private)

Runs the example(s) in the current line

Similar to #run_current, but tells spec to run only the example or example group corresponding to the line where the cursor is (using spec’s -l option). Besides, unlike #run_current, this method can only be called when the current file is the example file, not the source.

(including the case when the process was already running or autosaving failed)

Returns:

  • (Boolean)

    true if the spec program is started and false otherwise



378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
# File 'plugins/rspec/rspec.rb', line 378

def run_current_line
  doc = Ruber[:world].active_document
  unless doc
    raise "It shouldn't be possible to call #{self.class}#run_current_document when there's no active document"
  end
  prj = Ruber[:world].active_project
  unless doc
    raise "It shouldn't be possible to call #{self.class}#run_current_document when there's no active project"
  end
  opts = options prj
  ext = prj.extension(:rspec)
  if doc.path.empty?
    KDE::MessageBox.sorry nil, KDE.i18n("You must save the document to a file before running rspec on it")
    return
  elsif ext.spec_file? doc.path
    view = Ruber[:main_window].active_editor
  elsif ext.code_file?(doc.path) 
    specs = ext.specs_for_code doc.path
    view = Ruber[:world].active_environment.views.find do |v| 
      specs.include? v.document.path
    end
    unless view
      KDE::MessageBox.sorry nil, KDE.i18n('You don\'t have any spec file for %s opened. Without it it\'s impossible to find out what the current line is', doc.path)
      return
    end
    doc = view.document
  end
  opts[:files] = [view.document.path]
  line = view.cursor_position.line + 1
  opts[:spec_options] += ["-l", line.to_s]
  run_rspec_for prj, opts, :files => :documents_with_file, :on_failure => :ask,
      :message => 'Do you want to run the tests all the same?'
end

Slot Signature:

run_current_line()

- (Boolean) run_rspec(files, opts, autosave_opts = {}, &blk)

Runs rspec for the given files

The output of spec is displayed in the associated output widget.

Files are autosaved before running rspec.

If spec is already running, or if autosaving fails, noting is done.

passed to autosave

to use the options to pass to the ruby interpreter program to the spec program

It has the same meaning as second parameter to Autosave::AutosavePlugin#autosave. If it’s nil, autosave won’t be used as first argument to Autosave::AutosavePlugin#autosave (including the case when the process was already running or autosaving failed)

Parameters:

  • files (Array<String>)

    the spec files to run. Nonexisting files are ignored

  • opts (Hash)

    options to fine-tune the behaviour of spec

  • autosave_opts (Hash) (defaults to: {})

    options telling whether and how autosave files

  • blk (Proc)

    a block to pass to autosave. If not given, no block will be

Options Hash (opts):

  • :ruby (String) — default: Ruber[:config][:ruby, :ruby]

    the ruby interpreter

  • :ruby_options (Array<String>) — default: Ruber[:config][:ruby, :ruby_options]
  • :spec (String) — default: 'spec'

    the path of the spec command to use

  • :spec_options (Array<String>) — default: []

    the options to pass to the spec

  • :dir (String) — default: '.'

    the directory to run spec from

  • :full_backtraces (Boolean) — default: nil

    whether or not to pass the -b option

Options Hash (autosave_opts):

Returns:

  • (Boolean)

    true if the process is started and false otherwise



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'plugins/rspec/rspec.rb', line 192

def run_rspec files, opts, autosave_opts = {}, &blk
  default_opts = {
    :ruby => Ruber[:config][:ruby, :ruby],
    :ruby_options => Ruber[:config][:ruby, :ruby_options],
    :spec => 'spec',
    :spec_options => [],
    :dir => '.'
  }
  opts = default_opts.merge opts
  return false if @process.state != Qt::Process::NotRunning
  @widget.clear_output
  files = files.select{|f| File.exist? f}
  if autosave_opts[:files]
    plug = autosave_opts[:plugin] || self
    what = autosave_opts[:files]
    return false unless Ruber[:autosave].autosave plug, 
        what, autosave_opts, &blk
  end
  full_backtraces = opts[:full_backtraces] ? %w[-b] : []
  args = [opts[:spec]] + %W[-r #{@formatter} -f Ruber::RSpec::Formatter] + 
      opts[:spec_options] + full_backtraces + files
  @widget.working_directory = opts[:dir]
  @display_standard_error = opts[:stderr]
  Ruber[:main_window].activate_tool(@widget)
  Ruber[:main_window].change_state 'rspec_running', true
  title = ([opts[:spec].split('/')[-1]] + opts[:spec_options]+ full_backtraces + files).join ' '
  run_process opts[:ruby], opts[:dir], opts[:ruby_options] + args, title
  @widget.model.item(0,0).tool_tip = ([opts[:ruby]] + opts[:ruby_options] + args).join " "
  true
end

- (Boolean) run_rspec_for(origin, opts, autosave_opts = {}, &blk) (private)

Runs the spec command for the given object

It works like RubyRunner::RubyRunnerPlugin#ruby_command_for but already takes care of the user’s settings regarding the path of the spec program.

of RubyRunner::RubyRunnerPlugin#option_for meaning as the opts argument to #run_rspec, but can also contain an additional entry (see below) (including the case when the process was already running or autosaving failed)

Parameters:

  • origin (AbstractProject, Document, String, nil)

    see the target argument

  • opts (Hash)

    the options to pass to the spec program. It has the same

  • autosave_opts (Hash) (defaults to: {})

    see the autosave_opts argument of #run_rspec

  • blk (Proc)

    see the blk argument of #run_rspec

Options Hash (opts):

  • :files (Array<String>)

    the files to pass to the spec program

Returns:

  • (Boolean)

    true if the process is started and false otherwise



401
402
403
404
405
406
# File 'plugins/rspec/rspec.rb', line 401

def run_rspec_for origin, opts, autosave_opts = {}, &blk
  process.kill
  ruby, *cmd = ruby_command_for origin, opts[:dir]
  opts = {:ruby => ruby, :ruby_options => cmd}.merge opts
  run_rspec opts[:files], opts, autosave_opts, &blk
end

- (Boolean?) spec_file?(file, prj = Ruber[:world].active_project)

Whether or not a file is a spec file for a given project

It uses the rspec/spec_directory and rspec/spec_pattern options from the project to find out whether the file is a spec file or not.

Parameters:

  • file (String)

    the file to test

  • prj (Project, nil) (defaults to: Ruber[:world].active_project)

    the project file could be a spec for. If nil, the current project, if any, will be used

Returns:

  • (Boolean, nil)

    wheter or not file is a spec file for the given project or nil if no project was specified and there’s no open project



147
148
149
150
# File 'plugins/rspec/rspec.rb', line 147

def spec_file? file, prj = Ruber[:world].active_project
  return nil unless prj
  prj.extension(:rspec).spec_file? file
end

- (Object) spec_for_pattern(pattern, file)



245
246
247
248
249
250
251
252
253
254
# File 'plugins/rspec/rspec.rb', line 245

def spec_for_pattern pattern, file
  spec = pattern[:spec].gsub(/%f/, File.basename(file, '.rb'))
  dir = File.dirname(file)
  dir_parts = dir.split '/'
  spec.gsub! %r{%d\d+} do |str|
    dir_parts[str[2..-1].to_i-1] || ''
  end
  spec.gsub! %r{%d}, dir
  spec
end

- (<String>) specs_for_file(opts, file) (private)

Determines all possible specs files associated with a code file

The names of the possible spec files are obtained replacing the %f tag in each entry of the rspec/pattern setting with the name of the file (without checking whether the files actually exist).

Parameters:

  • file (String)

    the name of the code file

Returns:

  • (<String>)

    the names of the possible spec file associated with file



479
480
481
482
483
# File 'plugins/rspec/rspec.rb', line 479

def specs_for_file opts, file
  file = File.basename file, '.rb'
  res = opts[:pattern].map{|i| File.join opts[:specs_dir], i.gsub('%f', file)}
  res
end

- (EditorView?) switch (private)

Note:

this method assumes that both the current project and a current document exist

Slot associated with the Switch action

Displays the spec or code file associated with the current document, according to whether the current document is a code or spec file respectively.

It does nothing if the file corresponding to the current document isn’t found

Returns:

  • (EditorView, nil)

    an editor associated with the spec or code file associated with the current document or nil if no such file is found



452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
# File 'plugins/rspec/rspec.rb', line 452

def switch
  file = Ruber[:main_window].current_document.path
  prj = Ruber[:world].active_project
  ext = prj.extension(:rspec)
  if ext.spec_file? file then ;switch_to = ext.code_for_spec file
  else switch_to = ext.specs_for_code(file)[0]
  end
  if switch_to and File.exist? switch_to
    behaviour = Ruber[:config][:rspec, :switch_behaviour]
    if behaviour != :new_tab
      hints = {:strategy => :current_tab, :existing => :current_tab, :split => SWITCH_BEHAVIOUR[behaviour], :new => :current_tab}
    else hints = {}
    end
    Ruber[:main_window].display_document switch_to, hints
  end
end

Slot Signature:

switch()

Signal Details

- settings_changed