erector-rails4 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.coveralls.yml +2 -0
- data/.gitignore +10 -0
- data/.rspec +1 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +143 -0
- data/README.md +4 -0
- data/Rakefile +24 -0
- data/erector-rails4.gemspec +36 -0
- data/erector-rails4.sublimeproject +20 -0
- data/lib/erector/abstract_widget.rb +231 -0
- data/lib/erector/after_initialize.rb +29 -0
- data/lib/erector/attributes.rb +29 -0
- data/lib/erector/cache.rb +37 -0
- data/lib/erector/caching.rb +65 -0
- data/lib/erector/convenience.rb +98 -0
- data/lib/erector/dependencies.rb +24 -0
- data/lib/erector/dependency.rb +31 -0
- data/lib/erector/element.rb +113 -0
- data/lib/erector/externals.rb +104 -0
- data/lib/erector/html.rb +12 -0
- data/lib/erector/html_widget.rb +220 -0
- data/lib/erector/inline.rb +37 -0
- data/lib/erector/jquery.rb +28 -0
- data/lib/erector/mixin.rb +12 -0
- data/lib/erector/needs.rb +95 -0
- data/lib/erector/output.rb +144 -0
- data/lib/erector/promise.rb +141 -0
- data/lib/erector/rails/form_builder.rb +44 -0
- data/lib/erector/rails/railtie.rb +13 -0
- data/lib/erector/rails/template_handler.rb +16 -0
- data/lib/erector/rails/widget_renderer.rb +6 -0
- data/lib/erector/rails.rb +221 -0
- data/lib/erector/raw_string.rb +12 -0
- data/lib/erector/sass.rb +32 -0
- data/lib/erector/tag.rb +66 -0
- data/lib/erector/text.rb +123 -0
- data/lib/erector/unicode.rb +18185 -0
- data/lib/erector/unicode_builder.rb +67 -0
- data/lib/erector/version.rb +5 -0
- data/lib/erector/widget.rb +94 -0
- data/lib/erector/widgets.rb +5 -0
- data/lib/erector/xml_widget.rb +131 -0
- data/lib/erector.rb +28 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/controllers/application.rb +6 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/views/layouts/erb_as_layout.html.erb +2 -0
- data/spec/dummy/app/views/layouts/widget_as_layout.rb +8 -0
- data/spec/dummy/app/views/test/_erb.erb +1 -0
- data/spec/dummy/app/views/test/_erector.rb +5 -0
- data/spec/dummy/app/views/test/_partial_with_locals.rb +7 -0
- data/spec/dummy/app/views/test/bare.rb +5 -0
- data/spec/dummy/app/views/test/erb_from_erector.html.rb +5 -0
- data/spec/dummy/app/views/test/erector_from_erb.html.erb +1 -0
- data/spec/dummy/app/views/test/erector_with_locals_from_erb.html.erb +6 -0
- data/spec/dummy/app/views/test/implicit_assigns.html.rb +5 -0
- data/spec/dummy/app/views/test/needs.html.rb +7 -0
- data/spec/dummy/app/views/test/needs_subclass.html.rb +5 -0
- data/spec/dummy/app/views/test/protected_instance_variable.html.rb +5 -0
- data/spec/dummy/app/views/test/render_default.html.rb +5 -0
- data/spec/dummy/app/views/test/render_default_erb_with_layout.html.erb +1 -0
- data/spec/dummy/app/views/test/render_default_widget_with_layout.html.rb +5 -0
- data/spec/dummy/app/views/test/render_partial.html.rb +5 -0
- data/spec/dummy/app/views/test/render_with_widget_as_layout.rb +5 -0
- data/spec/dummy/app/views/test/render_with_widget_as_layout_using_content_for.rb +8 -0
- data/spec/dummy/config/application.rb +44 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +22 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +22 -0
- data/spec/dummy/config/environments/production.rb +49 -0
- data/spec/dummy/config/environments/test.rb +36 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +10 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +12 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +3 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/seeds.rb +7 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/dummy/spec/form_builder_spec.rb +21 -0
- data/spec/dummy/spec/rails_helpers_spec.rb +236 -0
- data/spec/dummy/spec/rails_spec_helper.rb +10 -0
- data/spec/dummy/spec/rails_widget_spec.rb +83 -0
- data/spec/dummy/spec/render_spec.rb +369 -0
- data/spec/erector/after_initialize_spec.rb +45 -0
- data/spec/erector/cache_spec.rb +133 -0
- data/spec/erector/caching_spec.rb +184 -0
- data/spec/erector/convenience_spec.rb +250 -0
- data/spec/erector/dependency_spec.rb +67 -0
- data/spec/erector/hello_from_readme.rb +18 -0
- data/spec/erector/hello_from_readme_spec.rb +11 -0
- data/spec/erector/html_spec.rb +585 -0
- data/spec/erector/indentation_spec.rb +211 -0
- data/spec/erector/inline_spec.rb +94 -0
- data/spec/erector/jquery_spec.rb +35 -0
- data/spec/erector/mixin_spec.rb +65 -0
- data/spec/erector/needs_spec.rb +141 -0
- data/spec/erector/output_spec.rb +293 -0
- data/spec/erector/promise_spec.rb +173 -0
- data/spec/erector/sample-file.txt +1 -0
- data/spec/erector/sass_spec.rb +57 -0
- data/spec/erector/tag_spec.rb +67 -0
- data/spec/erector/unicode_builder_spec.rb +75 -0
- data/spec/erector/widget_spec.rb +310 -0
- data/spec/erector/xml_widget_spec.rb +73 -0
- data/spec/spec_helper.rb +31 -0
- metadata +368 -0
data/lib/erector/html.rb
ADDED
@@ -0,0 +1,220 @@
|
|
1
|
+
require "erector/xml_widget"
|
2
|
+
|
3
|
+
module Erector
|
4
|
+
|
5
|
+
# A Widget is the center of the Erector universe.
|
6
|
+
#
|
7
|
+
# To create a widget, extend Erector::Widget and implement the +content+
|
8
|
+
# method. Inside this method you may call any of the tag methods like +span+
|
9
|
+
# or +p+ to emit HTML/XML tags.
|
10
|
+
#
|
11
|
+
# You can also define a widget on the fly by passing a block to +new+. This
|
12
|
+
# block will get executed when the widget's +content+ method is called. See
|
13
|
+
# the userguide for important details about the scope of this block when run --
|
14
|
+
# http://erector.rubyforge.org/userguide.html#blocks
|
15
|
+
#
|
16
|
+
# To render a widget from the outside, instantiate it and call its +to_html+
|
17
|
+
# method.
|
18
|
+
#
|
19
|
+
# A widget's +new+ method optionally accepts an options hash. Entries in
|
20
|
+
# this hash are converted to instance variables.
|
21
|
+
#
|
22
|
+
# You can add runtime input checking via the +needs+ macro. See #needs.
|
23
|
+
# This mechanism is meant to ameliorate development-time confusion about
|
24
|
+
# exactly what parameters are supported by a given widget, avoiding
|
25
|
+
# confusing runtime NilClass errors.
|
26
|
+
#
|
27
|
+
# To call one widget from another, inside the parent widget's +content+
|
28
|
+
# method, instantiate the child widget and call the +widget+ method. This
|
29
|
+
# assures that the same output stream is used, which gives better
|
30
|
+
# performance than using +capture+ or +to_html+. It also preserves the
|
31
|
+
# indentation and helpers of the enclosing class.
|
32
|
+
#
|
33
|
+
# In this documentation we've tried to keep the distinction clear between
|
34
|
+
# methods that *emit* text and those that *return* text. "Emit" means that
|
35
|
+
# it writes to the output stream; "return" means that it returns a string
|
36
|
+
# like a normal method and leaves it up to the caller to emit that string if
|
37
|
+
# it wants.
|
38
|
+
#
|
39
|
+
# This class extends AbstractWidget and includes several modules,
|
40
|
+
# so be sure to check all of those places for API documentation for the
|
41
|
+
# various methods of Widget:
|
42
|
+
#
|
43
|
+
# * AbstractWidget
|
44
|
+
# * Element
|
45
|
+
# * Attributes
|
46
|
+
# * Text
|
47
|
+
# * Needs
|
48
|
+
# * Caching
|
49
|
+
# * Externals
|
50
|
+
# * AfterInitialize
|
51
|
+
#
|
52
|
+
# * HTML
|
53
|
+
# * Convenience
|
54
|
+
# * JQuery
|
55
|
+
# * Sass
|
56
|
+
#
|
57
|
+
# Also read the API Cheatsheet in the user guide
|
58
|
+
# at http://erector.rubyforge.org/userguide#apicheatsheet
|
59
|
+
class HTMLWidget < Erector::XMLWidget
|
60
|
+
|
61
|
+
include Erector::HTML
|
62
|
+
include Erector::Convenience
|
63
|
+
include Erector::JQuery
|
64
|
+
include Erector::Sass
|
65
|
+
|
66
|
+
tag 'area', :self_closing
|
67
|
+
tag 'base', :self_closing
|
68
|
+
tag 'br', :self_closing
|
69
|
+
tag 'col', :self_closing
|
70
|
+
tag 'embed', :self_closing
|
71
|
+
tag 'frame', :self_closing
|
72
|
+
tag 'hr', :self_closing
|
73
|
+
tag 'img', :self_closing, :inline
|
74
|
+
tag 'input', :self_closing, :inline
|
75
|
+
tag 'link', :self_closing
|
76
|
+
tag 'meta', :self_closing
|
77
|
+
tag 'param', :self_closing
|
78
|
+
|
79
|
+
tag 'a', :inline
|
80
|
+
tag 'abbr', :inline
|
81
|
+
tag 'acronym', :inline
|
82
|
+
tag 'address'
|
83
|
+
tag 'article'
|
84
|
+
tag 'aside'
|
85
|
+
tag 'audio'
|
86
|
+
|
87
|
+
tag 'b', :inline
|
88
|
+
tag 'bdo', :inline
|
89
|
+
tag 'big', :inline
|
90
|
+
tag 'blockquote'
|
91
|
+
tag 'body'
|
92
|
+
tag 'button', :inline
|
93
|
+
|
94
|
+
tag 'canvas'
|
95
|
+
tag 'caption', :inline
|
96
|
+
tag 'center'
|
97
|
+
tag 'cite', :inline
|
98
|
+
tag 'code', :inline
|
99
|
+
tag 'colgroup'
|
100
|
+
tag 'command'
|
101
|
+
|
102
|
+
tag 'datalist'
|
103
|
+
tag 'dd'
|
104
|
+
tag 'del'
|
105
|
+
tag 'details'
|
106
|
+
tag 'dfn', :inline
|
107
|
+
tag 'dialog'
|
108
|
+
tag 'div'
|
109
|
+
tag 'dl'
|
110
|
+
tag 'dt', :inline
|
111
|
+
|
112
|
+
tag 'em', :inline
|
113
|
+
|
114
|
+
tag 'fieldset'
|
115
|
+
tag 'figure'
|
116
|
+
tag 'footer'
|
117
|
+
tag 'form'
|
118
|
+
tag 'frameset'
|
119
|
+
|
120
|
+
tag 'h1'
|
121
|
+
tag 'h2'
|
122
|
+
tag 'h3'
|
123
|
+
tag 'h4'
|
124
|
+
tag 'h5'
|
125
|
+
tag 'h6'
|
126
|
+
tag 'head'
|
127
|
+
tag 'header'
|
128
|
+
tag 'hgroup'
|
129
|
+
tag 'html'
|
130
|
+
tag 'i', :inline
|
131
|
+
|
132
|
+
tag 'iframe'
|
133
|
+
tag 'ins'
|
134
|
+
tag 'keygen'
|
135
|
+
tag 'kbd', :inline
|
136
|
+
tag 'label', :inline
|
137
|
+
tag 'legend', :inline
|
138
|
+
tag 'li'
|
139
|
+
|
140
|
+
tag 'map'
|
141
|
+
tag 'mark'
|
142
|
+
tag 'meter'
|
143
|
+
|
144
|
+
tag 'nav'
|
145
|
+
tag 'noframes'
|
146
|
+
tag 'noscript'
|
147
|
+
|
148
|
+
tag 'object'
|
149
|
+
tag 'ol'
|
150
|
+
tag 'optgroup'
|
151
|
+
tag 'option'
|
152
|
+
|
153
|
+
tag 'p'
|
154
|
+
tag 'pre'
|
155
|
+
tag 'progress'
|
156
|
+
|
157
|
+
tag 'q', :inline
|
158
|
+
tag 'ruby'
|
159
|
+
tag 'rt'
|
160
|
+
tag 'rp'
|
161
|
+
tag 's'
|
162
|
+
|
163
|
+
tag 'samp', :inline
|
164
|
+
tag 'script'
|
165
|
+
tag 'section'
|
166
|
+
tag 'select', :inline
|
167
|
+
tag 'small', :inline
|
168
|
+
tag 'source'
|
169
|
+
tag 'span', :inline
|
170
|
+
tag 'strike'
|
171
|
+
|
172
|
+
tag 'strong', :inline
|
173
|
+
tag 'style'
|
174
|
+
tag 'sub', :inline
|
175
|
+
tag 'sup', :inline
|
176
|
+
|
177
|
+
tag 'table'
|
178
|
+
tag 'tbody'
|
179
|
+
tag 'td'
|
180
|
+
tag 'textarea', :inline
|
181
|
+
tag 'tfoot'
|
182
|
+
|
183
|
+
tag 'th'
|
184
|
+
tag 'thead'
|
185
|
+
tag 'time'
|
186
|
+
tag 'title'
|
187
|
+
tag 'tr'
|
188
|
+
tag 'tt', :inline
|
189
|
+
|
190
|
+
tag 'u'
|
191
|
+
tag 'ul'
|
192
|
+
|
193
|
+
tag 'var', :inline
|
194
|
+
tag 'video'
|
195
|
+
|
196
|
+
|
197
|
+
# alias for AbstractWidget#render
|
198
|
+
def to_html(options = {})
|
199
|
+
raise "Erector::Widget#to_html takes an options hash, not a symbol. Try calling \"to_html(:content_method_name=> :#{options})\"" if options.is_a? Symbol
|
200
|
+
_render(options).to_s
|
201
|
+
end
|
202
|
+
|
203
|
+
# alias for #to_html
|
204
|
+
# @deprecated Please use {#to_html} instead
|
205
|
+
def to_s(*args)
|
206
|
+
unless defined? @@already_warned_to_s
|
207
|
+
$stderr.puts "Erector::Widget#to_s is deprecated. Please use #to_html instead. Called from #{caller.first}"
|
208
|
+
@@already_warned_to_s = true
|
209
|
+
end
|
210
|
+
to_html(*args)
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|
214
|
+
|
215
|
+
public
|
216
|
+
|
217
|
+
# Alias to make it easier to spell
|
218
|
+
HtmlWidget = HTMLWidget
|
219
|
+
|
220
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Erector
|
2
|
+
def self.inline(*args, &block)
|
3
|
+
InlineWidget.new(*args, &block)
|
4
|
+
end
|
5
|
+
|
6
|
+
module Inline
|
7
|
+
# Executes the widget's block (the one that was passed in the
|
8
|
+
# constructor). Since "self" is pointing to the new widget, the block does
|
9
|
+
# not naturally have access to parent method methods, so an
|
10
|
+
# Erector::Inline widget uses some method_missing black magic to propagate
|
11
|
+
# messages to the parent object. Since it executes inside the *called*
|
12
|
+
# widget's context, when the block refers to instance variables, it's
|
13
|
+
# talking about those of this widget, not the caller. It does, of course,
|
14
|
+
# have access to bound local variables of the caller, so you can use those
|
15
|
+
# to smuggle in instance variables.
|
16
|
+
def call_block
|
17
|
+
# note that instance_eval seems to pass in self as a parameter to the block
|
18
|
+
instance_eval(&block) if block
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
# This is part of the sub-widget/parent feature (see #widget method).
|
23
|
+
def method_missing(name, *args, &block)
|
24
|
+
if parent && parent.respond_to?(name)
|
25
|
+
block ||= lambda {} # captures self HERE
|
26
|
+
parent.send(name, *args, &block)
|
27
|
+
else
|
28
|
+
super
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class InlineWidget < Widget
|
34
|
+
include Inline
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Erector
|
2
|
+
module JQuery
|
3
|
+
# Emits a jQuery script, inside its own script tag, that is to be run on document ready or load.
|
4
|
+
#
|
5
|
+
# Usage (from inside a widget method):
|
6
|
+
# jquery "alert('hi')" :: a jquery ready handler
|
7
|
+
# jquery "alert('hi')", :id => 'foo' :: a jquery ready handler, with attributes in the script tag
|
8
|
+
# jquery :load, "alert('hi')" :: a jquery load handler
|
9
|
+
#
|
10
|
+
def jquery(*args)
|
11
|
+
event = if args.first.is_a? Symbol
|
12
|
+
args.shift
|
13
|
+
else
|
14
|
+
:ready
|
15
|
+
end
|
16
|
+
txt = args.shift
|
17
|
+
attributes = args.shift || {}
|
18
|
+
|
19
|
+
javascript attributes do
|
20
|
+
rawtext "\n"
|
21
|
+
rawtext "jQuery(document).#{event}(function($){\n"
|
22
|
+
rawtext txt
|
23
|
+
rawtext "\n});"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Erector
|
2
|
+
module Mixin
|
3
|
+
# Executes the block as if it were the content body of a fresh Erector::Inline,
|
4
|
+
# and returns the #to_html value. Since it executes inside the new widget it does not
|
5
|
+
# have access to instance variables of the caller, although it does
|
6
|
+
# have access to bound variables. Funnily enough, the options are passed in to both
|
7
|
+
# to_html *and* to the widget itself, so they show up as instance variables.
|
8
|
+
def erector(options = {}, &block)
|
9
|
+
Erector.inline(options, &block).to_html(options)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Erector
|
2
|
+
module Needs
|
3
|
+
def self.included(base)
|
4
|
+
base.extend ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
# Class method by which widget classes can declare that they need certain
|
9
|
+
# parameters. If needed parameters are not passed in to #new, then an
|
10
|
+
# exception will be thrown (with a hopefully useful message about which
|
11
|
+
# parameters are missing). This is intended to catch silly bugs like
|
12
|
+
# passing in a parameter called 'name' to a widget that expects a
|
13
|
+
# parameter called 'title'.
|
14
|
+
#
|
15
|
+
# You can also declare default values for parameters using hash syntax.
|
16
|
+
# You can put #needs declarations on multiple lines or on the same line;
|
17
|
+
# the only caveat is that if there are default values, they all have to be
|
18
|
+
# at the end of the line (so they go into the magic hash parameter).
|
19
|
+
#
|
20
|
+
# If a widget has no #needs declaration then it will accept any
|
21
|
+
# combination of parameters just like normal. If a widget wants to declare
|
22
|
+
# that it takes no parameters, use the special incantation "needs nil"
|
23
|
+
# (and don't declare any other needs, or kittens will cry).
|
24
|
+
#
|
25
|
+
# Usage:
|
26
|
+
# class FancyForm < Erector::Widget
|
27
|
+
# needs :title, :show_okay => true, :show_cancel => false
|
28
|
+
# ...
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# That means that
|
32
|
+
# FancyForm.new(:title => 'Login')
|
33
|
+
# will succeed, as will
|
34
|
+
# FancyForm.new(:title => 'Login', :show_cancel => true)
|
35
|
+
# but
|
36
|
+
# FancyForm.new(:name => 'Login')
|
37
|
+
# will fail.
|
38
|
+
#
|
39
|
+
def needs(*args)
|
40
|
+
args.each do |arg|
|
41
|
+
(@needs ||= []) << (arg.nil? ? nil : (arg.is_a? Hash) ? arg : arg.to_sym)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def get_needs
|
46
|
+
@needs ||= []
|
47
|
+
|
48
|
+
ancestors[1..-1].inject(@needs.dup) do |needs, ancestor|
|
49
|
+
needs.push(*ancestor.get_needs) if ancestor.respond_to?(:get_needs)
|
50
|
+
needs
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def needed_variables
|
55
|
+
@needed_variables ||= get_needs.map{|need| need.is_a?(Hash) ? need.keys : need}.flatten
|
56
|
+
end
|
57
|
+
|
58
|
+
def needed_defaults
|
59
|
+
@needed_defaults ||= get_needs.inject({}) do |defaults, need|
|
60
|
+
defaults = need.merge(defaults) if need.is_a? Hash
|
61
|
+
defaults
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def needs?(name)
|
66
|
+
needed_variables.empty? || needed_variables.include?(name)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def initialize(assigns = {})
|
71
|
+
super
|
72
|
+
|
73
|
+
assigned = assigns.keys
|
74
|
+
|
75
|
+
# set variables with default values
|
76
|
+
self.class.needed_defaults.each do |name, value|
|
77
|
+
unless assigned.include?(name)
|
78
|
+
value = [NilClass, FalseClass, TrueClass, Fixnum, Float, Symbol].include?(value.class) ? value : value.dup
|
79
|
+
instance_variable_set("@#{name}", value)
|
80
|
+
assigned << name
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
missing = self.class.needed_variables - assigned
|
85
|
+
unless missing.empty? || missing == [nil]
|
86
|
+
raise ArgumentError, "Missing parameter#{missing.size == 1 ? '' : 's'} for #{self.class.name}: #{missing.join(', ')}"
|
87
|
+
end
|
88
|
+
|
89
|
+
excess = assigned - self.class.needed_variables
|
90
|
+
unless self.class.needed_variables.empty? || excess.empty?
|
91
|
+
raise ArgumentError, "Excess parameter#{excess.size == 1 ? '' : 's'} for #{self.class.name}: #{excess.join(', ')}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'erector/abstract_widget'
|
2
|
+
|
3
|
+
module Erector
|
4
|
+
class Output
|
5
|
+
SPACES_PER_INDENT = 2
|
6
|
+
|
7
|
+
attr_reader :prettyprint, :widgets, :indentation, :max_length
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
@prettyprint = options.fetch(:prettyprint, AbstractWidget.prettyprint_default)
|
11
|
+
@indentation = options.fetch(:indentation, 0)
|
12
|
+
@current_line_length = 0
|
13
|
+
@max_length = options[:max_length]
|
14
|
+
@widgets = []
|
15
|
+
|
16
|
+
@get_buffer = if options[:buffer] and options[:buffer].respond_to? :call
|
17
|
+
options[:buffer]
|
18
|
+
elsif options[:buffer]
|
19
|
+
lambda { options[:buffer] }
|
20
|
+
else
|
21
|
+
buffer = []
|
22
|
+
lambda { buffer }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def buffer
|
27
|
+
@get_buffer.call
|
28
|
+
end
|
29
|
+
|
30
|
+
def <<(s)
|
31
|
+
# raise s.inspect unless s.is_a? String
|
32
|
+
#
|
33
|
+
s = s.to_s unless s.is_a? String
|
34
|
+
append_indentation
|
35
|
+
if @max_length && s.length + @current_line_length > @max_length
|
36
|
+
leading_spaces = s =~ /^( +)/ ? $1.size : 0
|
37
|
+
trailing_spaces = s =~ /( +)$/ ? $1.size : 0
|
38
|
+
|
39
|
+
append(" " * leading_spaces)
|
40
|
+
need_space = false
|
41
|
+
words = s.split(/ /)
|
42
|
+
words.each do |word|
|
43
|
+
if (need_space ? 1 : 0) + word.length > space_left
|
44
|
+
append_newline
|
45
|
+
append_indentation
|
46
|
+
need_space = false
|
47
|
+
end
|
48
|
+
append(" ") if need_space
|
49
|
+
append(word)
|
50
|
+
need_space = true
|
51
|
+
end
|
52
|
+
append(" " * trailing_spaces)
|
53
|
+
else
|
54
|
+
append(s)
|
55
|
+
end
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
# Inserts a blank string into the output stream and returns a pointer to
|
60
|
+
# it. If the caller holds on to this pointer, she can later go back and
|
61
|
+
# insert text earlier in the stream. This is used for, e.g., inserting
|
62
|
+
# stuff inside the HEAD element that is not known until after the entire
|
63
|
+
# page renders.
|
64
|
+
def placeholder
|
65
|
+
s = ""
|
66
|
+
buffer << s
|
67
|
+
s
|
68
|
+
end
|
69
|
+
|
70
|
+
def to_s
|
71
|
+
RawString.new(buffer.kind_of?(String) ? buffer : buffer.join)
|
72
|
+
end
|
73
|
+
|
74
|
+
def to_a
|
75
|
+
buffer.kind_of?(Array) ? buffer : [buffer]
|
76
|
+
end
|
77
|
+
|
78
|
+
def newline
|
79
|
+
if prettyprint
|
80
|
+
append_newline
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def at_line_start?
|
85
|
+
@current_line_length == 0
|
86
|
+
end
|
87
|
+
|
88
|
+
def indent
|
89
|
+
@indentation += 1 if prettyprint
|
90
|
+
end
|
91
|
+
|
92
|
+
def undent
|
93
|
+
@indentation -= 1 if prettyprint
|
94
|
+
end
|
95
|
+
|
96
|
+
# always append a newline, regardless of prettyprint setting
|
97
|
+
#todo: test
|
98
|
+
def append_newline
|
99
|
+
buffer << "\n"
|
100
|
+
@current_line_length = 0
|
101
|
+
end
|
102
|
+
|
103
|
+
def mark
|
104
|
+
@mark = buffer.size
|
105
|
+
end
|
106
|
+
|
107
|
+
def rewind pos = @mark
|
108
|
+
if buffer.kind_of?(Array)
|
109
|
+
buffer.slice!(pos..-1)
|
110
|
+
elsif (Object.const_defined?(:ActiveSupport) and
|
111
|
+
buffer.kind_of?(ActiveSupport::SafeBuffer))
|
112
|
+
# monkey patch to get around SafeBuffer's well-meaning paranoia
|
113
|
+
# see http://yehudakatz.com/2010/02/01/safebuffers-and-rails-3-0/
|
114
|
+
# and http://weblog.rubyonrails.org/2011/6/8/potential-xss-vulnerability-in-ruby-on-rails-applications
|
115
|
+
String.instance_method(:slice!).bind(buffer).call(pos..-1)
|
116
|
+
elsif buffer.kind_of?(String)
|
117
|
+
buffer.slice!(pos..-1)
|
118
|
+
else
|
119
|
+
raise "Don't know how to rewind a #{buffer.class}"
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
protected
|
125
|
+
|
126
|
+
def append(s)
|
127
|
+
buffer << s
|
128
|
+
@current_line_length += s.length
|
129
|
+
end
|
130
|
+
|
131
|
+
def space_left
|
132
|
+
@max_length - @current_line_length
|
133
|
+
end
|
134
|
+
|
135
|
+
def append_indentation
|
136
|
+
if prettyprint and at_line_start?
|
137
|
+
spaces = " " * ([@indentation, 0].max * SPACES_PER_INDENT)
|
138
|
+
buffer << spaces
|
139
|
+
@current_line_length += spaces.length
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
end
|