Can I specify dynamic dependencies for my gem based on command line input? - ruby

Intro: I'm working on a gem that, by default, will pull information out of some XML data and do some sort of processing on the document. I'm using nokogiri to parse the XML. However, I wish to allow the user to parse the XML themselves and pass in the necessary information for my data processing methods to run, in case they don't want to install nokogiri or have already parsed the XML.
Question: Is there any way to allow the user to specify, during gem installation, that they don't wish to install the nokogiri dependency? For example (very hand-wavey here),
gem install crazy_gem --no-nokogiri
and in the gemspec perhaps something like
Gem::Specification.new do |s|
...
s.add_dependency 'nokogiri' unless Proc.new { install_flags('no-nokogiri') }
...
end
[edit] I don't want to focus too much on the gemspec code above, as I know it doesn't work--it's just an example of the kind of thing I want to do. [/edit]
gem install crazy_gem --ignore-dependencies works great until there are additional dependencies.

I don't think you can do exactly what you're after, but there's apossible solution if you reframe what your gem does. Rather than a gem that by default parses some XML and processes the data, but optionally allows you to pass in the pre-parsed data, what about a gem that is mainly concerned with the processing, but optionally will parse the XML for you (if you have Nokogiri).
To do this just leave Nokogiri out of your gemspec dependencies (you could add it as a development dependency or a requirement).
Inside your code, make sure you only call require 'nokogiri' in a begin..end block with a rescue LoadError and deal with it as appropriate.

Gemspecs are turned into static files at build time, so that wouldn't work. You could try using -f which bypasses dependency checks.

Related

Can I programmatically retrieve a gem's spec from the gem itself?

I have a gem. Is there a way to retrieve the gem's specification from within the gem itself?
Specifically, I have some metadata that I want to retrieve and display when running one of the gem's executables. Is there a way to retrieve that metadata from within the gem?
Since your gem is definitionally already loaded by this point, Gem.loaded_specs['name-of-your-gem'] will give you what you want.
Documentation for Gem.loaded_specs

How can I write code that needs to run after some gem was loaded, but without requiring that gem?

I am writing a gem that "hooks" onto other gems. It's designed to work with multiple different gems that do similar things, this adds functionality onto any of them that may be present.
What my gem does at startup is something like this:
if defined?(GemAModule)
# Hook onto Gem A's observer methods
end
if defined?(GemBModule)
# Hook onto Gem B's observer methods
end
Because of this, I need this code to run after A and / or B have been loaded. But I can't require either A or B. I want this to work for people that use only A, or only B, so A and B are not in my gem's dependencies, and they can't be in require statements.
I could do this by having a: MyGem.hook() method that users call in their initializers, after they have initialized A or B or both. But I'm trying to avoid that. I want to have it so that adding my gem just "magically" work.
Is this possible? What is the best way to do this?
Can I somehow check if "a gem is in the gemfile", and in that case require the module?
You could make this work in the case where your gem is loaded first by overriding require. Combined with your code that works in the case when your gem is loaded after the other gems, this should solve the problem. Something like this:
module Kernel
alias original_require require
def require(name)
if original_require(name)
case name
when 'gem_A'
# stuff goes here
when 'gem_B'
# more stuff goes here
end
end
end
end
What I personally would do in this situation is have your users require your gem in different ways depending on which other gem they are using, for example:
# if using gem A
require 'gem_A'
require 'your_gem/gem_A_compat'
# if using gem B
require 'gem_B'
require 'your_gem/gem_B_compat'
Don't auto-detect code setup, or do anything overly complicated or "clever", you will regret it when it comes to testing and debugging (you will need both compatible gems in your development dependencies and have tests that load each in turn). Instead, document your own gem's requires so that the end user loads your gem depending on the link they wish to make.
So, assuming your gem is called my_gem, you would have files:
lib/my_gem/gem_a.rb
require 'my_gem'
require 'gem_a'
# Require Hook onto Gem A's observer methods
lib/my_gem/gem_b.rb
require 'my_gem'
require 'gem_b'
# Require Hook onto Gem B's observer methods
And you instruct the end user to require your gem as follows:
require 'my_gem/gem_a'
or . . .
require 'my_gem/gem_b'
as needed.
This is no more work for your gem user, and a lot easier to test, develop and debug for you and developers that use your gem.

How do I view my gem's (or any other gem's) gem.description and gem.summary texts (.gemspect items) from the command line?

The .gemspec file I so carefully documented when I created my gem, how do I access its contents? Specifically I'd like to access the gem.description and gem.summary entries because I put some very useful info in there.
I hope there is a better answer than this, reading YAML can be annoying, but you can use gem specification GEMNAME. This will spit out a lot of information, you might want to pipe that to grep.
You can provide a slightly more readable output by piping the output of gem specification to something to pull out what you want.
This can be much more legible, especially when the description is a multiline string:
% gem specification rack description | ruby -ryaml -e 'puts YAML.load(STDIN.read)'
Rack provides a minimal, modular and adaptable interface for developing
web applications in Ruby. By wrapping HTTP requests and responses in
the simplest way possible, it unifies and distills the API for web
servers, web frameworks, and software in between (the so-called
middleware) into a single method call.
Also see http://rack.github.com/.
% gem specification hoe description | ruby -ryaml -e 'puts YAML.load(STDIN.read)'
Hoe is a rake/rubygems helper for project Rakefiles. It helps you
manage, maintain, and release your project and includes a dynamic
plug-in system allowing for easy extensibility. Hoe ships with
plug-ins for all your usual project tasks including rdoc generation,
testing, packaging, deployment, and announcement..
See class rdoc for help. Hint: `ri Hoe` or any of the plugins listed
below.
For extra goodness, see: http://seattlerb.rubyforge.org/hoe/Hoe.pdf

How to work with classes and dependences in ruby

I have changed this question to better reflect what it is I do not understand.
For example if I try to access the methods in the railties class AppBuilder.
require 'rails/generators/rails/app/app_generator'
g = Rails::AppBuilder.new
puts g.rakefile.inspect
I get an error message activesupport-3.1.3/lib/active_support/secure_random.rb:5:in `': uninitialized constant SecureRandom (NameError)
I do not understand this. Should not each class be "independent" from other classes? Is that not the whole point of object oriented programing?
And now If it is not so more importantly how can I figure out what dependences I need to add? Is it some kind of workflow to solve this? Can I somehow figure out what dependencies to add looking at the documentation? Do this problem have something to do with load path? Can I load all dependences from a gem or rails or whatever? I just don't get it!
doc: http://api.rubyonrails.org/classes/Rails/AppBuilder.html github: https://gist.github.com/rails/rails/blob/master/railties/lib/rails/generators/rails/app/app_generator.rb
there is no easy way to find out which dependencies are used within AppBuilder, cause most of the dependencies are setup somewhere else. so most of the times you need to do some trial and error to get the dependencies right.
the code that you posted has bad style. please get familiar with how to write ruby code properly. buy yourself a book i.e. eloquent ruby or just start reading ruby blogs.
dependencies in ruby is quite simple. every file that you require will be loaded and the load will recurse through the files and then load other requires. the loading will only work if all the required files are on the load path. this load path is similar to your system path and you can add directories to it to tell ruby where to look for files.
in general, there are dedicated entry-points for libraries and their dependencies. those are normally documented, so that if you use them, you get all dependencies right from the beginning. an example for this would be to require 'rails' in order to use rails or require 'active_support/all' if you just want to use active-support. if you wan't to chery-pick files/classes than you are on your own finding out which other classes you need. that part has nothing to do with oop, it's more an dependency-issue (other languages have explicit decleration of dependencies).
in your case, the next step would be to add require "securerandom" to the beginning of your file and then check wich error comes up next.

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

Resources