Module: MetaRuby::DSLs

Defined in:
lib/metaruby/dsls.rb,
lib/metaruby/dsls/doc.rb,
lib/metaruby/dsls/find_through_method_missing.rb

Overview

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

Defined Under Namespace

Modules: FindThroughMethodMissing

Class Method Summary collapse

Class Method Details

.find_through_method_missing(object, m, args, suffix_match) ⇒ Object?

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

Examples:

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

Parameters:

  • object (Object)

    the object on which the find method is going to be called

  • m (Symbol)

    the method name

  • args (Array)

    the method arguments

  • suffixes (Array<String>)

    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.

Returns:

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

Raises:

  • (NoMethodError)

    if the requested object does not exist (i.e. if the find method returns nil)

  • (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



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# 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) ⇒ Boolean

Returns:

  • (Boolean)


125
126
127
128
129
130
131
132
133
134
135
136
# 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 = /.*/) ⇒ String?

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

Examples:

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")

Parameters:

  • file_match (#===)

    an object (typically a regular expression) that matches the file name in which the DSL is being used

  • trigger_method (#===) (defaults to: /.*/)

    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.

Returns:

  • (String, nil)

    the parsed documentation, or nil if there is no documentation



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# 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) ⇒ String?

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

Parameters:

  • file (String)
  • line (Integer)

Returns:

  • (String, nil)

    the parsed documentation, or nil if there is no documentation



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# 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