I have the following project structure:
├── plugin.rb
├── plugins
│ ├── acrosslite.rb
│ ├── excel.rb
│ └── text.rb
and in plugin.rb
class Plugin
def self.load_all
plugins = Dir.glob(File.dirname(__FILE__) + "/plugins/*.rb")
plugins.each do |f|
begin
require f
rescue StandardError, LoadError => e
puts "Could not load #{f}: #{e}"
end
end
end
end
Is there any better way to accomplish this? I know require_relative was
meant to prevent all that fiddling about with __FILE__, but there is no
corresponding glob_relative to collect the files to be required.
martin
Just cosmetic: maybe replace
Dir.glob(File.dirname(__FILE__) + "/plugins/*.rb")
with
Dir["#{__FILE__[/\A(.*)\.[^.]*\z/, 1]}/*.rb"]
or with lookahead
Dir["#{__FILE__[/\A.*(?=\.rb\z)/]}/*.rb"]
And I would probably not catch exceptions. So then we could shorten the
method to
Dir["#{__FILE__[/\A.*(?=\.rb\z)/]}/*.rb"].each(&:require)

Cheers
robert
···
On Wed, Aug 26, 2015 at 8:42 AM, Martin DeMello <martindemello@gmail.com> wrote:
I have the following project structure:
├── plugin.rb
├── plugins
│ ├── acrosslite.rb
│ ├── excel.rb
│ └── text.rb
and in plugin.rb
class Plugin
def self.load_all
plugins = Dir.glob(File.dirname(__FILE__) + "/plugins/*.rb")
plugins.each do |f|
begin
require f
rescue StandardError, LoadError => e
puts "Could not load #{f}: #{e}"
end
end
end
end
Is there any better way to accomplish this? I know require_relative was
meant to prevent all that fiddling about with __FILE__, but there is no
corresponding glob_relative to collect the files to be required.
--
[guy, jim, charlie].each {|him| remember.him do |as, often| as.you_can -
without end}
http://blog.rubybestpractices.com/
I think your approach is fine, Martin. I have a couple minor comments.
1. You can split the process of loading plugins into two parts: discovering
plugin files and loading a single plugin. This would make the method above
look like:
def self.load_all
plugin_files.each do |plugin_file|
load_plugin(plugin_file)
end
end
You get two benefits: better testability (you can test plugin discovery w/o
loading the plugins) and readability. If the code grows you can encapsulate
both actions in objects (e.g. a plugin finder and loader).
2. I'm not sure whether your problem domain is but I think it's better to
fail when a plugin cannot be initialised for any reason. Will this work for
your case?
Best regards
Greg Navis
···
On Wed, Aug 26, 2015 at 8:42 AM, Martin DeMello <martindemello@gmail.com> wrote:
I have the following project structure:
├── plugin.rb
├── plugins
│ ├── acrosslite.rb
│ ├── excel.rb
│ └── text.rb
and in plugin.rb
class Plugin
def self.load_all
plugins = Dir.glob(File.dirname(__FILE__) + "/plugins/*.rb")
plugins.each do |f|
begin
require f
rescue StandardError, LoadError => e
puts "Could not load #{f}: #{e}"
end
end
end
end
Is there any better way to accomplish this? I know require_relative was
meant to prevent all that fiddling about with __FILE__, but there is no
corresponding glob_relative to collect the files to be required.
martin
You could use the FileList class from the Rake module
2.2.1 :001 > require 'rake'
=> true 2.2.1 :002 > FileList
=> Rake::FileList 2.2.1 :003 > path = File.join(File.expand_path('../plugins', __FILE__), '**/*.rb')
=> "/Users/rsahae/plugins/**/*.rb" 2.2.1 :004 > FileList.new(path)
=>
···
On 08/26/2015 15:12:03, Robert Klemme wrote:
On Wed, Aug 26, 2015 at 8:42 AM, Martin DeMello <martindemello@gmail.com> >wrote:
I have the following project structure:
├── plugin.rb
├── plugins
│ ├── acrosslite.rb
│ ├── excel.rb
│ └── text.rb
and in plugin.rb
class Plugin
def self.load_all
plugins = Dir.glob(File.dirname(__FILE__) + "/plugins/*.rb")
plugins.each do |f|
begin
require f
rescue StandardError, LoadError => e
puts "Could not load #{f}: #{e}"
end
end
end
end
Is there any better way to accomplish this? I know require_relative was
meant to prevent all that fiddling about with __FILE__, but there is no
corresponding glob_relative to collect the files to be required.
Just cosmetic: maybe replace
Dir.glob(File.dirname(__FILE__) + "/plugins/*.rb")
with
Dir["#{__FILE__[/\A(.*)\.[^.]*\z/, 1]}/*.rb"]
or with lookahead
Dir["#{__FILE__[/\A.*(?=\.rb\z)/]}/*.rb"]
And I would probably not catch exceptions. So then we could shorten the
method to
Dir["#{__FILE__[/\A.*(?=\.rb\z)/]}/*.rb"].each(&:require)

Cheers
robert
--
[guy, jim, charlie].each {|him| remember.him do |as, often| as.you_can -
without end}
http://blog.rubybestpractices.com/
--
Raj Sahae
408.230.8531
I have to catch exceptions because some plugins are expected to fail if
they depend on gems that the user hasn't installed. Still working on a good
way to do this, perhaps something like
begin
require 'axlsx'
rescue LoadError => e
raise PluginException("excel plugin missing some dependencies: gem
install axslx")
end
for the expected error, but I do also want to catch StandardError so that
one buggy plugin doesn't render the whole app unusable.
martin
···
On Wed, Aug 26, 2015 at 6:12 AM, Robert Klemme <shortcutter@googlemail.com> wrote:
And I would probably not catch exceptions. So then we could shorten the
method to
Dir["#{__FILE__[/\A.*(?=\.rb\z)/]}/*.rb"].each(&:require)

I think your approach is fine, Martin. I have a couple minor comments.
1. You can split the process of loading plugins into two parts:
discovering plugin files and loading a single plugin. This would make the
method above look like:
def self.load_all
plugin_files.each do |plugin_file|
load_plugin(plugin_file)
end
end
You get two benefits: better testability (you can test plugin discovery
w/o loading the plugins) and readability. If the code grows you can
encapsulate both actions in objects (e.g. a plugin finder and loader).
Thanks, that's a nice idea.
2. I'm not sure whether your problem domain is but I think it's better to
fail when a plugin cannot be initialised for any reason. Will this work for
your case?
I don't think so; I'm writing something like pandoc but for crosswords [
https://github.com/martindemello/pangrid\], so some plugins may depend on
third party gems, but be optional because, e.g., if you don't care about
exporting to excel you should not need to install an excel library just to
use pangrid.
A possible solution might be to explicitly rescue load failures and reraise
a custom MissingGem error, and then only allow that to carry on running,
but I think that even if only one plugin loads, but it's the plugin you
need, the tool should carry on and use it.
martin
···
On Wed, Aug 26, 2015 at 12:41 PM, Greg Navis <contact@gregnavis.com> wrote:
I ended up doing it like this:
# Load all the gem dependencies of a plugin
def require_for_plugin(name, gems)
missing =
gems.each do |gem|
begin
require gem
rescue LoadError => e
# If requiring a gem raises something other than LoadError let it
# propagate upwards.
missing << gem
end
end
if !missing.empty?
raise PluginDependencyError.new(name, missing)
end
end
class Plugin
def self.load_plugin(filename)
begin
require filename
rescue PluginDependencyError => e
MISSING_DEPS[e.name] = e.gems
rescue StandardError => e
FAILED << "#{File.basename(filename)}: #{e}"
end
end
end
so that I can report on missing versus failed plugins separately in the UI
martin
···
On Wed, Aug 26, 2015 at 1:49 PM, Martin DeMello <martindemello@gmail.com> wrote:
On Wed, Aug 26, 2015 at 12:41 PM, Greg Navis <contact@gregnavis.com> > wrote:
I think your approach is fine, Martin. I have a couple minor comments.
1. You can split the process of loading plugins into two parts:
discovering plugin files and loading a single plugin. This would make the
method above look like:
def self.load_all
plugin_files.each do |plugin_file|
load_plugin(plugin_file)
end
end
You get two benefits: better testability (you can test plugin discovery
w/o loading the plugins) and readability. If the code grows you can
encapsulate both actions in objects (e.g. a plugin finder and loader).
Thanks, that's a nice idea.
2. I'm not sure whether your problem domain is but I think it's better to
fail when a plugin cannot be initialised for any reason. Will this work for
your case?
I don't think so; I'm writing something like pandoc but for crosswords [
https://github.com/martindemello/pangrid\], so some plugins may depend on
third party gems, but be optional because, e.g., if you don't care about
exporting to excel you should not need to install an excel library just to
use pangrid.
A possible solution might be to explicitly rescue load failures and
reraise a custom MissingGem error, and then only allow that to carry on
running, but I think that even if only one plugin loads, but it's the
plugin you need, the tool should carry on and use it.
martin