How to create DSL without a class in Ruby?

I am trying to figure out how to create a kind of “DSL-free class” for my Ruby project, similar to how step definitions are defined in a Cucumber step definition file or routes are defined in a Sinatra application.

For example, I want to have a file in which all my DSL functions are called:

#sample.rb

when_string_matches /hello (.+)/ do |name|
    call_another_method(name)
end

I suggest that bad practice pollutes the global ( Kernel) namespace with a variety of methods specific to my project. Thus, methods when_string_matchesand call_another_methodwill be defined in my library, and the file sample.rbwill be somehow evaluated in the context of my DSL methods.

Update: Here is an example of how these DSL methods are currently defined:

DSL methods are defined in a class that is subclassed (I would like to find a way to reuse these methods between simple DSL and class instances):

module MyMod
  class Action
    def call_another_method(value)
      puts value
    end

    def handle(text)
      # a subclass would be expected to define
      # this method (as an alternative to the 
      # simple DSL approach)
    end
  end
end

Then at some point, during the initialization of my program, I want to analyze the file sample.rband save these actions, which will be performed later:

module MyMod
  class Parser

    # parse the file, saving the blocks and regular expressions to call later
    def parse_it
      file_contents = File.read('sample.rb')
      instance_eval file_contents
    end

    # doesnt seem like this belongs here, but it won't work if it not
    def self.when_string_matches(regex, &block)
      MyMod.blocks_for_executing_later << { regex: regex, block: block }
    end
  end
end

# Later...

module MyMod
  class Runner

    def run
      string = 'hello Andrew'
      MyMod.blocks_for_executing_later.each do |action|
        if string =~ action[:regex]
          args = action[:regex].match(string).captures
          action[:block].call(args)
        end
      end
    end

  end
end

The problem with what I have so far (and various things I tried that I did not mention above) is when a block is defined in the file, the instance method is not available (I know that it is in another class right now) . But what I want to do is more like instantiating and eval'ing in this context, rather than eval'ing in the class Parser. But I do not know how to do this.

Hope this makes sense. Any help, experience or advice would be appreciated.

+5
source
3

, , . " " , , DSL, , , . , , , .

Sinatra

sinatra/main.rb, , Sinatra::Delegator . .

,

delegate :get, :patch, :put, :post, :delete, :head, :options, :template, :layout,
         :before, :after, :error, :not_found, :configure, :set, :mime_type,
         :enable, :disable, :use, :development?, :test?, :production?,
         :helpers, :settings

, ..

self.target = Application

respond_to? target, .

def self.delegate(*methods)
  methods.each do |method_name|
    define_method(method_name) do |*args, &block|
      return super(*args, &block) if respond_to? method_name
      Delegator.target.send(method_name, *args, &block)
    end
    private method_name
  end
end

Cucumber > . ( , .. ) DSL. , DSL , , , " " . .

HAML

HAML, DSL, "", .. . ( ) haml case...

def process_line(text, index)
  @index = index + 1

  case text[0]
  when DIV_CLASS; push div(text)
  when DIV_ID
    return push plain(text) if text[1] == ?{
    push div(text)
  when ELEMENT; push tag(text)
  when COMMENT; push comment(text[1..-1].strip)
  ...

, , . plain

FYI .

# Designates an XHTML/XML element.
ELEMENT         = ?%
# Designates a `<div>` element with the given class.
DIV_CLASS       = ?.
# Designates a `<div>` element with the given id.
DIV_ID          = ?#
# Designates an XHTML/XML comment.
COMMENT         = ?/
+4

. DSL Module Module#include. RSpec. - , , , . +1 @meagar DSL !

, @UncleGene, RSpec ​​ DSL. , . DSL describe, , describe.

module RSpec
  module Core
    # Adds the `describe` method to the top-level namespace.
    module DSL
      # Generates a subclass of {ExampleGroup}
      #
      # ## Examples:
      #
      #     describe "something" do
      #       it "does something" do
      #         # example code goes here
      #       end
      #     end
      #
      # @see ExampleGroup
      # @see ExampleGroup.describe
      def describe(*args, &example_group_block)
        RSpec::Core::ExampleGroup.describe(*args, &example_group_block).register
      end
    end
  end
end
extend RSpec::Core::DSL
Module.send(:include, RSpec::Core::DSL)
+3

when_string_matches, , "", , , name :

def when_string_matches(regex)
   # do whatever is required to produce `my_string` and `name`
   yield(name) if my_string =~ regex
end

, , Ruby DSL: , .

+2
source

All Articles