Module: MetaRuby::DSLs

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

Overview

Helper functions to build DSLs

Class Method Summary collapse

Class Method Details

.find_through_method_missing(object, m, args, *suffixes) ⇒ 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



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/metaruby/dsls/find_through_method_missing.rb', line 48

def self.find_through_method_missing(object, m, args, *suffixes)
    suffix_match = Hash.new
    if suffixes.last.kind_of?(Hash)
        suffix_match.merge!(suffixes.pop)
    end
    suffixes.each do |name|
        suffix_match[name] = "find_#{name}"
    end

    m = m.to_s
    suffix_match.each do |s, find_method_name|
        if m == find_method_name
            raise NoMethodError.new("#{object} has no method called #{find_method_name}", m)
        elsif m =~ /(.*)_#{s}$/
            name = $1
            if !args.empty?
                raise ArgumentError, "expected zero arguments to #{m}, got #{args.size}", caller(4)
            elsif found = object.send(find_method_name, name)
                return found
            else
                msg = "#{object} has no #{s} named #{name}"
                raise NoMethodError.new(msg, m), msg, caller(4)
            end
        end
    end
    nil
end

.parse_documentation_block(file_match, trigger_method = /.*/) ⇒ String?

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

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



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/metaruby/dsls/doc.rb', line 13

def self.parse_documentation_block(file_match, trigger_method = /.*/)
    last_method_matched = false
    call_stack.each do |call|
        this_method_matched =
            if trigger_method === call[2].to_s
                true
            elsif call[2] == :method_missing
                last_method_matched
            else
                false
            end

        if !this_method_matched && last_method_matched && (file_match === call[0])
            if File.file?(call[0])
                return parse_documentation_block_at(call[0], call[1])
            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



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/metaruby/dsls/doc.rb', line 43

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
    while true
        case l = lines[line]
        when /^\s*$/
            break
        when /^\s*#/
            block << l
        else break
        end
        line = line - 1
    end
    block = block.map do |l|
        l.strip.gsub(/^\s*#/, '')
    end
    # Now remove the same amount of spaces in front of each lines
    space_count = block.map do |l|
        l =~ /^(\s*)/
        if $1.size != l.size
            $1.size
        end
    end.compact.min
    block = block.map do |l|
        l[space_count..-1]
    end
    if !block.empty?
        block.reverse.join("\n")
    end
end