Class: Glimmer::DSL::Engine

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/glimmer/dsl/engine.rb

Overview

Glimmer DSL Engine

Follows Interpreter, Chain of Responsibility, and Singleton Design Patterns

When DSL engine interprets an expression, it attempts to handle with ordered expression array specified via ‘.expressions=` method.

Constant Summary collapse

MESSAGE_NO_DSLS =
"Glimmer has no DSLs configured. Add glimmer-dsl-swt gem or visit https://github.com/AndyObtiva/glimmer#multi-dsl-support for more details.\n"
STATIC_EXPRESSION_METHOD_FACTORY =
lambda do |keyword|
  lambda do |*args, &block|
    if Glimmer::DSL::Engine.no_dsls?
      Glimmer::Config.logger.error {Glimmer::DSL::Engine::MESSAGE_NO_DSLS}
    else
      retrieved_static_expression = Glimmer::DSL::Engine.static_expressions[keyword][Glimmer::DSL::Engine.dsl]
      # TODO consider replacing Glimmer::DSL::Engine.static_expressions[keyword].keys - Glimmer::DSL::Engine.disabled_dsls with Glimmer::DSL::Engine.enabled_static_expression_dsls(keyword)
      static_expression_dsl = (Glimmer::DSL::Engine.static_expressions[keyword].keys - Glimmer::DSL::Engine.disabled_dsls).first
      interpretation = nil
      if retrieved_static_expression.nil? && Glimmer::DSL::Engine.dsl && (static_expression_dsl.nil? || !Glimmer::DSL::Engine.static_expressions[keyword][static_expression_dsl].is_a?(TopLevelExpression))
        begin
          interpretation = Glimmer::DSL::Engine.interpret(keyword, *args, &block)
        rescue => e
          raise e if static_expression_dsl.nil? || !Glimmer::DSL::Engine.static_expressions[keyword][static_expression_dsl].is_a?(TopLevelExpression)
        end
      end
      if interpretation
        interpretation
      else
        raise Glimmer::Error, "Unsupported keyword: #{keyword}" unless static_expression_dsl || retrieved_static_expression
        Glimmer::DSL::Engine.dsl_stack.push(static_expression_dsl || Glimmer::DSL::Engine.dsl)
        Glimmer::Config.logger.info {"Assuming DSL: #{Glimmer::DSL::Engine.dsl_stack.last}"}
        static_expression = Glimmer::DSL::Engine.static_expressions[keyword][Glimmer::DSL::Engine.dsl]
        static_expression_can_interpret = nil
        if static_expression.nil? || !(static_expression_can_interpret = static_expression.can_interpret?(Glimmer::DSL::Engine.parent, keyword, *args, &block))
          begin
            Glimmer::DSL::Engine.interpret(keyword, *args, &block)
          rescue => e
            raise Error, "Invalid use of Glimmer keyword #{keyword} with args #{args} under parent #{Glimmer::DSL::Engine.parent.inspect} with DSL #{Glimmer::DSL::Engine.dsl.inspect} and static expression #{static_expression.inspect} having can_interpret? as #{static_expression_can_interpret.inspect} and no dynamic expressions to be able to handle either!"
          end
        else
          Glimmer::Config.logger.info {"#{static_expression.class.name} will handle expression keyword #{keyword}"}
          Glimmer::DSL::Engine.interpret_expression(static_expression, keyword, *args, &block)
        end
      end
    end
  end
end

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.dynamic_expression_chains_of_responsibilityObject

Dynamic expression chains of responsibility indexed by dsl



118
119
120
# File 'lib/glimmer/dsl/engine.rb', line 118

def dynamic_expression_chains_of_responsibility
  @dynamic_expression_chains_of_responsibility ||= Concurrent::Hash.new
end

.static_expressionsObject

Static expressions indexed by keyword and dsl



123
124
125
# File 'lib/glimmer/dsl/engine.rb', line 123

def static_expressions
  @static_expressions ||= Concurrent::Hash.new
end

Class Method Details

.add_capitalized_static_expression(static_expression) ⇒ Object



183
184
185
186
187
188
189
190
191
192
# File 'lib/glimmer/dsl/engine.rb', line 183

def add_capitalized_static_expression(static_expression)
  if static_expression.class.capitalized?
    Glimmer::Config.logger.info {"Adding capitalized static expression: #{static_expression.class.name}"}
    keyword = static_expression.class.keyword
    static_expression_dsl = static_expression.class.dsl
    static_expressions[keyword.capitalize] ||= Concurrent::Hash.new
    static_expressions[keyword.capitalize][static_expression_dsl] = static_expression
    Glimmer.send(:define_method, keyword.capitalize, &STATIC_EXPRESSION_METHOD_FACTORY.call(keyword.capitalize))
  end
end

.add_content(new_parent, expression, keyword, *args, &block) ⇒ Object

Adds content block to parent UI object

This allows evaluating parent UI object properties and children

For example, a shell widget would get properties set and children added



230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/glimmer/dsl/engine.rb', line 230

def add_content(new_parent, expression, keyword, *args, &block)
  if block_given? && expression.is_a?(ParentExpression)
    dsl_stack.push(expression.class.dsl)
    push_parent_into_parent_stack(new_parent)
    begin
      expression.add_content(new_parent, keyword, *args, &block)
    ensure
      pop_parent_from_parent_stack
      dsl_stack.pop
    end
  end
end

.add_downcased_static_expression(static_expression) ⇒ Object Also known as: add_static_expression



153
154
155
156
157
158
159
160
# File 'lib/glimmer/dsl/engine.rb', line 153

def add_downcased_static_expression(static_expression)
  Glimmer::Config.logger.info {"Adding static expression: #{static_expression.class.name}"}
  keyword = static_expression.class.keyword
  static_expressions[keyword] ||= Concurrent::Hash.new
  static_expression_dsl = static_expression.class.dsl
  static_expressions[keyword][static_expression_dsl] = static_expression
  Glimmer.send(:define_method, keyword, &STATIC_EXPRESSION_METHOD_FACTORY.call(keyword))
end

.add_dynamic_expressions(dsl_namespace, *expression_names) ⇒ Object

Sets an ordered array of DSL expressions to support

Every expression has an underscored name corresponding to an upper camelcase AbstractExpression subclass name in glimmer/dsl

They are used in order following the Chain of Responsibility Design Pattern when interpretting a DSL expression



140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/glimmer/dsl/engine.rb', line 140

def add_dynamic_expressions(dsl_namespace, *expression_names)
  expression_names = expression_names.flatten
  dsl = dsl_namespace.name.split("::").last.downcase.to_sym
  dynamic_expression_chains_of_responsibility[dsl] = expression_names.reverse.map do |expression_name|
    expression_class(dsl_namespace, expression_name).new
  end.reduce(nil) do |last_expresion_handler, expression|
    Glimmer::Config.logger.info {"Adding dynamic expression: #{expression.class.name}"}
    expression_handler = ExpressionHandler.new(expression)
    expression_handler.next = last_expresion_handler if last_expresion_handler
    expression_handler
  end
end

.add_upcased_static_expression(static_expression) ⇒ Object



172
173
174
175
176
177
178
179
180
181
# File 'lib/glimmer/dsl/engine.rb', line 172

def add_upcased_static_expression(static_expression)
  if static_expression.class.upcased?
    Glimmer::Config.logger.info {"Adding upcased static expression: #{static_expression.class.name}"}
    keyword = static_expression.class.keyword
    static_expression_dsl = static_expression.class.dsl
    static_expressions[keyword.upcase] ||= Concurrent::Hash.new
    static_expressions[keyword.upcase][static_expression_dsl] = static_expression
    Glimmer.send(:define_method, keyword.upcase, &STATIC_EXPRESSION_METHOD_FACTORY.call(keyword.upcase))
  end
end

.disable_dsl(dsl_name) ⇒ Object



85
86
87
88
# File 'lib/glimmer/dsl/engine.rb', line 85

def disable_dsl(dsl_name)
  dsl_name = dsl_name.to_sym
  disabled_dsls << dsl_name
end

.disabled_dslsObject



95
96
97
# File 'lib/glimmer/dsl/engine.rb', line 95

def disabled_dsls
  @disabled_dsls ||= Concurrent::Array.new
end

.dsl_parent_stacksObject



273
274
275
# File 'lib/glimmer/dsl/engine.rb', line 273

def dsl_parent_stacks
  @dsl_parent_stacks ||= Concurrent::Hash.new
end

.dsl_stackObject

Enables multiple DSLs to play well with each other when mixing together



278
279
280
# File 'lib/glimmer/dsl/engine.rb', line 278

def dsl_stack
  @dsl_stack ||= Concurrent::Array.new
end

.dslsObject



81
82
83
# File 'lib/glimmer/dsl/engine.rb', line 81

def dsls
  static_expressions.values.map(&:keys).flatten.uniq
end

.enable_dsl(dsl_name) ⇒ Object



90
91
92
93
# File 'lib/glimmer/dsl/engine.rb', line 90

def enable_dsl(dsl_name)
  dsl_name = dsl_name.to_sym
  disabled_dsls.delete(dsl_name)
end

.enabled_dsls=(dsl_names) ⇒ Object



99
100
101
102
# File 'lib/glimmer/dsl/engine.rb', line 99

def enabled_dsls=(dsl_names)
  dsls.each {|dsl_name| disable_dsl(dsl_name)}
  dsl_names.each {|dsl_name| enable_dsl(dsl_name)}
end

.expression_class(dsl_namespace, expression_name) ⇒ Object



194
195
196
# File 'lib/glimmer/dsl/engine.rb', line 194

def expression_class(dsl_namespace, expression_name)
  dsl_namespace.const_get(expression_class_name(expression_name).to_sym)
end

.expression_class_name(expression_name) ⇒ Object



198
199
200
# File 'lib/glimmer/dsl/engine.rb', line 198

def expression_class_name(expression_name)
  "#{expression_name}_expression".camelcase(:upper)
end

.interpret(keyword, *args, &block) ⇒ Object

Interprets Glimmer dynamic DSL expression consisting of keyword, args, and block (e.g. shell(:no_resize) { … })



203
204
205
206
207
208
209
210
211
212
# File 'lib/glimmer/dsl/engine.rb', line 203

def interpret(keyword, *args, &block)
  return puts(MESSAGE_NO_DSLS) if no_dsls? # TODO consider switching to an error log statement
  keyword = keyword.to_s
  dynamic_expression_dsl = (dynamic_expression_chains_of_responsibility.keys - disabled_dsls).first if dsl.nil?
  # TODO consider pushing this code into interpret_expresion to provide hooks that work around it regardless of static vs dynamic
  dsl_stack.push(dynamic_expression_dsl || dsl)
  Glimmer::Config.logger.info {"Assuming DSL: #{dsl_stack.last}"}
  expression = dynamic_expression_chains_of_responsibility[dsl].handle(parent, keyword, *args, &block)
  interpret_expression(expression, keyword, *args, &block)
end

.interpret_expression(expression, keyword, *args, &block) ⇒ Object



214
215
216
217
218
219
220
221
222
223
# File 'lib/glimmer/dsl/engine.rb', line 214

def interpret_expression(expression, keyword, *args, &block)
  new_parent = nil
  expression.around(parent, keyword, args, block) do
    new_parent = expression.interpret(parent, keyword, *args, &block).tap do |new_parent|
      add_content(new_parent, expression, keyword, *args, &block)
      dsl_stack.pop
    end
  end
  new_parent
end

.new_parent_stackObject



265
266
267
# File 'lib/glimmer/dsl/engine.rb', line 265

def new_parent_stack
  parent_stacks.push(Concurrent::Array.new)
end

.no_dsls?Boolean

Returns:

  • (Boolean)


113
114
115
# File 'lib/glimmer/dsl/engine.rb', line 113

def no_dsls?
  static_expressions.empty? && dynamic_expression_chains_of_responsibility.empty?
end

.parentObject

Current parent while evaluating Glimmer DSL (nil if just started or done evaluatiing)

Parents are maintained in a stack while evaluating Glimmer DSL to ensure properly ordered interpretation of DSL syntax



247
248
249
# File 'lib/glimmer/dsl/engine.rb', line 247

def parent
  parent_stack.last
end

.parent_stackObject



260
261
262
263
# File 'lib/glimmer/dsl/engine.rb', line 260

def parent_stack
  new_parent_stack if parent_stacks.last.nil?
  parent_stacks.last
end

.parent_stacksObject



269
270
271
# File 'lib/glimmer/dsl/engine.rb', line 269

def parent_stacks
  dsl_parent_stacks[dsl] ||= Concurrent::Array.new # TODO insted of having one array, we need to nest it within an array of arrays
end

.pop_parent_from_parent_stackObject



255
256
257
258
# File 'lib/glimmer/dsl/engine.rb', line 255

def pop_parent_from_parent_stack
  parent_stack.pop
  parent_stacks.pop if parent_stacks.size > 1 && parent_stacks.last.empty?
end

.push_parent_into_parent_stack(parent) ⇒ Object



251
252
253
# File 'lib/glimmer/dsl/engine.rb', line 251

def push_parent_into_parent_stack(parent)
  parent_stack.push(parent)
end

.remove_downcased_static_expression(static_expression) ⇒ Object



163
164
165
166
167
168
169
170
# File 'lib/glimmer/dsl/engine.rb', line 163

def remove_downcased_static_expression(static_expression)
  if !static_expression.class.downcased?
    keyword = static_expression.class.keyword
    static_expressions[keyword].delete(static_expression_dsl) if static_expressions[keyword]
    static_expressions.delete(keyword) if static_expressions[keyword].empty?
    Glimmer.send(:undef_method, keyword) if (Glimmer.method(keyword) rescue nil)
  end
end

.resetObject

Resets Glimmer’s engine activity and configuration. Useful in rspec before or after blocks in tests.



105
106
107
108
109
110
111
# File 'lib/glimmer/dsl/engine.rb', line 105

def reset
  dsl_parent_stacks.values.each do |a_parent_stack|
    a_parent_stack.clear
  end
  dsl_stack.clear
  disabled_dsls.clear
end