ruber 0.0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +339 -0
- data/INSTALL +137 -0
- data/LICENSE +8 -0
- data/bin/ruber +65 -0
- data/data/share/apps/ruber/core_components.yaml +31 -0
- data/data/share/apps/ruber/ruberui.rc +109 -0
- data/data/share/icons/ruber.png +0 -0
- data/data/share/pixmaps/ruby.png +0 -0
- data/icons/ruber-16.png +0 -0
- data/icons/ruber-32.png +0 -0
- data/icons/ruber-48.png +0 -0
- data/icons/ruber-8.png +0 -0
- data/lib/ruber/application/application.rb +288 -0
- data/lib/ruber/application/plugin.yaml +11 -0
- data/lib/ruber/component_manager.rb +899 -0
- data/lib/ruber/config/config.rb +82 -0
- data/lib/ruber/config/plugin.yaml +3 -0
- data/lib/ruber/document_project.rb +209 -0
- data/lib/ruber/documents/document_list.rb +416 -0
- data/lib/ruber/documents/plugin.yaml +4 -0
- data/lib/ruber/editor/document.rb +506 -0
- data/lib/ruber/editor/editor_view.rb +167 -0
- data/lib/ruber/editor/ktexteditor_wrapper.rb +202 -0
- data/lib/ruber/exception_widgets.rb +245 -0
- data/lib/ruber/external_program_plugin.rb +397 -0
- data/lib/ruber/filtered_output_widget.rb +342 -0
- data/lib/ruber/gui_states_handler.rb +231 -0
- data/lib/ruber/kde_config_option_backend.rb +167 -0
- data/lib/ruber/kde_sugar.rb +249 -0
- data/lib/ruber/main_window/choose_plugins_dlg.rb +353 -0
- data/lib/ruber/main_window/main_window.rb +524 -0
- data/lib/ruber/main_window/main_window_actions.rb +537 -0
- data/lib/ruber/main_window/main_window_internal.rb +239 -0
- data/lib/ruber/main_window/open_file_in_project_dlg.rb +212 -0
- data/lib/ruber/main_window/output_color_widget.rb +35 -0
- data/lib/ruber/main_window/plugin.yaml +58 -0
- data/lib/ruber/main_window/save_modified_files_dlg.rb +89 -0
- data/lib/ruber/main_window/status_bar.rb +156 -0
- data/lib/ruber/main_window/ui/choose_plugins_widget.rb +90 -0
- data/lib/ruber/main_window/ui/choose_plugins_widget.ui +77 -0
- data/lib/ruber/main_window/ui/main_window_settings_widget.rb +108 -0
- data/lib/ruber/main_window/ui/main_window_settings_widget.ui +89 -0
- data/lib/ruber/main_window/ui/new_project_widget.rb +119 -0
- data/lib/ruber/main_window/ui/new_project_widget.ui +178 -0
- data/lib/ruber/main_window/ui/open_file_in_project_dlg.rb +109 -0
- data/lib/ruber/main_window/ui/open_file_in_project_dlg.ui +168 -0
- data/lib/ruber/main_window/ui/output_color_widget.rb +241 -0
- data/lib/ruber/main_window/ui/output_color_widget.ui +204 -0
- data/lib/ruber/main_window/workspace.rb +442 -0
- data/lib/ruber/output_widget.rb +1093 -0
- data/lib/ruber/plugin.rb +264 -0
- data/lib/ruber/plugin_like.rb +589 -0
- data/lib/ruber/plugin_specification.rb +106 -0
- data/lib/ruber/plugin_specification_reader.rb +451 -0
- data/lib/ruber/project.rb +493 -0
- data/lib/ruber/project_backend.rb +105 -0
- data/lib/ruber/projects/plugin.yaml +11 -0
- data/lib/ruber/projects/project_files_list.rb +314 -0
- data/lib/ruber/projects/project_files_widget.rb +301 -0
- data/lib/ruber/projects/project_list.rb +314 -0
- data/lib/ruber/projects/ui/project_files_rule_chooser_widget.rb +74 -0
- data/lib/ruber/projects/ui/project_files_rule_chooser_widget.ui +61 -0
- data/lib/ruber/projects/ui/project_files_widget.rb +117 -0
- data/lib/ruber/projects/ui/project_files_widget.ui +123 -0
- data/lib/ruber/qt_sugar.rb +673 -0
- data/lib/ruber/settings_container.rb +515 -0
- data/lib/ruber/settings_dialog.rb +244 -0
- data/lib/ruber/settings_dialog_manager.rb +503 -0
- data/lib/ruber/utils.rb +414 -0
- data/lib/ruber/yaml_option_backend.rb +159 -0
- data/outsider_files +15 -0
- data/plugins/autosave/autosave.rb +404 -0
- data/plugins/autosave/plugin.yaml +16 -0
- data/plugins/autosave/ui/autosave_config_widget.rb +83 -0
- data/plugins/autosave/ui/autosave_config_widget.ui +68 -0
- data/plugins/command/command.png +0 -0
- data/plugins/command/command.rb +74 -0
- data/plugins/command/plugin.yaml +11 -0
- data/plugins/find_in_files/find_in_files.rb +337 -0
- data/plugins/find_in_files/find_in_files_dlg.rb +411 -0
- data/plugins/find_in_files/find_in_files_ui.rc +11 -0
- data/plugins/find_in_files/find_in_files_widgets.rb +485 -0
- data/plugins/find_in_files/plugin.yaml +23 -0
- data/plugins/find_in_files/ui/config_widget.rb +58 -0
- data/plugins/find_in_files/ui/config_widget.ui +41 -0
- data/plugins/find_in_files/ui/find_in_files_widget.rb +260 -0
- data/plugins/find_in_files/ui/find_in_files_widget.ui +324 -0
- data/plugins/project_browser/plugin.yaml +10 -0
- data/plugins/project_browser/project_browser.rb +245 -0
- data/plugins/rake/plugin.yaml +39 -0
- data/plugins/rake/rake.png +0 -0
- data/plugins/rake/rake.rb +567 -0
- data/plugins/rake/rake_extension.rb +153 -0
- data/plugins/rake/rake_widgets.rb +615 -0
- data/plugins/rake/rakeui.rc +27 -0
- data/plugins/rake/ui/add_quick_task_widget.rb +71 -0
- data/plugins/rake/ui/add_quick_task_widget.ui +59 -0
- data/plugins/rake/ui/choose_task_widget.rb +77 -0
- data/plugins/rake/ui/choose_task_widget.ui +72 -0
- data/plugins/rake/ui/config_widget.rb +127 -0
- data/plugins/rake/ui/config_widget.ui +123 -0
- data/plugins/rake/ui/project_widget.rb +217 -0
- data/plugins/rake/ui/project_widget.ui +246 -0
- data/plugins/rspec/plugin.yaml +30 -0
- data/plugins/rspec/rspec.png +0 -0
- data/plugins/rspec/rspec.rb +945 -0
- data/plugins/rspec/rspec.svg +90 -0
- data/plugins/rspec/rspecui.rc +20 -0
- data/plugins/rspec/ruber_rspec_formatter.rb +312 -0
- data/plugins/rspec/ui/rspec_project_widget.rb +170 -0
- data/plugins/rspec/ui/rspec_project_widget.ui +193 -0
- data/plugins/ruby_development/plugin.yaml +27 -0
- data/plugins/ruby_development/ruby_development.png +0 -0
- data/plugins/ruby_development/ruby_development.rb +453 -0
- data/plugins/ruby_development/ruby_developmentui.rc +19 -0
- data/plugins/ruby_development/ui/project_widget.rb +112 -0
- data/plugins/ruby_development/ui/project_widget.ui +108 -0
- data/plugins/ruby_runner/config_widget.rb +116 -0
- data/plugins/ruby_runner/plugin.yaml +26 -0
- data/plugins/ruby_runner/project_widget.rb +62 -0
- data/plugins/ruby_runner/ruby.png +0 -0
- data/plugins/ruby_runner/ruby_interpretersui.rc +26 -0
- data/plugins/ruby_runner/ruby_runner.rb +411 -0
- data/plugins/ruby_runner/ui/config_widget.rb +92 -0
- data/plugins/ruby_runner/ui/config_widget.ui +91 -0
- data/plugins/ruby_runner/ui/project_widget.rb +60 -0
- data/plugins/ruby_runner/ui/project_widget.ui +48 -0
- data/plugins/ruby_runner/ui/ruby_runnner_plugin_option_widget.rb +59 -0
- data/plugins/ruby_runner/ui/ruby_runnner_plugin_option_widget.ui +44 -0
- data/plugins/state/plugin.yaml +28 -0
- data/plugins/state/state.rb +520 -0
- data/plugins/state/ui/config_widget.rb +92 -0
- data/plugins/state/ui/config_widget.ui +89 -0
- data/plugins/syntax_checker/plugin.yaml +18 -0
- data/plugins/syntax_checker/syntax_checker.rb +662 -0
- data/ruber.desktop +10 -0
- data/spec/annotation_model_spec.rb +174 -0
- data/spec/common.rb +119 -0
- data/spec/component_manager_spec.rb +1259 -0
- data/spec/document_list_spec.rb +626 -0
- data/spec/document_project_spec.rb +373 -0
- data/spec/document_spec.rb +779 -0
- data/spec/editor_view_spec.rb +167 -0
- data/spec/external_program_plugin_spec.rb +676 -0
- data/spec/filtered_output_widget_spec.rb +642 -0
- data/spec/gui_states_handler_spec.rb +304 -0
- data/spec/kde_config_option_backend_spec.rb +214 -0
- data/spec/kde_sugar_spec.rb +101 -0
- data/spec/ktexteditor_wrapper_spec.rb +305 -0
- data/spec/output_widget_spec.rb +1703 -0
- data/spec/plugin_spec.rb +1393 -0
- data/spec/plugin_specification_reader_spec.rb +1765 -0
- data/spec/plugin_specification_spec.rb +401 -0
- data/spec/project_backend_spec.rb +172 -0
- data/spec/project_files_list_spec.rb +401 -0
- data/spec/project_list_spec.rb +511 -0
- data/spec/project_spec.rb +990 -0
- data/spec/qt_sugar_spec.rb +328 -0
- data/spec/settings_container_spec.rb +617 -0
- data/spec/settings_dialog_manager_spec.rb +773 -0
- data/spec/settings_dialog_spec.rb +419 -0
- data/spec/state_spec.rb +991 -0
- data/spec/utils_spec.rb +406 -0
- data/spec/workspace_spec.rb +869 -0
- data/spec/yaml_option_backend_spec.rb +246 -0
- metadata +284 -0
@@ -0,0 +1,11 @@
|
|
1
|
+
name: app
|
2
|
+
description: The application itself
|
3
|
+
require: application
|
4
|
+
class: 'Ruber::Application'
|
5
|
+
config_options:
|
6
|
+
:general:
|
7
|
+
:plugin_dirs: {:default: Ruber::Application::DEFAULT_PLUGIN_PATHS}
|
8
|
+
:plugins: {:default: 'Ruber::Application::DEFAULT_PLUGINS'}
|
9
|
+
:auto_load_project: {:default: false }
|
10
|
+
config_widgets:
|
11
|
+
- {:caption: General, :pixmap: configure, :code: "w=Qt::CheckBox.new('&Open last project at startup');w.object_name='kcfg_general_auto_load_project';w"}
|
@@ -0,0 +1,899 @@
|
|
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 'forwardable'
|
22
|
+
|
23
|
+
require 'dictionary'
|
24
|
+
require 'facets/kernel/deep_copy'
|
25
|
+
require 'facets/boolean'
|
26
|
+
|
27
|
+
require 'ruber/plugin'
|
28
|
+
require 'ruber/plugin_specification'
|
29
|
+
|
30
|
+
=begin
|
31
|
+
Workflow:
|
32
|
+
|
33
|
+
* bin/ruber creates an instance of the component manager
|
34
|
+
* the component manager adds itself to the list of components (in initialize)
|
35
|
+
* bin/ruber calls the component manager's load_core_components method
|
36
|
+
* load_core_components calls load_component for the application object
|
37
|
+
* Ruber::Application.new creates the application data and command line objects,
|
38
|
+
before creating the object itself
|
39
|
+
* the application's setup method starts a single shot timer which will cause
|
40
|
+
the user plugins to be loaded when the main window is displayed
|
41
|
+
* load_core_components calls load_component for the other core components:
|
42
|
+
configuration, document keeper, project keeper, main window
|
43
|
+
* the setup method of the config object calls application#read_settings
|
44
|
+
* bin/ruber displays the main window and starts the application
|
45
|
+
* user plugins are loaded
|
46
|
+
=end
|
47
|
+
|
48
|
+
module Ruber
|
49
|
+
|
50
|
+
class ComponentManager < Qt::Object
|
51
|
+
|
52
|
+
=begin rdoc
|
53
|
+
Helper class used to resolve dependencies among plugins. Most likely you don't
|
54
|
+
need to use it, but simply call <tt>Ruber::ComponentManager.sort_plugins</tt>.
|
55
|
+
=end
|
56
|
+
class PluginSorter
|
57
|
+
|
58
|
+
=begin rdoc
|
59
|
+
Creates a new +PluginSorter+. _pdfs_ is an array of the plugin descriptions to
|
60
|
+
sort. _ignored_ is an array containing dependencies to be ignored(maybe because
|
61
|
+
they're already loaded). _ignored_ can be either an array of symbols, where each
|
62
|
+
symbol is the name of a feature, or an array of +PluginSpecification+s.
|
63
|
+
|
64
|
+
<b>Note:</b> _pdfs_ should contain dependencies in terms of actual plugins, not
|
65
|
+
of features.
|
66
|
+
=end
|
67
|
+
def initialize pdfs, ignored = []
|
68
|
+
@pdfs = {}
|
69
|
+
@plugins = {}
|
70
|
+
pdfs.each do |i|
|
71
|
+
@pdfs[i.name] = i
|
72
|
+
@plugins[i.name] = i.deps
|
73
|
+
end
|
74
|
+
@ignored = ignored.map{|i| i.is_a?(OpenStruct) ? i.name : i}
|
75
|
+
@ready = []
|
76
|
+
@deps = {}
|
77
|
+
end
|
78
|
+
|
79
|
+
=begin rdoc
|
80
|
+
Sorts the plugins associated with the object, according with their dependencies
|
81
|
+
and returns an array containing the plugin descriptions sorted in dependence order,
|
82
|
+
from the dependence to the dependent.
|
83
|
+
|
84
|
+
If some of the plugins have dependency which doesn't correspond neither to another
|
85
|
+
plugin nor to one of the plugins to ignore, <tt>Ruber::ComponentManager::UnresolvedDep</tt>
|
86
|
+
will be raised.
|
87
|
+
|
88
|
+
If there's a circular dependency among the plugins, <tt>Ruber::ComponentManager::CircularDep</tt>
|
89
|
+
will be raised.
|
90
|
+
=end
|
91
|
+
def sort_plugins
|
92
|
+
@plugins.each_value do |v|
|
93
|
+
v.reject!{|d| @ignored.include? d}
|
94
|
+
end
|
95
|
+
unknown = find_unknown_deps
|
96
|
+
raise ComponentManager::UnresolvedDep.new unknown unless unknown.empty?
|
97
|
+
circular = @plugins.keys.inject([]){ |res, plug| res + find_dep( plug ) }
|
98
|
+
raise ComponentManager::CircularDep.new(circular.uniq) unless circular.empty?
|
99
|
+
deps = @deps.reject{|k, v| v.nil? }
|
100
|
+
res = []
|
101
|
+
old_size = deps.size
|
102
|
+
until deps.empty?
|
103
|
+
ready = deps.select{|k, v| v.empty?}.map{|i| i[0]}.sort_by{|i| i.to_s}
|
104
|
+
res += ready
|
105
|
+
ready.each do |i|
|
106
|
+
deps.each{|d| d[1].delete i}
|
107
|
+
deps.delete i
|
108
|
+
end
|
109
|
+
raise "Circular deps (this shouldn't happen)" unless old_size > deps.size
|
110
|
+
old_size = deps.size
|
111
|
+
end
|
112
|
+
res.map{|i| @pdfs[i]}
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
=begin rdoc
|
118
|
+
Checks whether all the dependencies among the plugins are satisifed either by
|
119
|
+
another plugin or by a plugin in the _ignore_ list. Returns a hash which is
|
120
|
+
empty if all the dependencies were satisifed and otherwise has for keys the
|
121
|
+
names of plugins whose dependencies couldn't be found and for values arrays
|
122
|
+
containing the names of the missing dependencies for that plugin.
|
123
|
+
=end
|
124
|
+
def find_unknown_deps
|
125
|
+
known = @plugins.keys
|
126
|
+
res = Hash.new{|h, k| h[k] = []}
|
127
|
+
known.each do |i|
|
128
|
+
missing_deps = @plugins[i] - known
|
129
|
+
missing_deps.each{|d| res[d] << i}
|
130
|
+
end
|
131
|
+
res
|
132
|
+
end
|
133
|
+
|
134
|
+
=begin rdoc
|
135
|
+
Finds the dependencies of the plugin _plug_. To do this, it calls itself
|
136
|
+
recursively for each of the direct dependencies of the plugin. The dependencies
|
137
|
+
found are stored in the <tt>@deps</tt> hash.
|
138
|
+
|
139
|
+
To avoid an endless loop or a SystemStackError in case of circular dependencies,
|
140
|
+
each time the method is called, it is also passed a second argument, an array
|
141
|
+
containing the names of the plugins whose dependencies have lead to that call.
|
142
|
+
|
143
|
+
If circular dependencies are found, the entry in <tt>@deps</tt> corresponding
|
144
|
+
to the plugin is set to *nil*, and an array containing the pairs of plugins with
|
145
|
+
circular dependencies is returned. If no circular dependencies exist, the returned
|
146
|
+
array is empty.
|
147
|
+
=end
|
148
|
+
def find_dep plug, stack = []
|
149
|
+
direct_deps = @plugins[plug] || []
|
150
|
+
circ = []
|
151
|
+
deps = []
|
152
|
+
circ << plug if stack.include? plug
|
153
|
+
direct_deps.each{|d| circ << plug << d if stack.include? d}
|
154
|
+
if circ.empty?
|
155
|
+
deps = []
|
156
|
+
res = direct_deps.each do |d|
|
157
|
+
circ += find_dep d, stack + [plug] unless @deps.has_key? d
|
158
|
+
deps += @deps[d] + [d] if @deps[d]
|
159
|
+
end
|
160
|
+
end
|
161
|
+
@deps[plug] = circ.empty? ? deps : nil
|
162
|
+
circ
|
163
|
+
end
|
164
|
+
|
165
|
+
=begin
|
166
|
+
Old implementation of sort_plugins (just in case it turns out to be necessary)
|
167
|
+
def sort_plugins
|
168
|
+
res = []
|
169
|
+
until @remaining.empty?
|
170
|
+
@ready = @remaining.select{|k, v| v.empty?}.map{|k, v| k}.sort_by{|i| i.to_s}
|
171
|
+
report_problem if @ready.empty?
|
172
|
+
res += @ready.map{|i| @descriptions[i]}
|
173
|
+
@remaining.delete_if{|k, v| @ready.include? k}
|
174
|
+
@remaining.each_key{|k| @remaining[k] -= @ready}
|
175
|
+
end
|
176
|
+
res
|
177
|
+
end
|
178
|
+
|
179
|
+
=end
|
180
|
+
|
181
|
+
|
182
|
+
end
|
183
|
+
|
184
|
+
=begin rdoc
|
185
|
+
Helper class which contains the methods needed to find all the plugins needed
|
186
|
+
to satisfy the dependencies of a given set of plugins.
|
187
|
+
|
188
|
+
The difference between this class and PluginSorter is that the latter needs
|
189
|
+
to know all the plugins which should be loaded, while this class has the job of
|
190
|
+
finding out which ones need to be loaded.
|
191
|
+
=end
|
192
|
+
class DepsSolver
|
193
|
+
|
194
|
+
=begin rdoc
|
195
|
+
Creates a new +DepsSolver+.
|
196
|
+
|
197
|
+
<i>to_load</i> is an array containing the PluginSpecification corresponding describing
|
198
|
+
the plugins to load, while <i>availlable</i> is an array containing the
|
199
|
+
PluginSpecification which can be used to resolve dependencies.
|
200
|
+
=end
|
201
|
+
def initialize to_load, availlable
|
202
|
+
@to_load = to_load.map{|i| [i.name, i]}.to_h
|
203
|
+
@availlable = availlable.map{|i| [i.name, i]}.to_h
|
204
|
+
@loaded_features = to_load.inject({}) do |res, i|
|
205
|
+
i.features.each{|f| (res[f] ||= []) << i}
|
206
|
+
res
|
207
|
+
end
|
208
|
+
@availlable_plugins = availlable.map{|i| [i.name, i]}.to_h
|
209
|
+
@availlable_features = availlable.inject({}) do |res, i|
|
210
|
+
i.features.each{|f| (res[f] ||= []) << i}
|
211
|
+
res
|
212
|
+
end
|
213
|
+
# @res is an array containing the result, that is the list of names of
|
214
|
+
# the plugins to load to satisfy all dependencies.
|
215
|
+
# @deps is a hash containing the reasons for which a given plugin should
|
216
|
+
# be loaded. Each key is the name of a plugin, while each value is an array
|
217
|
+
# containing the list of plugins directly depending on the key. If the key
|
218
|
+
# is in the list of plugins to load (that is in the first argument passed
|
219
|
+
# to the constructor), the array contains nil.
|
220
|
+
@res = []
|
221
|
+
@deps = Hash.new{|h, k| h[k] = []}
|
222
|
+
end
|
223
|
+
|
224
|
+
=begin rdoc
|
225
|
+
Tries to resolve the dependencies for the given plugins, returning an array
|
226
|
+
containing the plugins needed to satisfy all the dependencies. When a plugin
|
227
|
+
depends on a feature _f_, then _f_ is included in the list of needed plugins,
|
228
|
+
together with its dependencies, unless another required plugin already provides
|
229
|
+
that feature.
|
230
|
+
|
231
|
+
If some dependencies can't be satisfied, UnresolvedDep is raised. If there are
|
232
|
+
circular dependencies, CircularDep is raised.
|
233
|
+
=end
|
234
|
+
def solve
|
235
|
+
errors = {:missing => {}, :circular => []}
|
236
|
+
@res = @to_load.values.inject([]) do |r, i|
|
237
|
+
@deps[i.name] << nil
|
238
|
+
r + solve_for(i, errors, [])
|
239
|
+
end
|
240
|
+
if !errors[:missing].empty? then raise UnresolvedDep.new errors[:missing]
|
241
|
+
elsif !errors[:circular].empty? then raise CircularDep.new errors[:circular]
|
242
|
+
end
|
243
|
+
remove_unneeded_deps
|
244
|
+
@res
|
245
|
+
end
|
246
|
+
|
247
|
+
private
|
248
|
+
|
249
|
+
=begin rdoc
|
250
|
+
Recursively finds all the dependencies for the plugin described by the PluginSpecification
|
251
|
+
_pl_.
|
252
|
+
|
253
|
+
_errors_ is a hash used to store missing dependencies and circular dependencies.
|
254
|
+
It should have a <tt>:circular</tt> and a <tt>:missing</tt> key. The corresponding
|
255
|
+
values should be an array and a hash.
|
256
|
+
|
257
|
+
_stack_ is an array containing the names of the plugins whose dependencies are
|
258
|
+
being solved and is used to detect circular dependencies. For example, if _stack_
|
259
|
+
is: <tt>[:a, :b, :c]</tt>, it would mean that we're resolving the dependencies
|
260
|
+
of the plugin :c, which is a dependency of the plugin :b, which is a dependency
|
261
|
+
of the plugin :a. If this array contains <tt>pl.name</tt>, then there's a circular
|
262
|
+
dependency.
|
263
|
+
|
264
|
+
<b>Note:</b> this method doesn't raise exceptions if there are circular or missing
|
265
|
+
dependencies. Rather, it adds them to _errors_ and goes on (this means that it
|
266
|
+
skips both missing and circular dependencies).
|
267
|
+
=end
|
268
|
+
def solve_for pl, errors, stack
|
269
|
+
deps = []
|
270
|
+
if stack.include? pl.name
|
271
|
+
errors[:circular] << [stack.at(stack.index(pl.name + 1)), pl.name]
|
272
|
+
return deps
|
273
|
+
end
|
274
|
+
stack << pl.name
|
275
|
+
unless pl.deps.empty?
|
276
|
+
pl.deps.each do |dep|
|
277
|
+
next if @loaded_features.include? dep
|
278
|
+
new_pl = @availlable_plugins[dep]
|
279
|
+
if new_pl
|
280
|
+
deps << dep
|
281
|
+
@deps[dep] << pl.name
|
282
|
+
deps += solve_for new_pl, errors, stack
|
283
|
+
else
|
284
|
+
(errors[:missing][dep] ||= []) << pl.name
|
285
|
+
return []
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
stack.pop
|
290
|
+
deps
|
291
|
+
end
|
292
|
+
|
293
|
+
=begin rdoc
|
294
|
+
Attempts to remove from the list of needed dependencies all those dependencies
|
295
|
+
which are there only to provide features already provided by other plugins.
|
296
|
+
|
297
|
+
For example, if the list of needed plugins includes both the plugin <tt>:a</tt>
|
298
|
+
and the plugin <tt>:b</tt>, which provides the feature <tt>:a</tt>, then the
|
299
|
+
plugin <tt>:a</tt> whould be removed from the list.
|
300
|
+
|
301
|
+
If after removing plugins as described above, the list contains plugins which
|
302
|
+
aren't needed anymore, because they were there only to satisfy the dependencies
|
303
|
+
of plugins which have already been removed, they're also removed.
|
304
|
+
=end
|
305
|
+
def remove_unneeded_deps
|
306
|
+
h = Hash.new{|hash, k| hash[k] = []}
|
307
|
+
#A hash having the features as keys and the plugins providing them
|
308
|
+
#as values
|
309
|
+
deps_features = @res.inject(h) do |res, i|
|
310
|
+
@availlable_plugins[i].features.each{|f| res[f] << i}
|
311
|
+
res
|
312
|
+
end
|
313
|
+
to_delete = @res.find{|i| !@deps[i].include?(nil) and !deps_features[i].uniq.only? i}
|
314
|
+
until to_delete.nil?
|
315
|
+
@res.delete to_delete
|
316
|
+
deps_features.each_value{|i| i.delete to_delete}
|
317
|
+
new = deps_features[to_delete]
|
318
|
+
@deps[new] += @deps[to_delete]
|
319
|
+
@deps.delete to_delete
|
320
|
+
@deps.each_value{|i| i.delete to_delete}
|
321
|
+
to_delete = @res.find{|i| !@deps.include?(nil) and !deps_features[i].only? i}
|
322
|
+
to_delete = @deps.find{|k, v| v.empty?}[0] rescue nil unless to_delete
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
end
|
327
|
+
|
328
|
+
=begin rdoc
|
329
|
+
Base class for UnresolvedDep and CircularDep exceptions. It represents all the
|
330
|
+
possible kinds of dependency errors.
|
331
|
+
=end
|
332
|
+
class DependencyError < RuntimeError
|
333
|
+
end
|
334
|
+
|
335
|
+
=begin rdoc
|
336
|
+
Exception raised by <tt>Ruber::ComponentManager</tt> when some dependencies of
|
337
|
+
the plugins can't be found.
|
338
|
+
=end
|
339
|
+
class UnresolvedDep < DependencyError
|
340
|
+
|
341
|
+
=begin rdoc
|
342
|
+
a hash containing the missing dependencies. The keys are the names of the plugins
|
343
|
+
who have unknown dependencies, while the values are arrays with the name of the
|
344
|
+
missing dependencies
|
345
|
+
=end
|
346
|
+
attr_reader :missing
|
347
|
+
|
348
|
+
=begin rdoc
|
349
|
+
Creates a new <tt>Ruber::ComponentManager::UnresolvedDep</tt>. _missing_ is a
|
350
|
+
hash containing the missing dependencies. It must have the same format as
|
351
|
+
the +missing+ attribute.
|
352
|
+
=end
|
353
|
+
def initialize missing
|
354
|
+
@missing = Hash[missing]
|
355
|
+
text = @missing.map do |k, v|
|
356
|
+
"#{k} (needed by #{v.join ','})"
|
357
|
+
end
|
358
|
+
super "The following plugins couldn't be found: #{text.join(', ')}"
|
359
|
+
end
|
360
|
+
|
361
|
+
end
|
362
|
+
|
363
|
+
=begin rdoc
|
364
|
+
Exception raised by <tt>Ruber::ComponentManager</tt> when circular dependencies
|
365
|
+
among plugins are detected.
|
366
|
+
=end
|
367
|
+
class CircularDep < DependencyError
|
368
|
+
|
369
|
+
=begin rdoc
|
370
|
+
The plugins among which the circular dependencies exist. It's an array of
|
371
|
+
arrays. Each inner array contains the name of the two plugins depending
|
372
|
+
(perhaps indirectly) on each other.
|
373
|
+
=end
|
374
|
+
attr_reader :circular_deps
|
375
|
+
|
376
|
+
=begin rdoc
|
377
|
+
Creates a new <tt>Ruber::ComponentManager::CircularDep</tt>. _circular_ is an
|
378
|
+
array containing the plugins among which the circular dependencies exist, whith
|
379
|
+
the format described for <tt>Ruber::ComponentManager::CircularDep#circular_deps</tt>
|
380
|
+
=end
|
381
|
+
def initialize circular
|
382
|
+
@circular_deps = circular.deep_copy
|
383
|
+
super "There were circular dependencies among the following pairs of plugins: #{circular.map{|i| "#{i[0]} and #{i[1]}"}.join ', '}"
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
=begin rdoc
|
388
|
+
Exception raised when some plugins couldn't be found. _plugins_ is an array containing
|
389
|
+
the names of the plugins which couldn't be found, while _dirs_ is an array of
|
390
|
+
the directories searched for those plugins
|
391
|
+
=end
|
392
|
+
class MissingPlugins < StandardError
|
393
|
+
attr_reader :plugins, :dirs
|
394
|
+
def initialize plugins, dirs
|
395
|
+
@plugins = plugins.dup
|
396
|
+
@dirs = dirs.dup
|
397
|
+
super "The plugins #{@plugins.join ' '} couldn't be found in the directories #{@dirs.join ' '}"
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
=begin rdoc
|
402
|
+
Exception raised when some of the PDFs for the plugins to be loaded contain error.
|
403
|
+
It differs from <tt>Ruber::ComponentManager::PluginSpecification::PSFError</tt> only
|
404
|
+
in the fact that it contains the list of files which produced an error.
|
405
|
+
=end
|
406
|
+
class InvalidPDF < StandardError
|
407
|
+
|
408
|
+
# An array containing the files which produced errors
|
409
|
+
attr_reader :files
|
410
|
+
|
411
|
+
# Creates a new instance. _files_ is an array containing the names of the files
|
412
|
+
# which produced errors.
|
413
|
+
def initialize files
|
414
|
+
@files = files.dup
|
415
|
+
super "The following plugin description files contained errors: #{files.join ' '}"
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
=begin rdoc
|
420
|
+
Looks in the directories specified in the _dirs_ array for plugins and returns
|
421
|
+
a hash having the directory of each found plugin as keys and either the name
|
422
|
+
or the PluginSpecification for each plugin as values, depending on the value of the
|
423
|
+
_info_ parameter.
|
424
|
+
|
425
|
+
<b>Note:</b> if more than one directory contains a plugin with the given name,
|
426
|
+
only the first (according to the order in which directories are in _dirs_) will
|
427
|
+
be taken into account.
|
428
|
+
=end
|
429
|
+
def self.find_plugins dirs, info = false
|
430
|
+
res = {}
|
431
|
+
dirs.each do |dir|
|
432
|
+
Dir.entries(dir)[2..-1].each do |name|
|
433
|
+
next if res[name.to_sym]
|
434
|
+
d = File.join dir, name
|
435
|
+
if File.directory?(d) and File.exist?(File.join d, 'plugin.yaml')
|
436
|
+
if info then
|
437
|
+
res[name.to_sym] = PluginSpecification.intro(File.join d, 'plugin.yaml')
|
438
|
+
else res[name.to_sym] = d
|
439
|
+
end
|
440
|
+
end
|
441
|
+
end
|
442
|
+
end
|
443
|
+
res
|
444
|
+
end
|
445
|
+
|
446
|
+
|
447
|
+
=begin rdoc
|
448
|
+
Replaces features in plugin dependencies with the names of the plugin providing
|
449
|
+
them. _pdfs_ is an array containing the <tt>Ruber::PluginSpecification</tt>s of plugins whose dependencies should
|
450
|
+
be changed, while _extra_ is an array containing the <tt>PluginSpecification</tt>s of plugins
|
451
|
+
which should be used to look up features, but which should not be changed. For
|
452
|
+
example, _extra_ may contain descriptions for plugins which are already loaded.
|
453
|
+
|
454
|
+
It returns an array containing a copy of the <tt>Ruber::PluginSpecification</tt>s whith the dependencies
|
455
|
+
correctly changed. If a dependency is unknown, <tt>Ruber::ComponentManager::UnresolvedDep</tt>
|
456
|
+
will be raised.
|
457
|
+
=end
|
458
|
+
def self.resolve_features pdfs, extra = []
|
459
|
+
features = (pdfs+extra).inject({}) do |res, pl|
|
460
|
+
pl.features.each{|f| res[f] = pl.name}
|
461
|
+
res
|
462
|
+
end
|
463
|
+
missing = Hash.new{|h, k| h[k] = []}
|
464
|
+
new_pdfs = pdfs.map do |pl|
|
465
|
+
res = pl.deep_copy
|
466
|
+
res.deps = pl.deps.map do |d|
|
467
|
+
f = features[d]
|
468
|
+
missing[pl.name] << d unless f
|
469
|
+
f
|
470
|
+
end.uniq.compact
|
471
|
+
res
|
472
|
+
end
|
473
|
+
raise UnresolvedDep.new Hash[missing] unless missing.empty?
|
474
|
+
new_pdfs
|
475
|
+
end
|
476
|
+
|
477
|
+
=begin rdoc
|
478
|
+
Finds all the dependencies for the given plugins choosing among a list.
|
479
|
+
<i>to_load</i> is an array containing the +PluginSpecification+ for the plugins to load,
|
480
|
+
while _availlable_ is an array containing the plugins which can be used to satisfy
|
481
|
+
the dependencies.
|
482
|
+
|
483
|
+
This method uses <tt>DepsSolver#solve</tt>, so see the documentation for it for
|
484
|
+
a more complete description.
|
485
|
+
=end
|
486
|
+
def self.fill_dependencies to_load, availlable
|
487
|
+
solver = DepsSolver.new to_load, availlable
|
488
|
+
solver.solve
|
489
|
+
end
|
490
|
+
|
491
|
+
=begin rdoc
|
492
|
+
Sorts the plugins in the _pdfs_ array, according with their dependencies
|
493
|
+
and returns an array containing the plugin descriptions sorted in dependence order,
|
494
|
+
from the dependence to the dependent.
|
495
|
+
|
496
|
+
_known_ is an array of either symbols or <tt>Ruber::PluginSpecification</tt>s corresponding
|
497
|
+
to plugins which can be depended upon but which shouldn't be sorted with the
|
498
|
+
others (for example, because they're already loaded).
|
499
|
+
|
500
|
+
If some of the plugins have dependency which doesn't correspond neither to another
|
501
|
+
plugin nor to one of the knonw plugins, <tt>Ruber::ComponentManager::UnresolvedDep</tt>
|
502
|
+
will be raised.
|
503
|
+
|
504
|
+
If there's a circular dependency among the plugins, <tt>Ruber::ComponentManager::CircularDep</tt>
|
505
|
+
will be raised.
|
506
|
+
=end
|
507
|
+
def self.sort_plugins pdfs, known = []
|
508
|
+
PluginSorter.new( pdfs, known ).sort_plugins
|
509
|
+
end
|
510
|
+
|
511
|
+
extend Forwardable
|
512
|
+
|
513
|
+
include Enumerable
|
514
|
+
|
515
|
+
signals 'loading_component(QObject*)', 'component_loaded(QObject*)',
|
516
|
+
'feature_loaded(QString, QObject*)', 'unloading_component(QObject*)'
|
517
|
+
|
518
|
+
def_delegators :@features, :[]
|
519
|
+
|
520
|
+
# Returns the <tt>PluginSpecification</tt> describing the +ComponentManager+
|
521
|
+
attr_reader :plugin_description
|
522
|
+
|
523
|
+
# Creates a new +ComponentManager+
|
524
|
+
def initialize
|
525
|
+
super
|
526
|
+
@components = Dictionary[:components, self]
|
527
|
+
@features = {:components => self}
|
528
|
+
@plugin_description = PluginSpecification.full({:name => :components, :class => self.class})
|
529
|
+
end
|
530
|
+
|
531
|
+
# Returns <tt>:components</tt>
|
532
|
+
def component_name
|
533
|
+
@plugin_description.name
|
534
|
+
end
|
535
|
+
alias plugin_name component_name
|
536
|
+
|
537
|
+
=begin rdoc
|
538
|
+
Method required for the Plugin interface. Does nothing
|
539
|
+
=end
|
540
|
+
def register_with_project prj
|
541
|
+
end
|
542
|
+
|
543
|
+
=begin rdoc
|
544
|
+
Method required for the Plugin interface. Does nothing
|
545
|
+
=end
|
546
|
+
def remove_from_project prj
|
547
|
+
end
|
548
|
+
|
549
|
+
=begin rdoc
|
550
|
+
Method required for the Plugin interface. Does nothing
|
551
|
+
=end
|
552
|
+
def update_project prj
|
553
|
+
end
|
554
|
+
|
555
|
+
=begin rdoc
|
556
|
+
Returns an array containing all the loaded plugins (but not the components), in loading order
|
557
|
+
=end
|
558
|
+
def plugins
|
559
|
+
@components.inject([]) do |res, i|
|
560
|
+
res << i[1] if i[1].is_a? Ruber::Plugin
|
561
|
+
res
|
562
|
+
end
|
563
|
+
end
|
564
|
+
|
565
|
+
=begin rdoc
|
566
|
+
Returns an array containing all the loaded components, in loading order
|
567
|
+
=end
|
568
|
+
def components
|
569
|
+
@components.inject([]){|res, i| res << i[1]}
|
570
|
+
end
|
571
|
+
|
572
|
+
=begin rdoc
|
573
|
+
Calls the block for each component, passing it as argument to the block. The
|
574
|
+
components are passed in reverse loading order (i.e., the last loaded component
|
575
|
+
will be the first passed to the block.)
|
576
|
+
=end
|
577
|
+
def each_component #:yields: comp
|
578
|
+
@components.reverse_each{|k, v| yield v}
|
579
|
+
end
|
580
|
+
|
581
|
+
=begin rdoc
|
582
|
+
Calls the block for each plugin (that is, for every component of class
|
583
|
+
<tt>Ruber::Plugin</tt> or derived), passing it as argument to the block. The
|
584
|
+
plugins are passed in reverse loading order (i.e., the last loaded plugin
|
585
|
+
will be the first passed to the block.)
|
586
|
+
=end
|
587
|
+
def each_plugin #:yields: plug
|
588
|
+
@components.reverse_each do |k, v|
|
589
|
+
yield v if v.is_a?(Ruber::Plugin)
|
590
|
+
end
|
591
|
+
end
|
592
|
+
alias each each_plugin
|
593
|
+
|
594
|
+
=begin rdoc
|
595
|
+
<b>For internal use only</b>
|
596
|
+
|
597
|
+
Adds the given component to the list of components and at the end of the list
|
598
|
+
of sorted components.
|
599
|
+
=end
|
600
|
+
def add comp
|
601
|
+
@components<< [comp.component_name, comp]
|
602
|
+
comp.plugin_description.features.each{|f| @features[f] = comp}
|
603
|
+
end
|
604
|
+
|
605
|
+
=begin rdoc
|
606
|
+
Loads the component with name _name_.
|
607
|
+
|
608
|
+
_name_ is the name of a subdirectory (called the <i>component directory</i>
|
609
|
+
in the directory where <tt>component_manager.rb</tt>
|
610
|
+
is. That directory should contain the PDF file for the component to load.
|
611
|
+
|
612
|
+
The loading process works as follows:
|
613
|
+
* the component directory is added to the KDE resource dirs for the +pixmap+,
|
614
|
+
+data+ and +appdata+ resource types.
|
615
|
+
* A full <tt>Ruber::PluginSpecification</tt> is generated from the PDF
|
616
|
+
(see <tt>Ruber::PluginSpecification.full</tt>). If the file can't
|
617
|
+
be read, +SystemCallError+ is raised; if it isn't a valid PDF,
|
618
|
+
<tt>Ruber::PluginSpecification::PSFError</tt> is raised. In both cases, a message box
|
619
|
+
warning the user is shown.
|
620
|
+
* the component object (that is, an instance of the class specified in the +class+
|
621
|
+
entry of the PDF) is created
|
622
|
+
* the <tt>component_loaded(QObject*)</tt> signal is emitted, passing the component
|
623
|
+
object as argument
|
624
|
+
* the component object is returned.
|
625
|
+
|
626
|
+
<b>Note:</b> this method doesn't insert the component object in the components
|
627
|
+
list: the component should take care to do it itself, using the +add+ method.
|
628
|
+
=end
|
629
|
+
def load_component name
|
630
|
+
dir = File.expand_path File.join(File.dirname(__FILE__), name)
|
631
|
+
if KDE::Application.instance
|
632
|
+
KDE::Global.dirs.add_resource_dir 'pixmap', dir
|
633
|
+
KDE::Global.dirs.add_resource_dir 'data', dir
|
634
|
+
KDE::Global.dirs.add_resource_dir 'appdata', dir
|
635
|
+
end
|
636
|
+
file = File.join dir, 'plugin.yaml'
|
637
|
+
pdf = PluginSpecification.full file
|
638
|
+
parent = Ruber[:app] rescue self
|
639
|
+
comp = pdf.class_obj.new parent, pdf
|
640
|
+
emit component_loaded(comp)
|
641
|
+
comp
|
642
|
+
end
|
643
|
+
|
644
|
+
=begin rdoc
|
645
|
+
Loads the plugin in the directory _dir_.
|
646
|
+
|
647
|
+
The directory _dir_ should contain the PDF for the plugin, and its last part
|
648
|
+
should correspond to the plugin name.
|
649
|
+
|
650
|
+
The loading process works as follows:
|
651
|
+
* the plugin directory is added to the KDE resource dirs for the +pixmap+,
|
652
|
+
+data+ and +appdata+ resource types.
|
653
|
+
* A full <tt>Ruber::PluginSpecification</tt> is generated from the PDF
|
654
|
+
(see <tt>Ruber::PluginSpecification.full</tt>). If the file can't
|
655
|
+
be read, +SystemCallError+ is raised; if it isn't a valid PDF,
|
656
|
+
<tt>Ruber::PluginSpecification::PSFError</tt> is raised.
|
657
|
+
* the plugin object (that is, an instance of the class specified in the +class+
|
658
|
+
entry of the PDF) is created
|
659
|
+
* the <tt>component_loaded(QObject*)</tt> signal is emitted, passing the component
|
660
|
+
object as argument
|
661
|
+
* for each feature provided by the plugin, the signal <tt>feature_loaded(QString, QObject*)</tt>
|
662
|
+
is emitted, passing the name of the feature (as string) and the plugin object
|
663
|
+
as arguments
|
664
|
+
* for each feature _f_ provided by the plugin, a signal "unloading_f(QObject*)"
|
665
|
+
is defined
|
666
|
+
* the plugin object is returned.
|
667
|
+
|
668
|
+
<b>Note:</b> this method doesn't insert the plugin object in the components
|
669
|
+
list: the plugin should take care to do it itself, using the +add+ method.
|
670
|
+
=end
|
671
|
+
def load_plugin dir
|
672
|
+
KDE::Global.dirs.add_resource_dir 'pixmap', dir
|
673
|
+
KDE::Global.dirs.add_resource_dir 'data', dir
|
674
|
+
KDE::Global.dirs.add_resource_dir 'appdata', dir
|
675
|
+
file = File.join dir, 'plugin.yaml'
|
676
|
+
pdf = PluginSpecification.full YAML.load(File.read(file)), dir
|
677
|
+
pdf.directory = dir
|
678
|
+
plug = pdf.class_obj.new pdf
|
679
|
+
emit component_loaded(plug)
|
680
|
+
pdf.features.each do |f|
|
681
|
+
self.class.class_eval{signals "unloading_#{f}(QObject*)"}
|
682
|
+
emit feature_loaded(f.to_s, plug)
|
683
|
+
end
|
684
|
+
plug.send :delayed_initialize
|
685
|
+
plug
|
686
|
+
end
|
687
|
+
|
688
|
+
=begin rdoc
|
689
|
+
Makes the +ComponentManager+ load the given plugins. It is the standard method
|
690
|
+
to load plugins, because it takes into account dependency order and features.
|
691
|
+
|
692
|
+
For each plugin, a directory with the same name and containing a file
|
693
|
+
<tt>plugin.yaml</tt> is searched in the directories in the _dirs_ array.
|
694
|
+
Directories near the beginning of the array have the precedence with respect
|
695
|
+
to those near the end of the array (that is, if a plugin is found both in the
|
696
|
+
second and in the fourth directories of _dir_, the one in the second directory
|
697
|
+
is used). If the directory for some plugins can't be found, +MissingPlugins+ is
|
698
|
+
raised.
|
699
|
+
|
700
|
+
This method attempts to resolve the features for the plugins (see
|
701
|
+
<tt>Ruber::ComponentManager.resolve_features</tt>) and to sort them, using also
|
702
|
+
the already loaded plugins, if any. If it fails, it raises +UnresolvedDep+ or
|
703
|
+
+CircularDep+.
|
704
|
+
|
705
|
+
Once the plugins have been sorted, it attempts to load each one, according to
|
706
|
+
the dependency order. The order in which independent plugins are loaded is
|
707
|
+
arbitrary (but consistent: the order will be the same every time). If a plugin
|
708
|
+
fails to load, there are several behaviours:
|
709
|
+
* if no block has been given, the exception raised by the plugin is propagated
|
710
|
+
otherwise, the block is passed with the exception as argument. Depending on the
|
711
|
+
value returned by the block, the following happens:
|
712
|
+
* if the block returns <tt>:skip</tt>, all remaining plugins are skipped and
|
713
|
+
the method returns *true*
|
714
|
+
* if the block returns <tt>:silent</tt>, an attempt to load the remaining plugins
|
715
|
+
is made. Other loading failures will be ignored
|
716
|
+
* if the block any other true value, then the failed plugin is ignored and an
|
717
|
+
attempt to load the remaining plugins is made.
|
718
|
+
* if the block returns *false* or *nil*, the method immediately returns *false*
|
719
|
+
|
720
|
+
<tt>load_plugins</tt> returns *true* if all the plugins were successfully loaded
|
721
|
+
(or if some failed but the block always returned a true value) and false otherwise.
|
722
|
+
|
723
|
+
===== Notes
|
724
|
+
* After a failure, dependencies aren't recomputed. This means that most likely
|
725
|
+
all the plugins dependent on the failed one will fail, too
|
726
|
+
* This method can be conceptually divided into two phases: plugin ordering and
|
727
|
+
plugin loading. The first part doesn't change any state. This means that, if
|
728
|
+
it fails, the caller is free to attempt to solve the problem (for example,
|
729
|
+
to remove the missing plugins and the ones with invalid PDFs from the list)
|
730
|
+
and call again <tt>load_plugins</tt>. The part which actually _does_ something
|
731
|
+
is the second. If called twice with the same arguments, it can cause trouble,
|
732
|
+
since no attempt to skip already-loaded plugins is made. If the caller wants
|
733
|
+
to correct errors caused in the second phase, it should put the logic to do
|
734
|
+
so in the block.
|
735
|
+
=end
|
736
|
+
def load_plugins( plugins, dirs ) #:yields: ex
|
737
|
+
plugins = plugins.map(&:to_s)
|
738
|
+
plugin_files = locate_plugins dirs
|
739
|
+
plugins = create_plugins_info plugins, plugin_files, dirs
|
740
|
+
plugins = ComponentManager.resolve_features plugins, self.plugins.map{|pl| pl.plugin_description}
|
741
|
+
plugins = ComponentManager.sort_plugins plugins, @features.keys
|
742
|
+
silent = false
|
743
|
+
plugins.each do |pl|
|
744
|
+
begin load_plugin File.dirname(plugin_files[pl.name.to_s])
|
745
|
+
rescue Exception => e
|
746
|
+
@components.delete pl.name
|
747
|
+
if silent then next
|
748
|
+
elsif block_given?
|
749
|
+
res = yield pl, e
|
750
|
+
if res == :skip then break
|
751
|
+
elsif res == :silent then silent = true
|
752
|
+
elsif !res then return false
|
753
|
+
end
|
754
|
+
else raise
|
755
|
+
end
|
756
|
+
end
|
757
|
+
end
|
758
|
+
true
|
759
|
+
end
|
760
|
+
|
761
|
+
=begin rdoc
|
762
|
+
Prepares the application for being cleanly closed. To do so, it:
|
763
|
+
* asks each plugin to save its settings
|
764
|
+
* emits the signal <tt>unloading_component(QObject*)</tt> for each component,
|
765
|
+
in reverse loading order
|
766
|
+
* calls the shutdown method for each component (in their shutdown methods,
|
767
|
+
plugins should emit the "closing(QObject*)" signal)
|
768
|
+
* calls the delete_later method of the plugins (not of the components)
|
769
|
+
* deletes all the features provided by plugins from the list of features
|
770
|
+
* delete all the plugins from the list of loaded components.
|
771
|
+
=end
|
772
|
+
def shutdown
|
773
|
+
each_component{|c| c.save_settings unless c.equal?(self)}
|
774
|
+
@components[:config].write
|
775
|
+
each_component{|c| c.shutdown unless c.equal? self}
|
776
|
+
# @components[:config].write
|
777
|
+
# each_component do |c|
|
778
|
+
# unless c.equal? self
|
779
|
+
# if c.is_a? Plugin
|
780
|
+
# c.plugin_description.features.each{|f| emit method("unloading_#{f}").call( c)}
|
781
|
+
# end
|
782
|
+
# emit unloading_component(c)
|
783
|
+
# c.shutdown
|
784
|
+
# end
|
785
|
+
# end
|
786
|
+
# each_plugin {|pl| pl.delete_later}
|
787
|
+
# @features.delete_if{|f, pl| pl.is_a? Plugin}
|
788
|
+
# @components.delete_if{|_, pl| pl.is_a?(Plugin)}
|
789
|
+
end
|
790
|
+
|
791
|
+
=begin rdoc
|
792
|
+
Unloads the plugin called _name_ (_name_ must be a symbol) by doing the following:
|
793
|
+
* emit the signal "unloading_*(QObject*)" for each feature provided by the plugin
|
794
|
+
* emit the signal "unloading_component(QObject*)"
|
795
|
+
* call the +shutdown+ method of the plugin
|
796
|
+
* call the <tt>delete_later</tt> method of the plugin
|
797
|
+
* remove the features provided by the plugin from the list of features
|
798
|
+
* remove the plugin from the list of components
|
799
|
+
|
800
|
+
If _name_ corresponds to a basic component and not to a plugin, +ArgumentError+
|
801
|
+
will be raised (you can't unload a basic component).
|
802
|
+
=end
|
803
|
+
def unload_plugin name
|
804
|
+
plug = @components[name]
|
805
|
+
raise ArgumentError, "A component can't be unloaded" unless plug.is_a?(Plugin)
|
806
|
+
# plug.save_settings
|
807
|
+
plug.plugin_description.features.each do |f|
|
808
|
+
emit method("unloading_#{f}").call( plug )
|
809
|
+
end
|
810
|
+
emit unloading_component plug
|
811
|
+
plug.unload
|
812
|
+
plug.delete_later
|
813
|
+
plug.plugin_description.features.each{|f| @features.delete f}
|
814
|
+
@components.delete plug.plugin_name
|
815
|
+
end
|
816
|
+
|
817
|
+
=begin rdoc
|
818
|
+
Calls the <tt>query_close</tt> method of all the components (in arbitrary order).
|
819
|
+
As soon as one of them returns a false value, it stops and returns *false*. If all
|
820
|
+
the calls to <tt>query_close</tt> return a true value, *true* is returned.
|
821
|
+
|
822
|
+
This method is intented to be called from <tt>MainWindow#queryClose</tt>.
|
823
|
+
=end
|
824
|
+
def query_close
|
825
|
+
res = each_component do |c|
|
826
|
+
unless c.equal? self
|
827
|
+
break false unless c.query_close
|
828
|
+
end
|
829
|
+
end
|
830
|
+
res.to_bool
|
831
|
+
end
|
832
|
+
|
833
|
+
def session_data
|
834
|
+
res = {}
|
835
|
+
each_component do |c|
|
836
|
+
res.merge! c.session_data unless c.same? self
|
837
|
+
end
|
838
|
+
res
|
839
|
+
end
|
840
|
+
|
841
|
+
def restore_session data
|
842
|
+
each_component do |c|
|
843
|
+
c.restore_session data unless c.same? self
|
844
|
+
end
|
845
|
+
end
|
846
|
+
|
847
|
+
|
848
|
+
private
|
849
|
+
|
850
|
+
=begin rdoc
|
851
|
+
Searches the directories in the _dirs_ array for all the subdirectories containing
|
852
|
+
a plugin.yaml file and returns the paths of the files. Returns a hash with keys
|
853
|
+
corresponding to plugin names and values corresponding to the path of the PDF
|
854
|
+
for the plugin.
|
855
|
+
=end
|
856
|
+
def locate_plugins dirs
|
857
|
+
plugin_files = {}
|
858
|
+
dirs.reverse.each do |d|
|
859
|
+
Dir.entries(d)[2..-1].each do |f|
|
860
|
+
full_dir = File.join d, f
|
861
|
+
if File.directory?(full_dir) and File.exist?(File.join(full_dir, 'plugin.yaml'))
|
862
|
+
plugin_files[f] = File.join full_dir, 'plugin.yaml'
|
863
|
+
end
|
864
|
+
end
|
865
|
+
end
|
866
|
+
plugin_files
|
867
|
+
end
|
868
|
+
|
869
|
+
|
870
|
+
=begin rdoc
|
871
|
+
Attempts to create <tt>Ruber::PluginSpecification</tt>s for each plugin in the _plugins_
|
872
|
+
array. The path for the PDFs is taken from _files_, which is an hash with the
|
873
|
+
plugin names as keys and the PDFs paths as values.
|
874
|
+
|
875
|
+
If some PDFs are missing, <tt>MissingPlugins</tt> is raised. If some PDFs are
|
876
|
+
invalid, +InvalidPDF+ is raised. Otherwise, an array containing the <tt>PluginSpecification</tt>s
|
877
|
+
for the plugins is returned.
|
878
|
+
=end
|
879
|
+
def create_plugins_info plugins, files, dirs
|
880
|
+
missing = []
|
881
|
+
errors = []
|
882
|
+
res = plugins.map do |pl|
|
883
|
+
file = files[pl]
|
884
|
+
if file
|
885
|
+
begin PluginSpecification.new file
|
886
|
+
rescue ArgumentError, PluginSpecification::PSFError
|
887
|
+
errors << file
|
888
|
+
end
|
889
|
+
else missing << pl
|
890
|
+
end
|
891
|
+
end
|
892
|
+
raise MissingPlugins.new missing, dirs unless missing.empty?
|
893
|
+
raise InvalidPDF.new errors unless errors.empty?
|
894
|
+
res
|
895
|
+
end
|
896
|
+
|
897
|
+
end
|
898
|
+
|
899
|
+
end
|