ruber 0.0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (166) hide show
  1. data/COPYING +339 -0
  2. data/INSTALL +137 -0
  3. data/LICENSE +8 -0
  4. data/bin/ruber +65 -0
  5. data/data/share/apps/ruber/core_components.yaml +31 -0
  6. data/data/share/apps/ruber/ruberui.rc +109 -0
  7. data/data/share/icons/ruber.png +0 -0
  8. data/data/share/pixmaps/ruby.png +0 -0
  9. data/icons/ruber-16.png +0 -0
  10. data/icons/ruber-32.png +0 -0
  11. data/icons/ruber-48.png +0 -0
  12. data/icons/ruber-8.png +0 -0
  13. data/lib/ruber/application/application.rb +288 -0
  14. data/lib/ruber/application/plugin.yaml +11 -0
  15. data/lib/ruber/component_manager.rb +899 -0
  16. data/lib/ruber/config/config.rb +82 -0
  17. data/lib/ruber/config/plugin.yaml +3 -0
  18. data/lib/ruber/document_project.rb +209 -0
  19. data/lib/ruber/documents/document_list.rb +416 -0
  20. data/lib/ruber/documents/plugin.yaml +4 -0
  21. data/lib/ruber/editor/document.rb +506 -0
  22. data/lib/ruber/editor/editor_view.rb +167 -0
  23. data/lib/ruber/editor/ktexteditor_wrapper.rb +202 -0
  24. data/lib/ruber/exception_widgets.rb +245 -0
  25. data/lib/ruber/external_program_plugin.rb +397 -0
  26. data/lib/ruber/filtered_output_widget.rb +342 -0
  27. data/lib/ruber/gui_states_handler.rb +231 -0
  28. data/lib/ruber/kde_config_option_backend.rb +167 -0
  29. data/lib/ruber/kde_sugar.rb +249 -0
  30. data/lib/ruber/main_window/choose_plugins_dlg.rb +353 -0
  31. data/lib/ruber/main_window/main_window.rb +524 -0
  32. data/lib/ruber/main_window/main_window_actions.rb +537 -0
  33. data/lib/ruber/main_window/main_window_internal.rb +239 -0
  34. data/lib/ruber/main_window/open_file_in_project_dlg.rb +212 -0
  35. data/lib/ruber/main_window/output_color_widget.rb +35 -0
  36. data/lib/ruber/main_window/plugin.yaml +58 -0
  37. data/lib/ruber/main_window/save_modified_files_dlg.rb +89 -0
  38. data/lib/ruber/main_window/status_bar.rb +156 -0
  39. data/lib/ruber/main_window/ui/choose_plugins_widget.rb +90 -0
  40. data/lib/ruber/main_window/ui/choose_plugins_widget.ui +77 -0
  41. data/lib/ruber/main_window/ui/main_window_settings_widget.rb +108 -0
  42. data/lib/ruber/main_window/ui/main_window_settings_widget.ui +89 -0
  43. data/lib/ruber/main_window/ui/new_project_widget.rb +119 -0
  44. data/lib/ruber/main_window/ui/new_project_widget.ui +178 -0
  45. data/lib/ruber/main_window/ui/open_file_in_project_dlg.rb +109 -0
  46. data/lib/ruber/main_window/ui/open_file_in_project_dlg.ui +168 -0
  47. data/lib/ruber/main_window/ui/output_color_widget.rb +241 -0
  48. data/lib/ruber/main_window/ui/output_color_widget.ui +204 -0
  49. data/lib/ruber/main_window/workspace.rb +442 -0
  50. data/lib/ruber/output_widget.rb +1093 -0
  51. data/lib/ruber/plugin.rb +264 -0
  52. data/lib/ruber/plugin_like.rb +589 -0
  53. data/lib/ruber/plugin_specification.rb +106 -0
  54. data/lib/ruber/plugin_specification_reader.rb +451 -0
  55. data/lib/ruber/project.rb +493 -0
  56. data/lib/ruber/project_backend.rb +105 -0
  57. data/lib/ruber/projects/plugin.yaml +11 -0
  58. data/lib/ruber/projects/project_files_list.rb +314 -0
  59. data/lib/ruber/projects/project_files_widget.rb +301 -0
  60. data/lib/ruber/projects/project_list.rb +314 -0
  61. data/lib/ruber/projects/ui/project_files_rule_chooser_widget.rb +74 -0
  62. data/lib/ruber/projects/ui/project_files_rule_chooser_widget.ui +61 -0
  63. data/lib/ruber/projects/ui/project_files_widget.rb +117 -0
  64. data/lib/ruber/projects/ui/project_files_widget.ui +123 -0
  65. data/lib/ruber/qt_sugar.rb +673 -0
  66. data/lib/ruber/settings_container.rb +515 -0
  67. data/lib/ruber/settings_dialog.rb +244 -0
  68. data/lib/ruber/settings_dialog_manager.rb +503 -0
  69. data/lib/ruber/utils.rb +414 -0
  70. data/lib/ruber/yaml_option_backend.rb +159 -0
  71. data/outsider_files +15 -0
  72. data/plugins/autosave/autosave.rb +404 -0
  73. data/plugins/autosave/plugin.yaml +16 -0
  74. data/plugins/autosave/ui/autosave_config_widget.rb +83 -0
  75. data/plugins/autosave/ui/autosave_config_widget.ui +68 -0
  76. data/plugins/command/command.png +0 -0
  77. data/plugins/command/command.rb +74 -0
  78. data/plugins/command/plugin.yaml +11 -0
  79. data/plugins/find_in_files/find_in_files.rb +337 -0
  80. data/plugins/find_in_files/find_in_files_dlg.rb +411 -0
  81. data/plugins/find_in_files/find_in_files_ui.rc +11 -0
  82. data/plugins/find_in_files/find_in_files_widgets.rb +485 -0
  83. data/plugins/find_in_files/plugin.yaml +23 -0
  84. data/plugins/find_in_files/ui/config_widget.rb +58 -0
  85. data/plugins/find_in_files/ui/config_widget.ui +41 -0
  86. data/plugins/find_in_files/ui/find_in_files_widget.rb +260 -0
  87. data/plugins/find_in_files/ui/find_in_files_widget.ui +324 -0
  88. data/plugins/project_browser/plugin.yaml +10 -0
  89. data/plugins/project_browser/project_browser.rb +245 -0
  90. data/plugins/rake/plugin.yaml +39 -0
  91. data/plugins/rake/rake.png +0 -0
  92. data/plugins/rake/rake.rb +567 -0
  93. data/plugins/rake/rake_extension.rb +153 -0
  94. data/plugins/rake/rake_widgets.rb +615 -0
  95. data/plugins/rake/rakeui.rc +27 -0
  96. data/plugins/rake/ui/add_quick_task_widget.rb +71 -0
  97. data/plugins/rake/ui/add_quick_task_widget.ui +59 -0
  98. data/plugins/rake/ui/choose_task_widget.rb +77 -0
  99. data/plugins/rake/ui/choose_task_widget.ui +72 -0
  100. data/plugins/rake/ui/config_widget.rb +127 -0
  101. data/plugins/rake/ui/config_widget.ui +123 -0
  102. data/plugins/rake/ui/project_widget.rb +217 -0
  103. data/plugins/rake/ui/project_widget.ui +246 -0
  104. data/plugins/rspec/plugin.yaml +30 -0
  105. data/plugins/rspec/rspec.png +0 -0
  106. data/plugins/rspec/rspec.rb +945 -0
  107. data/plugins/rspec/rspec.svg +90 -0
  108. data/plugins/rspec/rspecui.rc +20 -0
  109. data/plugins/rspec/ruber_rspec_formatter.rb +312 -0
  110. data/plugins/rspec/ui/rspec_project_widget.rb +170 -0
  111. data/plugins/rspec/ui/rspec_project_widget.ui +193 -0
  112. data/plugins/ruby_development/plugin.yaml +27 -0
  113. data/plugins/ruby_development/ruby_development.png +0 -0
  114. data/plugins/ruby_development/ruby_development.rb +453 -0
  115. data/plugins/ruby_development/ruby_developmentui.rc +19 -0
  116. data/plugins/ruby_development/ui/project_widget.rb +112 -0
  117. data/plugins/ruby_development/ui/project_widget.ui +108 -0
  118. data/plugins/ruby_runner/config_widget.rb +116 -0
  119. data/plugins/ruby_runner/plugin.yaml +26 -0
  120. data/plugins/ruby_runner/project_widget.rb +62 -0
  121. data/plugins/ruby_runner/ruby.png +0 -0
  122. data/plugins/ruby_runner/ruby_interpretersui.rc +26 -0
  123. data/plugins/ruby_runner/ruby_runner.rb +411 -0
  124. data/plugins/ruby_runner/ui/config_widget.rb +92 -0
  125. data/plugins/ruby_runner/ui/config_widget.ui +91 -0
  126. data/plugins/ruby_runner/ui/project_widget.rb +60 -0
  127. data/plugins/ruby_runner/ui/project_widget.ui +48 -0
  128. data/plugins/ruby_runner/ui/ruby_runnner_plugin_option_widget.rb +59 -0
  129. data/plugins/ruby_runner/ui/ruby_runnner_plugin_option_widget.ui +44 -0
  130. data/plugins/state/plugin.yaml +28 -0
  131. data/plugins/state/state.rb +520 -0
  132. data/plugins/state/ui/config_widget.rb +92 -0
  133. data/plugins/state/ui/config_widget.ui +89 -0
  134. data/plugins/syntax_checker/plugin.yaml +18 -0
  135. data/plugins/syntax_checker/syntax_checker.rb +662 -0
  136. data/ruber.desktop +10 -0
  137. data/spec/annotation_model_spec.rb +174 -0
  138. data/spec/common.rb +119 -0
  139. data/spec/component_manager_spec.rb +1259 -0
  140. data/spec/document_list_spec.rb +626 -0
  141. data/spec/document_project_spec.rb +373 -0
  142. data/spec/document_spec.rb +779 -0
  143. data/spec/editor_view_spec.rb +167 -0
  144. data/spec/external_program_plugin_spec.rb +676 -0
  145. data/spec/filtered_output_widget_spec.rb +642 -0
  146. data/spec/gui_states_handler_spec.rb +304 -0
  147. data/spec/kde_config_option_backend_spec.rb +214 -0
  148. data/spec/kde_sugar_spec.rb +101 -0
  149. data/spec/ktexteditor_wrapper_spec.rb +305 -0
  150. data/spec/output_widget_spec.rb +1703 -0
  151. data/spec/plugin_spec.rb +1393 -0
  152. data/spec/plugin_specification_reader_spec.rb +1765 -0
  153. data/spec/plugin_specification_spec.rb +401 -0
  154. data/spec/project_backend_spec.rb +172 -0
  155. data/spec/project_files_list_spec.rb +401 -0
  156. data/spec/project_list_spec.rb +511 -0
  157. data/spec/project_spec.rb +990 -0
  158. data/spec/qt_sugar_spec.rb +328 -0
  159. data/spec/settings_container_spec.rb +617 -0
  160. data/spec/settings_dialog_manager_spec.rb +773 -0
  161. data/spec/settings_dialog_spec.rb +419 -0
  162. data/spec/state_spec.rb +991 -0
  163. data/spec/utils_spec.rb +406 -0
  164. data/spec/workspace_spec.rb +869 -0
  165. data/spec/yaml_option_backend_spec.rb +246 -0
  166. metadata +284 -0
@@ -0,0 +1,30 @@
1
+ name: rspec
2
+ version: 0.0.1
3
+ about:
4
+ authors: [Stefano Crocco, stefano.crocco@alice.it]
5
+ license: :gpl
6
+ description: Frontend to RSpec
7
+ bug_address: http://github.com/stcrocco/ruber/issues
8
+ icon: rspec.png
9
+ class: Ruber::RSpec::Plugin
10
+ require: rspec
11
+ deps: [ruby_development, autosave]
12
+ ui_file: rspecui.rc
13
+ project_widgets:
14
+ - {caption: RSpec, class: 'Ruber::RSpec::ProjectWidget', pixmap: rspec.png}
15
+ project_options:
16
+ rspec:
17
+ executable: {default: 'Ruber::RSpec::Plugin.find_default_executable'}
18
+ options: {default: []}
19
+ spec_directory: {relative_path: true, default: spec}
20
+ spec_files: {default: '*_spec.rb'}
21
+ spec_pattern: {default: ['%f_spec.rb']}
22
+ full_backtraces: {default: true}
23
+ tool_widgets: {class: 'Ruber::RSpec::ToolWidget', pixmap: rspec.png, caption: RSpec}
24
+ actions:
25
+ rspec-go_to_spec: {text: Switch to &Spec, shortcut: 'Alt+Shift+R, S', slot: go_to_spec(), states: [active_project_exists, current_document]}
26
+ rspec-go_to_file: {text: Switch to &File, shortcut: 'Alt+Shift+R, F', slot: go_to_file(), states: [active_project_exists, current_document]}
27
+ rspec-run_all: {text: Run &Project Specs, shortcut: 'Alt+Shift+R, P', slot: run_all(), states: [active_project_exists, rspec_running]}
28
+ rspec-run_current: {text: Run Specs for &Current File, shortcut: 'Alt+Shift+R, C', slot: run_current(), states: [active_project_exists, current_document, rspec_running]}
29
+ rspec-run_current_line: {text: Run Current Spec, shortcut: 'Alt+Shift+R, L', slot: run_current_line(), states: [active_project_exists, current_document, rspec_running]}
30
+ rspec-stop: {text: S&top, shortcut: Esc, icon: process-stop, slot: stop_process(), state: rspec_running}
Binary file
@@ -0,0 +1,945 @@
1
+ require 'ruby_runner/ruby_runner'
2
+ require_relative 'ui/rspec_project_widget'
3
+
4
+ require 'strscan'
5
+ require 'yaml'
6
+ require 'open3'
7
+
8
+ module Ruber
9
+
10
+ =begin rdoc
11
+ Frontend plugin to RSpec
12
+
13
+ This plugin provides commands to run examples for all the files in the current
14
+ project or on the current document (only if it's part of a project). In the latter
15
+ case, the command can be issued while viewing either the spec or the code file.
16
+ Besides, an action to switch from spec file to code file and one to do the reverse
17
+ switch are provided.
18
+
19
+ The pattern to extract the name of the spec file from that of the code file can
20
+ be customized in the project configuration dialog (by default the plugin assumes
21
+ that the spec file for for a file called @code.rb@ is @code_spec.rb@). The directory
22
+ where the spec files are can also be customized (by default it's the @spec@ subdirectory
23
+ under the project directory)
24
+
25
+ @api feature rspec
26
+ @plugin
27
+ @config_option rspec ruby_options [<String>] the options to pass to ruby when executing
28
+ the spec command
29
+ @project_option rspec executable [String] the path to the spec executable
30
+ @project_option rspec options [<String>] the options to pass to the _spec_ command
31
+ @project_option rspec spec_directory [String] the directory where the example files
32
+ are
33
+ @project_option rspec spec_pattern [<String>] patterns to generate the spec files
34
+ from the code files. The name of the code file is obtained by using the special
35
+ sequence @%f@ in the strings. Only the file name of the code file is taken into
36
+ account (not the directory). All the spec file names are relative to the directory
37
+ specified in the @rspec/spec_directory@ option. If more than one entry is specified,
38
+ all of them will be tried
39
+ =end
40
+ module RSpec
41
+
42
+ =begin rdoc
43
+ Plugin object for the RSpec plugin
44
+ @api_method #specs_for_file
45
+ @api_method #file_for_spec
46
+ @api_method #run_rspec
47
+ @api_method #run_rspec_for
48
+ =end
49
+ class Plugin < RubyRunner::RubyRunnerPlugin
50
+
51
+ =begin rdoc
52
+ The starting delimiter of the data written by the formatter
53
+ =end
54
+ STARTING_DELIMITER = /^####%%%%####KRUBY_BEGIN$/
55
+
56
+ =begin rdoc
57
+ The ending delimiter of the data written by the formatter
58
+ =end
59
+ ENDING_DELIMITER = /^####%%%%####KRUBY_END$/
60
+
61
+ =begin rdoc
62
+ Finds the rspec program to use by default
63
+
64
+ It looks for an executable called @rspec@ or @spec@ (this is to support both RSpec
65
+ 1 and 2) in @PATH@ (using the @which@ command).
66
+ @return [String] a string with the path to the rspec program or an empty string
67
+ if no rspec program was found
68
+ =end
69
+ def self.find_default_executable
70
+ path = Open3.popen3('which rspec'){|stdin, stdout, stderr| stdout.read}.strip
71
+ if path.empty?
72
+ path = Open3.popen3('which spec'){|stdin, stdout, stderr| stdout.read}.strip
73
+ end
74
+ path
75
+ end
76
+
77
+
78
+ slots :go_to_spec, :go_to_file, :run_all, :run_current, :run_current_line
79
+
80
+ =begin rdoc
81
+ @param [Ruber::PluginSpecification] the plugin specification object associated with
82
+ the plugin
83
+ =end
84
+ def initialize psf
85
+ super psf, :rspec, {:scope => [:global]}, nil, false
86
+ Ruber[:autosave].register_plugin self, true
87
+ @formatter = File.join File.dirname(__FILE__), 'ruber_rspec_formatter'
88
+ self.connect(SIGNAL('process_finished(int, QString)')){Ruber[:main_window].set_state 'rspec_running', false}
89
+ Ruber[:main_window].set_state 'rspec_running', false
90
+
91
+ switch_prc = Proc.new{|states| states['active_project_exists'] and states['current_document']}
92
+ register_action_handler 'rspec-go_to_file', &switch_prc
93
+ register_action_handler 'rspec-go_to_spec', &switch_prc
94
+ register_action_handler 'rspec-run_all' do |states|
95
+ states['active_project_exists'] and !states['rspec_running']
96
+ end
97
+ current_prc = Proc.new do |states|
98
+ states['active_project_exists'] and states['current_document'] and
99
+ !states['rspec_running']
100
+ end
101
+ register_action_handler 'rspec-run_current', &current_prc
102
+ register_action_handler 'rspec-run_current_line', &current_prc
103
+
104
+ Ruber[:components].connect(SIGNAL('feature_loaded(QString, QObject*)')) do |f, o|
105
+ o.register_plugin self, true if f == 'autosave'
106
+ end
107
+ @output_widget = @widget
108
+ end
109
+
110
+ =begin rdoc
111
+ Runs rspec for the given files
112
+
113
+ The output of spec is displayed in the associated output widget.
114
+
115
+ Files are autosaved before running rspec.
116
+
117
+ If spec is already running, or if autosaving fails, noting is done.
118
+
119
+ @param [Array<String>] files the spec files to run. Nonexisting files are ignored
120
+ @param [Hash] opts options to fine-tune the behaviour of spec
121
+ @param [Hash] autosave_opts options telling whether and how autosave files
122
+ @param [Proc] blk a block to pass to autosave. If not given, no block will be
123
+ passed to autosave
124
+
125
+ @option opts [String] :ruby (Ruber[:config][:ruby, :ruby]) the ruby interpreter
126
+ to use
127
+ @option opts [Array<String>] :ruby_options (Ruber[:config][:ruby, :ruby_options])
128
+ the options to pass to the ruby interpreter
129
+ @option opts [String] :spec ('spec') the path of the spec command to use
130
+ @option opts [Array<String>] :spec_options ([]) the options to pass to the spec
131
+ program
132
+ @option opts [String] :dir ('.') the directory to run spec from
133
+ @option opts [Boolean] :full_backtraces (nil) whether or not to pass the @-b@ option
134
+ to the spec program
135
+
136
+ @option autosave_opts [Array<Document>,Symbol] :files (nil) the documents to autosave.
137
+ It has the same meaning as second parameter to {Autosave::AutosavePlugin#autosave}.
138
+ If it's *nil*, autosave won't be used
139
+ @option autosave_opts [Symbol, Plugin] :plugin (Ruber[:rspec]) the value to pass
140
+ as first argument to {Autosave::AutosavePlugin#autosave}
141
+ @option autosave_opts [Boolean] :stop_on_failure (nil) as in {Autosave::AutosavePlugin#autosave}
142
+ @option autosave_opts [Symbol] :on_failure (nil) as in {Autosave::AutosavePlugin#autosave}
143
+ @option autosave_opts [String] :message (nil) as in {Autosave::AutosavePlugin#autosave}
144
+
145
+ @return [Boolean] *true* if the process is started and *false* otherwise
146
+ (including the case when the process was already running or autosaving failed)
147
+ =end
148
+ def run_rspec files, opts, autosave_opts = {}, &blk
149
+ default_opts = {
150
+ :ruby => Ruber[:config][:ruby, :ruby],
151
+ :ruby_options => Ruber[:config][:ruby, :ruby_options],
152
+ :spec => 'spec',
153
+ :spec_options => [],
154
+ :dir => '.'
155
+ }
156
+ opts = default_opts.merge opts
157
+ return false if @process.state != Qt::Process::NotRunning
158
+ @widget.clear_output
159
+ files = files.select{|f| File.exist? f}
160
+ # if files.empty?
161
+ # KDE::MessageBox.sorry nil, 'No spec file has been found. RSpec won\'t be run'
162
+ # return false
163
+ # end
164
+ if autosave_opts[:files]
165
+ plug = autosave_opts[:plugin] || self
166
+ what = autosave_opts[:files]
167
+ return false unless Ruber[:autosave].autosave plug,
168
+ what, autosave_opts, &blk
169
+ end
170
+ full_backtraces = opts[:full_backtraces] ? %w[-b] : []
171
+ args = [opts[:spec]] + %W[-r #{@formatter} -f Ruber::RSpec::Formatter] +
172
+ opts[:spec_options] + full_backtraces + files
173
+ @widget.working_directory = opts[:dir]
174
+ @display_standard_error = opts[:stderr]
175
+ Ruber[:main_window].activate_tool(@widget)
176
+ Ruber[:main_window].change_state 'rspec_running', true
177
+ title = ([opts[:spec].split('/')[-1]] + opts[:spec_options]+ full_backtraces + files).join ' '
178
+ run_process opts[:ruby], opts[:dir], opts[:ruby_options] + args, title
179
+ @widget.model.item(0,0).tool_tip = ([opts[:ruby]] + opts[:ruby_options] + args).join " "
180
+ true
181
+ end
182
+
183
+ private
184
+
185
+ =begin rdoc
186
+ Override of {ExternalProgramPlugin#process_standard_output}
187
+
188
+ It parses the output from the _spec_ program (generated with the
189
+ {Ruber::RSpec::Formatter Ruber rspec formatter}) and displays the data it contains
190
+ appropriately.
191
+ @param [Array] lines the output, split in lines
192
+ @return [nil]
193
+ =end
194
+ def process_standard_output lines
195
+ items = parse_spec_output lines.join "\n"
196
+ items.each do |it|
197
+ if it.is_a? String then @widget.model.insert_lines it, :output, nil
198
+ else @widget.display_example it
199
+ end
200
+ end
201
+ nil
202
+ end
203
+
204
+ =begin rdoc
205
+ Override of {ExternalProgramPlugin#process_standard_error}
206
+ @param [Array] lines the standard error output, split in lines
207
+ @return [nil]
208
+ =end
209
+ def process_standard_error lines
210
+ @widget.model.insert_lines lines, :output1, nil
211
+ nil
212
+ end
213
+
214
+ =begin rdoc
215
+ Parses the output of the _spec_ command
216
+
217
+ This method only works if the {Ruber::RSpec::Formatter Ruber rspec formatter}
218
+ is used.
219
+
220
+ The output is split according in regions between {STARTING_DELIMITER} and
221
+ {ENDING_DELIMITER}. All text inside these regions is considered the YAML dump of
222
+ a hash and converted back to a hash. All text which is not between a pair of
223
+ delimiters, as well as the text which can't be converted by YAML is left as is
224
+ @param [String] str the output to parse
225
+ @return [<Hash, String>] the result of the parse. The text which was successfully
226
+ parsed by YAML is stored as a hash, while the rest is stored as strings. Order is
227
+ preserved.
228
+ =end
229
+ def parse_spec_output str
230
+ sc = StringScanner.new str
231
+ res = []
232
+ until sc.eos?
233
+ match = sc.scan_until(STARTING_DELIMITER) || sc.rest
234
+ found = match.sub!(STARTING_DELIMITER, '')
235
+ res << match
236
+ if found
237
+ yaml = sc.scan_until(ENDING_DELIMITER) || sc.rest
238
+ yaml.sub! ENDING_DELIMITER, ''
239
+ res << (YAML.load(yaml) rescue yaml)
240
+ else sc.terminate
241
+ end
242
+ end
243
+ res
244
+ end
245
+
246
+ =begin rdoc
247
+ Runs all the specs for the project.
248
+ @return [nil]
249
+ =end
250
+ def run_all
251
+ prj = Ruber[:projects].current_project
252
+ unless prj
253
+ KDE::MessageBox.error nil, "You must have an open project to choose this entry.\nYOU SHOULD NEVER SEE THIS MESSAGE"
254
+ return
255
+ end
256
+ opts = options prj
257
+ opts[:files] = Dir.glob File.join(opts[:specs_dir], opts[:filter])
258
+ run_rspec_for prj, opts, :files => :project_files, :on_failure => :ask, :message => 'Do you want to run the tests all the same?'
259
+ nil
260
+ end
261
+
262
+ =begin rdoc
263
+ Runs the specs corresponding to the current file
264
+
265
+ If the current file is a code file, the corresponding spec file will be run. If
266
+ it is a spec file, the file itself will be run.
267
+
268
+ To decide whether the current file is a spec or a code file, this method uses
269
+ the patters stored in the @rspec/spec_pattern@ project setting to build the names
270
+ of the spec files associated with the current file, which is assumed to be a code
271
+ file. If none of these files exist, then the current file is treated as a spec file,
272
+ otherwise one of the spec files thus generated is run.
273
+
274
+ *Note:* the way this method works implies that if it is called on a code
275
+ file which doesn't have an associated spec file, rspec will be run on that file,
276
+ which, most likely, will cause it to fail.
277
+
278
+ @return [Boolean] *true* if the spec program is started and *false* otherwise
279
+ (including the case when the process was already running or autosaving failed)
280
+ =end
281
+ def run_current
282
+ prj = Ruber[:projects].current_project
283
+ unless prj
284
+ KDE::MessageBox.error nil, "You must have an open project to choose this entry.\nYOU SHOULD NEVER SEE THIS MESSAGE"
285
+ return
286
+ end
287
+ opts = options prj
288
+ doc = Ruber[:main_window].current_document
289
+ unless doc
290
+ KDE::MessageBox.error nil, "You must have an open document to choose this entry.\nYOU SHOULD NEVER SEE THIS MESSAGE"
291
+ return
292
+ end
293
+ files = specs_for_file opts, doc.path
294
+ files.reject!{|f| !File.exist? f}
295
+ opts[:files] = files.empty? ? [doc.path] : files
296
+ run_rspec_for prj, opts, :files => :documents_with_file, :on_failure => :ask,
297
+ :message => 'Do you want to run the tests all the same?'
298
+ end
299
+
300
+ =begin rdoc
301
+ Runs the example(s) in the current line
302
+
303
+ Similar to {#run_current}, but tells spec to run only the example or example
304
+ group corresponding to the line where the cursor is (using spec's -l option).
305
+ Besides, unlike {#run_current}, this method can only be called when the
306
+ current file is the example file, not the source.
307
+
308
+ @return [Boolean] *true* if the spec program is started and *false* otherwise
309
+ (including the case when the process was already running or autosaving failed)
310
+ =end
311
+ def run_current_line
312
+ prj = Ruber[:projects].current_project
313
+ unless prj
314
+ KDE::MessageBox.error nil, "You must have an open project to choose this entry.\nYOU SHOULD NEVER SEE THIS MESSAGE"
315
+ return
316
+ end
317
+ opts = options prj
318
+ doc = Ruber[:main_window].current_document
319
+ unless doc
320
+ KDE::MessageBox.error nil, "You must have an open document to choose this entry.\nYOU SHOULD NEVER SEE THIS MESSAGE"
321
+ return
322
+ end
323
+ line = doc.view.cursor_position.line + 1
324
+ files = specs_for_file opts, doc.path
325
+ files.reject!{|f| !File.exist? f}
326
+ opts[:files] = files.empty? ? [doc.path] : files
327
+ opts[:spec_options] += ["-l", line.to_s]
328
+ run_rspec_for prj, opts, :files => :documents_with_file, :on_failure => :ask,
329
+ :message => 'Do you want to run the tests all the same?'
330
+ end
331
+
332
+ =begin rdoc
333
+ Runs the spec command for the given object
334
+
335
+ It works like {RubyRunner::RubyRunnerPluginInternal#ruby_command_for RubyRunner::RubyRunnerPlugin#ruby_command_for}
336
+ but already takes care of the user's settings regarding the path of the spec program.
337
+
338
+ @param [AbstractProject, Document, String, nil] origin see the _target_ argument
339
+ of {RubyRunner::RubyRunnerPluginInternal#option_for RubyRunner::RubyRunnerPlugin#option_for}
340
+ @param [Hash] opts the options to pass to the _spec_ program. It has the same
341
+ meaning as the _opts_ argument to {#run_rspec}, but can also contain an additional
342
+ entry (see below)
343
+ @param [Hash] autosave_opts see the _autosave_opts_ argument of {#run_rspec}
344
+ @param [Proc] blk see the _blk_ argument of {#run_rspec}
345
+
346
+ @option opts [Array<String>] :files the files to pass to the spec program
347
+
348
+ @return [Boolean] *true* if the process is started and *false* otherwise
349
+ (including the case when the process was already running or autosaving failed)
350
+ =end
351
+ def run_rspec_for origin, opts, autosave_opts = {}, &blk
352
+ process.kill
353
+ ruby, *cmd = ruby_command_for origin, opts[:dir]
354
+ opts = {:ruby => ruby, :ruby_options => cmd}.merge opts
355
+ run_rspec opts[:files], opts, autosave_opts, &blk
356
+ end
357
+
358
+ =begin rdoc
359
+ Collects all the options relative to this plugin from a project
360
+
361
+ *Note:* *never* use destructive methods on the values contained in this hash. Doing so will
362
+ change the options stored in the project, which most likely isn't what you want.
363
+ If you need to change the options, make duplicates of the values
364
+
365
+ @param [AbstractProject] prj the project to retrieve options from
366
+ @return [Hash] an hash containing the options stored in the project. The correspondence
367
+ between options and entries in this hash is the following:
368
+ * @:rspec/executable@ &rarr; @:spec@
369
+ * @:rspec/options@ &rarr; @:spec_options@
370
+ * @:rspec/spec_directory@ &rarr; @:specs_dir@
371
+ * @:rspec/spec_files@ &rarr; @:filter@
372
+ * @:rspec/spec_pattern@ &rarr; @:pattern@
373
+ * @:rspec/full_backtraces@ &rarr; @:full_backtraces@
374
+
375
+ Besides, the above entries, the hash also contains a @:dir@ entry which contains
376
+ the project directory.
377
+ =end
378
+ def options prj
379
+ res = {}
380
+ res[:spec] = prj[:rspec, :executable]
381
+ res[:spec_options] = prj[:rspec, :options]
382
+ res[:specs_dir] = prj[:rspec, :spec_directory, :absolute]
383
+ res[:filter] = prj[:rspec, :spec_files]
384
+ res[:pattern] = prj[:rspec, :spec_pattern]
385
+ res[:dir] = prj.project_directory
386
+ res[:full_backtraces] = prj[:rspec, :full_backtraces]
387
+ res
388
+ end
389
+
390
+ =begin rdoc
391
+ Opens the spec file(s) associated with the current document in the editor
392
+
393
+ @return [nil]
394
+ =end
395
+ def go_to_spec
396
+ prj = Ruber[:projects].current_project
397
+ unless prj
398
+ KDE::MessageBox.error nil, "You must have an open project to choose this entry.\nYOU SHOULD NEVER SEE THIS MESSAGE"
399
+ return
400
+ end
401
+ opts = options prj
402
+ doc = Ruber[:main_window].current_document
403
+ unless prj
404
+ KDE::MessageBox.error nil, "You must have an open document to choose this entry.\nYOU SHOULD NEVER SEE THIS MESSAGE"
405
+ return
406
+ end
407
+ files = specs_for_file opts, doc.path
408
+ files.each{|f| Ruber[:main_window].display_document f if File.exist? f}
409
+ nil
410
+ end
411
+
412
+ =begin rdoc
413
+ Opens the code file associated with the current spec file in the editor
414
+
415
+ This method assumes the current file is a spec file and attempts to find the
416
+ correspondent code file and open it in the editor. It does nothing if no correspondent
417
+ code file can be found.
418
+ @return [nil]
419
+ @see #file_for_spec
420
+ =end
421
+ def go_to_file
422
+ prj = Ruber[:projects].current_project
423
+ unless prj
424
+ KDE::MessageBox.error nil, "You must have an open project to choose this entry.\nYOU SHOULD NEVER SEE THIS MESSAGE"
425
+ return
426
+ end
427
+ opts = options prj
428
+ doc = Ruber[:main_window].current_document
429
+ unless prj
430
+ KDE::MessageBox.error nil, "You must have an open document to choose this entry.\nYOU SHOULD NEVER SEE THIS MESSAGE"
431
+ return
432
+ end
433
+ file = file_for_spec prj, doc.path
434
+ Ruber[:main_window].display_document file if file and File.exist? file
435
+ nil
436
+ end
437
+
438
+ =begin rdoc
439
+ Determines all possible specs files associated with a code file
440
+
441
+ The names of the possible spec files are obtained replacing the @%f@ tag in each
442
+ entry of the @rspec/pattern@ setting with the name of the file (without checking
443
+ whether the files actually exist).
444
+
445
+ @param [String] file the name of the code file
446
+ @return [<String>] the names of the possible spec file associated with _file_
447
+ =end
448
+ def specs_for_file opts, file
449
+ file = File.basename file, '.rb'
450
+ res = opts[:pattern].map{|i| File.join opts[:specs_dir], i.gsub('%f', file)}
451
+ res
452
+ end
453
+
454
+ =begin rdoc
455
+ The name of the code file associated with a given spec file
456
+
457
+ To find out which code file is associated with the given spec file, this method
458
+ takes all the project files and constructs the file names of all the specs associated
459
+ to them according to the @rspec/spec_pattern@ project option. As soon as one of
460
+ the generated file names matches the given spec file, the generating file is returned.
461
+
462
+ @param [Ruber::AbstractProject] prj the project containing the settings to use
463
+ @param [String] file the name of the spec file to find the code file for
464
+ @return [String] the absolute path of a file _file_ is a spec of, according
465
+ to the settings contained in _prj_.
466
+ =end
467
+ def file_for_spec prj, file
468
+ pattern = prj[:spec_pattern]
469
+ opts = options prj
470
+ prj.project_files.abs.find{|f| specs_for_file( opts, f).include? file}
471
+ end
472
+
473
+ =begin rdoc
474
+ Override of {ExternalProgramPlugin#display_exit_message}
475
+
476
+ It works as the base class's method except when the program exits successfully,
477
+ in which case it does nothing.
478
+
479
+ See {ExternalProgramPlugin#display_exit_message} for the meaning of the parameters
480
+
481
+ @return nil
482
+ =end
483
+ def display_exit_message code, reason
484
+ super unless reason.empty?
485
+ end
486
+
487
+ end
488
+
489
+ =begin rdoc
490
+ Filter model used by the RSpec output widget
491
+
492
+ It allows to choose whether to accept items corresponding to output to standard error or to reject
493
+ it. To find out if a given item corresponds to the output of standard error or
494
+ standard output, this model uses the data contained in a custom role in the output.
495
+ The index of this role is {RSpec::OutputWidget::OutputTypeRole}.
496
+ =end
497
+ class FilterModel < FilteredOutputWidget::FilterModel
498
+
499
+ slots 'toggle_display_stderr(bool)'
500
+
501
+ =begin rdoc
502
+ Whether output from standard error should be displayed or not
503
+ @return [Boolean]
504
+ =end
505
+ attr_reader :display_stderr
506
+
507
+ =begin rdoc
508
+ Create a new instance
509
+
510
+ The new instance is set not to show the output from standard error
511
+
512
+ @param [Qt::Object, nil] parent the parent object
513
+ =end
514
+ def initialize parent = nil
515
+ super
516
+ @display_stderr = false
517
+ end
518
+
519
+ =begin rdoc
520
+ Sets whether to display or ignore items corresponding to output to standard error
521
+
522
+ If this choice has changed, the model is invalidated.
523
+
524
+ @param [Boolean] val whether to display or ignore the output to standard error
525
+ @return [Boolean] _val_
526
+ =end
527
+ def display_stderr= val
528
+ old, @display_stderr = @display_stderr, val
529
+ invalidate if old != @display_stderr
530
+ @display_standard_error
531
+ end
532
+ alias_method :toggle_display_stderr, :display_stderr=
533
+
534
+ =begin rdoc
535
+ Override of {FilteredOutputWidget::FilterModel#filterAcceptsRow}
536
+
537
+ According to the value of {#display_stderr}, it can filter out items corresponding
538
+ to standard error. In all other respects, it behaves as the base class method.
539
+ @param [Integer] r the row number
540
+ @param [Qt::ModelIndex] parent the parent index
541
+ @return [Boolean] *true* if the row should be displayed and *false* otherwise
542
+ =end
543
+ def filterAcceptsRow r, parent
544
+ if !@display_stderr
545
+ idx = source_model.index(r,0,parent)
546
+ return false if idx.data(OutputWidget::OutputTypeRole).to_string == 'output1'
547
+ end
548
+ super
549
+ end
550
+
551
+ end
552
+
553
+ =begin rdoc
554
+ Tool widget used by the rspec plugin.
555
+
556
+ It displays the output from the spec program in a multi column tree. The name of
557
+ failing or pending examples are displayed in a full line; all other information,
558
+ such as the location of the example, the error message and so on are displayed
559
+ in child items.
560
+
561
+ While the examples are being run, a progress bar is shown.
562
+ =end
563
+ class ToolWidget < FilteredOutputWidget
564
+
565
+ slots :spec_started, 'spec_finished(int, QString)'
566
+
567
+ =begin rdoc
568
+ @param [Qt::Widget, nil] parent the parent widget
569
+ =end
570
+ def initialize parent = nil
571
+ super parent, :view => :tree, :filter => FilterModel.new
572
+ @ignore_word_wrap_option = true
573
+ view.word_wrap = true
574
+ view.horizontal_scroll_mode = Qt::AbstractItemView::ScrollPerItem
575
+ # view.text_elide_mode = Qt::ElideNone
576
+ model.append_column [] if model.column_count < 2
577
+ @progress_bar = Qt::ProgressBar.new(self){|w| w.hide}
578
+ layout.add_widget @progress_bar, 1,0
579
+ view.header_hidden = true
580
+ view.header.resize_mode = Qt::HeaderView::ResizeToContents
581
+ connect Ruber[:rspec], SIGNAL(:process_started), self, SLOT(:spec_started)
582
+ connect Ruber[:rspec], SIGNAL('process_finished(int, QString)'), self, SLOT('spec_finished(int, QString)')
583
+ filter.connect(SIGNAL('rowsInserted(QModelIndex, int, int)')) do |par, st, en|
584
+ if !par.valid?
585
+ st.upto(en) do |i|
586
+ view.set_first_column_spanned i, par, true
587
+ end
588
+ end
589
+ end
590
+ setup_actions
591
+ end
592
+
593
+ =begin rdoc
594
+ Displays the data relative to an example in the widget
595
+
596
+ Actually, this method simply passes its argument to a more specific method, depending
597
+ on the data it contains.
598
+
599
+ @param [Hash] data a hash containing the data describing the results of running
600
+ the example. This hash must contain the @:type@ key, which tells which kind of
601
+ event the hash describes. The other entries change depending on the method which
602
+ will be called, which is determined according to the @:type@ entry:
603
+ * @:success@: {#display_successful_example}
604
+ * @:failure@: {#display_failed_example}
605
+ * @:pending@: {#display_pending_example}
606
+ * @:new_example@: {#change_current_example}
607
+ * @:start@: {#set_example_count}
608
+ * @:summary@: {#display_summary}
609
+ If the @:type@ entry doesn't have one of the previous values, the hash will be
610
+ converted to a string and displayed in the widget
611
+ =end
612
+ def display_example data
613
+ unless data.is_a?(Hash)
614
+ model.insert_lines data.to_s, :output, nil
615
+ return
616
+ end
617
+ case data[:type]
618
+ when :success then display_successful_example data
619
+ when :failure then display_failed_example data
620
+ when :pending then display_pending_example data
621
+ when :new_example then change_current_example data
622
+ when :start then set_example_count data
623
+ when :summary then display_summary data
624
+ else model.insert_lines data.to_s, :output, nil
625
+ end
626
+ end
627
+
628
+ =begin rdoc
629
+ Changes the current example
630
+
631
+ Currently, this only affects the tool tip displayed by the progress bar.
632
+
633
+ @param [Hash] data the data to use. It must contain the @:description@ entry,
634
+ which contains the text of the tool tip to use.
635
+ @return [nil]
636
+ =end
637
+ def change_current_example data
638
+ @progress_bar.tool_tip = data[:description]
639
+ nil
640
+ end
641
+
642
+ =begin rdoc
643
+ Sets the number of examples found by the spec program.
644
+
645
+ This is used to set the maximum value of the progress bar.
646
+
647
+ @param [Hash] data the data to use. It must contain the @:count@ entry,
648
+ which contains the number of examples
649
+ @return [nil]
650
+ =end
651
+ def set_example_count data
652
+ @progress_bar.maximum = data[:count]
653
+ nil
654
+ end
655
+
656
+
657
+ =begin rdoc
658
+ Updates the progress bar by incrementing its value by one
659
+
660
+ @param [Hash] data the data to use. Currently it's unused
661
+ @return [nil]
662
+ =end
663
+ def display_successful_example data
664
+ @progress_bar.value += 1
665
+ nil
666
+ end
667
+
668
+ =begin rdoc
669
+ Displays information about a failed example in the tool widget.
670
+
671
+ @param [Hash] data the data about the example.
672
+
673
+ @option data [String] :location the line number where the error occurred
674
+ @option data [String] :description the name of the failed example
675
+ @option data [String] :message the explaination of why the example failed
676
+ @option data [String] :exception the content of the exception
677
+ @option data [String] :backtrace the backtrace of the exception (a single new-line separated string)
678
+ @return [nil]
679
+ =end
680
+ def display_failed_example data
681
+ @progress_bar.value += 1
682
+ top = model.insert("[FAILURE] #{data[:description]}", :error, nil).first
683
+ model.insert ['From:', data[:location]], :message, nil, :parent => top
684
+ ex_label = model.insert('Exception:', :message, nil, :parent => top).first
685
+ exception_body = "#{data[:message]} (#{data[:exception]})".split_lines.delete_if{|l| l.strip.empty?}
686
+ #exception_body may contain more than one line and some of them may be empty
687
+ model.set exception_body.shift, :message, ex_label.row, :col => 1, :parent => top
688
+ exception_body.each do |l|
689
+ unless l.strip.empty?
690
+ model.set l, :message, top.row_count, :col => 1, :parent => top
691
+ end
692
+ end
693
+ backtrace = data[:backtrace].split_lines
694
+ back_label, back = model.insert(['Backtrace:', backtrace.shift], :message, nil, :parent => top)
695
+ backtrace.each do |l|
696
+ model.insert [nil, l], :message, nil, :parent => back_label
697
+ end
698
+ top_index = filter.map_from_source(top.index)
699
+ view.collapse top_index
700
+ view.set_first_column_spanned top_index.row, Qt::ModelIndex.new, true
701
+ view.expand filter.map_from_source(back_label.index)
702
+ nil
703
+ end
704
+
705
+ =begin rdoc
706
+ Displays information about a pending example in the tool widget
707
+
708
+ @param [Hash] data
709
+ @option data [String] :location the line number where the error occurred
710
+ @option data [String] :description the name of the failed example
711
+ @option data [String] :message the explaination of why the example failed
712
+ @return [nil]
713
+ =end
714
+ def display_pending_example data
715
+ @progress_bar.value += 1
716
+ top = model.insert("[PENDING] #{data[:description]}", :warning, nil)[0]
717
+ model.insert ['From:', data[:location]], :message, nil, :parent => top
718
+ model.insert ['Message: ', "#{data[:message]} (#{data[:exception]})"], :message, nil, :parent => top
719
+ nil
720
+ end
721
+
722
+ =begin rdoc
723
+ Displays a summary of the spec run in the tool widget
724
+
725
+ The summary is a single title line which contains the number or successful, pending
726
+ and failed example.
727
+
728
+ @param [Hash] data
729
+ @option data [Integer] :total the number of run examples
730
+ @option data [Integer] :passed the number of passed examples
731
+ @option data [Integer] :failed the number of failed examples
732
+ @option data [Integer] :pending the number of pending examples
733
+ @return [nil]
734
+ =end
735
+ def display_summary data
736
+ @progress_bar.hide
737
+ if data[:passed] == data[:total]
738
+ self.title = "[SUMMARY] All #{data[:total]} examples passed"
739
+ set_output_type model.index(0,0), :message_good
740
+ else
741
+ text = "[SUMMARY] Examples: #{data[:total]}"
742
+ text << " Failed: #{data[:failure]}" if data[:failure] > 0
743
+ text << " Pending: #{data[:pending]}" if data[:pending] > 0
744
+ text << " Passed: #{data[:passed]}"
745
+ self.title = text
746
+ type = data[:failure] > 0 ? :message_bad : :message
747
+ set_output_type model.index(0,0), type
748
+ end
749
+ nil
750
+ end
751
+
752
+ =begin rdoc
753
+ Override of {OutputWidget#title=}
754
+
755
+ It's needed to have the title element span all columns
756
+
757
+ @param [String] val the new title
758
+ =end
759
+ def title= val
760
+ super
761
+ model.item(0,0).tool_tip = val
762
+ view.set_first_column_spanned 0, Qt::ModelIndex.new, true
763
+ end
764
+
765
+ private
766
+
767
+ =begin rdoc
768
+ Resets the tool widget and sets the cursor to busy
769
+ @return [nil]
770
+ =end
771
+ def spec_started
772
+ @progress_bar.maximum = 0
773
+ @progress_bar.value = 0
774
+ @progress_bar.show
775
+ @progress_bar.tool_tip = ''
776
+ actions['show_stderr'].checked = false
777
+ self.cursor = Qt::Cursor.new(Qt::BusyCursor)
778
+ nil
779
+ end
780
+
781
+ =begin rdoc
782
+ Does the necessary cleanup for when spec finishes running
783
+
784
+ It hides the progress widget and restores the default cursor.
785
+
786
+ @param [Integer] code the exit code
787
+ @param [String] reason why the program exited
788
+ @return [nil]
789
+ =end
790
+ def spec_finished code, reason
791
+ @progress_bar.hide
792
+ @progress_bar.value = 0
793
+ @progress_bar.maximum = 100
794
+ self.set_focus
795
+ unset_cursor
796
+ unless reason == 'killed'
797
+ non_stderr_types = %w[message message_good message_bad warning error]
798
+ only_stderr = !model.item(0,0).text.match(/^\[SUMMARY\]/)
799
+ if only_stderr
800
+ 1.upto(model.row_count - 1) do |i|
801
+ if non_stderr_types.include? model.item(i,0).data(OutputWidget::OutputTypeRole).to_string
802
+ only_stderr = false
803
+ break
804
+ end
805
+ end
806
+ end
807
+ if only_stderr
808
+ actions['show_stderr'].checked = true
809
+ model.insert "spec wasn't able to run the examples", :message_bad, nil
810
+ end
811
+ end
812
+ nil
813
+ end
814
+
815
+ =begin rdoc
816
+ Creates the additional actions.
817
+
818
+ It adds a single action, which allows the user to chose whether messages from
819
+ standard error should be displayed or not.
820
+
821
+ @return [nil]
822
+ =end
823
+ def setup_actions
824
+ action_list << nil << 'show_stderr'
825
+ a = KDE::ToggleAction.new 'S&how Standard Error', self
826
+ actions['show_stderr'] = a
827
+ a.checked = false
828
+ connect a, SIGNAL('toggled(bool)'), filter, SLOT('toggle_display_stderr(bool)')
829
+ end
830
+
831
+ =begin rdoc
832
+ Override of {OutputWidget#find_filename_in_index}
833
+
834
+ It works as the base class method, but, if it doesn't find a result in _idx_,
835
+ it looks for it in the parent indexes
836
+
837
+ @param [Qt::ModelIndex] idx the index where to look for a file name
838
+ @return [Array<String,Integer>,String,nil] see {OutputWidget#find_filename_in_index}
839
+ =end
840
+ def find_filename_in_index idx
841
+ res = super
842
+ unless res
843
+ idx = idx.parent while idx.parent.valid?
844
+ idx = idx.child(0,1)
845
+ res = super idx if idx.valid?
846
+ end
847
+ res
848
+ end
849
+
850
+ =begin rdoc
851
+ Override of {OutputWidget#text_for_clipboard}
852
+
853
+ @param [<Qt::ModelIndex>] idxs the selected indexes
854
+ @return [QString] the text to copy to the clipboard
855
+ =end
856
+ def text_for_clipboard idxs
857
+ order = {}
858
+ idxs.each do |i|
859
+ val = []
860
+ parent = i
861
+ while parent.parent.valid?
862
+ parent = parent.parent
863
+ val.unshift parent.row
864
+ end
865
+ val << [i.row, i.column]
866
+ order[val] = i
867
+ end
868
+ order = order.sort do |a, b|
869
+ a, b = a[0], b[0]
870
+ res = a[0..-2] <=> b[0..-2]
871
+ if res == 0 then a[-1] <=> b[-1]
872
+ else res
873
+ end
874
+ end
875
+ prev = order.shift[1]
876
+ text = prev.data.valid? ? prev.data.to_string : ''
877
+ order.each do |_, v|
878
+ text << ( (prev.parent == v.parent and prev.row == v.row) ? "\t" : "\n")
879
+ text << (v.data.valid? ? v.data.to_string : '')
880
+ prev = v
881
+ end
882
+ text
883
+ end
884
+
885
+ end
886
+
887
+ =begin rdoc
888
+ Project widget for the RSpec frontend plugin
889
+ =end
890
+ class ProjectWidget < ProjectConfigWidget
891
+
892
+ =begin rdoc
893
+ @param [Qt::Widget,nil] prj the parent widget
894
+ =end
895
+ def initialize prj
896
+ super
897
+ @ui = Ui::RSpecProjectWidget.new
898
+ @ui.setupUi self
899
+ end
900
+
901
+ private
902
+
903
+ =begin rdoc
904
+ Sets the text of the pattern widget
905
+ @param [Array<String>] the pattern to use. They'll be joined with commas to create
906
+ the text to put in the widget
907
+ =end
908
+ def pattern= value
909
+ value.join ', '
910
+ end
911
+
912
+ =begin rdoc
913
+ Parses the content of the pattern widget
914
+ @return [Array<String>] an array containing the patterns
915
+ =end
916
+ def pattern
917
+ @ui._rspec__spec_pattern.text.split(/;\s*/)
918
+ end
919
+
920
+ =begin rdoc
921
+ Changes the text of the "RSpec options" widget
922
+
923
+ @param [Array<String>] value the options to pass to spec. They'll be joined with
924
+ spaces
925
+ =end
926
+ def spec_options= value
927
+ @ui._rspec__options.text = value.join ' '
928
+ end
929
+
930
+ =begin rdoc
931
+ Parses the text of the "RSpec options" widget
932
+
933
+ @return [Array<String>] an array with the options to pass to spec (options with
934
+ quotes around them keep them, as described in {Shellwords.split_with_quotes})
935
+ =end
936
+ def spec_options
937
+ Shellwords.split_with_quotes @ui._rspec__options.text
938
+ end
939
+
940
+ end
941
+
942
+
943
+ end
944
+
945
+ end