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 +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