ruber 0.0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,10 @@
1
+ name: project_browser
2
+ version: 0.0.1
3
+ about:
4
+ authors: [Stefano Crocco, stefano.crocco@alice.it]
5
+ license: gpl
6
+ description: A tool widget displaying the contents of the project directory
7
+ bug_address: http://github.com/stcrocco/ruber/issues
8
+ icon: project-development.png
9
+ require: project_browser.rb
10
+ tool_widgets: {class: Ruber::ProjectBrowser::ToolWidget, caption: Project browser, position: left, name: project_browser}
@@ -0,0 +1,245 @@
1
+ =begin
2
+ Copyright (C) 2010 by Stefano Crocco
3
+ stefano.crocco@alice.it
4
+
5
+ This program is free software; you can redistribute it andor modify
6
+ it under the terms of the GNU General Public License as published by
7
+ the Free Software Foundation; either version 2 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU General Public License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License
16
+ along with this program; if not, write to the
17
+ Free Software Foundation, Inc.,
18
+ 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
+ =end
20
+
21
+ module Ruber
22
+
23
+ =begin rdoc
24
+ Plugin providing a tool widget which displays the files in the project directory
25
+
26
+ The tool widget consists of a tree view displaying the contents of the project directory,
27
+ which is automatically updated whenever the current project changes. The view
28
+ provides a menu which allows the user to choose whether to show only the files
29
+ belonging to the project or all files in the directory.
30
+
31
+ The view is updated whenever the contents of the project directory change or whenever
32
+ the @general/project_files@ project option changes.
33
+ =end
34
+ module ProjectBrowser
35
+
36
+ =begin rdoc
37
+ The tool widget displaying the project directory
38
+ =end
39
+ class ToolWidget < Qt::Widget
40
+
41
+ =begin rdoc
42
+ Filter used by the tree view to hide non-project files. It also allow to turn off
43
+ filtering, which is used when the user choose to display all files.
44
+
45
+ @todo currently, directories containing only non-project files are still shown. This
46
+ is because of how the filtering is done: filtering the parent object is done before
47
+ filtering child objects, so there's no direct way to remove empty items. See whether
48
+ something can be done about it
49
+ =end
50
+ class FilterModel < KDE::DirSortFilterProxyModel
51
+
52
+ =begin rdoc
53
+ @param [Qt::Object] parent the parent object
54
+ =end
55
+ def initialize parent = nil
56
+ super
57
+ @project = Ruber[:projects].current
58
+ @do_filtering = true
59
+ self.dynamic_sort_filter = true
60
+ end
61
+
62
+ =begin rdoc
63
+ Override of @KDE::DirSortFilterProxyModel#filterAcceptsRow@ which rejects all files
64
+ not belonging to the project, unless filtering has been turned off.
65
+
66
+ @param [Integer] row the number of the row to be filtered (in the source model)
67
+ @param [Qt::ModelIndex] parent the parent of the row to be filtered (in the source model)
68
+ @return [Boolean] always *true* if filtering has been turned off or no project has
69
+ been set, otherwise *true*
70
+ if row corresponds to a directory or to a file belonging to the project and *false*
71
+ otherwise
72
+ =end
73
+ def filterAcceptsRow row, parent
74
+ return true if @project.nil? or !@do_filtering
75
+ it = source_model.item_for_index source_model.index(row,0,parent)
76
+ return true if it.dir?
77
+ @project.project_files.file_in_project? it.local_path
78
+ end
79
+
80
+ =begin rdoc
81
+ Changes the project to use for filtering and invalidates the filter
82
+ @param [Ruber::Project,nil] prj the project to use for filtering (if *nil*, all
83
+ items will be accepted)
84
+ @return [nil]
85
+ =end
86
+ def project= prj
87
+ @project = prj
88
+ invalidate_filter
89
+ end
90
+
91
+ =begin rdoc
92
+ Tells whether to exclude files not belonging to the project or to accept all items
93
+ @param [Boolean] val whether or not to accept all files
94
+ @return [Boolean] _val_
95
+ =end
96
+ def do_filtering= val
97
+ @do_filtering = val
98
+ invalidate_filter
99
+ end
100
+
101
+ =begin rdoc
102
+ Override of @KDE::DirSortFilterProxyModel#filterAcceptsRow@ which works as the
103
+ parent method but is public
104
+ @return [nil]
105
+ =end
106
+ def invalidate_filter
107
+ super
108
+ end
109
+
110
+ end
111
+
112
+ =begin rdoc
113
+ The view used by the plugin
114
+
115
+ The only scope of this class is to provide the context menu
116
+ =end
117
+ class View < Qt::TreeView
118
+
119
+ =begin rdoc
120
+ Signal emitted whenever the user toggles the "Show only project files" action
121
+ @param [Boolean] *true* if the user checked the action and *false* if he unchecked
122
+ it
123
+ =end
124
+ signals 'only_project_files_triggered(bool)'
125
+
126
+ =begin rdoc
127
+ @param [Qt::Widget,nil] parent the parent widget
128
+ =end
129
+ def initialize parent = nil
130
+ super
131
+ @menu = Qt::Menu.new self
132
+ @toggle_filter_action = KDE::ToggleAction.new 'Show only project files', @menu
133
+ @toggle_filter_action.checked = true
134
+ @menu.add_action @toggle_filter_action
135
+ connect @toggle_filter_action, SIGNAL('toggled(bool)'), self, SIGNAL('only_project_files_triggered(bool)')
136
+ end
137
+
138
+ =begin rdoc
139
+ Override of @Qt::AbstractScrollArea#contextMenuEvent@ which displays a menu containing
140
+ the action
141
+ @param [Qt::ContextMenuEvent] e the event object
142
+ =end
143
+ def contextMenuEvent e
144
+ @menu.popup e.global_pos
145
+ end
146
+
147
+ end
148
+
149
+ =begin rdoc
150
+ Creates a new instance
151
+
152
+ The view of the new instance displays the contents of the directory of the current
153
+ project, if any
154
+ @param [Qt::Widget,nil] parent the parent widget
155
+ =end
156
+ def initialize parent = nil
157
+ super
158
+ connect Ruber[:projects], SIGNAL('current_project_changed(QObject*)'), self, SLOT('current_project_changed(QObject*)')
159
+ self.layout = Qt::VBoxLayout.new self
160
+ @view = View.new self
161
+ @model = KDE::DirModel.new @view
162
+ @model.dir_lister.open_url KDE::Url.new('/')
163
+ @filter = FilterModel.new @view
164
+ @filter.source_model = @model
165
+ @view.model = @filter
166
+ @view.edit_triggers = Qt::AbstractItemView::NoEditTriggers
167
+ 1.upto(@model.column_count-1){|i| @view.hide_column i}
168
+ @view.header_hidden = true
169
+ layout.add_widget @view
170
+ @project = nil
171
+ current_project_changed Ruber[:projects].current
172
+ @view.connect(SIGNAL('only_project_files_triggered(bool)')){|val| @filter.do_filtering = val}
173
+ connect @view, SIGNAL('activated(QModelIndex)'), self, SLOT('open_file_in_editor(QModelIndex)')
174
+ end
175
+
176
+ private
177
+
178
+ =begin rdoc
179
+ Slot called whenever the current project changes
180
+
181
+ This method updates the view so that it displays the contents of the project directory
182
+ (or disables the view if there's no open project) and sets up the needed connections
183
+ with the project
184
+
185
+ @param [Ruber::Project,nil] prj the current project
186
+ @return [nil]
187
+ =end
188
+ def current_project_changed prj
189
+ @project.disconnect SIGNAL('option_changed(QString,QString)'), self if @project
190
+ if prj
191
+ @project = prj
192
+ connect @project, SIGNAL('option_changed(QString, QString)'), self, SLOT('project_option_changed(QString, QString)')
193
+ @model.dir_lister.open_url KDE::Url.new(prj.project_directory)
194
+ @view.enabled = true
195
+ else @view.enabled = false
196
+ end
197
+ @filter.project = prj
198
+ nil
199
+ end
200
+ slots 'current_project_changed(QObject*)'
201
+
202
+ =begin rdoc
203
+ Slot called whenever a setting of the current project changes
204
+
205
+ It is needed to re-applicate the filter after the @general/project_files@ project
206
+ setting has changed
207
+ @param [String] group the group the changed setting belongs to
208
+ @param [String] the name of the changed setting
209
+ @return [nil]
210
+ =end
211
+ def project_option_changed group, name
212
+ @filter.invalidate_filter if group == 'general' and name == 'project_files'
213
+ nil
214
+ end
215
+ slots 'project_option_changed(QString, QString)'
216
+
217
+ =begin rdoc
218
+ Slot called whenever the user activates an item in the view
219
+
220
+ If the item corresponds to a file, it will be opened in an editor, otherwise nothing
221
+ will be done. The tool widget will be closed unless the Meta key is pressed
222
+ @param [Qt::ModelIndex] idx the activated index (referred to the filter model)
223
+ @return [nil]
224
+ =end
225
+ def open_file_in_editor idx
226
+ #Currently, only the name column is supported. However, in the future,
227
+ #other columns can be supported
228
+ unless idx.column == KDE::DirModel::Name
229
+ idx = @filter.index(KDE::DirModel::Name, idx.row, idx.parent)
230
+ end
231
+ item = @model.item_for_index @filter.map_to_source(idx)
232
+ return if item.dir?
233
+ file = item.local_path
234
+ modifiers = Ruber[:app].keyboard_modifiers
235
+ Ruber[:main_window].display_document file
236
+ Ruber[:main_window].hide_tool self if (Qt::MetaModifier & modifiers) == 0
237
+ nil
238
+ end
239
+ slots 'open_file_in_editor(QModelIndex)'
240
+
241
+ end
242
+
243
+ end
244
+
245
+ end
@@ -0,0 +1,39 @@
1
+ name: rake
2
+ version: 0.0.1
3
+ about:
4
+ authors: [Stefano Crocco, stefano.crocco@alice.it]
5
+ license: :gpl
6
+ description: A Ruber interface to rake
7
+ bug_address: http://github.com/stcrocco/ruber/issues
8
+ icon: rake.png
9
+ deps: ruby_runner
10
+ require: [rake, rake_extension]
11
+ class: Ruber::Rake::Plugin
12
+ ui_file: rakeui.rc
13
+ actions:
14
+ rake-run: {text: Run Rake &Task..., shortcut: 'Alt+Shift+K, T', slot: choose_and_run_task(), states: [rake_running, rake_has_target]}
15
+ rake-run_default: {text: Run Default Rake Task, shortcut: 'Alt+Shift+K, D', slot: run_default_task(), states: [rake_running, rake_has_target]}
16
+ rake-refresh: {text: Refresh Tasks, slot: refresh_tasks(), state: rake_has_target}
17
+ rake-stop: {text: Stop, shortcut: Esc, icon: process-stop, slot: stop_process(), state: rake_running}
18
+ config_options:
19
+ rake:
20
+ quick_tasks: {default: {}}
21
+ rake: {default: `which rake`.strip, relative_path: false}
22
+ sync_stdout: {default: true}
23
+ config_widgets:
24
+ - {class: Ruber::Rake::ConfigWidget, pixmap: rake.png, caption: Rake}
25
+ project_options:
26
+ rake:
27
+ rake: {default: `which rake`.strip, scope: all, type: user}
28
+ rakefile: {default: ~, relative_path: true}
29
+ options: {default: [], scope: all}
30
+ environment: {default: [], scope: all}
31
+ tasks: {type: user, default: {}, scope: all, file_extension: [Rakefile, rakefile, Rakefile.rb, rakefile.rb]}
32
+ sync_stdout: {default: 'Ruber[:config][:rake, :sync_stdout]', scope: all}
33
+ timeout: {default: 30, scope: all}
34
+ tool_widgets:
35
+ - {class: Ruber::FilteredOutputWidget, caption: Rake, pixmap: rake.png}
36
+ extensions:
37
+ rake: {class: Ruber::Rake::ProjectExtension, scope: all, file_extension: [Rakefile, rakefile, Rakefile.rb, rakefile.rb]}
38
+ project_widgets:
39
+ - {class: Ruber::Rake::ProjectWidget, pixmap: rake.png, caption: Rake, scope: all, file_extension: [Rakefile, rakefile, Rakefile.rb, rakefile.rb]}
Binary file
@@ -0,0 +1,567 @@
1
+ =begin
2
+ Copyright (C) 2010 by Stefano Crocco
3
+ stefano.crocco@alice.it
4
+
5
+ This program is free software; you can redistribute it andor modify
6
+ it under the terms of the GNU General Public License as published by
7
+ the Free Software Foundation; either version 2 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU General Public License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License
16
+ along with this program; if not, write to the
17
+ Free Software Foundation, Inc.,
18
+ 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
+ =end
20
+
21
+ require 'timeout'
22
+
23
+ require 'ruby_runner/ruby_runner'
24
+
25
+ require 'rake/rake_widgets'
26
+
27
+ module Ruber
28
+
29
+ =begin rdoc
30
+ Plugin which allows to run rake tasks from within Ruber, displaying the output
31
+ in a tool widget.
32
+
33
+ It provides a dialog which lists all the tasks defined in the rakefile (which can
34
+ be either chosen by the user or left to rake to find), a menu containing the tasks
35
+ defined in the current rakefile and a menu containing globally defined tasks. To
36
+ both project tasks and globally defined tasks can be assigned a shortcut.
37
+
38
+ *Note:* the list of project tasks, both in the menu and in the dialog, is computed
39
+ automatically only once, the first time it's needed. If the rakefile is later changed,
40
+ the user must manually ask to refresh the list using either the appropriate entry
41
+ in the menu, the button in the dialog or in the project configuration widget.
42
+
43
+ *Note:* this plugin only works for projects, not for single documents (the reason
44
+ being that if you have more than one file working together, you should create a
45
+ project).
46
+
47
+ @api feature rake
48
+ @plugin
49
+ =end
50
+ module Rake
51
+
52
+ =begin rdoc
53
+ Plugin object for the Rake plugin.
54
+
55
+ This plugin relies in a project extension to keep a list of project tasks.
56
+
57
+ @api_method #tasks
58
+ @api_method #run_rake
59
+ =end
60
+ class Plugin < RubyRunner::RubyRunnerPlugin
61
+
62
+ =begin rdoc
63
+ Base class for all the exception used by this plugin
64
+ =end
65
+ class Error < StandardError
66
+ end
67
+
68
+ =begin rdoc
69
+ Exception raised when the rake program exits with an error
70
+ =end
71
+ class RakeError < Error
72
+
73
+ =begin rdoc
74
+ @return [String] the error message produced by rake
75
+ =end
76
+ attr_reader :rake_error
77
+
78
+ =begin rdoc
79
+ @return [<String>] the backtrace produced by rake
80
+ =end
81
+ attr_reader :rake_backtrace
82
+
83
+ =begin rdoc
84
+ @param [String] error the error message produced by rake
85
+ @param <String> backtrace an array containing the rake backtrace
86
+ =end
87
+ def initialize error, backtrace
88
+ super "Rake aborted saying: #{error}"
89
+ @rake_error = error
90
+ @rake_backtrace = backtrace
91
+ end
92
+ end
93
+
94
+ =begin rdoc
95
+ Exception raised if rake exits because it can't find a rakefile
96
+ =end
97
+ class RakefileNotFound < Error
98
+ end
99
+
100
+ =begin rdoc
101
+ Exception raised if rake doesn't exit after a given amount of time
102
+ =end
103
+ class Timeout < Error
104
+
105
+ =begin rdoc
106
+ @return [Integer] the number of seconds waited before raising the exception
107
+ =end
108
+ attr_reader :time
109
+
110
+ =begin rdoc
111
+ @param [Integer] seconds the the number of seconds waited before raising the exception
112
+ =end
113
+ def initialize seconds
114
+ super "Rake failed to finish within the allowed time (#{seconds} seconds)"
115
+ @time = seconds
116
+ end
117
+
118
+ end
119
+
120
+
121
+ slots :choose_and_run_task, :run_default_task, :refresh_tasks, :run_quick_task,
122
+ :set_current_target, :fill_project_menu, :run_project_task
123
+
124
+ =begin rdoc
125
+ Signal emitted when the rake program started with {#run_rake} has exited
126
+ =end
127
+ signals :rake_finished
128
+
129
+ =begin rdoc
130
+ @param [Ruber::PluginSpecification] psf
131
+ =end
132
+ def initialize psf
133
+ @quick_tasks_actions = []
134
+ @project_tasks_actions = []
135
+ @current_target = nil
136
+ super psf, :rake, :scope => [:global, :document]
137
+ setup_handlers
138
+ Ruber[:main_window].change_state 'rake_running', false
139
+ Ruber[:autosave].register_plugin self, true
140
+ @output_widget = @widget
141
+ self.connect(SIGNAL(:rake_finished)) do
142
+ Ruber[:main_window].change_state 'rake_running', false
143
+ end
144
+ self.connect(SIGNAL(:process_failed_to_start)){Ruber[:main_window].change_state 'rake_running', false}
145
+ connect Ruber[:projects], SIGNAL('current_project_changed(QObject*)'), self, SLOT(:set_current_target)
146
+ connect Ruber[:main_window], SIGNAL('current_document_changed(QObject*)'), self, SLOT(:set_current_target)
147
+ connect self, SIGNAL('process_finished(int, QString)'), self, SIGNAL(:rake_finished)
148
+ connect self, SIGNAL('extension_added(QString, QObject*)'), self, SLOT(:set_current_target)
149
+ connect self, SIGNAL('extension_removed(QString, QObject*)'), self, SLOT(:set_current_target)
150
+ Ruber[:components].connect(SIGNAL('feature_loaded(QString, QObject*)')) do |f, o|
151
+ o.register_plugin self, true if f == 'autosave'
152
+ end
153
+ fill_quick_tasks_menu
154
+ set_current_target
155
+ end
156
+
157
+ =begin rdoc
158
+ Loads the settings
159
+
160
+ While loading the settings, it also builds the Quick Tasks menu
161
+
162
+ @return [nil]
163
+ =end
164
+ def load_settings
165
+ cfg = Ruber[:config][:rake]
166
+ #This method is called from Plugin#initialize before the gui is created
167
+ fill_quick_tasks_menu if @gui
168
+ nil
169
+ end
170
+ slots :load_settings
171
+
172
+ =begin rdoc
173
+ The rake tasks availlable, according to the given options.
174
+
175
+ To obtain the task list, this method calls <tt>rake -T</tt> synchronously (this
176
+ means that the method won't return until rake has finished). To avoid freezing
177
+ Ruber should rake go into an endless loop, this method will give up after a given
178
+ time (default: 30 seconds).
179
+
180
+ For this method to work, the output from <tt>rake -T</tt> should be a series of lines
181
+ of the form
182
+
183
+ <tt>rake task_name # Description</tt>
184
+
185
+ However, rake accepts a number of options which may (at least potentially) change
186
+ this output. If any of these is included in the @:options@ entry of _opts_, it'll
187
+ be removed (see {#filter_options_to_find_tasks} for a list of the problematic options)
188
+
189
+ @param [String] ruby the path of the ruby interpreter to use
190
+ @param [String] dir the directory rake should be run from
191
+ @param [Hash] opts a hash containing options to pass to rake and ruby
192
+
193
+ @option opts [String] :rake (Ruber[:config][:rake, :rake]) the path to
194
+ rake.
195
+ @option opts [<String>] :options ([]) the options to pass to rake
196
+ @option opts [<String>] :env ([]) the environment to pass to rake. Each entry
197
+ must be of the form <tt>VARIABLE=VALUE</tt>
198
+ @option opts [<String>] :ruby_options ([]) the arguments to pass to ruby
199
+ @option opts [String] :rakefile (nil) the rakefile to use. If *nil*, the -f option
200
+ won't be passed to rake, which will choose the rakefile by itself
201
+ @option opts [Integer] :timeout (30) the number of seconds before giving up if
202
+ rake hasn't finished
203
+ @raise {RakeError} if rake reports an error while executing the rakefile
204
+ @raise {RakefileNotFound} if rake can't find the rakefile
205
+ @raise {Timeout} if <tt>rake -T</tt> doesn't exit after a suitable time
206
+
207
+ @return [Hash] a hash with task names as keys and task descriptions as values
208
+ =end
209
+ def tasks ruby, dir, opts
210
+ rake = opts[:rake]
211
+ default_opts = {:options => [], :env => [], :ruby_options => []}
212
+ options = default_opts.merge opts
213
+ options = filter_options_to_find_tasks options[:options]
214
+ args = [rake, '-T'] + options
215
+ args << '-f' << opts[:rakefile] if opts[:rakefile]
216
+ env = opts[:env].join ' '
217
+ cmd = [ruby] + opts[:ruby_options] + args
218
+ timeout = opts[:timeout] || 30
219
+ begin
220
+ out, err = ::Timeout.timeout(timeout) do
221
+ _in, out, err = Open3.popen3 env + cmd.join(' ')
222
+ [out.read, err.read]
223
+ end
224
+ rescue ::Timeout::Error
225
+ ['', 'timeout']
226
+ end
227
+ if err.sub!(/^\s*rake aborted!\s*\n/i, '')
228
+ if err=~ /no rakefile found/i then raise RakefileNotFound, err
229
+ elsif err == 'timeout'
230
+ raise Timeout.new(timeout)
231
+ else
232
+ lines = err.split_lines
233
+ err = lines.shift
234
+ lines.delete_at(-1) if lines[-1] =~ /^\s*\(See full trace/
235
+ raise RakeError.new err, lines
236
+ end
237
+ end
238
+ out = out.split_lines.map do |l|
239
+ if l.match(/^\s*rake\s*(.*)\s#\s+(.*)/) then [$1.strip, $2]
240
+ else nil
241
+ end
242
+ end
243
+ out.compact!
244
+ out.to_h
245
+ end
246
+
247
+ =begin rdoc
248
+ Runs rake, displaying the output in the associated output widget, according to
249
+ the given options.
250
+
251
+ @param [String] ruby the path of the ruby interpreter to use to run rake
252
+ @param [String] dir the directory rake should be run from
253
+ @param [Hash] data a hash containing the options to pass to rake or ruby
254
+ @option data [Array<String>] :ruby_options ([]) The arguments to pass to ruby itself
255
+ @option data [String] :rake (Ruber[:config][:rake, :rake]) The path to the rake program to use
256
+ @option data [Array<String>] :options ([]) The options to pass to rake
257
+ @option data [String] :rakefile (nil) The rakefile to use (use the default rakefile
258
+ if *nil*)
259
+ @option data [Array<String>] :env ([]) The environment variables to set before calling
260
+ rake. The system environment will be used if this is empty
261
+ @option data [String] :task (nil) The task to execute. The default task will be
262
+ executed if this is missing
263
+
264
+ @return [nil]
265
+ =end
266
+ def run_rake ruby, dir, data
267
+ rake = data[:rake]
268
+ args = Array(data[:ruby_options]) + [rake] + Array(data[:options])
269
+ args += ['-f', data[:rakefile]] if data[:rakefile]
270
+ args << data[:task] if data[:task]
271
+ env = Array(data[:env])
272
+ process.environment = process.system_environment + env
273
+ cmd = env + [ruby] + args
274
+ @widget.clear_output
275
+ @widget.working_directory = dir
276
+ Ruber[:main_window].activate_tool @widget
277
+ Ruber[:main_window].change_state 'rake_running', true
278
+ run_process ruby, dir, args
279
+ nil
280
+ end
281
+
282
+ =begin rdoc
283
+ Displays a message box telling why rake failed to retrieve tasks
284
+
285
+ This method is meant to be called in a @rescue@ clause for {RakeError}
286
+ exceptions from methods which call {#tasks}. According to the type of exception
287
+ raised, the appropriate text will be displayed in the message box.
288
+
289
+ @param [RakeError] ex the exception describing the error
290
+
291
+ @return [self]
292
+ =end
293
+ def display_task_retrival_error_dialog ex
294
+ msg = case ex
295
+ when RakeError
296
+ # The <i></i> tag is needed because (according to the QMessageBox documentation),
297
+ # for the text to be interpreted as rich text, an html tag must be present
298
+ # before the first newline.
299
+ "Rake aborted with the following error message:<i></i>\n<pre>#{e.rake_error}\n#{e.rake_backtrace.join "\n"}</pre>"
300
+ when RakefileNotFound then "No rakefile was found"
301
+ when Timeout then e.message
302
+ end
303
+ KDE::MessageBox.sorry Ruber[:main_window], msg
304
+ self
305
+ end
306
+
307
+ private
308
+
309
+ =begin rdoc
310
+ Creates and registers the gui state handlers for the actions provided by the plugin.
311
+
312
+ @return [nil]
313
+ =end
314
+ def setup_handlers
315
+ @quick_tasks_handler_prc = Proc.new do |sts|
316
+ !sts['rake_running'] and sts['rake_has_target']
317
+ end
318
+ register_action_handler 'rake-run', &@quick_tasks_handler_prc
319
+ register_action_handler 'rake-run_default', &@quick_tasks_handler_prc
320
+ nil
321
+ end
322
+
323
+ =begin rdoc
324
+ Fills the Quick Tasks menu
325
+
326
+ It removes the @rake-quick_tasks_list@ action list from the menu, then
327
+ creates the actions according to the current content of the rake/quick_tasks
328
+ option and fills the menu again.
329
+
330
+ If the rake/quick_tasks option is empty, a single, disabled action with text
331
+ @(Empty)@ is inserted.
332
+
333
+ @return nil
334
+ =end
335
+ def fill_quick_tasks_menu
336
+ mw = Ruber[:main_window]
337
+ @quick_tasks_actions.each do |a|
338
+ mw.remove_action_handler_for a if a.object_name != 'rake-quick_task_empty_action'
339
+ a.dispose
340
+ end
341
+ @gui.unplug_action_list "rake-quick_tasks_list"
342
+ coll = @gui.action_collection
343
+ @quick_tasks_actions = Ruber[:config][:rake, :quick_tasks].sort.map do |k, v|
344
+ a = coll.add_action "rake-quick_task-#{k}", self, SLOT(:run_quick_task)
345
+ a.text = k
346
+ a.shortcut = KDE::Shortcut.new(v)
347
+ mw.register_action_handler a, %w[rake_running active_project_exists current_document], :extra_id => self, &@quick_tasks_handler_prc
348
+ a
349
+ end
350
+ if @quick_tasks_actions.empty?
351
+ a = coll.add_action 'rake-quick_task_empty_action'
352
+ a.text = '(Empty)'
353
+ a.enabled = false
354
+ a.object_name = 'rake-quick_task_empty_action'
355
+ @quick_tasks_actions << a
356
+ end
357
+ @gui.plug_action_list "rake-quick_tasks_list", @quick_tasks_actions
358
+ nil
359
+ end
360
+
361
+ =begin rdoc
362
+ Slot associated with the @rake-run@ action
363
+
364
+ It displays a dialog where the user can choose a taks for the current target,
365
+ then executes it. If the user cancels the dialog, nothing else happens
366
+
367
+ @return [nil]
368
+ =end
369
+ def choose_and_run_task
370
+ task = choose_task_for @current_target
371
+ return unless task
372
+ @current_target.extension(:rake).run_rake task
373
+ end
374
+
375
+ =begin rdoc
376
+ Displays a dialog where the user can choose the task to run according to the settings
377
+ of the given project.
378
+
379
+ @param [AbstractProject] prj the project to read the settings from
380
+
381
+ @return [String, nil] the name of the chosen task or *nil* if the user closed the
382
+ dialog with the Cancel button
383
+ =end
384
+ def choose_task_for prj
385
+ dlg = ChooseTaskDlg.new prj
386
+ return if dlg.exec == Qt::Dialog::Rejected
387
+ dlg.task
388
+ end
389
+
390
+ =begin rdoc
391
+ Slot associated with the @rake-run_default@ action.
392
+
393
+ Runs the default task for the current target
394
+ @return [nil]
395
+ =end
396
+ def run_default_task
397
+ @current_target.extension(:rake).run_rake nil
398
+ nil
399
+ end
400
+
401
+ =begin rdoc
402
+ Slot associated with the various quick tasks actions defined by the user
403
+
404
+ Runs the rake task whose name is equal to the name of the action which called
405
+ this slot
406
+
407
+ @return [nil]
408
+ =end
409
+ def run_quick_task
410
+ task = sender.text.gsub('&', '')
411
+ @current_target.extension(:rake).run_rake task
412
+ end
413
+
414
+ =begin rdoc
415
+ Slot associated with the various project tasks actions defined by the user for the
416
+ current target
417
+
418
+ Runs the rake task whose name is equal to the name of the action which called
419
+ this slot
420
+
421
+ @return [nil]
422
+ =end
423
+ def run_project_task
424
+ task = sender.text.gsub('&', '')
425
+ @current_target.extension(:rake).run_rake task
426
+ end
427
+
428
+
429
+ =begin rdoc
430
+ The project to run rake for when one of the rake menu entries is chosen
431
+
432
+ @return [DocumentProject] the {DocumentProject} associated with the current
433
+ document if the latter is a rakefile and doesn't belong to the current project
434
+ @return [Project] the current project, if the current file belongs to it or if
435
+ it isn't a rakefile
436
+ @return [nil] if there's no open document or if the current document isn't a rakefile
437
+ and there's no open project
438
+ =end
439
+ def find_current_target
440
+ target = Ruber[:main_window].current_document.project rescue nil
441
+ if target.nil? or !target.has_extension? :rake
442
+ prj = Ruber[:projects].current
443
+ target = if prj and prj.has_extension? :rake then prj
444
+ else nil
445
+ end
446
+ end
447
+ target
448
+ end
449
+
450
+
451
+ =begin rdoc
452
+ Given a list of rake options, creates a list containing only those which are safe
453
+ to use from {tasks}.
454
+
455
+ The options which will be removed are: -D, -n, -P, -q, --rules, -s,
456
+ -t, v, -V, -h, -e, -p and -E, because in a way or another have the capability
457
+ to change the rake output from what <tt>tasks_for</tt> expects.
458
+
459
+ <b>Note:</b> this method creates a new array; it doesn't modify _opts_.
460
+
461
+ @param [Array <String>] opts a list of options to be passed to rake
462
+
463
+ @return [Array<String>] an array containing only the safe options from _opts_
464
+ =end
465
+ def filter_options_to_find_tasks opts
466
+ flags_to_delete = %w[-D --describe -n --dry-run -P --prereqs -q --quiet --rules -s --silent -t --trace -v --verbose -V --version -h -H --help]
467
+ opts = opts.dup
468
+ opts.delete '-D'
469
+ opts.delete '--describe'
470
+ opts.delete '-n'
471
+ %w[-e --execute -p --execute-print -E --execute-continue].each do |o|
472
+ idxs = opts.each_index.find_all{|i| opts[i] == o}
473
+ idxs.reverse_each do |i|
474
+ opts.delete_at i + 1
475
+ opts.delete_at i
476
+ end
477
+ end
478
+ opts
479
+ end
480
+
481
+ =begin rdoc
482
+ Changes the current target.
483
+
484
+ It uses {#find_current_target} to find out the new current target, then, if it
485
+ is different from the old one, makes the necessary connections and disconnections
486
+ and refills the project menu
487
+
488
+ @return [nil]
489
+ =end
490
+ def set_current_target
491
+ target = find_current_target
492
+ return if target == @current_target
493
+ # If the project is being closed, the extension may have been removed
494
+ if @current_target and @current_target.extension(:rake)
495
+ @current_target.extension(:rake).disconnect SIGNAL(:tasks_updated)
496
+ end
497
+ @current_target = target
498
+ fill_project_menu
499
+ if @current_target
500
+ connect @current_target.extension(:rake), SIGNAL(:tasks_updated), self, SLOT(:fill_project_menu)
501
+ end
502
+ Ruber[:main_window].set_state 'rake_has_target', !@current_target.nil?
503
+ nil
504
+ end
505
+
506
+ =begin rdoc
507
+ Fills the Project Tasks menu, according to the current target
508
+
509
+ It clears the menu, then inserts in the menu one action for each entry in the
510
+ current target's rake/tasks option. If that option is empty, or if there's no
511
+ current target, then a single, disabled entry with text '(Empty)' is inserted in
512
+ the menu
513
+
514
+ @return [nil]
515
+ =end
516
+ def fill_project_menu
517
+ @gui.unplug_action_list 'rake-project_tasks_list'
518
+ mw = Ruber[:main_window]
519
+ @project_tasks_actions.each do |a|
520
+ mw.remove_action_handler_for a
521
+ a.dispose
522
+ end
523
+ @project_tasks_actions.clear
524
+ coll = @gui.action_collection
525
+ if @current_target
526
+ tasks = @current_target[:rake, :tasks]
527
+ tasks.each_pair do |t, x|
528
+ desc, short = *x
529
+ a = coll.add_action "rake-project_task-#{t}", self, SLOT(:run_project_task)
530
+ a.text = t
531
+ a.shortcut = KDE::Shortcut.new short if short
532
+ a.help_text = desc
533
+ Ruber[:main_window].register_action_handler a,
534
+ %w[rake_running active_project_exists], :extra_id => self,
535
+ &@quick_tasks_handler_prc
536
+ @project_tasks_actions << a
537
+ end
538
+ end
539
+ if @project_tasks_actions.empty?
540
+ a = coll.add_action 'rake-project_tasks_empty'
541
+ a.text = "(Empty)"
542
+ a.enabled = false
543
+ @project_tasks_actions << a
544
+ end
545
+ @gui.plug_action_list 'rake-project_tasks_list', @project_tasks_actions
546
+ nil
547
+ end
548
+
549
+ =begin rdoc
550
+ Updates the tasks
551
+
552
+ @return [nil]
553
+ =end
554
+ def refresh_tasks
555
+ Ruber[:app].with_override_cursor do
556
+ begin @current_target.extension(:rake).update_tasks
557
+ rescue Error => ex
558
+ display_task_retrival_error_dialog ex
559
+ end
560
+ end
561
+ end
562
+
563
+ end
564
+
565
+ end
566
+
567
+ end