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


139
140
141
142
# File 'lib/instrument/control.rb', line 139

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


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

def method_missing(method, *params, &block)
  if method.to_s =~ /^to_/
    format = method.to_s.gsub(/^to_/, "")
    self.send(:render, format, *params, &block)
  else
    control_class = self.class.lookup(method.to_s)
    if control_class != nil
      control_class.new(*params, &block)
    else
      raise NoMethodError,
        "undefined method `#{method}' for " +
        "#{self.inspect}:#{self.class.name}"
    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


154
155
156
# File 'lib/instrument/control.rb', line 154

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


148
149
150
# File 'lib/instrument/control.rb', line 148

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


161
162
163
164
165
166
167
168
169
# File 'lib/instrument/control.rb', line 161

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

.inherited(klass) ⇒ Object

Registers subclasses with the Control base class. Called automatically.

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


109
110
111
112
113
114
115
116
# File 'lib/instrument/control.rb', line 109

def self.inherited(klass)
  if !defined?(@@control_subclasses) || @@control_subclasses == nil
    @@control_subclasses = []
  end
  @@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


124
125
126
127
128
129
130
131
# File 'lib/instrument/control.rb', line 124

def self.lookup(control_name)
  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


91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/instrument/control.rb', line 91

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] the template input
@yieldparam [Object] the execution context for the template


60
61
62
63
64
65
66
67
68
69
70
# File 'lib/instrument/control.rb', line 60

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


77
78
79
80
81
82
# File 'lib/instrument/control.rb', line 77

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


203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'lib/instrument/control.rb', line 203

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, self)
  rescue Exception => e
    e.message <<
      "\nError occurred while rendering " +
      "'#{self.class.control_name}.#{format}.#{type}'"
    raise e
  end
end