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



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

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

.static_expressionsObject

Static expressions indexed by keyword and dsl



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

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

Class Method Details

.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



194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/glimmer/dsl/engine.rb', line 194

def add_content(new_parent, expression, keyword, *args, &block)
  if block_given? && expression.is_a?(ParentExpression)
    dsl_stack.push(expression.class.dsl)
    parent_stack.push(new_parent)
    begin
      expression.add_content(new_parent, keyword, *args, &block)
    ensure
      parent_stack.pop
      dsl_stack.pop
    end
  end
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



136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/glimmer/dsl/engine.rb', line 136

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_static_expression(static_expression) ⇒ Object



149
150
151
152
153
154
155
156
# File 'lib/glimmer/dsl/engine.rb', line 149

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

.disable_dsl(dsl_name) ⇒ Object



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

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

.disabled_dslsObject



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

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

.dsl_stackObject

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



222
223
224
# File 'lib/glimmer/dsl/engine.rb', line 222

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

.dslsObject



77
78
79
# File 'lib/glimmer/dsl/engine.rb', line 77

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

.enable_dsl(dsl_name) ⇒ Object



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

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

.enabled_dsls=(dsl_names) ⇒ Object



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

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



158
159
160
# File 'lib/glimmer/dsl/engine.rb', line 158

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



162
163
164
# File 'lib/glimmer/dsl/engine.rb', line 162

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) { … })



167
168
169
170
171
172
173
174
175
176
# File 'lib/glimmer/dsl/engine.rb', line 167

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



178
179
180
181
182
183
184
185
186
187
# File 'lib/glimmer/dsl/engine.rb', line 178

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

.no_dsls?Boolean

Returns:

  • (Boolean)


109
110
111
# File 'lib/glimmer/dsl/engine.rb', line 109

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

.parent_stackObject



213
214
215
# File 'lib/glimmer/dsl/engine.rb', line 213

def parent_stack
  parent_stacks[dsl] ||= Concurrent::Array.new
end

.parent_stacksObject



217
218
219
# File 'lib/glimmer/dsl/engine.rb', line 217

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

.resetObject

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



101
102
103
104
105
106
107
# File 'lib/glimmer/dsl/engine.rb', line 101

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