Class: DSLCompose::Interpreter

Inherits:
Object
  • Object
show all
Defined in:
lib/dsl_compose/interpreter.rb,
lib/dsl_compose/interpreter/execution.rb,
lib/dsl_compose/interpreter/interpreter_error.rb,
lib/dsl_compose/interpreter/execution/arguments.rb,
lib/dsl_compose/interpreter/execution/method_calls.rb,
lib/dsl_compose/interpreter/execution/method_calls/method_call.rb

Overview

The class is reponsible for parsing and executing a dynamic DSL (dynamic DSLs are created using the DSLCompose::DSL class).

Defined Under Namespace

Classes: Execution, InterpreterError, InvalidDescriptionError

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeInterpreter

Returns a new instance of Interpreter.



15
16
17
# File 'lib/dsl_compose/interpreter.rb', line 15

def initialize
  @executions = []
end

Instance Attribute Details

#executionsObject (readonly)

A dynamic DSL can be used multiple times on the same class, each time the DSL is used a corresponding execution will be created. The execution contains the resulting configuration from that particular use of the DSL.



13
14
15
# File 'lib/dsl_compose/interpreter.rb', line 13

def executions
  @executions
end

Instance Method Details

#add_parser_usage_note(child_class, note) ⇒ Object

the parser can provide usage notes for how this dsl is being used, these are used to generate documentation



21
22
23
24
25
26
27
28
# File 'lib/dsl_compose/interpreter.rb', line 21

def add_parser_usage_note child_class, note
  unless note.is_a?(String) && note.strip.length > 0
    raise InvalidDescriptionError.new("The parser usage description `#{note}` is invalid, it must be of type string and have length greater than 0", called_from)
  end
  @parser_usage_notes ||= {}
  @parser_usage_notes[child_class] ||= []
  @parser_usage_notes[child_class] << note.strip
end

#class_dsl_executions(klass, dsl_name, on_current_class, on_ancestor_class, first_use_only) ⇒ Object

Returns an array of all executions for a given name and class. This includes any ancestors of the provided class



69
70
71
72
73
74
75
76
77
78
# File 'lib/dsl_compose/interpreter.rb', line 69

def class_dsl_executions klass, dsl_name, on_current_class, on_ancestor_class, first_use_only
  filtered_executions = @executions.filter { |e| e.dsl.name == dsl_name && ((on_current_class && e.klass == klass) || (on_ancestor_class && klass < e.klass)) }
  # Because the classes were evaluated in order, we can just return the
  # last execution
  if first_use_only && filtered_executions.length > 0
    [filtered_executions.last]
  else
    filtered_executions
  end
end

#class_executions(klass) ⇒ Object

Returns an array of all executions for a given class.



58
59
60
# File 'lib/dsl_compose/interpreter.rb', line 58

def class_executions klass
  @executions.filter { |e| e.klass == klass }
end

#clearObject

removes all executions from the interpreter, and any parser_usage_notes this is primarily used from within a test suite when dynamically creating classes for tests and then wanting to clear the interpreter before the next test.



100
101
102
103
# File 'lib/dsl_compose/interpreter.rb', line 100

def clear
  @executions = []
  @parser_usage_notes = {}
end

#dsl_executions(dsl_name) ⇒ Object

Returns an array of all executions for a given name.



63
64
65
# File 'lib/dsl_compose/interpreter.rb', line 63

def dsl_executions dsl_name
  @executions.filter { |e| e.dsl.name == dsl_name }
end

#execute_dsl(klass, dsl, called_from) ⇒ Object

Execute/process a dynamically defined DSL on a class. ‘klass` is the class in which the DSL is being used, not the class in which the DSL was defined.



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/dsl_compose/interpreter.rb', line 40

def execute_dsl(klass, dsl, called_from, ...)
  # make sure we have these variables for the exception message below
  # set to nil first, so that if we get an exception while setting them
  # it wont break the error message generation
  class_name = nil
  dsl_name = nil
  class_name = klass.name
  dsl_name = dsl.name

  execution = Execution.new(klass, dsl, called_from, ...)
  @executions << execution
  execution
rescue => e
  raise e, "Error while defining DSL #{dsl_name} for class #{class_name}:\n#{e.message}", e.backtrace
end

#executions_by_classObject



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/dsl_compose/interpreter.rb', line 120

def executions_by_class
  h = {}
  executions.each do |execution|
    h[execution.klass] ||= {}
    h[execution.klass][execution.dsl.name] ||= []
    execution_h = {
      arguments: execution.arguments.to_h,
      method_calls: {}
    }
    execution.method_calls.method_calls.each do |method_call|
      execution_h[:method_calls][method_call.method_name] ||= []
      execution_h[:method_calls][method_call.method_name] << method_call.to_h
    end
    h[execution.klass][execution.dsl.name] << execution_h
  end
  h
end

#get_last_dsl_execution(klass, dsl_name) ⇒ Object

returns the most recent, closest single execution of a dsl with the provided name for the provided class

If the dsl has been executed once or more on the provided class, then the last (most recent) execution will be returned. If the DSL was not executed on the provided class, then we traverse up the classes ancestors until we reach the ancestor where the DSL was originally defined and test each of them and return the first most recent execution of the DSL. If no execution of the DSL is found, then nil will be returned



89
90
91
92
93
94
# File 'lib/dsl_compose/interpreter.rb', line 89

def get_last_dsl_execution klass, dsl_name
  # note that this method does not need to do any special sorting, the required
  # order for getting the most recent execution is already guaranteed because
  # parent classes in ruby always have to be evaluated before their descendants
  class_dsl_executions(klass, dsl_name, true, true, false).last
end

#parser_usage_notes(child_class) ⇒ Object

return the list of notes which describe how the parsers are using this DSL



31
32
33
34
35
# File 'lib/dsl_compose/interpreter.rb', line 31

def parser_usage_notes child_class
  @parser_usage_notes ||= {}
  @parser_usage_notes[child_class] ||= []
  @parser_usage_notes[child_class]
end

#to_h(dsl_name) ⇒ Object



105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/dsl_compose/interpreter.rb', line 105

def to_h dsl_name
  h = {}
  dsl_executions(dsl_name).each do |execution|
    h[execution.klass] ||= {
      arguments: execution.arguments.to_h,
      method_calls: {}
    }
    execution.method_calls.method_calls.each do |method_call|
      h[execution.klass][:method_calls][method_call.method_name] ||= []
      h[execution.klass][:method_calls][method_call.method_name] << method_call.to_h
    end
  end
  h
end