erector 0.8.3 → 0.9.0.pre1
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/Gemfile +21 -0
- data/Rakefile +171 -0
- data/VERSION.yml +3 -2
- data/lib/erector.rb +3 -5
- data/lib/erector/abstract_widget.rb +76 -31
- data/lib/erector/attributes.rb +34 -0
- data/lib/erector/caching.rb +1 -0
- data/lib/erector/convenience.rb +12 -4
- data/lib/erector/dependency.rb +2 -1
- data/lib/erector/element.rb +113 -0
- data/lib/erector/erect/erect.rb +10 -7
- data/lib/erector/externals.rb +2 -1
- data/lib/erector/html.rb +6 -340
- data/lib/erector/html_widget.rb +300 -0
- data/lib/erector/inline.rb +1 -1
- data/lib/erector/output.rb +39 -12
- data/lib/erector/promise.rb +137 -0
- data/lib/erector/rails2/extensions/rails_widget.rb +1 -1
- data/lib/erector/rails3.rb +1 -1
- data/lib/erector/sass.rb +14 -19
- data/lib/erector/tag.rb +65 -0
- data/lib/erector/text.rb +123 -0
- data/lib/erector/version.rb +1 -1
- data/lib/erector/widget.rb +52 -12
- data/lib/erector/widgets/page.rb +12 -10
- data/lib/erector/xml_widget.rb +131 -0
- data/spec/erector/caching_spec.rb +1 -0
- data/spec/erector/externals_spec.rb +0 -1
- data/spec/erector/html_spec.rb +9 -25
- data/spec/erector/output_spec.rb +102 -9
- data/spec/erector/promise_spec.rb +173 -0
- data/spec/erector/sass_spec.rb +1 -1
- data/spec/erector/tag_spec.rb +67 -0
- data/spec/erector/widget_spec.rb +53 -2
- data/spec/erector/xml_widget_spec.rb +74 -0
- data/spec/rails2/rails_app/Gemfile +1 -0
- data/spec/rails2/rails_app/spec/rails_spec_helper.rb +2 -0
- data/spec/rails_root/Gemfile +11 -0
- data/spec/rails_root/README +256 -0
- data/spec/rails_root/Rakefile +7 -0
- data/spec/rails_root/app/controllers/application.rb +6 -0
- data/spec/rails_root/app/controllers/application_controller.rb +3 -0
- data/spec/rails_root/app/helpers/application_helper.rb +2 -0
- data/spec/rails_root/app/views/layouts/application.html.erb +14 -0
- data/spec/rails_root/app/views/test/_erb.erb +1 -0
- data/spec/rails_root/app/views/test/_erector.rb +5 -0
- data/spec/rails_root/app/views/test/_partial_with_locals.rb +7 -0
- data/spec/rails_root/app/views/test/bare.rb +5 -0
- data/spec/rails_root/app/views/test/erb_from_erector.html.rb +5 -0
- data/spec/rails_root/app/views/test/erector_from_erb.html.erb +1 -0
- data/spec/rails_root/app/views/test/erector_with_locals_from_erb.html.erb +6 -0
- data/spec/rails_root/app/views/test/implicit_assigns.html.rb +5 -0
- data/spec/rails_root/app/views/test/needs.html.rb +7 -0
- data/spec/rails_root/app/views/test/needs_subclass.html.rb +5 -0
- data/spec/rails_root/app/views/test/protected_instance_variable.html.rb +5 -0
- data/spec/rails_root/app/views/test/render_default.html.rb +5 -0
- data/spec/rails_root/app/views/test/render_partial.html.rb +5 -0
- data/spec/rails_root/config.ru +4 -0
- data/spec/rails_root/config/application.rb +42 -0
- data/spec/rails_root/config/boot.rb +13 -0
- data/spec/rails_root/config/database.yml +22 -0
- data/spec/rails_root/config/environment.rb +5 -0
- data/spec/rails_root/config/environments/development.rb +22 -0
- data/spec/rails_root/config/environments/production.rb +49 -0
- data/spec/rails_root/config/environments/test.rb +35 -0
- data/spec/rails_root/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/rails_root/config/initializers/inflections.rb +10 -0
- data/spec/rails_root/config/initializers/mime_types.rb +5 -0
- data/spec/rails_root/config/initializers/secret_token.rb +7 -0
- data/spec/rails_root/config/initializers/session_store.rb +8 -0
- data/spec/rails_root/config/locales/en.yml +5 -0
- data/spec/rails_root/config/routes.rb +58 -0
- data/spec/rails_root/db/seeds.rb +7 -0
- data/spec/rails_root/doc/README_FOR_APP +2 -0
- data/spec/rails_root/log/development.log +17 -0
- data/spec/rails_root/log/test.log +3750 -0
- data/spec/rails_root/public/404.html +26 -0
- data/spec/rails_root/public/422.html +26 -0
- data/spec/rails_root/public/500.html +26 -0
- data/spec/rails_root/public/dispatch.cgi +10 -0
- data/spec/rails_root/public/dispatch.fcgi +24 -0
- data/spec/rails_root/public/dispatch.rb +10 -0
- data/spec/rails_root/public/favicon.ico +0 -0
- data/spec/rails_root/public/images/rails.png +0 -0
- data/spec/rails_root/public/index.html +262 -0
- data/spec/rails_root/public/javascripts/application.js +2 -0
- data/spec/rails_root/public/javascripts/controls.js +965 -0
- data/spec/rails_root/public/javascripts/dragdrop.js +974 -0
- data/spec/rails_root/public/javascripts/effects.js +1123 -0
- data/spec/rails_root/public/javascripts/prototype.js +6001 -0
- data/spec/rails_root/public/javascripts/rails.js +175 -0
- data/spec/rails_root/public/robots.txt +5 -0
- data/spec/rails_root/script/about +3 -0
- data/spec/rails_root/script/console +3 -0
- data/spec/rails_root/script/destroy +3 -0
- data/spec/rails_root/script/generate +3 -0
- data/spec/rails_root/script/performance/benchmarker +3 -0
- data/spec/rails_root/script/performance/profiler +3 -0
- data/spec/rails_root/script/performance/request +3 -0
- data/spec/rails_root/script/plugin +3 -0
- data/spec/rails_root/script/process/inspector +3 -0
- data/spec/rails_root/script/process/reaper +3 -0
- data/spec/rails_root/script/process/spawner +3 -0
- data/spec/rails_root/script/rails +6 -0
- data/spec/rails_root/script/runner +3 -0
- data/spec/rails_root/script/server +3 -0
- data/spec/rails_root/spec/form_builder_spec.rb +21 -0
- data/spec/rails_root/spec/rails_helpers_spec.rb +220 -0
- data/spec/rails_root/spec/rails_spec_helper.rb +10 -0
- data/spec/rails_root/spec/rails_widget_spec.rb +83 -0
- data/spec/rails_root/spec/render_spec.rb +298 -0
- data/spec/rails_root/test/performance/browsing_test.rb +9 -0
- data/spec/rails_root/test/test_helper.rb +13 -0
- data/spec/spec_helper.rb +3 -1
- metadata +202 -66
@@ -45,7 +45,7 @@ module Erector
|
|
45
45
|
|
46
46
|
output_buffer = view.with_output_buffer do
|
47
47
|
# Set parent to the view and use Rails's output buffer.
|
48
|
-
new_output = Output.new { view.output_buffer }
|
48
|
+
new_output = Output.new :buffer => lambda { view.output_buffer }
|
49
49
|
widget.to_html(options.merge(:parent => view,
|
50
50
|
:output => new_output))
|
51
51
|
end
|
data/lib/erector/rails3.rb
CHANGED
@@ -39,7 +39,7 @@ module Erector
|
|
39
39
|
# Set parent and helpers to the view and use Rails's output buffer.
|
40
40
|
widget.to_html(options.merge(:helpers => view,
|
41
41
|
:parent => view,
|
42
|
-
:output => Output.new { view.output_buffer }))
|
42
|
+
:output => Output.new(:buffer => lambda { view.output_buffer })))
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
data/lib/erector/sass.rb
CHANGED
@@ -1,22 +1,17 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
# loading from files, precompilation, etc.
|
16
|
-
module Sass
|
17
|
-
def sass(sass_text)
|
18
|
-
style ::Sass::Engine.new(sass_text, :cache => false).render
|
19
|
-
end
|
1
|
+
module Erector
|
2
|
+
# Adds sass support to Erector widgets.
|
3
|
+
#
|
4
|
+
# Sass is an *optional dependency* of the Erector gem, so
|
5
|
+
# a call to +sass+ inside a widget will fail unless you have already
|
6
|
+
# installed the sass gem (e.g. "gem 'sass'" in your code or Gemfile).
|
7
|
+
#
|
8
|
+
# Current support is barebones. Please offer suggestions (or better
|
9
|
+
# yet, patches) for whether and how to support, e.g., caching,
|
10
|
+
# loading from files, precompilation, etc.
|
11
|
+
module Sass
|
12
|
+
def sass(sass_text)
|
13
|
+
require "sass"
|
14
|
+
style ::Sass::Engine.new(sass_text, :cache => false).render
|
20
15
|
end
|
21
16
|
end
|
22
17
|
end
|
data/lib/erector/tag.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
module Erector
|
2
|
+
|
3
|
+
# Defines a type of tag (not an actual element with attributes and contents)
|
4
|
+
class Tag
|
5
|
+
|
6
|
+
# Pass the self_closing and inline params as symbols, e.g.
|
7
|
+
#
|
8
|
+
# Tag.new("i", :inline)
|
9
|
+
# Tag.new("input", :inline, :self_closing)
|
10
|
+
#
|
11
|
+
# @param name the name of the tag, e.g. "div"
|
12
|
+
# @param self_closing whether it can (false) or cannot (true) contain text or other elements. Default: false
|
13
|
+
# @param inline whether it should appear in line with other elements (true) or on a line by itself (false) in pretty mode. Default: false
|
14
|
+
# @param snake whether to covert the method name into "snake case" (aka underscorized). Default: false
|
15
|
+
#
|
16
|
+
def initialize(name, *params)
|
17
|
+
@name = name.to_s
|
18
|
+
@method_name = if params.first.is_a? String
|
19
|
+
params.shift
|
20
|
+
else
|
21
|
+
@name
|
22
|
+
end
|
23
|
+
@self_closing = params.include?(:self_closing)
|
24
|
+
@inline = params.include?(:inline)
|
25
|
+
@method_name = snake_case(@method_name) if params.include?(:snake_case)
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_reader :name, :method_name
|
29
|
+
|
30
|
+
def self_closing?
|
31
|
+
@self_closing
|
32
|
+
end
|
33
|
+
|
34
|
+
def newliney?
|
35
|
+
!@inline
|
36
|
+
end
|
37
|
+
|
38
|
+
def inline?
|
39
|
+
@inline
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# Convert to snake case.
|
44
|
+
#
|
45
|
+
# "FooBar".snake_case #=> "foo_bar"
|
46
|
+
# "HeadlineCNNNews".snake_case #=> "headline_cnn_news"
|
47
|
+
# "CNN".snake_case #=> "cnn"
|
48
|
+
#
|
49
|
+
# @return [String] Receiver converted to snake case.
|
50
|
+
#
|
51
|
+
# @api public
|
52
|
+
# borrowed from https://github.com/datamapper/extlib/blob/master/lib/extlib/string.rb
|
53
|
+
def snake_case(s)
|
54
|
+
if s.match(/\A[A-Z]+\z/)
|
55
|
+
s.downcase
|
56
|
+
else
|
57
|
+
s.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').
|
58
|
+
gsub(/([a-z])([A-Z])/, '\1_\2').
|
59
|
+
downcase
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
data/lib/erector/text.rb
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
require "erector/raw_string"
|
2
|
+
|
3
|
+
module Erector
|
4
|
+
module Text
|
5
|
+
# Emits text to the output buffer, e.g.
|
6
|
+
#
|
7
|
+
# text "my dog smells awful"
|
8
|
+
# => "my dog smells awful"
|
9
|
+
#
|
10
|
+
# If a string is passed in, it will be HTML-escaped. If the
|
11
|
+
# result of calling methods such as raw is passed in, the HTML will not be
|
12
|
+
# HTML-escaped again. If another kind of object is passed in, the result
|
13
|
+
# of calling its to_s method will be treated as a string would be.
|
14
|
+
#
|
15
|
+
# You shouldn't pass a widget in to this method, as that will cause
|
16
|
+
# performance problems (as well as being semantically goofy). Use the
|
17
|
+
# #widget method instead.
|
18
|
+
#
|
19
|
+
# You may pass a series of values (i.e. varargs). In that case, each value
|
20
|
+
# will be emitted to the output stream in turn. You can specify a delimiter
|
21
|
+
# by using an options hash with as the final argument, using +:join+ as the key,
|
22
|
+
# e.g.
|
23
|
+
#
|
24
|
+
# text "my", "dog", "smells", :join => " "
|
25
|
+
# => "my dog smells"
|
26
|
+
#
|
27
|
+
# You may also pass a Promise as a parameter; every tag
|
28
|
+
# method now returns a Promise after emitting. This allows
|
29
|
+
# you to easily embed simple HTML formatting into a sentence, e.g.
|
30
|
+
#
|
31
|
+
# text "my", "dog", "smells", b("great!"), :join => " "
|
32
|
+
# => "my dog smells <b>great!</b>"
|
33
|
+
#
|
34
|
+
# (Yes, the initial call to +b+ emits "\<b>great\</b>" to the output buffer;
|
35
|
+
# the Promise feature takes care of rewinding and rewriting the output
|
36
|
+
# buffer during the later call to +text+.)
|
37
|
+
#
|
38
|
+
def text(*values)
|
39
|
+
options = if values.last.is_a? Hash
|
40
|
+
values.pop
|
41
|
+
else
|
42
|
+
{}
|
43
|
+
end
|
44
|
+
delimiter = options[:join]
|
45
|
+
|
46
|
+
values.select{|value| value.is_a? Promise}.each do |promise|
|
47
|
+
# erase whatever the promises wrote already
|
48
|
+
promise._rewind
|
49
|
+
end
|
50
|
+
|
51
|
+
first = true
|
52
|
+
values.each do |value|
|
53
|
+
if !first and delimiter
|
54
|
+
output << h(delimiter)
|
55
|
+
end
|
56
|
+
first = false
|
57
|
+
|
58
|
+
case value
|
59
|
+
when AbstractWidget
|
60
|
+
# todo: better deprecation
|
61
|
+
raise "Don't pass a widget to the text method. Use the widget method instead."
|
62
|
+
when Promise
|
63
|
+
value._mark # so the promise's rewind won't erase anything
|
64
|
+
value._render # render the promise to the output stream again
|
65
|
+
# note: we could let the promise cache its first effort, but
|
66
|
+
# here I think it's better to optimize for memory over speed
|
67
|
+
else
|
68
|
+
output << h(value)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns text which will *not* be HTML-escaped.
|
75
|
+
def raw(value)
|
76
|
+
RawString.new(value.to_s)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Emits text which will *not* be HTML-escaped. Same effect as text(raw(s))
|
80
|
+
def text!(value)
|
81
|
+
text raw(value)
|
82
|
+
end
|
83
|
+
|
84
|
+
alias rawtext text!
|
85
|
+
|
86
|
+
# Returns a copy of value with spaces replaced by non-breaking space characters.
|
87
|
+
# With no arguments, return a single non-breaking space.
|
88
|
+
# The output uses the escaping format ' ' since that works
|
89
|
+
# in both HTML and XML (as opposed to ' ' which only works in HTML).
|
90
|
+
def nbsp(value = " ")
|
91
|
+
raw(h(value).gsub(/ /,' '))
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns an HTML-escaped version of its parameter. Leaves the output
|
95
|
+
# string untouched. This method is idempotent: h(h(text)) will not
|
96
|
+
# double-escape text. This means that it is safe to do something like
|
97
|
+
# text(h("2<4")) -- it will produce "2<4", not "2&lt;4".
|
98
|
+
def h(content)
|
99
|
+
if content.respond_to?(:html_safe?) && content.html_safe?
|
100
|
+
content
|
101
|
+
else
|
102
|
+
raw(CGI.escapeHTML(content.to_s))
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Return a character given its unicode code point or unicode name.
|
107
|
+
def character(code_point_or_name)
|
108
|
+
if code_point_or_name.is_a?(Symbol)
|
109
|
+
require "erector/unicode"
|
110
|
+
found = Erector::CHARACTERS[code_point_or_name]
|
111
|
+
if found.nil?
|
112
|
+
raise "Unrecognized character #{code_point_or_name}"
|
113
|
+
end
|
114
|
+
raw("&#x#{sprintf '%x', found};")
|
115
|
+
elsif code_point_or_name.is_a?(Integer)
|
116
|
+
raw("&#x#{sprintf '%x', code_point_or_name};")
|
117
|
+
else
|
118
|
+
raise "Unrecognized argument to character: #{code_point_or_name}"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
end
|
data/lib/erector/version.rb
CHANGED
@@ -6,7 +6,7 @@ module Erector
|
|
6
6
|
if !Erector.const_defined?(:VERSION)
|
7
7
|
dir = File.dirname(__FILE__)
|
8
8
|
version = YAML.load_file(File.expand_path("#{dir}/../../VERSION.yml"))
|
9
|
-
VERSION =
|
9
|
+
VERSION = [version[:major], version[:minor], version[:patch], version[:build]].compact.join('.')
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
data/lib/erector/widget.rb
CHANGED
@@ -1,5 +1,13 @@
|
|
1
|
+
require "erector/element"
|
2
|
+
require "erector/attributes"
|
3
|
+
require "erector/promise"
|
4
|
+
require "erector/text"
|
5
|
+
require "erector/tag"
|
6
|
+
require "erector/html_widget"
|
7
|
+
require "erector/needs"
|
8
|
+
|
1
9
|
module Erector
|
2
|
-
|
10
|
+
|
3
11
|
# A Widget is the center of the Erector universe.
|
4
12
|
#
|
5
13
|
# To create a widget, extend Erector::Widget and implement the +content+
|
@@ -36,19 +44,51 @@ module Erector
|
|
36
44
|
#
|
37
45
|
# This class extends AbstractWidget and includes several modules,
|
38
46
|
# so be sure to check all of those places for API documentation for the
|
39
|
-
# various methods of Widget
|
40
|
-
#
|
47
|
+
# various methods of Widget:
|
48
|
+
#
|
49
|
+
# * AbstractWidget
|
50
|
+
# * Element
|
51
|
+
# * Attributes
|
52
|
+
# * Text
|
53
|
+
# * Needs
|
54
|
+
# * Caching
|
55
|
+
# * Externals
|
56
|
+
# * AfterInitialize
|
41
57
|
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
58
|
+
# * HTML
|
59
|
+
# * Convenience
|
60
|
+
# * JQuery
|
61
|
+
# * Sass
|
62
|
+
#
|
63
|
+
# Also read the API Cheatsheet in the user guide
|
64
|
+
# at http://erector.rubyforge.org/userguide#apicheatsheet
|
65
|
+
class Widget < HTMLWidget
|
66
|
+
|
67
|
+
# for some reason these need to be included in Widget and not AbstractWidget
|
68
|
+
include Needs
|
69
|
+
include Caching
|
70
|
+
include Externals
|
71
|
+
|
72
|
+
include HTML
|
73
|
+
include Convenience
|
50
74
|
include Erector::JQuery
|
51
|
-
include Erector::AfterInitialize
|
52
75
|
include Erector::Sass if Object.const_defined?(:Sass)
|
76
|
+
|
77
|
+
# alias for AbstractWidget#render
|
78
|
+
def to_html(options = {})
|
79
|
+
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
|
80
|
+
_render(options).to_s
|
81
|
+
end
|
82
|
+
|
83
|
+
# alias for #to_html
|
84
|
+
# @deprecated Please use {#to_html} instead
|
85
|
+
def to_s(*args)
|
86
|
+
unless defined? @@already_warned_to_s
|
87
|
+
$stderr.puts "Erector::Widget#to_s is deprecated. Please use #to_html instead. Called from #{caller.first}"
|
88
|
+
@@already_warned_to_s = true
|
89
|
+
end
|
90
|
+
to_html(*args)
|
91
|
+
end
|
92
|
+
|
53
93
|
end
|
54
94
|
end
|
data/lib/erector/widgets/page.rb
CHANGED
@@ -7,14 +7,15 @@
|
|
7
7
|
# declare it.
|
8
8
|
#
|
9
9
|
# The script and style declarations are accumulated at class load time, as
|
10
|
-
# 'dependencies'. This technique allows all widgets to add their own
|
11
|
-
# to the page header without extra logic for declaring which
|
12
|
-
# which nested widgets. Fortunately, Page is now smart enough to
|
13
|
-
# which widgets were actually rendered during the body_content run,
|
14
|
-
# emits into its HEAD the dependencies that are relevant. If it
|
15
|
-
# if you want to add some extra dependencies -- for instance,
|
16
|
-
# to widgets that are rendered later via AJAX -- then return
|
17
|
-
# widget classes in your subclass by overriding the
|
10
|
+
# 'dependencies'. This technique allows all widgets to add their own
|
11
|
+
# requirements to the page header without extra logic for declaring which
|
12
|
+
# pages include which nested widgets. Fortunately, Page is now smart enough to
|
13
|
+
# figure out which widgets were actually rendered during the body_content run,
|
14
|
+
# so it only emits into its HEAD the dependencies that are relevant. If it
|
15
|
+
# misses some, or if you want to add some extra dependencies -- for instance,
|
16
|
+
# styles that apply to widgets that are rendered later via AJAX -- then return
|
17
|
+
# an array of those widget classes in your subclass by overriding the
|
18
|
+
# #extra_widgets method.
|
18
19
|
#
|
19
20
|
# If you want something to show up in the headers for just one page type
|
20
21
|
# (subclass), then override #head_content, call super, and then emit it
|
@@ -93,8 +94,9 @@
|
|
93
94
|
# end
|
94
95
|
#
|
95
96
|
# = Thoughts:
|
96
|
-
# * It may be desirable to unify #js and #script, and #css and #style, and
|
97
|
-
# smart enough to analyze its parameter to decide
|
97
|
+
# * It may be desirable to unify #js and #script, and #css and #style, and
|
98
|
+
# have the routine be smart enough to analyze its parameter to decide
|
99
|
+
# whether to make it a file or a script.
|
98
100
|
#
|
99
101
|
class Erector::Widgets::Page < Erector::InlineWidget
|
100
102
|
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'erector/abstract_widget'
|
2
|
+
require 'erector/tag'
|
3
|
+
require 'erector/needs'
|
4
|
+
|
5
|
+
module Erector
|
6
|
+
|
7
|
+
# Abstract base class for XML Widgets and HTMLWidget.
|
8
|
+
# Declares "tags" which define methods that emit tags.
|
9
|
+
class XMLWidget < AbstractWidget
|
10
|
+
include Needs
|
11
|
+
|
12
|
+
def self.tag_named tag_name, checked = []
|
13
|
+
@tags ||= {}
|
14
|
+
@tags[tag_name] || begin
|
15
|
+
tag = nil
|
16
|
+
checked << self
|
17
|
+
taggy_ancestors = (ancestors - checked).select{|k| k.respond_to? :tag_named}
|
18
|
+
taggy_ancestors.each do |k|
|
19
|
+
tag = k.tag_named(tag_name, checked)
|
20
|
+
if tag
|
21
|
+
@tags[tag_name] = tag
|
22
|
+
break
|
23
|
+
end
|
24
|
+
end
|
25
|
+
tag
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.tag *args
|
30
|
+
tag = Tag.new(*args)
|
31
|
+
@tags ||= {}
|
32
|
+
@tags[tag.name] = tag
|
33
|
+
|
34
|
+
if instance_methods.include?(tag.method_name.to_sym)
|
35
|
+
warn "method '#{tag.method_name}' is already defined; skipping #{caller[1]}"
|
36
|
+
return
|
37
|
+
end
|
38
|
+
|
39
|
+
if tag.self_closing?
|
40
|
+
self.class_eval(<<-SRC, __FILE__, __LINE__ + 1)
|
41
|
+
def #{tag.method_name}(*args, &block)
|
42
|
+
_empty_element('#{tag.name}', *args, &block)
|
43
|
+
end
|
44
|
+
SRC
|
45
|
+
else
|
46
|
+
self.class_eval(<<-SRC, __FILE__, __LINE__ + 1)
|
47
|
+
def #{tag.method_name}(*args, &block)
|
48
|
+
_element('#{tag.name}', *args, &block)
|
49
|
+
end
|
50
|
+
|
51
|
+
def #{tag.method_name}!(*args, &block)
|
52
|
+
_element('#{tag.name}', *(args.map{|a|raw(a)}), &block)
|
53
|
+
end
|
54
|
+
SRC
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Tags which are always self-closing
|
59
|
+
def self.self_closing_tags
|
60
|
+
@tags.values.select{|tag| tag.self_closing?}.map{|tag| tag.name}
|
61
|
+
end
|
62
|
+
|
63
|
+
# Tags which can contain other stuff
|
64
|
+
def self.full_tags
|
65
|
+
@tags.values.select{|tag| !tag.self_closing?}.map{|tag| tag.name}
|
66
|
+
end
|
67
|
+
|
68
|
+
def newliney?(tag_name)
|
69
|
+
tag = self.class.tag_named tag_name
|
70
|
+
if tag
|
71
|
+
tag.newliney?
|
72
|
+
else
|
73
|
+
true
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Emits an XML instruction, which looks like this: <?xml version=\"1.0\" encoding=\"UTF-8\" ?>
|
78
|
+
def instruct(attributes={:version => "1.0", :encoding => "UTF-8"})
|
79
|
+
output << raw("<?xml#{format_sorted(sort_for_xml_declaration(attributes))} ?>")
|
80
|
+
end
|
81
|
+
|
82
|
+
# Emits an XML/HTML comment (<!-- ... -->) surrounding +text+ and/or
|
83
|
+
# the output of +block+. see
|
84
|
+
# http://www.w3.org/TR/html4/intro/sgmltut.html#h-3.2.4
|
85
|
+
#
|
86
|
+
# If +text+ is an Internet Explorer conditional comment condition such as
|
87
|
+
# "[if IE]", the output includes the opening condition and closing
|
88
|
+
# "[endif]". See http://www.quirksmode.org/css/condcom.html
|
89
|
+
#
|
90
|
+
# Since "Authors should avoid putting two or more adjacent hyphens inside
|
91
|
+
# comments," we emit a warning if you do that.
|
92
|
+
def comment(text = '')
|
93
|
+
puts "Warning: Authors should avoid putting two or more adjacent hyphens inside comments." if text =~ /--/
|
94
|
+
|
95
|
+
conditional = text =~ /\[if .*\]/
|
96
|
+
|
97
|
+
rawtext "<!--"
|
98
|
+
rawtext text
|
99
|
+
rawtext ">" if conditional
|
100
|
+
|
101
|
+
if block_given?
|
102
|
+
rawtext "\n"
|
103
|
+
yield
|
104
|
+
rawtext "\n"
|
105
|
+
end
|
106
|
+
|
107
|
+
rawtext "<![endif]" if conditional
|
108
|
+
rawtext "-->\n"
|
109
|
+
end
|
110
|
+
|
111
|
+
alias_method :to_xml, :render
|
112
|
+
|
113
|
+
protected
|
114
|
+
|
115
|
+
def sort_for_xml_declaration(attributes)
|
116
|
+
# correct order is "version, encoding, standalone" (XML 1.0 section 2.8).
|
117
|
+
# But we only try to put version before encoding for now.
|
118
|
+
stringized = []
|
119
|
+
attributes.each do |key, value|
|
120
|
+
stringized << [key.to_s, value]
|
121
|
+
end
|
122
|
+
stringized.sort{|a, b| b <=> a}
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
public
|
128
|
+
|
129
|
+
XmlWidget = XMLWidget
|
130
|
+
|
131
|
+
end
|