module MetaRuby::DSLs

DSLs-related tools

Find through method missing

The find through method missing functionality is meant to allow classes to turn objects that can be found (with a method that finds an object by its name) into an attribute call as e.g.

task.test_event # => task.find_event("test")

See {DSLs::FindThroughMethodMissing} for a complete description

Documentation parsing

This provides the logic to find a documentation block above a DSL-like object creation. For instance, given a class that looks like

class Task
  def event(name) # creates an event object with the given name
  end
end

Used in a DSL context like so:

# The test event allows us
#
# To provide an example
event 'test'

The parse_documentation method allows to extract the comment block above the 'event' call. See {DSLs.parse_documentation} for more information

Public Class Methods

find_through_method_missing(object, m, args, suffix_match) click to toggle source

Generic implementation to create suffixed accessors for child objects on a class

Given an object category (let's say 'state'), this allows to properly implement a method-missing based accessor of the style

blabla_state

using a find_state method that the object should respond to

@param [Object] object the object on which the find method is going to

be called

@param [Symbol] m the method name @param [Array] args the method arguments @param [Array<String>] suffixes the accessor suffixes that should be

resolved. The last argument can be a hash, in which case the keys
are used as suffixes and the values are the name of the find methods
that should be used.

@return [Object,nil] an object if one of the listed suffixes matches

the method name, or nil if the method name does not match the
requested pattern.

@raise [NoMethodError] if the requested object does not exist (i.e. if

the find method returns nil)

@raise [ArgumentError] if the method name matches one of the suffixes,

but arguments were given. It is raised regardless of the existence
of the requested object

@example

class MyClass
  def find_state(name)
    states[name]
  end
  def find_transition(name)
    transitions[name]
  end
  def method_missing(m, *args, &block)
    MetaRuby::DSLs.find_through_method_missing(self, m, args,
      'state', 'transition') || super
  end
end
object = MyClass.new
object.add_state 'my'
object.my_state # will resolve the 'my' state
# File lib/metaruby/dsls/find_through_method_missing.rb, line 108
def self.find_through_method_missing(object, m, args, suffix_match)
    return false if m == :to_ary

    m = m.to_s
    suffix_match.each do |s, find_method_name|
        if m.end_with?(s)
            name = m[0, m.size - s.size]
            if !args.empty?
                raise ArgumentError, "expected zero arguments to #{m}, got #{args.size}", caller(4)
            else
                return object.send(find_method_name, name)
            end
        end
    end
    nil
end
has_through_method_missing?(object, m, suffix_match) click to toggle source
# File lib/metaruby/dsls/find_through_method_missing.rb, line 125
def self.has_through_method_missing?(object, m, suffix_match)
    return false if m == :to_ary

    m = m.to_s
    suffix_match.each do |s, has_method_name|
        if m.end_with?(s)
            name = m[0, m.size - s.size]
            return !!object.send(has_method_name, name)
        end
    end
    false
end
parse_documentation_block(file_match, trigger_method = /.*/) click to toggle source

Looks for the documentation block for the element that is being built.

@param [#===] file_match an object (typically a regular expression)

that matches the file name in which the DSL is being used

@param [#===] trigger_method an object (typically a regular expression)

that matches the name of the method that initiates the creation of
the element whose documentation we are looking for.

@return [String,nil] the parsed documentation, or nil if there is no

documentation

@example find the documentation block of an event creation

# assuming the following toplevel DSL code in a file called test.orogen
task "Task" do
  # Just an example event
  event "test"
end

# One would use the following code to extract the documentation
# above the test event declaration. The call must be made within the
# event creation code
MetaRuby::DSLs.parse_documentation_block(/test\.orogen$/, "event")
# File lib/metaruby/dsls/doc.rb, line 26
def self.parse_documentation_block(file_match, trigger_method = /.*/)
    last_method_matched = false
    caller_locations(1).each do |call|
        this_method_matched =
            if trigger_method === call.label
                true
            elsif call.label == 'method_missing'
                last_method_matched
            else
                false
            end

        if !this_method_matched && last_method_matched && (file_match === call.absolute_path)
            if File.file?(call.absolute_path)
                return parse_documentation_block_at(call.absolute_path, call.lineno)
            else return
            end
        end
        last_method_matched = this_method_matched
    end
    nil
end
parse_documentation_block_at(file, line) click to toggle source

Parses upwards a Ruby documentation block whose last line starts at or just before the given line in the given file

@param [String] file @param [Integer] line @return [String,nil] the parsed documentation, or nil if there is no

documentation
# File lib/metaruby/dsls/doc.rb, line 56
def self.parse_documentation_block_at(file, line)
    lines = File.readlines(file)

    block = []
    # Lines are given 1-based (as all editors work that way), and we
    # want the line before the definition. Remove two
    line = line - 2

    space_count = nil
    while true
        l = lines[line]
        comment_match = /^\s*#/.match(l)
        if comment_match
            comment_line  = comment_match.post_match.rstrip
            stripped_line = comment_line.lstrip
            leading_spaces = comment_line.size - stripped_line.size
            if !stripped_line.empty? && (!space_count || space_count > leading_spaces)
                space_count = leading_spaces
            end
            block.unshift(comment_line)
        else break
        end
        line = line - 1
    end
    if !block.empty?
        space_count ||= 0
        block.map { |l| l[space_count..-1] }.join("\n")
    end
end