iron-web 1.0.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.
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require <%= File.join(File.expand_path(File.dirname(__FILE__)), 'spec', 'spec_helper.rb') %>
data/History.txt ADDED
@@ -0,0 +1,8 @@
1
+ == 1.0.0 / 2012-03-02
2
+
3
+ * Broke out extensions from older irongaze gem
4
+ * Updated Url and Color classes with misc fixes
5
+ * Added Url and Color spec coverage
6
+ * Major Html::Element spec work, basic Html spec coverage
7
+ * Add html_safe support for non-Rails environments
8
+ * Added to GitHub
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Irongaze Consulting LLC
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 NONINFRINGEMENT.
17
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,91 @@
1
+ = GEM: iron-web
2
+
3
+ Written by Rob Morris @ Irongaze Consulting LLC (http://irongaze.com)
4
+
5
+ == DESCRIPTION
6
+
7
+ A set of classes useful in the generation of HTML content.
8
+
9
+ == CLASSES
10
+
11
+ * Url - a url parsing and manipulation class
12
+
13
+ >> url = Url.build('/home')
14
+ >> url.to_s
15
+ => '/home'
16
+ >> url.params[:key] = 'some value'
17
+ >> url.to_s
18
+ => '/home?key=some+value'
19
+
20
+ * Color - an RGB/A color manipulation class, useful in generating CSS etc.
21
+
22
+ >> rgb('#f00').to_s # Parse a color string into a Color then render it
23
+ => '#FF0000'
24
+ >> rgb('#f00').darken(0.5).to_s # Darken bright red 50%
25
+ => '#800000'
26
+ >> rgb('#f00').blend('#0f0', 0.5).to_s # Blend pure red and pure green, get medium yellow
27
+ => "#807F00"
28
+
29
+ * Html / Html::Element - a builder-syntax HTML generation class set
30
+
31
+ >> html = Html.build do |html|
32
+ >> html.div(:id => 'primary') {
33
+ >> html.h1('Title-town!')
34
+ >> }
35
+ >> html.div(:id => 'secondary') {|div|
36
+ >> div.style = 'text-align: center;'
37
+ >> div.text! "My body text..."
38
+ >> }
39
+ >> end
40
+ >> puts html
41
+ => <div id="primary">
42
+ => <h1>
43
+ => Title-town!
44
+ => </h1>
45
+ => </div>
46
+ => <div id="secondary" style="text-align: center;">
47
+ => My body text...
48
+ => </div>
49
+
50
+ == SYNOPSIS
51
+
52
+ To use:
53
+
54
+ require 'iron/web'
55
+
56
+ Sample usage of all components:
57
+
58
+ Html.build do |html|
59
+ html.h1('Hello World') {|h1|
60
+ h1.style = "color: #{rgb('#f00').darken};"
61
+ }
62
+
63
+ html.hr
64
+
65
+ homepage = Url.parse('http://irongaze.com')
66
+ homepage.fragment = 'incoming'
67
+ html.a('Say hello to my log file', :href => homepage)
68
+ end
69
+
70
+ Which would result in:
71
+
72
+ <h1 style="color: #CC0000;">
73
+ Hello World
74
+ </h1>
75
+ <hr>
76
+ <a href="http://irongaze.com#incoming">Say hello to my log file</a>
77
+
78
+ Notice the darkened and correctly formatted CSS color value and the newly fragment-ized url.
79
+ HTML elements are build by calling their tag name on the Html instance. Similarly, element
80
+ attributes are added by calling the attribute's name on the element instance, or by simply
81
+ setting them during construction.
82
+
83
+ == REQUIREMENTS
84
+
85
+ * iron-extensions gem
86
+
87
+ == INSTALL
88
+
89
+ To install, simply run:
90
+
91
+ sudo gem install iron-web
data/Version.txt ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
data/lib/iron/web.rb ADDED
@@ -0,0 +1,8 @@
1
+ # Load our dependencies
2
+ require 'iron/extensions'
3
+
4
+ # Requires all classes
5
+ search_path = File.join(File.expand_path(File.dirname(__FILE__)), '*', '*.rb')
6
+ Dir.glob(search_path) do |path|
7
+ require path
8
+ end
@@ -0,0 +1,267 @@
1
+
2
+ # Implements a color (r,g,b + a) with conversion to/from web format (eg #aabbcc), and
3
+ # with a number of utilities to lighten, darken and blend values.
4
+ class Color
5
+
6
+ # Basic attributes - holds each channel as 0-255 fixnum
7
+ attr_reader :r, :g, :b, :a
8
+
9
+ # Table for conversion to hex
10
+ HEXVAL = (('0'..'9').to_a).concat(('A'..'F').to_a).freeze
11
+ # Default value for #darken, #lighten etc.
12
+ BRIGHTNESS_DEFAULT = 0.2
13
+
14
+ # Construct ourselves from whatever is passed in
15
+ def initialize(*args)
16
+ @r = 255
17
+ @g = 255
18
+ @b = 255
19
+ @a = 255
20
+
21
+ if args.size.between?(3,4)
22
+ self.r = args[0]
23
+ self.g = args[1]
24
+ self.b = args[2]
25
+ self.a = args[3] if args[3]
26
+ else
27
+ set(*args)
28
+ end
29
+ end
30
+
31
+ # All-purpose setter - pass in another Color, '#000000', rgb vals... whatever
32
+ def set(*args)
33
+ val = Color.parse(*args)
34
+ unless val.nil?
35
+ self.r = val.r
36
+ self.g = val.g
37
+ self.b = val.b
38
+ self.a = val.a
39
+ end
40
+ self
41
+ end
42
+
43
+ # Test for equality, accepts string vals as well, eg Color.new('aaa') == '#AAAAAA' => true
44
+ def ==(val)
45
+ val = Color.parse(val)
46
+ return false if val.nil?
47
+ return r == val.r && g == val.g && b == val.b && a == val.a
48
+ end
49
+
50
+ # Setters for individual channels - take 0-255 or '00'-'FF' values
51
+ def r=(val); @r = from_hex(val); end
52
+ def g=(val); @g = from_hex(val); end
53
+ def b=(val); @b = from_hex(val); end
54
+ def a=(val); @a = from_hex(val); end
55
+
56
+ # Assigns a callback lambda to convert symbols into
57
+ # parse-able values. Sample usage:
58
+ # Color.lookup_callback = lambda {|val| Settings[:site][:colors].get!(val) }
59
+ # rgb(:off_white) # => '#f8f0ee'
60
+ def self.lookup_callback=(callback)
61
+ @lookup_callback = callback
62
+ end
63
+
64
+ # Get the lookup callback, if any
65
+ def self.lookup_callback
66
+ @lookup_callback
67
+ end
68
+
69
+ # Attempt to read in a string and parse it into values
70
+ def self.parse(*args)
71
+ case args.size
72
+
73
+ when 0 then
74
+ return nil
75
+
76
+ when 1 then
77
+ val = args[0]
78
+
79
+ # Trivial parse... :-)
80
+ return val if val.is_a?(Color)
81
+
82
+ # Lookup site settings if symbol
83
+ if val.is_a?(Symbol)
84
+ callback = Color.lookup_callback
85
+ return callback.nil? ? nil : self.parse(callback.call(val))
86
+ end
87
+
88
+ # Single value, assume grayscale
89
+ return Color.new(val, val, val) if val.is_a?(Fixnum)
90
+
91
+ # Assume string
92
+ str = val.to_s.upcase
93
+ str = str[/[0-9A-F]{3,8}/] || ''
94
+ case str.size
95
+ when 3, 4 then
96
+ r, g, b, a = str.scan(/[0-9A-F]/)
97
+ when 6, 8 then
98
+ r, g, b, a = str.scan(/[0-9A-F]{2}/)
99
+ else
100
+ return nil
101
+ end
102
+ a = 255 if a.nil?
103
+
104
+ return Color.new(r,g,b,a)
105
+
106
+ when 3,4 then
107
+ return Color.new(*args)
108
+
109
+ end
110
+ nil
111
+ end
112
+
113
+ def inspect
114
+ to_s
115
+ end
116
+
117
+ def to_s(add_hash = true)
118
+ trans? ? to_rgba(add_hash) : to_rgb(add_hash)
119
+ end
120
+
121
+ def to_rgb(add_hash = true)
122
+ (add_hash ? '#' : '') + to_hex(r) + to_hex(g) + to_hex(b)
123
+ end
124
+
125
+ def to_rgba(add_hash = true)
126
+ to_rgb(add_hash) + to_hex(a)
127
+ end
128
+
129
+ def opaque?
130
+ @a == 255
131
+ end
132
+
133
+ def trans?
134
+ @a != 255
135
+ end
136
+
137
+ def grayscale?
138
+ @r == @g && @g == @b
139
+ end
140
+
141
+ # Lighten color by amt
142
+ def lighten(amt = BRIGHTNESS_DEFAULT)
143
+ return self if amt <= 0
144
+ return WHITE if amt >= 1.0
145
+ val = Color.new(self)
146
+ val.r += ((255-val.r) * amt).to_i
147
+ val.g += ((255-val.g) * amt).to_i
148
+ val.b += ((255-val.b) * amt).to_i
149
+ val
150
+ end
151
+
152
+ def lighten!(amt = BRIGHTNESS_DEFAULT)
153
+ set(lighten(amt))
154
+ self
155
+ end
156
+
157
+ # Darken color by amt
158
+ def darken(amt = BRIGHTNESS_DEFAULT)
159
+ return self if amt <= 0
160
+ return BLACK if amt >= 1.0
161
+ val = Color.new(self)
162
+ val.r -= (val.r * amt).to_i
163
+ val.g -= (val.g * amt).to_i
164
+ val.b -= (val.b * amt).to_i
165
+ val
166
+ end
167
+
168
+ def darken!(amt = BRIGHTNESS_DEFAULT)
169
+ set(darken(amt))
170
+ self
171
+ end
172
+
173
+ # Go towards middle of brightness scale, based on whether color is dark or light.
174
+ def contrast(amt = BRIGHTNESS_DEFAULT)
175
+ dark? ? lighten(amt) : darken(amt)
176
+ end
177
+
178
+ def contrast!(amt = BRIGHTNESS_DEFAULT)
179
+ set(contrast(amt))
180
+ self
181
+ end
182
+
183
+ # Convert to grayscale
184
+ def grayscale
185
+ Color.new(self.brightness)
186
+ end
187
+
188
+ # Compute our overall brightness, using perception-based weighting, from 0.0 to 1.0
189
+ def brightness
190
+ (0.2126 * self.r + 0.7152 * self.g + 0.0722 * self.b)
191
+ end
192
+
193
+ def dark?
194
+ brightness < 0.5
195
+ end
196
+
197
+ def light?
198
+ !dark?
199
+ end
200
+
201
+ def grayscale!
202
+ set(grayscale)
203
+ self
204
+ end
205
+
206
+ # Blend to a color amt % towards another color value
207
+ def blend(other, amt)
208
+ other = Color.parse(other)
209
+ return Color.new(self) if amt <= 0 || other.nil?
210
+ return Color.new(other) if amt >= 1.0
211
+ val = Color.new(self)
212
+ val.r += ((other.r - val.r)*amt).to_i
213
+ val.g += ((other.g - val.g)*amt).to_i
214
+ val.b += ((other.b - val.b)*amt).to_i
215
+ val
216
+ end
217
+
218
+ def blend!(other, amt)
219
+ set(blend(other, amt))
220
+ self
221
+ end
222
+
223
+ def self.blend(col1, col2, amt)
224
+ col1 = Color.parse(col1)
225
+ col2 = Color.parse(col2)
226
+ col1.blend(col2, amt)
227
+ end
228
+
229
+ def self.average(col1, col2)
230
+ self.blend(col1, col2, 0.5)
231
+ end
232
+
233
+ protected
234
+
235
+ # Convert int to string hex, eg 255 => 'FF'
236
+ def to_hex(val)
237
+ HEXVAL[val / 16] + HEXVAL[val % 16]
238
+ end
239
+
240
+ # Convert int or string to int, eg 80 => 80, 'FF' => 255, '7' => 119
241
+ def from_hex(val)
242
+ if val.is_a?(String)
243
+ # Double up if single char form
244
+ val = val + val if val.size == 1
245
+ # Convert to integer
246
+ val = val.hex
247
+ end
248
+ # Clamp
249
+ val = 0 if val < 0
250
+ val = 255 if val > 255
251
+ val
252
+ end
253
+
254
+ public
255
+
256
+ # Some constants for general use
257
+ WHITE = Color.new(255,255,255).freeze
258
+ BLACK = Color.new(0,0,0).freeze
259
+
260
+ end
261
+
262
+ # "Global" method for creating Color objects, eg:
263
+ # new_color = rgb(params[:new_color])
264
+ # style="border: 1px solid <%= rgb(10,50,80).lighten %>"
265
+ def rgb(*args)
266
+ Color.parse(*args)
267
+ end
@@ -0,0 +1,150 @@
1
+ require 'iron/web/html/element'
2
+
3
+ # == Html Creation and Rendering
4
+ #
5
+ # This class, combined with the Html::Element class, provides a DSL for html creation, similar to the XmlBuilder class, but tailored for HTML generation.
6
+ #
7
+ # An Html class instance is an ordered collection of Html::Elements and Strings, that together can be rendered out as HTML.
8
+ #
9
+ # Usage:
10
+ #
11
+ # Html.build do |html|
12
+ # html.div(:id => 'some-div') {
13
+ # html.em('HTML is neat!')
14
+ # }
15
+ # end
16
+ class Html
17
+
18
+ # Constants
19
+ HTML_ESCAPE = {"&"=>"&amp;", ">"=>"&gt;", "<"=>"&lt;", "\""=>"&quot;"}.freeze
20
+
21
+ # Remove everything that would normally come from Object and Kernel etc. so our keys can be anything
22
+ instance_methods.each do |m|
23
+ keepers = [] #['inspect']
24
+ undef_method m if m =~ /^[a-z]+[0-9]?$/ && !keepers.include?(m)
25
+ end
26
+
27
+ # So we can behave as a collection
28
+ include Enumerable
29
+ undef_method :select #This is an HTML tag, dammit
30
+
31
+ # Primary entry point for HTML generation using these tools.
32
+ def self.build
33
+ builder = Html.new
34
+ yield builder if block_given?
35
+ builder.render.html_safe
36
+ end
37
+
38
+ # Ripped from Rails...
39
+ def self.escape_once(html)
40
+ return html if html.html_safe?
41
+ html.to_s.gsub(/[\"><]|&(?!([a-zA-Z]+|(#\d+));)/) { |special| HTML_ESCAPE[special] }.html_safe
42
+ end
43
+
44
+ # Sets up internal state, natch, and accepts a block that customizes the resulting object.
45
+ def initialize
46
+ @items = []
47
+ @item_stack = []
48
+ yield self if block_given?
49
+ end
50
+
51
+ # Inserts an HTML comment (eg <!-- yo -->)
52
+ def comment!(str)
53
+ if str.include? "\n"
54
+ text! "<!--\n#{str}\n-->\n"
55
+ else
56
+ text! "<!-- #{str} -->\n"
57
+ end
58
+ end
59
+
60
+ # Inserts raw text
61
+ def text!(str)
62
+ self << str
63
+ end
64
+
65
+ # Allow pushing new elements
66
+ def <<(new_item)
67
+ if @item_stack.empty?
68
+ @items << new_item
69
+ else
70
+ @item_stack.last.html << new_item
71
+ end
72
+ self
73
+ end
74
+
75
+ # Implement enumerable
76
+ def each
77
+ @items.each {|v| yield v} if block_given?
78
+ end
79
+
80
+ def count
81
+ @items.count
82
+ end
83
+
84
+ def empty?
85
+ @items.empty?
86
+ end
87
+
88
+ def blank?
89
+ empty?
90
+ end
91
+
92
+ # Create a new element explicitly
93
+ def tag(tag, *args, &block)
94
+ item = Html::Element.new(tag, *args)
95
+ self << item
96
+ if block
97
+ @item_stack.push item
98
+ block.call(item)
99
+ @item_stack.pop
100
+ end
101
+ return self
102
+ end
103
+
104
+ # Creates a new element on any method missing calls.
105
+ # Returns self, so you can chain calls (eg html.div('foo').span('bar') )
106
+ def method_missing(method, *args, &block)
107
+ parts = method.to_s.match(/^([a-z]+[0-9]?)$/)
108
+ if parts
109
+ # Assume it's a new element, create the tag
110
+ tag(parts[1], *args, &block)
111
+ else
112
+ # There really is no method...
113
+ super
114
+ end
115
+ end
116
+
117
+ # Renders out as html - accepts depth param to indicate level of indentation
118
+ def render(depth = 0, inblock = true)
119
+ # Convert elements to strings
120
+ @items.collect do |item|
121
+ if item.is_a?(String)
122
+ if inblock
123
+ inblock = false
124
+ ' '*depth + item
125
+ else
126
+ item
127
+ end
128
+ elsif item.nil?
129
+ ''
130
+ else
131
+ item.render(depth,inblock)
132
+ end
133
+ end.join('')
134
+ end
135
+
136
+ # Alias for #render
137
+ def to_s
138
+ render
139
+ end
140
+
141
+ # Alias for #render
142
+ def inspect
143
+ render
144
+ end
145
+
146
+ def is_a?(other)
147
+ return other == Html
148
+ end
149
+
150
+ end