[Rant] Plugin/generator guide

Stefan Lang langstefan at gmx.at
Sat Dec 17 15:11:45 EST 2005


On Saturday 17 December 2005 19:56, Kevin Burge wrote:
> Stefan,
>
> One thing that would really be useful would be examples on writing
> plugins (or at least more comments in the source) that explains how
> to extend rant with my own "gen Package" or "file " type
> generators/tasks. That way end-users can be consistent in syntax,
> rather than writing functions for doing the same thing as a
> generator would do.
>
> i.e.
>
> I have:
>
> def gen_project_deploy()
>   ...
> end
>
> defined, when I'd rather do:
>
> gen Deploy, :myproject do
> end
>
> ..etc..

I'll try to explain enough for you to understand. Note that most
of this is not officially supported. Sooner or later there will
be an official and documented way to extend Rant. Anyway, to help
you now:

* All Rantfiles (i.e. the toplevel Rantfile, sub.rant files,
  files read with the source method) are evaluated in the context
  of a special object (via instance_eval). This object defines
  the methods:
      task
      file
      gen
      rant          # accessor to the rant application object
                    # which holds all tasks, variables, does
                    # argument processing etc.
      import
      sys
      desc
      source
      var
      make
      subdirs
  
  These methods are not global (i.e. Kernel or Object methods).
  The context object is available as "rant.context". So in an
  Rantfile the code "rant.context == self" is true.

The "gen" method is mainly a redirection mechanism. It requires
an object that responds to "rant_gen" as first argument.

To explore the "gen" functionality, run this Rantfile:

    obj = Object.new
    def obj.rant_gen(rant, ch, args, &block)
        # rant is the Rant application managing the build
        # rant.context is the context in which Rantfiles are
        # evaluated.
        p ch    # a hash containing file and line number
                # of the "gen" call. ( e.g. {:ln => 5, :file => 
"/home/stefan/Rantfile"} )
        p args  # an array of the additional arguments to "gen"
        p block # the do end block given to "gen", or nil

        # usually tasks are defined now via rant.context (alias 
rant.cx)
    end

    gen obj
    gen obj, "foo", "bar"
    gen obj, "foo" => "bar" do end

    task :default

The generators defined by Rant are class objects, so the rant_gen
method of this generators are class methods. The generator classes
live in the module Rant::Generators (not required) and are usually
defined in a file under rant/import/ in Ruby's $LOAD_PATH.

Let's create a simple My::FileCopy generator. Usage should be:

    import "my/filecopy"

    gen My::FileCopy, "dest" => "src"

which should be equivalent to:

    file "dest" => "src" do |t|
        sys.cp t.source, t.name
    end

Save the following code in a file rant/import/my/filecopy.rb
somewhere in a $LOAD_PATH directory:
#############################################################

module Rant
    # This method is called on an (more exactly: the first)
    #   import "my/filecopy"
    # statement from an Rantfile. Note that the slash
    # is translated into a double underscore.
    def self.init_import_my__filecopy(rant, *rest)
        # With current Rant versions, rest is always empty,
        # just ignore it.

        # rant is the Rant application managing the current build
        # rant.context is the Rantfile context
        cx = rant.context
        # you could initialize variables via cx.var[:foo] = "bar"
        # or call sys methods, like cx.sys.touch "foo"
        # or load other imports, e.g. cx.import "package/zip"
        cx.import "subfile"

        # This puts statement is just for demonstration
        puts 'initialized import "my/filecopy"'
    end

    # Note that we could define anything in this file. Rant
    # doesn't require that we define a FileCopy class
    # just because this file is called filecopy.rb!

    module Generators::My; end
    class Generators::My::FileCopy
        def self.rant_gen(rant, ch, args, &block)
            cx = rant.context

            # we omit argument validation...
            # i.e.
            # if block
            #   rant.abort_at(ch, "My::FileCopy doesn't take a block")
            # end

            target, source = nil
            args.first.each { |key, value|
                # only one key => value pair expected
                target = key
                source = value
            }
            # SubFile creates parent directories, if necessary
            # Note that we pass on rant and ch
            Rant::Generators::SubFile.rant_gen(
                    rant, ch, [target => source]) do |t|
                cx.sys.cp source, target
            end
            # alternative: (without automatic parent
            # directory creation)
            # cx.file({:__caller__ => ch, target => source}) do |t|
            #   cx.sys.cp source, target
            # end

            # The return value of "gen" is the return value of this
            # method.
        end
    end
end # module Rant
#####################################################################

The Rant application (which I've always named "rant" above) is an
instance of Rant::RantApplication. It is defined in the file
lib/rant/rantlib.rb together with the rant.context methods.

Other things that might be useful:

    # project root directory
    rant.rootdir      # e.g. "/home/stefan/myproject"

    # current build subdirectory
    rant.current_subdir    # e.g. "lib"

    # Change pwd and to project_root_dir/"dir"
    rant.goto_project_dir "dir"
    # The following task definitions are like treated
    # as if they were define in "dir/sub.rant".
    
    rant.at_return do
        # this block is executed after rant is done
    end
    rant.at_return do
         # this too
    end

Don't use anything related to the "plugin" method. This is highly
deprecated and won't be in the 1.0.0 release.

Regards,
  Stefan


More information about the make-cafe mailing list