iron-web 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +1 -0
- data/History.txt +8 -0
- data/LICENSE +20 -0
- data/README.rdoc +91 -0
- data/Version.txt +1 -0
- data/lib/iron/web.rb +8 -0
- data/lib/iron/web/color.rb +267 -0
- data/lib/iron/web/html.rb +150 -0
- data/lib/iron/web/html/element.rb +154 -0
- data/lib/iron/web/string.rb +60 -0
- data/lib/iron/web/url.rb +228 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/web/color_spec.rb +58 -0
- data/spec/web/element_spec.rb +69 -0
- data/spec/web/html_safe_string_spec.rb +25 -0
- data/spec/web/html_spec.rb +20 -0
- data/spec/web/string_spec.rb +16 -0
- data/spec/web/url_spec.rb +101 -0
- metadata +87 -0
@@ -0,0 +1,154 @@
|
|
1
|
+
|
2
|
+
# = HTML Element Class
|
3
|
+
#
|
4
|
+
# Used with the Html class to generate html content for a single tag/element. Represents
|
5
|
+
# a single element with attributes and optional contents (including other elements). Generally, you
|
6
|
+
# won't use this by itself. Check out Html.build() instead.
|
7
|
+
#
|
8
|
+
# Simple useage:
|
9
|
+
# >> Html::Element.new('span','some text', :id => 'title-text').render
|
10
|
+
# => '<span id="title-text">some text</span>'
|
11
|
+
#
|
12
|
+
# Complex usage:
|
13
|
+
# span = Html::Element.new('span') # Creates a span element for customization
|
14
|
+
# span.id = 'title-text' # Set some attributes
|
15
|
+
# span.style = 'color: #f00;'
|
16
|
+
# span.html = 'some text' # Adds some content
|
17
|
+
# span.render # Converts to html string
|
18
|
+
# => '<span id="title-text" style="color: #f00;">some text</span>
|
19
|
+
#
|
20
|
+
class Html
|
21
|
+
class Element
|
22
|
+
# Add back in our accessors
|
23
|
+
attr_accessor :tag, :attrs
|
24
|
+
|
25
|
+
# Commonly empty tags
|
26
|
+
SINGLETON_SET = ['area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source'].freeze
|
27
|
+
# Inline formatting tags
|
28
|
+
INLINE_SET = ['a','abbr','b','button','em','i','input','img','label','li','option','span','strong','title','textarea','u'].freeze
|
29
|
+
# Attributes that should render even if blank
|
30
|
+
BLANK_ATTRS = ['value','alt'].freeze
|
31
|
+
|
32
|
+
# One-stop shop for building content
|
33
|
+
def self.build(*args)
|
34
|
+
el = self.new(*args)
|
35
|
+
yield el if block_given?
|
36
|
+
el.render
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize(tag, text=nil, attrs={})
|
40
|
+
@tag = tag.to_s
|
41
|
+
@force_end = !SINGLETON_SET.include?(@tag)
|
42
|
+
@skip_newline = INLINE_SET.include?(@tag)
|
43
|
+
|
44
|
+
if text.is_a?(Hash)
|
45
|
+
@attrs = text
|
46
|
+
text = nil
|
47
|
+
else
|
48
|
+
@attrs = attrs || {}
|
49
|
+
end
|
50
|
+
|
51
|
+
if text.is_a?(String)
|
52
|
+
html << ((!text.respond_to?(:html_safe?) || !text.html_safe?) ? Html.escape_once(text) : text)
|
53
|
+
elsif text.is_a?(Html::Element)
|
54
|
+
html << text
|
55
|
+
elsif text.is_a?(Html)
|
56
|
+
@html = text
|
57
|
+
end
|
58
|
+
|
59
|
+
yield self if block_given?
|
60
|
+
|
61
|
+
self
|
62
|
+
end
|
63
|
+
|
64
|
+
def force_end!
|
65
|
+
@force_end = true
|
66
|
+
end
|
67
|
+
|
68
|
+
def skip_newline!
|
69
|
+
@skip_newline = true
|
70
|
+
end
|
71
|
+
|
72
|
+
def html
|
73
|
+
@html ||= Html.new
|
74
|
+
@html
|
75
|
+
end
|
76
|
+
|
77
|
+
def html=(arg)
|
78
|
+
if arg.is_a? String
|
79
|
+
@html = Html.new
|
80
|
+
@html.text! arg
|
81
|
+
elsif arg.is_a?(Html)
|
82
|
+
@html = arg
|
83
|
+
elsif arg.is_a?(Array)
|
84
|
+
@html = Html.new
|
85
|
+
arg.each do |el|
|
86
|
+
@html << el
|
87
|
+
end
|
88
|
+
else
|
89
|
+
raise 'Invalid input'
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Set/get attrs on any method missing calls
|
94
|
+
def method_missing(method, *args)
|
95
|
+
parts = method.to_s.match(/^([a-z0-9_]+)(=?)$/i)
|
96
|
+
if parts
|
97
|
+
key = parts[1].to_sym
|
98
|
+
if parts[2] && args.length == 1
|
99
|
+
# We have an attempt to set a missing field...
|
100
|
+
@attrs[key] = args[0]
|
101
|
+
return args[0]
|
102
|
+
else
|
103
|
+
raise "I think you meant <#{@tag}>.html.#{method} instead of <#{@tag}>.#{method}" if block_given?
|
104
|
+
return @attrs[key]
|
105
|
+
end
|
106
|
+
else
|
107
|
+
# There really is no method...
|
108
|
+
super
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def to_s
|
113
|
+
render
|
114
|
+
end
|
115
|
+
|
116
|
+
def inspect
|
117
|
+
render
|
118
|
+
end
|
119
|
+
|
120
|
+
def is_a?(other)
|
121
|
+
return other == Html::Element
|
122
|
+
end
|
123
|
+
|
124
|
+
def render(depth = 0, inblock = false)
|
125
|
+
# Convert attrs to strings
|
126
|
+
attrStr = @attrs.collect do |k,v|
|
127
|
+
if v.nil?
|
128
|
+
nil
|
129
|
+
elsif v.blank? && ! BLANK_ATTRS.include?(k.to_s)
|
130
|
+
" #{k}"
|
131
|
+
else
|
132
|
+
v = Html.escape_once(v) unless v.html_safe?
|
133
|
+
" #{k}=\"#{v}\""
|
134
|
+
end
|
135
|
+
end.compact.join('')
|
136
|
+
|
137
|
+
# Build start tag
|
138
|
+
val = ''
|
139
|
+
val += "\n" if !inblock && !@skip_newline
|
140
|
+
val += ' ' * depth unless !inblock && @skip_newline
|
141
|
+
val += "<#{@tag}#{attrStr}>"
|
142
|
+
unless @html.nil?
|
143
|
+
val += "\n" unless @skip_newline
|
144
|
+
val += @html.render(depth+1, !@skip_newline)
|
145
|
+
val += "\n" unless @skip_newline || val.ends_with?("\n")
|
146
|
+
val += ' ' * depth unless @skip_newline
|
147
|
+
end
|
148
|
+
val += "</#{@tag}>" if (@force_end || !@html.blank?)
|
149
|
+
val += "\n" unless @skip_newline
|
150
|
+
val
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# Only add these changes if we're NOT running Rails
|
2
|
+
unless ''.respond_to?(:html_safe)
|
3
|
+
|
4
|
+
# Add Rails-like html safeness awareness to objects
|
5
|
+
class Object
|
6
|
+
|
7
|
+
def html_safe?
|
8
|
+
false
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
# Add Rails-like html safeness awareness to strings
|
14
|
+
class String
|
15
|
+
|
16
|
+
# Return a copy of the string marked as being html safe (ie not requiring further encoding)
|
17
|
+
def html_safe
|
18
|
+
HtmlSafeString.new(self)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
# Simple class that extends strings to do html escapes on incoming concats, only defined
|
24
|
+
# if not running under Rails
|
25
|
+
class HtmlSafeString < String
|
26
|
+
|
27
|
+
def initialize(*args)
|
28
|
+
@html_safe = true
|
29
|
+
super
|
30
|
+
end
|
31
|
+
|
32
|
+
def html_safe?
|
33
|
+
@html_safe
|
34
|
+
end
|
35
|
+
|
36
|
+
def html_safe
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
# The magic - escape values that are concatenated onto this string
|
41
|
+
# before concatenation.
|
42
|
+
def concat(value)
|
43
|
+
if !html_safe? || value.html_safe?
|
44
|
+
super(value)
|
45
|
+
else
|
46
|
+
super(Html.escape_once(value))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def +(other)
|
51
|
+
dup.concat(other)
|
52
|
+
end
|
53
|
+
|
54
|
+
def <<(value)
|
55
|
+
concat(value)
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
data/lib/iron/web/url.rb
ADDED
@@ -0,0 +1,228 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
class Url
|
4
|
+
|
5
|
+
attr_accessor :scheme, :server, :port, :path, :params, :fragment
|
6
|
+
|
7
|
+
SECURE_SCHEMES = ['https', 'sftp', 'ssh'].freeze
|
8
|
+
SECURE_SCHEME_MAPPING = { 'http' => 'https', 'ftp' => 'sftp' }.freeze
|
9
|
+
SCHEME_DEFAULT_PORTS = {
|
10
|
+
'ftp' => 21,
|
11
|
+
'ssh' => 22,
|
12
|
+
'smtp' => 25,
|
13
|
+
'http' => 80,
|
14
|
+
'pop3' => 110,
|
15
|
+
'pop' => 110,
|
16
|
+
'sftp' => 115,
|
17
|
+
'imap' => 143,
|
18
|
+
'https' => 443,
|
19
|
+
'ssl' => 443,
|
20
|
+
'irc' => 531,
|
21
|
+
'imaps' => 993,
|
22
|
+
'pop3s' => 995,
|
23
|
+
'pops' => 995
|
24
|
+
}.freeze
|
25
|
+
|
26
|
+
# Returns a new Url from the given string
|
27
|
+
def self.parse(str)
|
28
|
+
Url.new(str)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Return default domain for urls, used
|
32
|
+
# to convert relative urls to absolute
|
33
|
+
def self.default_server
|
34
|
+
@default_server
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.default_server=(server)
|
38
|
+
@default_server = server
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.default_scheme
|
42
|
+
@default_scheme || 'http'
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.default_scheme=(scheme)
|
46
|
+
@default_scheme = scheme
|
47
|
+
end
|
48
|
+
|
49
|
+
# Construct a full URL from pieces
|
50
|
+
def self.build(base, params = {}, fragment = nil)
|
51
|
+
url = base + to_param_string(params)
|
52
|
+
url += '#' + fragment unless fragment.blank?
|
53
|
+
url
|
54
|
+
end
|
55
|
+
|
56
|
+
# Construct a param string from key/value pairs
|
57
|
+
def self.to_param_string(params)
|
58
|
+
str = ''
|
59
|
+
# Remove blank/nil keys
|
60
|
+
params.delete_if {|k,v| v.to_s.blank? || k.to_s.blank?}
|
61
|
+
# Convert to param string
|
62
|
+
unless params.empty?
|
63
|
+
str += '?'
|
64
|
+
str += params.collect do |k,v|
|
65
|
+
if v.is_a?(Array)
|
66
|
+
v.collect do |vs|
|
67
|
+
val = vs.respond_to?(:to_param) ? vs.to_param : vs
|
68
|
+
val = val.to_s
|
69
|
+
CGI::escape(k.to_s) + '=' + CGI::escape(val)
|
70
|
+
end
|
71
|
+
else
|
72
|
+
val = v.respond_to?(:to_param) ? v.to_param : v
|
73
|
+
val = val.to_s
|
74
|
+
CGI::escape(k.to_s) + '=' + CGI::escape(val)
|
75
|
+
end
|
76
|
+
end.flatten.join('&')
|
77
|
+
end
|
78
|
+
str
|
79
|
+
end
|
80
|
+
|
81
|
+
def initialize(str = nil)
|
82
|
+
@scheme = @port = @server = nil
|
83
|
+
set(str)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Parse and set internals from given url
|
87
|
+
def set(url)
|
88
|
+
# Decompose into major components
|
89
|
+
url = (url || '').strip
|
90
|
+
base, params, @fragment = url.extract(/^([^\?#]*)\??([^#]*)#?(.*)$/)
|
91
|
+
|
92
|
+
# Parse out base
|
93
|
+
base ||= ''
|
94
|
+
if base.match(/^[a-z\+]*:\/\//)
|
95
|
+
@scheme, @server, ignore, @port, @path = base.extract(/^([a-z]*):\/\/([a-z0-9\-_\.]+)(:([0-9]+))?(\/.*)?/i)
|
96
|
+
@path ||= ''
|
97
|
+
@port = @port.blank? ? nil : @port.to_i
|
98
|
+
else
|
99
|
+
@path = base
|
100
|
+
end
|
101
|
+
|
102
|
+
# Parse out params
|
103
|
+
@params = {}
|
104
|
+
params.split('&').each do |p|
|
105
|
+
k, v = p.split('=')
|
106
|
+
add_param(CGI::unescape(k), CGI::unescape(v)) if k && v
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Makee the url
|
111
|
+
def to_s
|
112
|
+
Url::build(base, @params, @fragment)
|
113
|
+
end
|
114
|
+
|
115
|
+
def to_html
|
116
|
+
to_s
|
117
|
+
end
|
118
|
+
|
119
|
+
def inspect
|
120
|
+
to_s
|
121
|
+
end
|
122
|
+
|
123
|
+
def empty?
|
124
|
+
blank?
|
125
|
+
end
|
126
|
+
|
127
|
+
def blank?
|
128
|
+
self.base.blank?
|
129
|
+
end
|
130
|
+
|
131
|
+
# Returns the full start of the url, minus params and fragment
|
132
|
+
def base
|
133
|
+
val = ''
|
134
|
+
unless @server.blank?
|
135
|
+
val = (@scheme || Url::default_scheme) + '://' + @server
|
136
|
+
val += ':' + @port.to_s unless @port.to_s.blank?
|
137
|
+
end
|
138
|
+
p = (@path || '')
|
139
|
+
p = '/' + p unless p.blank? || p.starts_with?('/')
|
140
|
+
val + p
|
141
|
+
end
|
142
|
+
|
143
|
+
def append_path(str, escape = false)
|
144
|
+
str = str.to_s
|
145
|
+
@path ||= ''
|
146
|
+
@path += escape ? CGI::escape(str).gsub('+', '%20') : str
|
147
|
+
end
|
148
|
+
|
149
|
+
def +(str)
|
150
|
+
append_path(str)
|
151
|
+
self
|
152
|
+
end
|
153
|
+
|
154
|
+
# Override current param val or set if none
|
155
|
+
def set_param(k, v)
|
156
|
+
@params[k] = v
|
157
|
+
end
|
158
|
+
|
159
|
+
# Add a param value (can be called multiply for the same param key)
|
160
|
+
def add_param(k, v)
|
161
|
+
oldval = @params[k]
|
162
|
+
if oldval
|
163
|
+
@params[k] = oldval.is_a?(Array) ? oldval + [v] : [oldval, v]
|
164
|
+
else
|
165
|
+
@params[k] = v
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# Wipe out keyed value by string match or regex match
|
170
|
+
def remove_param(key_or_regex)
|
171
|
+
@params.delete_if do |k, v|
|
172
|
+
if key_or_regex.is_a?(Regexp)
|
173
|
+
k.match(key_or_regex)
|
174
|
+
else
|
175
|
+
k == key_or_regex
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Reset params
|
181
|
+
def clear_params
|
182
|
+
@params = {}
|
183
|
+
end
|
184
|
+
def reset_params ; clear_params ; end
|
185
|
+
|
186
|
+
def has_params?
|
187
|
+
@params && !@params.empty?
|
188
|
+
end
|
189
|
+
|
190
|
+
def secure?
|
191
|
+
SECURE_SCHEMES.include?(scheme)
|
192
|
+
end
|
193
|
+
|
194
|
+
def relative?
|
195
|
+
@server.blank?
|
196
|
+
end
|
197
|
+
|
198
|
+
def absolute?
|
199
|
+
!relative?
|
200
|
+
end
|
201
|
+
|
202
|
+
# Ensure url contains a server + scheme section, eg converts '/foo' into 'http://example.com/foo'.
|
203
|
+
def make_absolute(secure = false)
|
204
|
+
unless absolute? && secure? == secure
|
205
|
+
raise 'No default server in Url#make_absolute' unless @server || Url.default_server
|
206
|
+
@server = Url.default_server unless @server
|
207
|
+
unless @scheme
|
208
|
+
@scheme = Url.default_scheme
|
209
|
+
@port = nil
|
210
|
+
end
|
211
|
+
if secure
|
212
|
+
raise "No secure scheme for scheme #{@scheme} in Url#make_absolute" unless SECURE_SCHEME_MAPPING[@scheme]
|
213
|
+
@scheme = SECURE_SCHEME_MAPPING[@scheme]
|
214
|
+
end
|
215
|
+
end
|
216
|
+
to_s
|
217
|
+
end
|
218
|
+
|
219
|
+
def make_relative
|
220
|
+
unless relative?
|
221
|
+
@server = nil
|
222
|
+
@scheme = nil
|
223
|
+
@port = nil
|
224
|
+
end
|
225
|
+
to_s
|
226
|
+
end
|
227
|
+
|
228
|
+
end
|
data/spec/spec_helper.rb
ADDED