plugin_manager 1.1 → 1.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -54,6 +54,9 @@ The code in the appropriate plugins will be loaded and you will then have availa
54
54
  # plugins that raised exceptions while being loaded
55
55
  manager.plugins_with_errors
56
56
 
57
+ NB. There is a BIG difference between this and say, Rubygems, in that plugin_manager will ONLY EVER
58
+ LOAD THE MOST RECENT VERSION OF A PLUGIN. Older versions essentially DON'T EXIST from the point of view
59
+ of the plugin_manager.
57
60
 
58
61
  License
59
62
  =======
data/Rakefile ADDED
@@ -0,0 +1,22 @@
1
+ require "rubygems"
2
+ require "rake/rdoctask"
3
+
4
+ require "spec"
5
+ require "spec/rake/spectask"
6
+ Spec::Rake::SpecTask.new do |t|
7
+ t.spec_opts = %w(--format specdoc --colour)
8
+ t.libs = ["spec"]
9
+ end
10
+
11
+ task :default => ["spec"]
12
+
13
+ # Generate documentation
14
+ Rake::RDocTask.new do |rd|
15
+ rd.main = "README.md"
16
+ rd.rdoc_files.include("README.md", "lib/**/*.rb")
17
+ rd.rdoc_dir = "rdoc"
18
+ end
19
+
20
+ desc 'Clear out RDoc and generated packages'
21
+ task :clean => [:clobber_rdoc, :clobber_package] do
22
+ end
@@ -6,75 +6,199 @@ require 'plugin_manager/plugin_definition'
6
6
  require 'plugin_manager/definition_builder'
7
7
 
8
8
  class PluginManager
9
- attr_reader :unreadable_definitions, :plugins_with_errors, :loaded_plugins, :unloaded_plugins
9
+ attr_reader :unreadable_definitions, :plugins_with_errors, :loaded_plugins, :unloaded_plugins, :output
10
10
 
11
- def initialize
12
- @unloaded_plugins = []
13
- @loaded_plugins = []
11
+ class << self
12
+ attr_accessor :current
13
+ end
14
+
15
+ def initialize(output = STDOUT)
16
+ @plugins = []
17
+ @unloaded_plugins = []
18
+ @loaded_plugins = []
14
19
  @unreadable_definitions = []
15
- @plugins_with_errors = []
20
+ @plugins_with_errors = []
21
+ @output = output
16
22
  end
17
23
 
24
+ def on_load(&block)
25
+ @load_observer = block
26
+ end
27
+
18
28
  def plugins
19
- @unloaded_plugins + @loaded_plugins
29
+ @plugins
20
30
  end
31
+
32
+ def plugin_objects
33
+ @loaded_plugins.map {|definition| definition.object}
34
+ end
35
+
36
+ def objects_implementing(method_name)
37
+ plugin_objects.select {|obj| obj.respond_to?(method_name) }
38
+ end
39
+
40
+ class Dependency
41
+ attr_reader :required_name
42
+ attr_reader :required_version
43
+
44
+ def initialize(plugin_manager, required_name, required_version)
45
+ @plugin_manager = plugin_manager
46
+ @required_name = required_name
47
+ @required_version = required_version
48
+ end
49
+
50
+ def satisfied?
51
+ if loaded_plugin = @plugin_manager.loaded_plugins.detect {|pl| pl.name == required_name }
52
+ PluginManager.compare_version(required_version, loaded_plugin.version)
53
+ end
54
+ end
55
+
56
+ def inspect
57
+ "dep(#{required_name} #{required_version})"
58
+ end
59
+ end
60
+
61
+ def add_gem_plugin_source
62
+ all_gem_names = Gem::SourceIndex.from_installed_gems.map {|n, _| n}
63
+ redcar_plugin_gem_names = all_gem_names.select {|n| n =~ /^redcar-/}
64
+
65
+ definition_files = redcar_plugin_gem_names.map do |gem_name|
66
+ [gem_name, ENV["GEM_HOME"] + "/gems/" + gem_name + "/plugin.rb"]
67
+ end
68
+
69
+ definition_files = definition_files.select do |name, definition_file|
70
+ File.exist?(definition_file)
71
+ end
72
+
73
+ if definition_files.any?
74
+ gem_names = definition_files.map {|n, _| n }
75
+ @output.puts "[PluginManager] found gem plugins #{gem_names.inspect}" if ENV["PLUGIN_DEBUG"]
76
+ end
77
+
21
78
 
79
+ add_definition_files(definition_files.map {|_, f| f})
80
+ end
81
+
22
82
  def add_plugin_source(directory)
23
83
  definition_files = Dir[File.join(File.expand_path(directory), "*", "plugin.rb")]
24
84
  definition_files.reject! {|f| plugins.any? {|pl| pl.definition_file == File.expand_path(f) } }
25
- new_definitions =
26
- definition_files.map do |file|
27
- begin
28
- definition = instance_eval(File.read(file))
29
- definition.definition_file = File.expand_path(file)
30
- definition
31
- rescue Object => e
32
- puts "Unreadable plugin definition: #{file}"
33
- puts " " + e.message
34
- puts e.backtrace.map {|l| " " + l }
35
- @unreadable_definitions << file
36
- nil
85
+
86
+ add_definition_files(definition_files)
87
+ end
88
+
89
+ def add_definition_files(definition_files)
90
+ definition_files.each do |file|
91
+ begin
92
+ PluginManager.current = self
93
+ definition = instance_eval(File.read(file))
94
+ PluginManager.current = nil
95
+ definition.definition_file = File.expand_path(file)
96
+ if already_with_that_name = @plugins.detect {|pl| pl.name == definition.name }
97
+ if already_with_that_name.version.to_f < definition.version.to_f
98
+ @unloaded_plugins.delete(already_with_that_name)
99
+ @plugins.delete(already_with_that_name)
100
+ @unloaded_plugins << definition
101
+ @plugins << definition
102
+ end
103
+ else
104
+ @unloaded_plugins << definition
105
+ @plugins << definition
37
106
  end
38
- end.compact.sort_by {|p| p.name.downcase }
39
- @unloaded_plugins += new_definitions
107
+ rescue Object => e
108
+ @output.puts "Unreadable plugin definition: #{file}"
109
+ @output.puts " " + e.message
110
+ @output.puts e.backtrace.map {|l| " " + l }
111
+ @unreadable_definitions << file
112
+ nil
113
+ end
114
+ end
115
+
116
+ @plugins = @plugins.sort_by {|pl| pl.name }
117
+ @unloaded_plugins = @unloaded_plugins.sort_by {|pl| pl.name }
40
118
  end
41
119
 
42
- def load
43
- previous_length = @unloaded_plugins.length + 1
44
- while previous_length > @unloaded_plugins.length
45
- previous_length = @unloaded_plugins.length
46
- if plugin = next_to_load
47
- begin
48
- puts "[PluginManager] loading #{plugin.name}" if ENV["PLUGIN_DEBUG"]
49
- plugin.load
50
- @loaded_plugins << plugin
51
- rescue Object => e
52
- puts "Error loading plugin: #{plugin.inspect}"
53
- puts " " + e.message
54
- puts e.backtrace.map {|l| " " + l }
55
- @plugins_with_errors << plugin
120
+ def load(*plugin_names)
121
+ if plugin_names.empty?
122
+ return load_maximal
123
+ else
124
+ target_dependencies = plugin_names.map do |n|
125
+ unless result = latest_version_by_name(n)
126
+ raise "can't find plugin named #{n}"
56
127
  end
57
- @unloaded_plugins.delete(plugin)
128
+ Dependency.new(self, n, ">0")
129
+ end
130
+ end
131
+ remaining_to_load = expand_dependencies(target_dependencies)
132
+ while remaining_to_load.length > 0
133
+ previous_length = remaining_to_load.length
134
+ if plugin = next_to_load(remaining_to_load)
135
+ load_plugin(plugin)
136
+ remaining_to_load = remaining_to_load.reject {|dep| dep.required_name == plugin.name }
137
+ else
138
+ puts "no plugin to load from #{remaining_to_load.inspect}"
139
+ return
58
140
  end
141
+ new_length = remaining_to_load.length
59
142
  end
60
143
  end
61
144
 
62
- private
145
+ def latest_version_by_name(name)
146
+ @plugins.select {|pl| pl.name == name }.sort_by {|pl| pl.version }.last
147
+ end
63
148
 
64
- def next_to_load
65
- # this ordering ensures we try the most recent version of a plugin first
66
- remaining_plugins = @unloaded_plugins.sort_by {|pl| pl.version }.reverse
67
-
68
- remaining_plugins.detect do |d|
69
- next if @loaded_plugins.map {|pl| pl.name }.include?(d.name)
70
- (d.dependencies||[]).all? do |dep|
71
- req_name, req_ver = *dep
72
- @loaded_plugins.detect do |d1|
73
- d1.name == req_name and
74
- PluginManager.compare_version(req_ver, d1.version)
149
+ def load_plugin(plugin)
150
+ begin
151
+ @output.puts "[PluginManager] loading #{plugin.name}" if ENV["PLUGIN_DEBUG"]
152
+ plugin.load
153
+ if @load_observer
154
+ @load_observer.call(plugin)
155
+ end
156
+ @loaded_plugins << plugin
157
+ rescue Object => e
158
+ @output.puts "Error loading plugin: #{plugin.inspect}"
159
+ @output.puts " " + e.message
160
+ @output.puts e.backtrace.map {|l| " " + l }
161
+ @plugins_with_errors << plugin
162
+ end
163
+ @unloaded_plugins.delete(plugin)
164
+ end
165
+
166
+ def load_maximal
167
+ while ready_plugin = @unloaded_plugins.detect {|pl| pl.dependencies.all? {|dep| dep.satisfied? }}
168
+ load_plugin(ready_plugin)
169
+ end
170
+ @load_observer = nil # After loading all possible plugins, remove the load observer
171
+ end
172
+
173
+ def expand_dependencies(dependency_array)
174
+ previous_length = dependency_array.length
175
+ new_dependency_array = dependency_array.map do |dep|
176
+ if pl = latest_version_by_name(dep.required_name)
177
+ [dep, pl.dependencies]
178
+ else
179
+ dep
180
+ end
181
+ end.flatten.uniq
182
+ if new_dependency_array.length > previous_length
183
+ expand_dependencies(new_dependency_array)
184
+ else
185
+ new_dependency_array
186
+ end
187
+ end
188
+
189
+ def next_to_load(dependency_array)
190
+ hash = Hash.new {|h,k| h[k] = []}
191
+ dependency_array.each {|dep| hash[dep.required_name] << dep.required_version}
192
+ hash.each do |name, version_requirements|
193
+ if candidate_for_loading = unloaded_plugins.detect {|pl| pl.name == name}
194
+ all_requirements_met = version_requirements.all? do |version_requirement|
195
+ PluginManager.compare_version(version_requirement, candidate_for_loading.version)
75
196
  end
197
+ all_candidate_deps_met = candidate_for_loading.dependencies.all? {|dep| dep.satisfied?}
198
+ return candidate_for_loading if all_requirements_met and all_candidate_deps_met
76
199
  end
77
200
  end
201
+ nil
78
202
  end
79
203
 
80
204
  def self.compare_version(required, got)
@@ -98,7 +222,7 @@ class PluginManager
98
222
  md[2] != got
99
223
  end
100
224
  else
101
- puts "don't recognize version string: #{required.inspect}"
225
+ @output.puts "don't recognize version string: #{required.inspect}"
102
226
  end
103
227
  end
104
228
  end
@@ -27,10 +27,28 @@ class PluginManager
27
27
  @definition.file = values
28
28
  end
29
29
 
30
+ def install(*args)
31
+ if args.length == 1
32
+ if Hash === args.first
33
+ hash = args.first
34
+ else
35
+ hash = { args.first => File.basename(args.first) }
36
+ end
37
+ elsif args.length == 2
38
+ prefix = args.first
39
+ if Hash === args[1]
40
+ hash = Hash[args[1].map {|k, v| [prefix + k, v]}]
41
+ elsif Array === args[1]
42
+ hash = Hash[args[1].map {|path| [prefix + path, File.basename(path) ] } ]
43
+ end
44
+ end
45
+ @definition.resources.merge!(hash)
46
+ end
47
+
30
48
  def dependencies(*deps)
31
49
  @definition.dependencies ||= []
32
50
  deps.each_slice(2) do |name, ver|
33
- @definition.dependencies << [name, ver]
51
+ @definition.dependencies << PluginManager::Dependency.new(PluginManager.current, name, ver)
34
52
  end
35
53
  end
36
54
  end
@@ -5,22 +5,35 @@ class PluginManager
5
5
  :version,
6
6
  :object_string,
7
7
  :file,
8
- :dependencies,
9
- :definition_file
8
+ :definition_file,
9
+ :resources
10
10
 
11
11
  def inspect1
12
- "<Plugin #{name} #{version} depends:[#{(dependencies||[]).map{|dep| dep.join("")}.join(", ")}] #{required_files.length} files>"
12
+ "<Plugin #{name} #{version} depends:#{dependencies.inspect} #{required_files.length} files>"
13
13
  end
14
14
 
15
15
  def inspect
16
16
  inspect1
17
17
  end
18
18
 
19
+ def resources
20
+ @resources ||= {}
21
+ end
22
+
19
23
  def required_files
20
24
  @required_files ||= []
21
25
  end
22
26
 
27
+ def dependencies
28
+ @dependencies ||= []
29
+ end
30
+
31
+ def load_time
32
+ @load_time
33
+ end
34
+
23
35
  def load
36
+ s = Time.now
24
37
  required_files.each {|file| $".delete(file) }
25
38
  load_file = File.expand_path(File.join(File.dirname(definition_file), file))
26
39
  $:.unshift(File.dirname(load_file))
@@ -31,6 +44,7 @@ class PluginManager
31
44
  if object.respond_to?(:loaded)
32
45
  object.loaded
33
46
  end
47
+ @load_time = Time.now - s
34
48
  end
35
49
 
36
50
  def object
metadata CHANGED
@@ -1,67 +1,70 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: plugin_manager
3
3
  version: !ruby/object:Gem::Version
4
- version: "1.1"
4
+ prerelease:
5
+ version: "1.2"
5
6
  platform: ruby
6
7
  authors:
7
- - Daniel Lucraft
8
+ - Daniel Lucraft
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
12
 
12
- date: 2010-01-17 00:00:00 +00:00
13
+ date: 2011-06-12 00:00:00 +01:00
13
14
  default_executable:
14
15
  dependencies:
15
- - !ruby/object:Gem::Dependency
16
- name: rspec
17
- type: :development
18
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
20
- requirements:
21
- - - ">="
22
- - !ruby/object:Gem::Version
23
- version: "0"
24
- version:
16
+ - !ruby/object:Gem::Dependency
17
+ name: rspec
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: "0"
25
+ type: :development
26
+ version_requirements: *id001
25
27
  description:
26
28
  email: dan@fluentradical.com
27
- executables:
28
- - plugin_manager
29
+ executables: []
30
+
29
31
  extensions: []
30
32
 
31
33
  extra_rdoc_files:
32
- - README.md
34
+ - README.md
33
35
  files:
34
- - README.md
35
- - lib/plugin_manager/definition_builder.rb
36
- - lib/plugin_manager/plugin.rb
37
- - lib/plugin_manager/plugin_definition.rb
38
- - lib/plugin_manager.rb
36
+ - README.md
37
+ - Rakefile
38
+ - lib/plugin_manager.rb
39
+ - lib/plugin_manager/definition_builder.rb
40
+ - lib/plugin_manager/plugin.rb
41
+ - lib/plugin_manager/plugin_definition.rb
39
42
  has_rdoc: true
40
43
  homepage: http://github.com/danlucraft/plugin_manager
41
44
  licenses: []
42
45
 
43
46
  post_install_message:
44
47
  rdoc_options:
45
- - --main
46
- - README.md
48
+ - --main
49
+ - README.md
47
50
  require_paths:
48
- - lib
51
+ - lib
49
52
  required_ruby_version: !ruby/object:Gem::Requirement
53
+ none: false
50
54
  requirements:
51
- - - ">="
52
- - !ruby/object:Gem::Version
53
- version: "0"
54
- version:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
55
58
  required_rubygems_version: !ruby/object:Gem::Requirement
59
+ none: false
56
60
  requirements:
57
- - - ">="
58
- - !ruby/object:Gem::Version
59
- version: "0"
60
- version:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
61
64
  requirements: []
62
65
 
63
66
  rubyforge_project:
64
- rubygems_version: 1.3.5
67
+ rubygems_version: 1.5.1
65
68
  signing_key:
66
69
  specification_version: 3
67
70
  summary: A Ruby plugin loader
data/bin/plugin_manager DELETED
@@ -1,4 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require File.join(File.dirname(__FILE__), "..", "lib", "plugin_manager")
4
-