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