如何使用rubygems构建模块化命令行界面?

时间:2011-08-31 01:13:02

标签: ruby plugins command-line-interface organization

我为manipulating with genome scaffolds called "Scaffolder"编写了一个命令行工具。目前,我想要使用的所有工具都被硬编码到库中。例如,这些工具“验证”或“构建”脚手架。我想将这些工具拆分成他们自己的宝石,使其更加模块化,并允许第三方编写自己的命令。

理想的情况是我运行“gem install scaffolder-validate”,然后这个gem-bundled命令将作为scaffolder的一部分提供。我知道有几个库可以很容易地构建一个命令行界面:thor,commander,gli,....但我不认为它们中的任何一个都能满足这种功能。

我的问题是我如何使用gem结构来创建用于安装这些命令的模块结构?具体如何自动检测和加载已安装的命令?在gem名称scaffolder- *中有一些前缀然后搜索rubygems?我怎么能用黄瓜测试呢?

2 个答案:

答案 0 :(得分:1)

嗯,狡猾的。我有一个简单的想法是,主要宝石只会尝试require所有其他宝石,并在它们不存在时捕获加载错误并禁用相应的功能。我在我的一个宝石中这样做。如果存在HighLine,则会提示用户输入密码,如果不存在配置文件。

begin
  require 'highline'
rescue LoadError
  highline = false
end

如果你有很多宝石,这可能会变得很丑陋......

答案 1 :(得分:1)

因此,您可以做的一件事是确定插件的规范名称,然后使用该约定动态加载内容。

您的代码看起来都在模块Scaffolder下,因此您可以按照以下规则创建插件:

  • Scaffolder个宝石必须命名为scaffold-tools-plugin- pluginname
  • 所有插件都会公开一个名为Scaffolder::Plugin:: 插件名
  • 的类
  • 该类必须符合您记录的某些接口,并可能为
  • 提供基类

鉴于此,您可以接受要加载的插件的命令行参数(假设OptionParser):

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

然后:

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

现在plugin_classes是配置的插件的类对象的Array。假设它们都符合一些常见的构造函数和一些常用方法:

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

显然,在进行这样的大量动态类加载时,您需要小心并信任您正在运行的代码。我假设这么小的域名,它不会引起关注,但只要警惕require随机码。