iron-web 1.0.0

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