Class: Instrument::Control

Inherits:
Object
  • Object
show all
Defined in:
lib/instrument/control.rb

Overview

The Instrument::Control class provides a simple way to render nested templates.

Example

select_control = SelectControl.new(:name => "base", :selections => [
  {:label => "One", :value => "1"},
  {:label => "Two", :value => "2"},
  {:label => "Three", :value => "3"},
  {:label => "Four", :value => "4"}
])
xhtml_output = select_control.to_xhtml

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}, &block) ⇒ Control

Creates a new Control object. Subclasses should not override this.

@param [Hash] options a set of options required by the control
@yield optionally accepts a block used by the control
@return [Instrument::Control] the instanitated control


143
144
145
146
# File 'lib/instrument/control.rb', line 143

def initialize(options={}, &block)
  @options = options
  @block = block
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *params, &block) ⇒ Object

Relays to_format messages to the render method.

@param [Symbol] method the method being called
@param [Array] params the method's parameters
@param [Proc] block the block being passed to the method
@return [Object] the return value
@raise NoMethodError if the method wasn't handled
@see Instrument::Control#render


231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/instrument/control.rb', line 231

def method_missing(method, *params, &block)
  if method.to_s =~ /^to_/
    format = method.to_s.gsub(/^to_/, "")
    self.render(format, *params, &block)
  else
    control_class = self.class.lookup(method.to_s)
    if control_class != nil
      control_class.new(*params, &block)
    else
      if options[:delegate] != nil &&
          options[:delegate].respond_to?(method)
        options[:delegate].send(method, *params, &block)
      else
        raise NoMethodError,
          "undefined method `#{method}' for " +
          "#{self.inspect}:#{self.class.name}"
      end
    end
  end
end

Instance Attribute Details

#blockObject (readonly)

Returns the block that was supplied when the Control was created.

@return [Proc] the block used to create the Control


158
159
160
# File 'lib/instrument/control.rb', line 158

def block
  @block
end

#optionsObject (readonly)

Returns the options that were used to create the Control.

@return [Hash] a set of options required by the control


152
153
154
# File 'lib/instrument/control.rb', line 152

def options
  @options
end

Class Method Details

.control_nameObject

Returns the Control’s name. By default, this is the control’s class name, tranformed into This method may be overridden by a Control.

@return [String] the control name


165
166
167
168
169
170
171
172
173
# File 'lib/instrument/control.rb', line 165

def self.control_name
  return nil if self.name == "Instrument::Control"
  return self.name.
    gsub(/^.*::/, "").
    gsub(/([A-Z]+)([A-Z][a-z])/, "\\1_\\2").
    gsub(/([a-z\d])([A-Z])/, "\\1_\\2").
    tr("-", "_").
    downcase
end

.formatsObject

Returns a list of formats that this control may be rendered as.

@return [Array] the available formats


179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/instrument/control.rb', line 179

def self.formats
  return [] if self.control_name == nil
  all_templates = []
  all_formats = []
  for load_path in $CONTROL_PATH
    full_name = File.expand_path(
      File.join(load_path, self.control_name))

    # Check to make sure the requested template is within the load path
    # to avoid inadvertent rendering of say, /etc/passwd
    next if full_name.index(File.expand_path(load_path)) != 0

    all_templates.concat(Dir.glob(full_name + ".*"))
  end
  for template in all_templates
    next if File.directory?(template)
    all_formats << template[/^.*\.([-_a-zA-Z0-9]+)\..*$/, 1]
  end
  return all_formats.uniq.reject { |f| f.nil? }
end

.inherited(klass) ⇒ Object

Registers subclasses with the Control base class. Called automatically.

@param [Class] klass the subclass that is extending Control


114
115
116
117
118
119
# File 'lib/instrument/control.rb', line 114

def self.inherited(klass)
  @@control_subclasses ||= []
  @@control_subclasses << klass
  @@control_subclasses.uniq!
  super
end

.lookup(control_name) ⇒ Object

Looks up a Control by name.

@param [String] control_name the control name of the Control
@return [Instrument::Control, NilClass] the desired control or nil
@see Instrument::Control.control_name


127
128
129
130
131
132
133
134
135
# File 'lib/instrument/control.rb', line 127

def self.lookup(control_name)
  @@control_subclasses ||= []
  for control_subclass in @@control_subclasses
    if control_subclass.control_name == control_name
      return control_subclass
    end
  end
  return nil
end

.processor(type) ⇒ Object

Returns the processor Proc for the specified type.

@param [Array] type_list the template types being registered
@raise ArgumentError raises an error if the type is invalid.
@return [Proc] the proc that handles template execution
@see Instrument::Control.register_type


96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/instrument/control.rb', line 96

def self.processor(type)
  # Normalize to symbol
  type = type.to_s.to_sym

  if !self.types.include?(type)
    raise ArgumentError,
      "Unrecognized template type: #{type.inspect}\n" +
      "Valid types: " +
      "#{(self.types.map {|t| t.inspect}).join(", ")}"
  end

  return @@type_map[type]
end

.register_type(*type_list, &block) ⇒ Object

Registers a template type. Takes a symbol naming the type, and a block which takes a String as input and an Object to use as the execution context and returns the rendered template output as a String. The block should ensure that all necessary libraries are loaded.

@param [Array] type_list The template types being registered.
@yield The block generates the template output.
@yieldparam [String] input
  The template input.
@yieldparam [Hash] options
  Additional parameters.
  :context - The execution context for the template, which will be set
    to the control object.
  :filename - The filename of the template being rendered.


65
66
67
68
69
70
71
72
73
74
75
# File 'lib/instrument/control.rb', line 65

def self.register_type(*type_list, &block)
  # Ensure the @@type_map is initialized.
  self.types

  for type in type_list
    # Normalize to symbol
    type = type.to_s.to_sym
    @@type_map[type] = block
  end
  return nil
end

.typesObject

Returns a list of registered template types.

@return [Array] a list of Symbols for the registered template types
@see Instrument::Control.register_type


82
83
84
85
86
87
# File 'lib/instrument/control.rb', line 82

def self.types
  if !defined?(@@type_map) || @@type_map == nil
    @@type_map = {}
  end
  return @@type_map.keys
end

Instance Method Details

#render(format) ⇒ Object

Renders a control in a specific format.

@param [String] format the format name for the template output
@return [String] the rendered output in the desired format
@raise Instrument::ResourceNotFoundError if the template is missing
@raise Instrument::InvalidTemplateEngineError if type isn't registered


259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'lib/instrument/control.rb', line 259

def render(format)
  # Locate the template.
  path = nil
  for load_path in $CONTROL_PATH
    full_name = File.expand_path(
      File.join(load_path, self.class.control_name))

    # Check to make sure the requested template is within the load path
    # to avoid inadvertent rendering of say, /etc/passwd
    next if full_name.index(File.expand_path(load_path)) != 0

    templates = Dir.glob(full_name + ".#{format}.*")

    # Select the first template matched.  If there's more than one,
    # the extras will be ignored.
    template = templates.first
    if template != nil
      path = template
      break
    end
  end

  if path == nil
    raise Instrument::ResourceNotFoundError,
      "Template not found: '#{self.class.control_name}.#{format}.*'"
  elsif File.directory?(path)
    raise Instrument::ResourceNotFoundError,
      "Template not found: '#{self.class.control_name}.#{format}.*'"
  end

  # Normalize to symbol
  type = File.extname(path).gsub(/^\./, "").to_s
  if type != "" && !self.class.types.include?(type.to_sym)
    raise Instrument::InvalidTemplateEngineError,
      "Unrecognized template type: #{type.inspect}\n" +
      "Valid types: [" +
      "#{(self.class.types.map {|t| t.inspect}).join(", ")}]"
  end
  raw_content = File.open(path, "r") do |file|
    file.read
  end

  begin
    return self.class.processor(type).call(
      raw_content, {:context => self, :filename => path}
    )
  rescue Exception => e
    e.message <<
      "\nError occurred while rendering " +
      "'#{self.class.control_name}.#{format}.#{type}'"
    raise e
  end
end

#respond_to?(method, include_private = false) ⇒ Boolean

Returns true if the control responds to the given message.

@return [Boolean] if the control responds

Returns:

  • (Boolean)


204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/instrument/control.rb', line 204

def respond_to?(method, include_private=false)
  if method.to_s =~ /^to_/
    format = method.to_s.gsub(/^to_/, "")
    return self.class.formats.include?(format)
  else
    control_class = self.class.lookup(method.to_s)
    if control_class != nil
      return true
    else
      if options[:delegate] != nil &&
          options[:delegate].respond_to?(method)
        return true
      end
    end
  end
  super
end