instrument 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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