instrument 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
File without changes
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Instrument, Copyright (c) 2008 Day Automation Systems, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,97 @@
1
+ == Instrument
2
+
3
+ Homepage:: instrument.rubyforge.org[http://instrument.rubyforge.org/]
4
+ Author:: Bob Aman (mailto:bob@sporkmonger.com)
5
+ Copyright:: Copyright © 2008 Day Automation Systems, Inc.
6
+ License:: MIT
7
+
8
+ == Description
9
+
10
+ Instrument is a simple library for producing dynamically generated "controls"
11
+ with various templating languages.
12
+
13
+ == Features
14
+
15
+ * Generate controls with Erb, Haml, Markaby, or XML Builder. Instrument
16
+ doesn't care what template language you prefer.
17
+ * Output XHTML, XML, or JSON. Instrument doesn't care what your output
18
+ format is.
19
+
20
+ == Example Usage
21
+
22
+ select_control = SelectControl.new(:name => "base", :selections => [
23
+ {:label => "One", :value => "1"},
24
+ {:label => "Two", :value => "2"},
25
+ {:label => "Three", :value => "3"},
26
+ {:label => "Four", :value => "4"}
27
+ ])
28
+ xhtml_output = select_control.to_xhtml
29
+
30
+ or
31
+
32
+ include Instrument::ControlBuilder
33
+
34
+ select_control(:name => "base", :selections => [
35
+ {:label => "One", :value => "1"},
36
+ {:label => "Two", :value => "2"},
37
+ {:label => "Three", :value => "3"},
38
+ {:label => "Four", :value => "4"}
39
+ ]).to_xhtml
40
+
41
+ select_control.rb:
42
+
43
+ require "instrument"
44
+
45
+ class SelectControl < Instrument::Control
46
+ class Option
47
+ def initialize(label, value)
48
+ @label, @value = label, value
49
+ end
50
+
51
+ attr_accessor :label
52
+ attr_accessor :value
53
+ end
54
+
55
+ def element_id
56
+ return self.options[:id] || self.options[:name]
57
+ end
58
+
59
+ def element_name
60
+ return self.options[:name]
61
+ end
62
+
63
+ def selections
64
+ if !defined?(@selections) || @selections == nil
65
+ @selections = []
66
+ for selection in self.options[:selections]
67
+ if selection.kind_of?(Hash)
68
+ @selections << Option.new(selection[:label], selection[:value])
69
+ else
70
+ @selections << Option.new(selection, selection)
71
+ end
72
+ end
73
+ end
74
+ return @selections
75
+ end
76
+ end
77
+
78
+ select.xhtml.haml:
79
+
80
+ %select{:id => element_id, :name => element_name}
81
+ - for selection in selections
82
+ %option{:value => selection.value}
83
+ = selection.label
84
+
85
+ == Requirements
86
+
87
+ * Instrument has no explicit dependencies. If you want to output Haml, you
88
+ will need the Haml library installed. Same goes for any of the other
89
+ template languages.
90
+
91
+ == Install
92
+
93
+ * sudo gem install instrument
94
+ * sudo gem install haml (optional)
95
+ * sudo gem install erubis (optional)
96
+ * sudo gem install markaby (optional)
97
+ * sudo gem install builder (optional)
@@ -0,0 +1,45 @@
1
+ lib_dir = File.expand_path(File.join(File.dirname(__FILE__), "lib"))
2
+ $:.unshift(lib_dir)
3
+ $:.uniq!
4
+
5
+ require 'rubygems'
6
+ require 'rake'
7
+ require 'rake/testtask'
8
+ require 'rake/rdoctask'
9
+ require 'rake/packagetask'
10
+ require 'rake/gempackagetask'
11
+ require 'rake/contrib/rubyforgepublisher'
12
+ require 'spec/rake/spectask'
13
+
14
+ require File.join(File.dirname(__FILE__), 'lib/instrument', 'version')
15
+
16
+ PKG_DISPLAY_NAME = 'Instrument'
17
+ PKG_NAME = PKG_DISPLAY_NAME.downcase
18
+ PKG_VERSION = Instrument::VERSION::STRING
19
+ PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
20
+
21
+ RELEASE_NAME = "REL #{PKG_VERSION}"
22
+
23
+ RUBY_FORGE_PROJECT = PKG_NAME
24
+ RUBY_FORGE_USER = "sporkmonger"
25
+ RUBY_FORGE_PATH = "/var/www/gforge-projects/#{RUBY_FORGE_PROJECT}"
26
+ RUBY_FORGE_URL = "http://#{RUBY_FORGE_PROJECT}.rubyforge.org/"
27
+
28
+ PKG_SUMMARY = "Template-based Controls"
29
+ PKG_DESCRIPTION = <<-TEXT
30
+ Instrument is a simple library for producing dynamically generated
31
+ "controls" with various templating languages.
32
+ TEXT
33
+
34
+ PKG_FILES = FileList[
35
+ "lib/**/*", "spec/**/*", "vendor/**/*",
36
+ "tasks/**/*", "website/**/*",
37
+ "[A-Z]*", "Rakefile"
38
+ ].exclude(/database\.yml/).exclude(/[_\.]git$/)
39
+
40
+ task :default => "spec:verify"
41
+
42
+ WINDOWS = (RUBY_PLATFORM =~ /mswin|win32|mingw|bccwin|cygwin/) rescue false
43
+ SUDO = WINDOWS ? '' : ('sudo' unless ENV['SUDOLESS'])
44
+
45
+ Dir['tasks/**/*.rake'].each { |rake| load rake }
@@ -0,0 +1,27 @@
1
+ # ++
2
+ # Instrument, Copyright (c) 2008 Day Automation Systems, Inc.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ # --
23
+
24
+ require "instrument/version"
25
+ require "instrument/errors"
26
+ require "instrument/control"
27
+ require "instrument/control_builder"
@@ -0,0 +1,269 @@
1
+ # ++
2
+ # Instrument, Copyright (c) 2008 Day Automation Systems, Inc.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ # --
23
+
24
+ require "instrument/version"
25
+ require "instrument/errors"
26
+
27
+ # This variable stores a list of all paths to search for when locating a
28
+ # named control.
29
+ $CONTROL_PATH = ["."]
30
+ if defined?(RAILS_ROOT)
31
+ $CONTROL_PATH.unshift(File.join(RAILS_ROOT, "app/controls"))
32
+ end
33
+
34
+ module Instrument
35
+ ##
36
+ # The Instrument::Control class provides a simple way to render nested
37
+ # templates.
38
+ #
39
+ # == Example
40
+ #
41
+ # select_control = SelectControl.new(:name => "base", :selections => [
42
+ # {:label => "One", :value => "1"},
43
+ # {:label => "Two", :value => "2"},
44
+ # {:label => "Three", :value => "3"},
45
+ # {:label => "Four", :value => "4"}
46
+ # ])
47
+ # xhtml_output = select_control.to_xhtml
48
+ class Control
49
+ ##
50
+ # Registers a template type. Takes a symbol naming the type, and a
51
+ # block which takes a String as input and an Object to use as the
52
+ # execution context and returns the rendered template output as a
53
+ # String. The block should ensure that all necessary libraries are
54
+ # loaded.
55
+ #
56
+ # @param [Array] type_list the template types being registered
57
+ # @yield the block generates the template output
58
+ # @yieldparam [String] the template input
59
+ # @yieldparam [Object] the execution context for the template
60
+ def self.register_type(*type_list, &block)
61
+ # Ensure the @@type_map is initialized.
62
+ self.types
63
+
64
+ for type in type_list
65
+ # Normalize to symbol
66
+ type = type.to_s.to_sym
67
+ @@type_map[type] = block
68
+ end
69
+ return nil
70
+ end
71
+
72
+ ##
73
+ # Returns a list of registered template types.
74
+ #
75
+ # @return [Array] a list of Symbols for the registered template types
76
+ # @see Instrument::Control.register_type
77
+ def self.types
78
+ if !defined?(@@type_map) || @@type_map == nil
79
+ @@type_map = {}
80
+ end
81
+ return @@type_map.keys
82
+ end
83
+
84
+ ##
85
+ # Returns the processor Proc for the specified type.
86
+ #
87
+ # @param [Array] type_list the template types being registered
88
+ # @raise ArgumentError raises an error if the type is invalid.
89
+ # @return [Proc] the proc that handles template execution
90
+ # @see Instrument::Control.register_type
91
+ def self.processor(type)
92
+ # Normalize to symbol
93
+ type = type.to_s.to_sym
94
+
95
+ if !self.types.include?(type)
96
+ raise ArgumentError,
97
+ "Unrecognized template type: #{type.inspect}\n" +
98
+ "Valid types: " +
99
+ "#{(self.types.map {|t| t.inspect}).join(", ")}"
100
+ end
101
+
102
+ return @@type_map[type]
103
+ end
104
+
105
+ ##
106
+ # Registers subclasses with the Control base class. Called automatically.
107
+ #
108
+ # @param [Class] klass the subclass that is extending Control
109
+ def self.inherited(klass)
110
+ if !defined?(@@control_subclasses) || @@control_subclasses == nil
111
+ @@control_subclasses = []
112
+ end
113
+ @@control_subclasses << klass
114
+ @@control_subclasses.uniq!
115
+ super
116
+ end
117
+
118
+ ##
119
+ # Looks up a Control by name.
120
+ #
121
+ # @param [String] control_name the control name of the Control
122
+ # @return [Instrument::Control, NilClass] the desired control or nil
123
+ # @see Instrument::Control.control_name
124
+ def self.lookup(control_name)
125
+ for control_subclass in (@@control_subclasses || [])
126
+ if control_subclass.control_name == control_name
127
+ return control_subclass
128
+ end
129
+ end
130
+ return nil
131
+ end
132
+
133
+ ##
134
+ # Creates a new Control object. Subclasses should not override this.
135
+ #
136
+ # @param [Hash] options a set of options required by the control
137
+ # @return [Instrument::Control] the instanitated control
138
+ def initialize(options={})
139
+ @options = options
140
+ end
141
+
142
+ ##
143
+ # Returns the options that were used to create the Control.
144
+ #
145
+ # @return [Hash] a set of options required by the control
146
+ attr_reader :options
147
+
148
+ ##
149
+ # Returns the Control's name. By default, this is the control's class
150
+ # name, tranformed into This method may be overridden by a Control.
151
+ #
152
+ # @return [String] the control name
153
+ def self.control_name
154
+ return nil if self.name == "Instrument::Control"
155
+ return self.name.
156
+ gsub(/^.*::/, "").
157
+ gsub(/([A-Z]+)([A-Z][a-z])/, "\\1_\\2").
158
+ gsub(/([a-z\d])([A-Z])/, "\\1_\\2").
159
+ tr("-", "_").
160
+ downcase
161
+ end
162
+
163
+ ##
164
+ # Relays to_format messages to the render method.
165
+ #
166
+ # @param [Symbol] method the method being called
167
+ # @param [Array] params the method's parameters
168
+ # @param [Proc] block the block being passed to the method
169
+ # @return [Object] the return value
170
+ # @raise NoMethodError if the method wasn't handled
171
+ # @see Instrument::Control#render
172
+ def method_missing(method, *params, &block)
173
+ if method.to_s =~ /^to_/
174
+ format = method.to_s.gsub(/^to_/, "")
175
+ self.send(:render, format, *params, &block)
176
+ else
177
+ control_class = self.class.lookup(method.to_s)
178
+ if control_class != nil
179
+ control_class.new(*params, &block)
180
+ else
181
+ raise NoMethodError,
182
+ "undefined method `#{method}' for " +
183
+ "#{self.inspect}:#{self.class.name}"
184
+ end
185
+ end
186
+ end
187
+
188
+ ##
189
+ # Renders a control in a specific format.
190
+ #
191
+ # @param [String] format the format name for the template output
192
+ # @return [String] the rendered output in the desired format
193
+ # @raise Instrument::ResourceNotFoundError if the template is missing
194
+ # @raise Instrument::InvalidTemplateEngineError if type isn't registered
195
+ def render(format)
196
+ # Locate the template.
197
+ path = nil
198
+ for load_path in $CONTROL_PATH
199
+ full_name = File.expand_path(
200
+ File.join(load_path, self.class.control_name))
201
+
202
+ # Check to make sure the requested template is within the load path
203
+ # to avoid inadvertent rendering of say, /etc/passwd
204
+ next if full_name.index(File.expand_path(load_path)) != 0
205
+
206
+ templates = Dir.glob(full_name + ".#{format}.*")
207
+
208
+ # Select the first template matched. If there's more than one,
209
+ # the extras will be ignored.
210
+ template = templates.first
211
+ if template != nil
212
+ path = template
213
+ break
214
+ end
215
+ end
216
+
217
+ if path == nil
218
+ raise Instrument::ResourceNotFoundError,
219
+ "Template not found: '#{self.class.control_name}.#{format}.*'"
220
+ elsif File.directory?(path)
221
+ raise Instrument::ResourceNotFoundError,
222
+ "Template not found: '#{self.class.control_name}.#{format}.*'"
223
+ end
224
+
225
+ # Normalize to symbol
226
+ type = File.extname(path).gsub(/^\./, "").to_s
227
+ if type != "" && !self.class.types.include?(type.to_sym)
228
+ raise Instrument::InvalidTemplateEngineError,
229
+ "Unrecognized template type: #{type.inspect}\n" +
230
+ "Valid types: [" +
231
+ "#{(self.class.types.map {|t| t.inspect}).join(", ")}]"
232
+ end
233
+ raw_content = File.open(path, "r") do |file|
234
+ file.read
235
+ end
236
+
237
+ begin
238
+ return self.class.processor(type).call(raw_content, self)
239
+ rescue Exception => e
240
+ e.message <<
241
+ "\nError occurred while rendering " +
242
+ "'#{self.class.control_name}.#{format}.#{type}'"
243
+ raise e
244
+ end
245
+ end
246
+ end
247
+ end
248
+
249
+ # Register the default types.
250
+ Instrument::Control.register_type(:haml) do |input, context|
251
+ require "haml"
252
+ Haml::Engine.new(input, {:attr_wrapper => "\""}).render(context)
253
+ end
254
+ Instrument::Control.register_type(:erb, :rhtml) do |input, context|
255
+ begin; require "erubis"; rescue LoadError; require "erb"; end
256
+ erb = Erubis::Eruby.new(input) rescue ERB.new(input)
257
+ erb.result(context.send(:binding))
258
+ end
259
+ Instrument::Control.register_type(:mab) do |input, context|
260
+ require "markaby"
261
+ Markaby::Builder.new({}, context).capture do
262
+ eval(input)
263
+ end
264
+ end
265
+ Instrument::Control.register_type(:rxml) do |input, context|
266
+ require "builder"
267
+ xml = Builder::XmlMarkup.new(:indent => 2)
268
+ eval(input, context.send(:binding))
269
+ end