htmless 0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +19 -0
- data/README.md +7 -0
- data/README_FULL.md +585 -0
- data/lib/copy_to_doc.rb +43 -0
- data/lib/htmless/abstract/abstract_double_tag.rb +144 -0
- data/lib/htmless/abstract/abstract_single_tag.rb +18 -0
- data/lib/htmless/abstract/abstract_tag.rb +246 -0
- data/lib/htmless/abstract.rb +252 -0
- data/lib/htmless/data/html5.rb +179 -0
- data/lib/htmless/data.rb +11 -0
- data/lib/htmless/doc.rb +394 -0
- data/lib/htmless/dynamic_classes.rb +216 -0
- data/lib/htmless/formatted.rb +43 -0
- data/lib/htmless/helper.rb +24 -0
- data/lib/htmless/pool.rb +72 -0
- data/lib/htmless/rails.rb +40 -0
- data/lib/htmless/standard.rb +48 -0
- data/lib/htmless/strings_injector.rb +43 -0
- data/lib/htmless.rb +3 -0
- data/lib/js.rb +176 -0
- data/spec/htmless_spec.rb +290 -0
- metadata +279 -0
@@ -0,0 +1,144 @@
|
|
1
|
+
module Htmless
|
2
|
+
class Abstract
|
3
|
+
dynamic_classes do
|
4
|
+
|
5
|
+
def_class :AbstractDoubleTag, :AbstractTag do ###import
|
6
|
+
nil
|
7
|
+
|
8
|
+
# @api private
|
9
|
+
def initialize(builder)
|
10
|
+
super
|
11
|
+
@content = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
# allows data-* attributes and id, classes by method_missing
|
15
|
+
def method_missing(method, *args, &block)
|
16
|
+
method = method.to_s
|
17
|
+
if method =~ METHOD_MISSING_REGEXP
|
18
|
+
if $1
|
19
|
+
self.rclass.add_attributes Data::Attribute.new(method.to_sym, :string)
|
20
|
+
self.send method, *args, &block
|
21
|
+
else
|
22
|
+
attributes(if args.last.is_a?(Hash)
|
23
|
+
args.pop
|
24
|
+
end)
|
25
|
+
content args.first
|
26
|
+
self.__send__($3 == '!' ? :id : :class, $2.gsub(@_str_underscore, @_str_dash), &block)
|
27
|
+
end
|
28
|
+
else
|
29
|
+
super(method, *args, &block)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# @api private
|
34
|
+
def open(*args, &block)
|
35
|
+
attributes = if args.last.is_a?(Hash)
|
36
|
+
args.pop
|
37
|
+
end
|
38
|
+
content args[0]
|
39
|
+
super attributes
|
40
|
+
@stack << @tag_name
|
41
|
+
if block
|
42
|
+
with &block
|
43
|
+
else
|
44
|
+
self
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# @api private
|
49
|
+
# closes the tag
|
50
|
+
def flush
|
51
|
+
flush_classes
|
52
|
+
@output << @_str_gt
|
53
|
+
@output << CGI.escapeHTML(@content) if @content
|
54
|
+
@output << @_str_slash_lt << @stack.pop << @_str_gt
|
55
|
+
@content = nil
|
56
|
+
end
|
57
|
+
|
58
|
+
# sets content of the double tag
|
59
|
+
# @example
|
60
|
+
# div 'content' # => <div>content</div>
|
61
|
+
# div.content 'content' # => <div>content</div>
|
62
|
+
# div :content => 'content' # => <div>content</div>
|
63
|
+
def content(content)
|
64
|
+
@content = content.to_s
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
# renders content of the double tag with block
|
69
|
+
# @yield content of the tag
|
70
|
+
# @example
|
71
|
+
# div { text 'content' } # => <div>content</div>
|
72
|
+
# div :id => 'id' do
|
73
|
+
# text 'content'
|
74
|
+
# end # => <div id="id">content</div>
|
75
|
+
def with
|
76
|
+
flush_classes
|
77
|
+
@output << @_str_gt
|
78
|
+
@content = nil
|
79
|
+
@builder.current = nil
|
80
|
+
yield
|
81
|
+
#if (content = yield).is_a?(String)
|
82
|
+
# @output << EscapeUtils.escape_html(content)
|
83
|
+
#end
|
84
|
+
@builder.flush
|
85
|
+
@output << @_str_slash_lt << @stack.pop << @_str_gt
|
86
|
+
nil
|
87
|
+
end
|
88
|
+
|
89
|
+
alias_method :w, :with
|
90
|
+
|
91
|
+
def mimic(obj, &block)
|
92
|
+
super(obj, &nil)
|
93
|
+
return with(&block) if block
|
94
|
+
self
|
95
|
+
end
|
96
|
+
|
97
|
+
def data(hash, &block)
|
98
|
+
super(hash, &nil)
|
99
|
+
return with(&block) if block
|
100
|
+
self
|
101
|
+
end
|
102
|
+
|
103
|
+
def attribute(name, value, &block)
|
104
|
+
super(name, value, &nil)
|
105
|
+
return with(&block) if block
|
106
|
+
self
|
107
|
+
end
|
108
|
+
|
109
|
+
def attributes(attrs, &block)
|
110
|
+
super(attrs, &nil)
|
111
|
+
return with(&block) if block
|
112
|
+
self
|
113
|
+
end
|
114
|
+
|
115
|
+
protected
|
116
|
+
|
117
|
+
# @api private
|
118
|
+
def self.define_attribute_method(attribute)
|
119
|
+
return if instance_methods(false).include?(attribute.name)
|
120
|
+
name = attribute.name.to_s
|
121
|
+
|
122
|
+
if instance_methods.include?(attribute.name)
|
123
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
124
|
+
def #{name}(*args, &block)
|
125
|
+
super(*args, &nil)
|
126
|
+
return with(&block) if block
|
127
|
+
self
|
128
|
+
end
|
129
|
+
RUBY
|
130
|
+
else
|
131
|
+
content_rendering = attribute_content_rendering(attribute)
|
132
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
133
|
+
def #{name}(content#{' = true' if attribute.type == :boolean}, &block)
|
134
|
+
#{content_rendering}
|
135
|
+
return with(&block) if block
|
136
|
+
self
|
137
|
+
end
|
138
|
+
RUBY
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end ###import
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Htmless
|
2
|
+
class Abstract
|
3
|
+
dynamic_classes do
|
4
|
+
def_class :AbstractSingleTag, :AbstractTag do ###import
|
5
|
+
nil
|
6
|
+
|
7
|
+
# @api private
|
8
|
+
# closes the tag
|
9
|
+
def flush
|
10
|
+
flush_classes
|
11
|
+
@output << @_str_slash_gt
|
12
|
+
nil
|
13
|
+
end
|
14
|
+
end ###import
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,246 @@
|
|
1
|
+
module Htmless
|
2
|
+
class Abstract
|
3
|
+
dynamic_classes do
|
4
|
+
def_class :AbstractTag do ###import
|
5
|
+
|
6
|
+
def self.strings_injector
|
7
|
+
dynamic_class_base.strings_injector
|
8
|
+
end
|
9
|
+
|
10
|
+
def self._attributes=(_attributes)
|
11
|
+
@_attributes = _attributes
|
12
|
+
end
|
13
|
+
|
14
|
+
def self._attributes
|
15
|
+
@_attributes or (superclass._attributes if superclass.respond_to? :_attributes)
|
16
|
+
end
|
17
|
+
|
18
|
+
self._attributes = []
|
19
|
+
|
20
|
+
# @return [Array<String>] array of available attributes for the tag
|
21
|
+
def self.attributes
|
22
|
+
_attributes
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [String] tag's name
|
26
|
+
def self.tag_name
|
27
|
+
@tag || superclass.tag_name
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
# sets the tag's name
|
33
|
+
# @api private
|
34
|
+
def self.set_tag(tag)
|
35
|
+
@tag = tag.to_s.freeze
|
36
|
+
end
|
37
|
+
|
38
|
+
set_tag 'abstract'
|
39
|
+
|
40
|
+
# defines dynamically methods for attributes
|
41
|
+
# @api private
|
42
|
+
def self.define_attribute_methods
|
43
|
+
attributes.each { |attr| define_attribute_method(attr) }
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.inherited(base)
|
47
|
+
base.define_attribute_methods
|
48
|
+
end
|
49
|
+
|
50
|
+
# defines dynamically method for +attribute+
|
51
|
+
# @param [Data::Attribute] attribute
|
52
|
+
# @api private
|
53
|
+
def self.define_attribute_method(attribute)
|
54
|
+
return if instance_methods.include?(attribute.name)
|
55
|
+
name = attribute.name.to_s
|
56
|
+
content_rendering = attribute_content_rendering(attribute)
|
57
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
58
|
+
def #{name}(content#{' = true' if attribute.type == :boolean})
|
59
|
+
#{content_rendering}
|
60
|
+
self
|
61
|
+
end
|
62
|
+
RUBY
|
63
|
+
end
|
64
|
+
|
65
|
+
# @api private
|
66
|
+
# @param [Data::Attribute] attribute
|
67
|
+
# @returns Ruby code as string
|
68
|
+
def self.attribute_content_rendering(attribute)
|
69
|
+
name = attribute.name.to_s
|
70
|
+
case attribute.type
|
71
|
+
when :string
|
72
|
+
strings_injector.add "attr_#{name}", " #{name.gsub('_', '-')}=#{strings_injector[:quote]}"
|
73
|
+
"@output << @_str_attr_#{name} << CGI.escapeHTML(content.to_s) << @_str_quote"
|
74
|
+
when :boolean
|
75
|
+
strings_injector.add(
|
76
|
+
"attr_#{name}",
|
77
|
+
" #{name.gsub('_', '-')}=#{strings_injector[:quote]}#{name}#{strings_injector[:quote]}")
|
78
|
+
"@output << @_str_attr_#{name} if content"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# adds attribute to class, triggers dynamical creation of needed instance methods etc.
|
83
|
+
# @param [Array<Data::Attribute>] attributes
|
84
|
+
# @api private
|
85
|
+
def self.add_attributes(attributes)
|
86
|
+
attributes = [attributes] unless attributes.is_a? Array
|
87
|
+
raise ArgumentError, attributes.inspect unless attributes.all? { |v| v.is_a? Data::Attribute }
|
88
|
+
self.send :_attributes=, _attributes + attributes
|
89
|
+
define_attribute_methods
|
90
|
+
end
|
91
|
+
|
92
|
+
public
|
93
|
+
|
94
|
+
attr_reader :builder
|
95
|
+
|
96
|
+
# @api private
|
97
|
+
def initialize(builder)
|
98
|
+
@builder = builder
|
99
|
+
@output = builder.instance_eval { @_output }
|
100
|
+
@stack = builder.instance_eval { @_stack }
|
101
|
+
@classes = []
|
102
|
+
@tag_name = self.rclass.tag_name
|
103
|
+
|
104
|
+
self.rclass.strings_injector.inject_to self
|
105
|
+
end
|
106
|
+
|
107
|
+
# @api private
|
108
|
+
def open(attributes = nil)
|
109
|
+
@output << @_str_lt << @tag_name
|
110
|
+
@builder.current = self
|
111
|
+
attributes(attributes)
|
112
|
+
default
|
113
|
+
self
|
114
|
+
end
|
115
|
+
|
116
|
+
# it renders attribute using defined attribute method or by rendering attribute directly
|
117
|
+
# @param [String, Symbol] name
|
118
|
+
# @param [#to_s] value
|
119
|
+
def attribute(name, value)
|
120
|
+
return __send__(name, value) if respond_to?(name)
|
121
|
+
@output << @_str_space << name.to_s << @_str_eql_quote << CGI.escapeHTML(value.to_s) << @_str_quote
|
122
|
+
self
|
123
|
+
end
|
124
|
+
|
125
|
+
# @example
|
126
|
+
# div.attributes :id => 'id' # => <div id="id"></div>
|
127
|
+
# div :id => 'id', :class => %w{left right} # => <div id="id" class="left right"></div>
|
128
|
+
# img :src => 'path' # => <img src="path"></div>
|
129
|
+
# attribute`s methods are called on background (in this case #id is called)
|
130
|
+
def attributes(attrs)
|
131
|
+
return self unless attrs
|
132
|
+
attrs.each do |attr, value|
|
133
|
+
if value.kind_of?(Array)
|
134
|
+
__send__(attr, *value)
|
135
|
+
else
|
136
|
+
__send__(attr, value)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
self
|
140
|
+
end
|
141
|
+
|
142
|
+
# original Ruby method for class, class is used for html classes
|
143
|
+
alias_method(:rclass, :class)
|
144
|
+
|
145
|
+
id_class = /^([\w]+)(!|)$/
|
146
|
+
data_attribute = /^data_([a-z_]+)$/
|
147
|
+
METHOD_MISSING_REGEXP = /#{data_attribute}|#{id_class}/ unless defined? METHOD_MISSING_REGEXP
|
148
|
+
|
149
|
+
# allows data-* attributes and id, classes by method_missing
|
150
|
+
def method_missing(method, *args, &block)
|
151
|
+
method = method.to_s
|
152
|
+
if method =~ METHOD_MISSING_REGEXP
|
153
|
+
if $1
|
154
|
+
self.rclass.add_attributes Data::Attribute.new(method, :string)
|
155
|
+
self.send method, *args
|
156
|
+
else
|
157
|
+
self.__send__($3 == '!' ? :id : :class, $2.gsub(@_str_underscore, @_str_dash))
|
158
|
+
self.attributes args.first
|
159
|
+
end
|
160
|
+
else
|
161
|
+
super(method, *args, &block)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
#def respond_to?(symbol, include_private = false)
|
166
|
+
# symbol.to_s =~ METHOD_MISSING_REGEXP || super(symbol, include_private)
|
167
|
+
#end
|
168
|
+
|
169
|
+
strings_injector.add "attr_class", " class=#{strings_injector[:quote]}"
|
170
|
+
# adds classes to the tag by joining +classes+ with ' ' and skipping non-true classes
|
171
|
+
# @param [Array<#to_s>] classes
|
172
|
+
# @example
|
173
|
+
# class(!visible? && 'hidden', 'left') #=> class="hidden left" or class="left"
|
174
|
+
def class(*classes)
|
175
|
+
@classes.push(*classes.select { |c| c })
|
176
|
+
self
|
177
|
+
end
|
178
|
+
|
179
|
+
strings_injector.add "attr_id", " id=#{strings_injector[:quote]}"
|
180
|
+
# adds id to the tag by joining +values+ with '_'
|
181
|
+
# @param [Array<#to_s>] values
|
182
|
+
# @example
|
183
|
+
# id('user', 12) #=> id="user-15"
|
184
|
+
def id(*values)
|
185
|
+
@output << @_str_attr_id << CGI.escapeHTML(values.select { |v| v }.join(@_str_dash)) << @_str_quote
|
186
|
+
self
|
187
|
+
end
|
188
|
+
|
189
|
+
# adds id and class to a tag by an object
|
190
|
+
# @param [Object] obj
|
191
|
+
# To determine the class it looks for .htmless_ref or
|
192
|
+
# it uses class.to_s.underscore.tr('/', '-').
|
193
|
+
# To determine id it looks for #htmless_ref or it takes class and #id or #object_id.
|
194
|
+
# @example
|
195
|
+
# div[AUser.new].with { text 'a' } # => <div id="a_user_1" class="a_user">a</div>
|
196
|
+
def mimic(obj)
|
197
|
+
klass = if obj.class.respond_to? :htmless_ref
|
198
|
+
obj.class.htmless_ref
|
199
|
+
else
|
200
|
+
obj.class.to_s.scan(/[A-Z][a-z\d]*/).join('_').downcase.gsub('::', '-')
|
201
|
+
end
|
202
|
+
|
203
|
+
id = case
|
204
|
+
when obj.respond_to?(:htmless_ref)
|
205
|
+
obj.htmless_ref
|
206
|
+
when obj.respond_to?(:id)
|
207
|
+
[klass, obj.id]
|
208
|
+
else
|
209
|
+
[klass, obj.object_id]
|
210
|
+
end
|
211
|
+
#noinspection RubyArgCount
|
212
|
+
self.class(klass).id(id)
|
213
|
+
end
|
214
|
+
|
215
|
+
alias_method :[], :mimic
|
216
|
+
|
217
|
+
# renders data-* attributes by +hash+
|
218
|
+
# @param [Hash] hash
|
219
|
+
# @example
|
220
|
+
# div.data(:remote => true, :id => 'an_id') # => <div data-remote="true" data-id="an_id"></div>
|
221
|
+
def data(hash)
|
222
|
+
hash.each { |k, v| __send__ "data_#{k}", v }
|
223
|
+
self
|
224
|
+
end
|
225
|
+
|
226
|
+
protected
|
227
|
+
|
228
|
+
# this method is called on each tag opening, useful for default attributes
|
229
|
+
# @example html tag uses this to add xmlns attr.
|
230
|
+
# html # => <html xmlns="http://www.w3.org/1999/xhtml"></html>
|
231
|
+
def default
|
232
|
+
end
|
233
|
+
|
234
|
+
# flushes classes to output
|
235
|
+
# @api private
|
236
|
+
def flush_classes
|
237
|
+
unless @classes.empty?
|
238
|
+
@output << @_str_attr_class << CGI.escapeHTML(@classes.join(@_str_space)) << @_str_quote
|
239
|
+
@classes.clear
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end ###import
|
243
|
+
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
@@ -0,0 +1,252 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
#require 'active_support/core_ext/string/inflections'
|
3
|
+
|
4
|
+
require 'htmless/dynamic_classes'
|
5
|
+
require 'htmless/strings_injector'
|
6
|
+
require 'htmless/data'
|
7
|
+
require 'htmless/data/html5'
|
8
|
+
require "htmless/pool"
|
9
|
+
require "htmless/helper"
|
10
|
+
|
11
|
+
module Htmless
|
12
|
+
|
13
|
+
# Abstract implementation of Builder
|
14
|
+
class Abstract
|
15
|
+
extend DynamicClasses
|
16
|
+
|
17
|
+
require "htmless/abstract/abstract_tag"
|
18
|
+
require "htmless/abstract/abstract_single_tag"
|
19
|
+
require "htmless/abstract/abstract_double_tag"
|
20
|
+
|
21
|
+
def self.strings_injector
|
22
|
+
@strings_injector ||= StringsInjector.new do
|
23
|
+
add :lt, '<'
|
24
|
+
add :gt, '>'
|
25
|
+
add :slash_lt, '</'
|
26
|
+
add :slash_gt, ' />'
|
27
|
+
add :dash, '-'
|
28
|
+
add :underscore, '_'
|
29
|
+
add :space, ' '
|
30
|
+
add :spaces, Array.new(300) { |i| (' ' * i).freeze }
|
31
|
+
add :newline, "\n"
|
32
|
+
add :quote, '"'
|
33
|
+
add :eql, '='
|
34
|
+
add :eql_quote, self[:eql] + self[:quote]
|
35
|
+
add :comment_start, '<!--'
|
36
|
+
add :comment_end, '-->'
|
37
|
+
add :cdata_start, '<![CDATA['
|
38
|
+
add :cdata_end, ']]>'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# << faster then +
|
43
|
+
# yield faster then block.call
|
44
|
+
# accessing ivar and constant is faster then accesing hash or cvar
|
45
|
+
# class_eval faster then define_method
|
46
|
+
# beware of strings in methods -> creates a lot of garbage
|
47
|
+
|
48
|
+
def self.tags=(tags)
|
49
|
+
@tags = tags
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.tags
|
53
|
+
@tags or (superclass.tags if superclass.respond_to? :tags)
|
54
|
+
end
|
55
|
+
|
56
|
+
def tags
|
57
|
+
self.class.tags
|
58
|
+
end
|
59
|
+
|
60
|
+
self.tags = []
|
61
|
+
|
62
|
+
protected
|
63
|
+
|
64
|
+
# defines instance method for +tag+ in builder
|
65
|
+
def self.define_tag(tag)
|
66
|
+
tag = tag.to_s
|
67
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
68
|
+
def #{tag}(*args, &block)
|
69
|
+
flush
|
70
|
+
@_#{tag}.open(*args, &block)
|
71
|
+
end
|
72
|
+
RUBY
|
73
|
+
self.tags += [tag]
|
74
|
+
end
|
75
|
+
|
76
|
+
public
|
77
|
+
|
78
|
+
|
79
|
+
# current tag being builded
|
80
|
+
attr_accessor :_current
|
81
|
+
alias_method :current, :_current
|
82
|
+
alias_method :current=, :_current=
|
83
|
+
|
84
|
+
|
85
|
+
# creates a new builder
|
86
|
+
# This is quite expensive, HammerBuilder::Pool should be used
|
87
|
+
def initialize()
|
88
|
+
@_output = ""
|
89
|
+
@_stack = []
|
90
|
+
@_current = nil
|
91
|
+
|
92
|
+
self.class.strings_injector.inject_to self
|
93
|
+
|
94
|
+
# tag classes initialization
|
95
|
+
tags.each do |klass|
|
96
|
+
instance_variable_set(:"@_#{klass}", self.class.dynamic_classes[camelize_string(klass).to_sym].new(self))
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# escapes +text+ to output
|
101
|
+
def text(text)
|
102
|
+
flush
|
103
|
+
@_output << CGI.escapeHTML(text.to_s)
|
104
|
+
end
|
105
|
+
|
106
|
+
# unescaped +text+ to output
|
107
|
+
def raw(text)
|
108
|
+
flush
|
109
|
+
@_output << text.to_s
|
110
|
+
end
|
111
|
+
|
112
|
+
# inserts +comment+
|
113
|
+
def comment(comment)
|
114
|
+
flush
|
115
|
+
@_output << @_str_comment_start << comment.to_s << @_str_comment_end
|
116
|
+
end
|
117
|
+
|
118
|
+
# insersts CDATA with +content+
|
119
|
+
def cdata(content)
|
120
|
+
flush
|
121
|
+
@_output << @_str_cdata_start << content.to_s << @_str_cdata_end
|
122
|
+
end
|
123
|
+
|
124
|
+
# renders html5 doc type
|
125
|
+
# @example
|
126
|
+
# html5 # => <!DOCTYPE html>
|
127
|
+
def html5
|
128
|
+
raw "<!DOCTYPE html>\n"
|
129
|
+
end
|
130
|
+
|
131
|
+
# resets the builder to the state after creation - much faster then creating a new one
|
132
|
+
def reset
|
133
|
+
flush
|
134
|
+
@_output.clear
|
135
|
+
@_stack.clear
|
136
|
+
self
|
137
|
+
end
|
138
|
+
|
139
|
+
#def capture
|
140
|
+
# flush
|
141
|
+
# _output = @_output.clone
|
142
|
+
# _stack = @_stack.clone
|
143
|
+
# @_output.clear
|
144
|
+
# @_stack.clear
|
145
|
+
# yield
|
146
|
+
# to_html
|
147
|
+
#ensure
|
148
|
+
# @_output.replace _output
|
149
|
+
# @_stack.replace _stack
|
150
|
+
#end
|
151
|
+
|
152
|
+
# enables you to evaluate +block+ inside the builder with +variables+
|
153
|
+
# @example
|
154
|
+
# HammerBuilder::Formatted.new.go_in('asd') do |string|
|
155
|
+
# div string
|
156
|
+
# end.to_html #=> "<div>asd</div>"
|
157
|
+
#
|
158
|
+
def go_in(*variables, &block)
|
159
|
+
instance_exec *variables, &block
|
160
|
+
self
|
161
|
+
end
|
162
|
+
|
163
|
+
alias_method :dive, :go_in
|
164
|
+
|
165
|
+
# sets instance variables when block is yielded
|
166
|
+
# @param [Hash{String => Object}] instance_variables hash of names and values to set
|
167
|
+
# @yield block when variables are set, variables are cleaned up afterwards
|
168
|
+
def set_variables(instance_variables)
|
169
|
+
instance_variables.each { |name, value| instance_variable_set("@#{name}", value) }
|
170
|
+
yield(self)
|
171
|
+
instance_variables.each { |name, _| remove_instance_variable("@#{name}") }
|
172
|
+
self
|
173
|
+
end
|
174
|
+
|
175
|
+
# @return [String] output
|
176
|
+
def to_html()
|
177
|
+
flush
|
178
|
+
@_output.clone
|
179
|
+
end
|
180
|
+
|
181
|
+
# flushes open tag
|
182
|
+
# @api private
|
183
|
+
def flush
|
184
|
+
if @_current
|
185
|
+
@_current.flush
|
186
|
+
@_current = nil
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# renders +object+ with +method+
|
191
|
+
# @param [Object] object an object to render
|
192
|
+
# @param [Symbol] method a method name which is used for rendering
|
193
|
+
# @param args arguments passed to rendering method
|
194
|
+
# @yield block passed to rendering method
|
195
|
+
def render(object, method, *args, &block)
|
196
|
+
object.__send__ method, self, *args, &block
|
197
|
+
self
|
198
|
+
end
|
199
|
+
|
200
|
+
alias_method :r, :render
|
201
|
+
|
202
|
+
# renders js
|
203
|
+
# @option options [Boolean] :cdata (false) should cdata be used?
|
204
|
+
# @example
|
205
|
+
# js 'a_js_function();' #=> <script type="text/javascript">a_js_function();</script>
|
206
|
+
def js(js, options = {})
|
207
|
+
use_cdata = options.delete(:cdata) || false
|
208
|
+
script({ :type => "text/javascript" }.merge(options)) { use_cdata ? cdata(js) : text(js) }
|
209
|
+
end
|
210
|
+
|
211
|
+
# joins and renders +collection+ with +glue+
|
212
|
+
# @param [Array<Proc, Object>] collection of objects or lambdas
|
213
|
+
# @param [Proc, String] glue can be String which is rendered with #text or block to render
|
214
|
+
# @yield how to render objects from +collection+, Proc in collection does not use this block
|
215
|
+
# @example
|
216
|
+
# join([1, 1.2], lambda { text ', ' }) {|o| text o } # => "1, 1.2"
|
217
|
+
# join([1, 1.2], ', ') {|o| text o } # => "1, 1.2"
|
218
|
+
# join([->{ text 1 }, 1.2], ', ') {|o| text o } # => "1, 1.2"
|
219
|
+
def join(collection, glue = nil, &it)
|
220
|
+
# TODO as helper? two block method call #join(collection, &item).with(&glue)
|
221
|
+
glue_block = case glue
|
222
|
+
when String
|
223
|
+
lambda { text glue }
|
224
|
+
when Proc
|
225
|
+
glue
|
226
|
+
else
|
227
|
+
lambda {}
|
228
|
+
end
|
229
|
+
|
230
|
+
collection.each_with_index do |obj, i|
|
231
|
+
glue_block.call() if i > 0
|
232
|
+
obj.is_a?(Proc) ? obj.call : it.call(obj)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
|
237
|
+
private
|
238
|
+
|
239
|
+
def self.camelize_string(term)
|
240
|
+
term.
|
241
|
+
to_s.
|
242
|
+
sub(/^[a-z\d]*/) { $&.capitalize }.
|
243
|
+
gsub(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{$2.capitalize}" }.
|
244
|
+
gsub('/', '::')
|
245
|
+
end
|
246
|
+
|
247
|
+
def camelize_string(term)
|
248
|
+
self.class.camelize_string term
|
249
|
+
end
|
250
|
+
|
251
|
+
end
|
252
|
+
end
|