开发者

How can I build a modular command-line interface using rubygems?

I've written a command-line tool for manipulating with genome scaffolds called "Scaffolder". At the moment all the tools I want to use are hard-coded into the library. For instance these tools "validate" or "build" the scaffold. I'd like to split these tools out into their own gems, make it more modular, and to allow third parties to write their own commands.

The ideal case would be that I run "gem install scaffolder-validate" and this gem-bundled command would then be available as part of scaffolder. I know a couple of libraries make it easy to build a command-line interface: thor, commander, gli, .... However I don't think any of them cater for this type of functionality.

My question is how can I use a gem structure to create a module structure for installing these commands? Specifically how can 开发者_JAVA技巧the installed commands be auto-detected and loaded? With some prefix in the gem name scaffolder-* then searching rubygems? How could I test this with cucumber?


So, one thing you can do is to decide on a canonical name for your plugins, and then use that convention to load things dynamically.

It looks like your code is all under a module Scaffolder, so you can create plugins following the following rules:

  • Scaffolder gems must be named scaffold-tools-plugin-pluginname
  • All plugins expose one class, named Scaffolder::Plugin::Pluginname
  • That class must conform to some interface you document, and possibly provide a base class for

Given that, you can then accept a command-line argument of the plugins to load (assuming OptionParser):

plugin_names = []
opts.on('--plugins PLUGINS','List of plugins') do |plug|
  plugin_names << plug
end

Then:

plugin_classes = []
plugin_names.each do |plugin_name|
  require "scaffold-tools-plugin-#{plugin_name}"
  plugin_classes << Kernel.const_get("Scaffold::Plugin::#{plugin_name}")
end

Now plugin_classes is an Array of the class objects for the plugins configured. Supposing they all conform to some common constructor and some common methods:

plugin_classes.each do |plugin_class|
  plugin = plugin_class.new(args)
  plugin.do_its_thing(other,args)
end

Obviously, when doing a lot of dynamic class loading like this, you need to be careful and trust the code that you are running. I'm assuming for such a small domain, it won't be a concern, but just be wary of requireing random code.


Hm, tricky one. One simple idea I have is that the main gem just tries to require all the others and catches the load error when they are not there and disables the respective features. I do this in one of my gems. If HighLine is present, the user gets prompted for a password, if it isn't there has to be a config file.

begin
  require 'highline'
rescue LoadError
  highline = false
end

If you have a lot of gems this could become ugly though...

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜