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,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