[ANN] LibInject 0.1.0: Small Pieces Smooshed Together

(Francis Hwang) #1

I've just released the first version of LibInject, which is a developer
tool for injecting external dependencies into a Ruby file. More
specifically, it finds require statements that point to files that
aren't in the standard library, expands those files, and drops the raw
text into the original file.

http://libinject.rubyforge.org/

Why would somebody do this instead of just using a plain old require
statement? One reason might be because you want to write a one-file
script that can also have external dependencies. I wrote LibInject for
use with FeedBlender, which is intended as a one-file script aimed at
Ruby newbies who might not even know how to install from .tar.gz files
or from RubyGems. With LibInject, I can have external dependencies in
FeedBlender, use a Rake task to inject those libraries into
feedblender.rb, and then distribute the end result.

For example, let's say you've three files, a.rb, b.rb, and c.rb. a.rb
requires b.rb and c.rb, and b.rb requires c.rb:

a.rb:

  require 'b'
  require 'c'

  class A; end

b.rb:

  require 'c'

  class B; end

c.rb:

  class C; end

If you wanted to distribute a version of a.rb without any external
dependencies, this is how you'd run LibInject:

  require 'libinject'

  puts LibInject.lib_inject( File.open( 'a.rb' ) )

  # We could also pass LibInject.lib_inject a String, like so:
  # contents = File.open( 'a.rb' ) do |f| f.gets( nil ); end
  # puts LibInject.lib_inject( contents )

And this is what you'll get:

···

#
v---------v---------v---------v---------v---------v---------v---------v
  # LibInject: begin 'b' library injection
  #
v---------v---------v---------v---------v---------v---------v---------v

  #
v---------v---------v---------v---------v---------v---------v---------v
  # LibInject: begin 'c' library injection
  #
v---------v---------v---------v---------v---------v---------v---------v

  class C; end

  #
^---------^---------^---------^---------^---------^---------^---------^
  # LibInject: end 'c' library injection
  #
^---------^---------^---------^---------^---------^---------^---------^

  class B; end

  #
^---------^---------^---------^---------^---------^---------^---------^
  # LibInject: end 'b' library injection
  #
^---------^---------^---------^---------^---------^---------^---------^

  class A; end

The code looks sort of messy, but now it's all in one file.

Anyway, it's a strange little script, and I'm curious as to whether
anybody else will get some use out of it. Let me know if you do!

F.

(Jim Weirich) #2

Cool! Does it handle things like this:

  class Command
    def create_option_parser
      require 'optparse'
      @parser = OptionParser.new
      ...
    end
  end

···

On Thursday 11 August 2005 11:31 pm, Francis Hwang wrote:

I've just released the first version of LibInject, which is a developer
tool for injecting external dependencies into a Ruby file. More
specifically, it finds require statements that point to files that
aren't in the standard library, expands those files, and drops the raw
text into the original file.

http://libinject.rubyforge.org/

Why would somebody do this instead of just using a plain old require
statement? One reason might be because you want to write a one-file
script that can also have external dependencies. I wrote LibInject for
use with FeedBlender, which is intended as a one-file script aimed at
Ruby newbies who might not even know how to install from .tar.gz files
or from RubyGems. With LibInject, I can have external dependencies in
FeedBlender, use a Rake task to inject those libraries into
feedblender.rb, and then distribute the end result.

For example, let's say you've three files, a.rb, b.rb, and c.rb. a.rb
requires b.rb and c.rb, and b.rb requires c.rb:

a.rb:

  require 'b'
  require 'c'

  class A; end

b.rb:

  require 'c'

  class B; end

c.rb:

  class C; end

If you wanted to distribute a version of a.rb without any external
dependencies, this is how you'd run LibInject:

  require 'libinject'

  puts LibInject.lib_inject( File.open( 'a.rb' ) )

  # We could also pass LibInject.lib_inject a String, like so:
  # contents = File.open( 'a.rb' ) do |f| f.gets( nil ); end
  # puts LibInject.lib_inject( contents )

And this is what you'll get:

  #
v---------v---------v---------v---------v---------v---------v---------v
  # LibInject: begin 'b' library injection
  #
v---------v---------v---------v---------v---------v---------v---------v

  #
v---------v---------v---------v---------v---------v---------v---------v
  # LibInject: begin 'c' library injection
  #
v---------v---------v---------v---------v---------v---------v---------v

  class C; end

  #
^---------^---------^---------^---------^---------^---------^---------^
  # LibInject: end 'c' library injection
  #
^---------^---------^---------^---------^---------^---------^---------^

  class B; end

  #
^---------^---------^---------^---------^---------^---------^---------^
  # LibInject: end 'b' library injection
  #
^---------^---------^---------^---------^---------^---------^---------^

  class A; end

The code looks sort of messy, but now it's all in one file.

Anyway, it's a strange little script, and I'm curious as to whether
anybody else will get some use out of it. Let me know if you do!

F.

--
-- Jim Weirich jim@weirichhouse.org http://onestepback.org
-----------------------------------------------------------------
"Beware of bugs in the above code; I have only proved it correct,
not tried it." -- Donald Knuth (in a memo to Peter van Emde Boas)

(Joel VanderWerf) #3

Francis Hwang wrote:

I've just released the first version of LibInject, which is a
developer tool for injecting external dependencies into a Ruby file.
More specifically, it finds require statements that point to files
that aren't in the standard library, expands those files, and drops
the raw text into the original file.

http://libinject.rubyforge.org/

That's nice. Has anybody thought about a custom #require that looks in
the DATA of the main file, and if it finds a table of filename=>offsets
there, loads that file from between those offsets in DATA, else
falls back to Kernel#require ?

Anyway, here's a little proof of concept, called darb (DATA-Archived
RuBy script). Note that this solves a different problem than Francis's
LibInject: you provide list of files, and it generates an archive from
which those files can be loaded on demand. Francis's solution finds the
files for you, but they are all loaded automatically. (Also, note that
darb doesn't unpack the archive, as tar2rubyscript does. Everything
stays in the original archive file.)

What does darb mean? According to http://www.bartleby.com/68/2/1602.html:

darb is an Americanism probably nearly obsolete today, a slang word
from the 1920s meaning "something or someone very handsome, valuable,
attractive, or otherwise excellent."

No comment!

darb (3.06 KB)

transcript.txt (625 Bytes)

···

--
      vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

(Francis Hwang) #4

Jim Weirich wrote:

Cool! Does it handle things like this:

  class Command
    def create_option_parser
      require 'optparse'
      @parser = OptionParser.new
      ...
    end
  end

Nope. It really only handles the simplest, most common case, of the
require statement being on its own line outside of any class/method
definitions. I could try to make it smart enough to pull require
statements out of class & method definitions, but I'm not certain if
it'd be worth the effort. Are there actually many libs out there that
use requires this way? I personally try to avoid just 'cause I think it
makes the code look messier, but obviously that's sort of just a
personal style thing.

f.

(Austin Ziegler) #5

I generally have all of my includes outside of methods, but there are
definite times when it is advantageous to do that in a method (e.g.,
when your code requires something 'big' and you want to delay loading
as long as possible). PDF::Writer *sort-of* does that with REXML, only
requiring it if on a Unix/Linux platform.

-austin

···

On 8/12/05, Francis Hwang <sera@fhwang.net> wrote:

Jim Weirich wrote:
> Cool! Does it handle things like this:
>
> class Command
> def create_option_parser
> require 'optparse'
> @parser = OptionParser.new
> ...
> end
> end

Nope. It really only handles the simplest, most common case, of the
require statement being on its own line outside of any class/method
definitions. I could try to make it smart enough to pull require
statements out of class & method definitions, but I'm not certain if
it'd be worth the effort. Are there actually many libs out there that
use requires this way? I personally try to avoid just 'cause I think it
makes the code look messier, but obviously that's sort of just a
personal style thing.

--
Austin Ziegler * halostatue@gmail.com
               * Alternate: austin@halostatue.ca

(Jim Weirich) #6

Francis Hwang said:

Jim Weirich wrote:

Cool! Does it handle things like this:

  class Command
    def create_option_parser
      require 'optparse'
      @parser = OptionParser.new
      ...
    end
  end

Nope. It really only handles the simplest, most common case, of the
require statement being on its own line outside of any class/method
definitions. I could try to make it smart enough to pull require
statements out of class & method definitions, but I'm not certain if
it'd be worth the effort. Are there actually many libs out there that
use requires this way? I personally try to avoid just 'cause I think it
makes the code look messier, but obviously that's sort of just a
personal style thing.

My personal style (in general) is to put all the requires at the top level
at the beginning. However in RubyGems we use the above idiom often to
avoid requiring things that are not needed. The optparse is a bad example
because it is always (eventually) needed, but things like the compression
libraries or the network http libraries are only required for certain
variations of the gem command. By requiring them inline at the place they
are used means they are not loaded when they are not needed.

···

--
-- Jim Weirich jim@weirichhouse.org http://onestepback.org
-----------------------------------------------------------------
"Beware of bugs in the above code; I have only proved it correct,
not tried it." -- Donald Knuth (in a memo to Peter van Emde Boas)

(Francis Hwang) #7

Jim Weirich wrote:

Francis Hwang said:
> Jim Weirich wrote:
>> Cool! Does it handle things like this:
>>
>> class Command
>> def create_option_parser
>> require 'optparse'
>> @parser = OptionParser.new
>> ...
>> end
>> end
>
> Nope. It really only handles the simplest, most common case, of the
> require statement being on its own line outside of any class/method
> definitions. I could try to make it smart enough to pull require
> statements out of class & method definitions, but I'm not certain if
> it'd be worth the effort. Are there actually many libs out there that
> use requires this way? I personally try to avoid just 'cause I think it
> makes the code look messier, but obviously that's sort of just a
> personal style thing.

My personal style (in general) is to put all the requires at the top level
at the beginning. However in RubyGems we use the above idiom often to
avoid requiring things that are not needed. The optparse is a bad example
because it is always (eventually) needed, but things like the compression
libraries or the network http libraries are only required for certain
variations of the gem command. By requiring them inline at the place they
are used means they are not loaded when they are not needed.

Thanks for the input, Jim & Austin. Okay, I'll put a fix for this in my
queue.

f.

(Florian Frank) #8

Jim Weirich wrote:

My personal style (in general) is to put all the requires at the top level
at the beginning. However in RubyGems we use the above idiom often to
avoid requiring things that are not needed.

You can have both, if you do
  autload :OptionParser, 'optparse'
at the beginning. 'optparse' is only loaded, when the OptionParser
constant is accessed and doesn't exist.

It's also possible to do something like

module AllMyStuff
  module A
    autoload :MyClass, 'all_my_stuff/a/my_class'
  end
end

and

a = AllMyStuff::a::MyClass.new

for more complicated setups, because autoload is a method of Kernel and Module.

···

--
Florian Frank

(Jim Weirich) #9

Cool! I never stumbled across autoload before. I'm going to give it a try.

···

On Friday 12 August 2005 08:11 pm, Florian Frank wrote:

You can have both, if you do
autload :OptionParser, 'optparse'

--
-- Jim Weirich jim@weirichhouse.org http://onestepback.org
-----------------------------------------------------------------
"Beware of bugs in the above code; I have only proved it correct,
not tried it." -- Donald Knuth (in a memo to Peter van Emde Boas)