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.
Files changed (115) hide show
  1. data/Gemfile +21 -0
  2. data/Rakefile +171 -0
  3. data/VERSION.yml +3 -2
  4. data/lib/erector.rb +3 -5
  5. data/lib/erector/abstract_widget.rb +76 -31
  6. data/lib/erector/attributes.rb +34 -0
  7. data/lib/erector/caching.rb +1 -0
  8. data/lib/erector/convenience.rb +12 -4
  9. data/lib/erector/dependency.rb +2 -1
  10. data/lib/erector/element.rb +113 -0
  11. data/lib/erector/erect/erect.rb +10 -7
  12. data/lib/erector/externals.rb +2 -1
  13. data/lib/erector/html.rb +6 -340
  14. data/lib/erector/html_widget.rb +300 -0
  15. data/lib/erector/inline.rb +1 -1
  16. data/lib/erector/output.rb +39 -12
  17. data/lib/erector/promise.rb +137 -0
  18. data/lib/erector/rails2/extensions/rails_widget.rb +1 -1
  19. data/lib/erector/rails3.rb +1 -1
  20. data/lib/erector/sass.rb +14 -19
  21. data/lib/erector/tag.rb +65 -0
  22. data/lib/erector/text.rb +123 -0
  23. data/lib/erector/version.rb +1 -1
  24. data/lib/erector/widget.rb +52 -12
  25. data/lib/erector/widgets/page.rb +12 -10
  26. data/lib/erector/xml_widget.rb +131 -0
  27. data/spec/erector/caching_spec.rb +1 -0
  28. data/spec/erector/externals_spec.rb +0 -1
  29. data/spec/erector/html_spec.rb +9 -25
  30. data/spec/erector/output_spec.rb +102 -9
  31. data/spec/erector/promise_spec.rb +173 -0
  32. data/spec/erector/sass_spec.rb +1 -1
  33. data/spec/erector/tag_spec.rb +67 -0
  34. data/spec/erector/widget_spec.rb +53 -2
  35. data/spec/erector/xml_widget_spec.rb +74 -0
  36. data/spec/rails2/rails_app/Gemfile +1 -0
  37. data/spec/rails2/rails_app/spec/rails_spec_helper.rb +2 -0
  38. data/spec/rails_root/Gemfile +11 -0
  39. data/spec/rails_root/README +256 -0
  40. data/spec/rails_root/Rakefile +7 -0
  41. data/spec/rails_root/app/controllers/application.rb +6 -0
  42. data/spec/rails_root/app/controllers/application_controller.rb +3 -0
  43. data/spec/rails_root/app/helpers/application_helper.rb +2 -0
  44. data/spec/rails_root/app/views/layouts/application.html.erb +14 -0
  45. data/spec/rails_root/app/views/test/_erb.erb +1 -0
  46. data/spec/rails_root/app/views/test/_erector.rb +5 -0
  47. data/spec/rails_root/app/views/test/_partial_with_locals.rb +7 -0
  48. data/spec/rails_root/app/views/test/bare.rb +5 -0
  49. data/spec/rails_root/app/views/test/erb_from_erector.html.rb +5 -0
  50. data/spec/rails_root/app/views/test/erector_from_erb.html.erb +1 -0
  51. data/spec/rails_root/app/views/test/erector_with_locals_from_erb.html.erb +6 -0
  52. data/spec/rails_root/app/views/test/implicit_assigns.html.rb +5 -0
  53. data/spec/rails_root/app/views/test/needs.html.rb +7 -0
  54. data/spec/rails_root/app/views/test/needs_subclass.html.rb +5 -0
  55. data/spec/rails_root/app/views/test/protected_instance_variable.html.rb +5 -0
  56. data/spec/rails_root/app/views/test/render_default.html.rb +5 -0
  57. data/spec/rails_root/app/views/test/render_partial.html.rb +5 -0
  58. data/spec/rails_root/config.ru +4 -0
  59. data/spec/rails_root/config/application.rb +42 -0
  60. data/spec/rails_root/config/boot.rb +13 -0
  61. data/spec/rails_root/config/database.yml +22 -0
  62. data/spec/rails_root/config/environment.rb +5 -0
  63. data/spec/rails_root/config/environments/development.rb +22 -0
  64. data/spec/rails_root/config/environments/production.rb +49 -0
  65. data/spec/rails_root/config/environments/test.rb +35 -0
  66. data/spec/rails_root/config/initializers/backtrace_silencers.rb +7 -0
  67. data/spec/rails_root/config/initializers/inflections.rb +10 -0
  68. data/spec/rails_root/config/initializers/mime_types.rb +5 -0
  69. data/spec/rails_root/config/initializers/secret_token.rb +7 -0
  70. data/spec/rails_root/config/initializers/session_store.rb +8 -0
  71. data/spec/rails_root/config/locales/en.yml +5 -0
  72. data/spec/rails_root/config/routes.rb +58 -0
  73. data/spec/rails_root/db/seeds.rb +7 -0
  74. data/spec/rails_root/doc/README_FOR_APP +2 -0
  75. data/spec/rails_root/log/development.log +17 -0
  76. data/spec/rails_root/log/test.log +3750 -0
  77. data/spec/rails_root/public/404.html +26 -0
  78. data/spec/rails_root/public/422.html +26 -0
  79. data/spec/rails_root/public/500.html +26 -0
  80. data/spec/rails_root/public/dispatch.cgi +10 -0
  81. data/spec/rails_root/public/dispatch.fcgi +24 -0
  82. data/spec/rails_root/public/dispatch.rb +10 -0
  83. data/spec/rails_root/public/favicon.ico +0 -0
  84. data/spec/rails_root/public/images/rails.png +0 -0
  85. data/spec/rails_root/public/index.html +262 -0
  86. data/spec/rails_root/public/javascripts/application.js +2 -0
  87. data/spec/rails_root/public/javascripts/controls.js +965 -0
  88. data/spec/rails_root/public/javascripts/dragdrop.js +974 -0
  89. data/spec/rails_root/public/javascripts/effects.js +1123 -0
  90. data/spec/rails_root/public/javascripts/prototype.js +6001 -0
  91. data/spec/rails_root/public/javascripts/rails.js +175 -0
  92. data/spec/rails_root/public/robots.txt +5 -0
  93. data/spec/rails_root/script/about +3 -0
  94. data/spec/rails_root/script/console +3 -0
  95. data/spec/rails_root/script/destroy +3 -0
  96. data/spec/rails_root/script/generate +3 -0
  97. data/spec/rails_root/script/performance/benchmarker +3 -0
  98. data/spec/rails_root/script/performance/profiler +3 -0
  99. data/spec/rails_root/script/performance/request +3 -0
  100. data/spec/rails_root/script/plugin +3 -0
  101. data/spec/rails_root/script/process/inspector +3 -0
  102. data/spec/rails_root/script/process/reaper +3 -0
  103. data/spec/rails_root/script/process/spawner +3 -0
  104. data/spec/rails_root/script/rails +6 -0
  105. data/spec/rails_root/script/runner +3 -0
  106. data/spec/rails_root/script/server +3 -0
  107. data/spec/rails_root/spec/form_builder_spec.rb +21 -0
  108. data/spec/rails_root/spec/rails_helpers_spec.rb +220 -0
  109. data/spec/rails_root/spec/rails_spec_helper.rb +10 -0
  110. data/spec/rails_root/spec/rails_widget_spec.rb +83 -0
  111. data/spec/rails_root/spec/render_spec.rb +298 -0
  112. data/spec/rails_root/test/performance/browsing_test.rb +9 -0
  113. data/spec/rails_root/test/test_helper.rb +13 -0
  114. data/spec/spec_helper.rb +3 -1
  115. metadata +202 -66
data/Gemfile ADDED
@@ -0,0 +1,21 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "treetop", ">= 1.2.3"
4
+
5
+ group :development do
6
+ gem "activesupport", "~>3"
7
+ gem "rspec", "~>2"
8
+ gem "rubyforge"
9
+ gem "rr"
10
+ gem "nokogiri"
11
+ gem "jeweler"
12
+ gem "haml"
13
+ gem "sass"
14
+ gem "erubis"
15
+ gem "rdoc", "~>2.3"
16
+ gem "wrong", ">=0.5.4"
17
+ end
18
+
19
+ group :rails do
20
+ gem "rails", "~> 3.0.0"
21
+ end
@@ -0,0 +1,171 @@
1
+ puts "RUBY_VERSION=#{RUBY_VERSION}"
2
+
3
+ begin
4
+ # fix http://stackoverflow.com/questions/4932881/gemcutter-rake-build-now-throws-undefined-method-write-for-syckemitter
5
+ require 'psych' unless RUBY_VERSION =~ /^1\.8/
6
+ rescue LoadError
7
+ warn "Couldn't find psych; continuing."
8
+ end
9
+
10
+ require 'rake'
11
+ require 'rake/testtask'
12
+
13
+ require "rspec/core/rake_task"
14
+
15
+ require 'rdoc'
16
+ here = File.expand_path(File.dirname(__FILE__))
17
+ $LOAD_PATH.unshift("#{here}/lib")
18
+
19
+ require "erector/version"
20
+
21
+ begin
22
+ require 'jeweler'
23
+ Jeweler::Tasks.new do |gemspec|
24
+ gemspec.version = Erector::VERSION
25
+ gemspec.name = "erector"
26
+ gemspec.summary = "HTML/XML Builder library"
27
+ gemspec.email = "erector@googlegroups.com"
28
+ gemspec.description = "Erector is a Builder-like view framework, inspired by Markaby but overcoming some of its flaws. In Erector all views are objects, not template files, which allows the full power of object-oriented programming (inheritance, modular decomposition, encapsulation) in views."
29
+ gemspec.files = FileList[
30
+ "README.txt",
31
+ "VERSION.yml",
32
+ "lib/**/*",
33
+ "bin/erector",
34
+ ]
35
+ gemspec.executables = ["erector"]
36
+ specs = Dir.glob("spec/**/*") #.reject { |file| file =~ %r{spec/rails2/} }
37
+ gemspec.test_files = ([
38
+ "Rakefile",
39
+ "Gemfile",
40
+ ] + specs).flatten
41
+ gemspec.homepage = "http://erector.rubyforge.org/"
42
+ gemspec.authors = [
43
+ "Alex Chaffee",
44
+ "Brian Takita",
45
+ "Jeff Dean",
46
+ "Jim Kingdon",
47
+ "John Firebaugh",
48
+ ]
49
+ # gemspec.add_dependency 'treetop', ">= 1.2.3" # Jeweler now reads Gemfile, I think
50
+ end
51
+
52
+ Jeweler::RubyforgeTasks.new do |rubyforge|
53
+ rubyforge.doc_task = "rdoc"
54
+ rubyforge.remote_doc_path = "rdoc"
55
+ end
56
+
57
+ rescue LoadError
58
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install jeweler"
59
+ end
60
+
61
+ desc "Default: run most tests"
62
+ task :default => :spec
63
+ task :cruise => [:install_gems, :print_environment, :test]
64
+ task :test => :spec
65
+
66
+ task :install_gems do
67
+ sh "bundle check"
68
+ end
69
+
70
+ desc "Build the web site from the .rb files in web/"
71
+ task :web do
72
+ files = Dir["web/*.rb"] - ["web/page.rb", "web/sidebar.rb", "web/clickable_li.rb"]
73
+ require 'erector'
74
+ require 'erector/erect/erect'
75
+ $: << "."
76
+ Erector::Widget.prettyprint_default = true
77
+ Erector::Erect.new(["--to-html", * files]).run
78
+ end
79
+
80
+ desc "Generate rdoc"
81
+ task :docs => :rdoc
82
+
83
+ task :rdoc => :clean_rdoc
84
+ task :clean_rdoc do
85
+ FileUtils.rm_rf("rdoc")
86
+ end
87
+
88
+ # push the docs to Rubyforge
89
+ task :publish_docs => :"rubyforge:release:docs"
90
+
91
+ desc "Publish web site to RubyForge"
92
+ task :publish_web do
93
+ config = YAML.load(File.read(File.expand_path("~/.rubyforge/user-config.yml")))
94
+ host = "#{config["username"]}@rubyforge.org"
95
+ rubyforge_name = "erector"
96
+ remote_dir = "/var/www/gforge-projects/#{rubyforge_name}"
97
+ local_dir = "web"
98
+ rdoc_dir = "rdoc"
99
+ rsync_args = '--archive --verbose --delete'
100
+
101
+ sh %{rsync #{rsync_args} --exclude=#{rdoc_dir} #{local_dir}/ #{host}:#{remote_dir}}
102
+ end
103
+
104
+ require 'rdoc/task'
105
+ RDoc::Task.new(:rdoc) do |rdoc|
106
+ rdoc.rdoc_dir = 'rdoc'
107
+ rdoc.title = "Erector #{Erector::VERSION}"
108
+ rdoc.options << '--inline-source' << "--promiscuous"
109
+ rdoc.options << "--main=README.txt"
110
+ # rdoc.options << '--diagram' if RUBY_PLATFORM !~ /win32/ and `which dot` =~ /\/dot/ and not ENV['NODOT']
111
+ rdoc.rdoc_files.include('README.txt')
112
+ rdoc.rdoc_files.include('lib/**/*.rb')
113
+ rdoc.rdoc_files.include('bin/**/*')
114
+ end
115
+
116
+ desc "Regenerate unicode.rb from UnicodeData.txt from unicode.org. Only needs to be run when there is a new version of the Unicode specification"
117
+ task(:build_unicode) do
118
+ require 'lib/erector/unicode_builder'
119
+ builder = Erector::UnicodeBuilder.new(
120
+ File.open("/usr/lib/perl5/5.8.8/unicore/UnicodeData.txt"),
121
+ File.open("lib/erector/unicode.rb", "w")
122
+ )
123
+ builder.generate
124
+ end
125
+
126
+ task :print_environment do
127
+ puts <<-ENVIRONMENT
128
+ Build environment:
129
+ #{`uname -a`.chomp}
130
+ #{`ruby -v`.chomp}
131
+ SQLite3: #{`sqlite3 -version`}
132
+ #{`gem env`}
133
+ Local gems:
134
+ #{`gem list`.gsub(/^/, ' ')}
135
+ ENVIRONMENT
136
+ end
137
+
138
+ namespace :spec do
139
+ desc "Run core specs."
140
+ RSpec::Core::RakeTask.new(:core) do |spec|
141
+ spec.pattern = 'spec/erector/*_spec.rb'
142
+ end
143
+
144
+ desc "Run specs for the 'erector' command line tool."
145
+ RSpec::Core::RakeTask.new(:erect) do |spec|
146
+ spec.pattern = 'spec/erect/*_spec.rb'
147
+ end
148
+
149
+ desc "Run specs for erector's Rails integration."
150
+ RSpec::Core::RakeTask.new(:rails) do |spec|
151
+ spec.pattern = 'spec/rails_root/spec/*_spec.rb'
152
+ end
153
+
154
+ desc "Run specs for erector's Rails integration under Rails 2."
155
+ task :rails2 do
156
+ Dir.chdir("spec/rails2/rails_app") do
157
+ # Bundler.with_clean_env do
158
+ sh "BUNDLE_GEMFILE='./Gemfile' bundle exec rake rails2"
159
+ # end
160
+ end
161
+ end
162
+
163
+ desc "Run all specs under Rails 3.1 - prepare with 'bundle install --gemfile Gemfile-rails31'"
164
+ task :rails31 do
165
+ gemfile = "#{here}/Gemfile-rails31"
166
+ sh "BUNDLE_GEMFILE='#{gemfile}' bundle exec rake spec:core spec:erect spec:rails"
167
+ end
168
+ end
169
+
170
+ desc "Run most specs"
171
+ task :spec => ['spec:core', 'spec:erect', 'spec:rails', 'spec:rails2']
@@ -1,4 +1,5 @@
1
1
  ---
2
2
  :major: 0
3
- :minor: 8
4
- :patch: 3
3
+ :minor: 9
4
+ :patch: 0
5
+ :build: pre1
@@ -3,11 +3,6 @@ end
3
3
 
4
4
  require "cgi"
5
5
  require "yaml"
6
- begin
7
- require "sass"
8
- rescue LoadError => e
9
- # oh well, no Sass
10
- end
11
6
 
12
7
  require "erector/raw_string"
13
8
  require "erector/dependencies"
@@ -21,7 +16,10 @@ require "erector/html"
21
16
  require "erector/convenience"
22
17
  require "erector/jquery"
23
18
  require "erector/sass"
19
+
24
20
  require "erector/abstract_widget"
21
+ require "erector/xml_widget"
22
+ require "erector/html_widget"
25
23
  require "erector/widget"
26
24
 
27
25
  require "erector/inline"
@@ -1,8 +1,25 @@
1
+ require 'erector/element'
2
+ require 'erector/attributes'
3
+ require 'erector/text'
4
+ require 'erector/convenience'
5
+ require 'erector/after_initialize'
6
+ require 'erector/output'
7
+
1
8
  module Erector
2
9
 
3
- # Abstract base class for Widget. This pattern allows Widget to include lots of nicely organized modules and still
4
- # have proper semantics for "super" in subclasses. See the rdoc for Widget for the list of all the included modules.
10
+ # Abstract base class for Widget. This pattern allows Widget to include lots
11
+ # of nicely organized modules and still have proper semantics for "super" in
12
+ # subclasses. See the rdoc for Widget for the list of all the included
13
+ # modules.
5
14
  class AbstractWidget
15
+
16
+ include Erector::Element
17
+ include Erector::Attributes
18
+ include Erector::Text
19
+ include Erector::AfterInitialize
20
+
21
+ include Erector::Convenience
22
+
6
23
  @@prettyprint_default = false
7
24
  def prettyprint_default
8
25
  @@prettyprint_default
@@ -50,30 +67,30 @@ module Erector
50
67
  # method and returns the string.
51
68
  #
52
69
  # Options:
53
- # output:: the string to output to. Default: a new empty string
70
+ # output:: the string (or array, or Erector::Output) to output to.
71
+ # Default: a new empty string
54
72
  # prettyprint:: whether Erector should add newlines and indentation.
55
- # Default: the value of prettyprint_default (which is false
56
- # by default).
73
+ # Default: the value of prettyprint_default (which, in turn,
74
+ # is false by default).
57
75
  # indentation:: the amount of spaces to indent. Ignored unless prettyprint
58
76
  # is true.
59
77
  # max_length:: preferred maximum length of a line. Line wraps will only
60
- # occur at space characters, so a long word may end up creating
61
- # a line longer than this. If nil (default), then there is no
62
- # arbitrary limit to line lengths, and only internal newline
63
- # characters and prettyprinting will determine newlines in the
64
- # output.
78
+ # occur at space characters, so a long word may end up
79
+ # creating a line longer than this. If nil (default), then
80
+ # there is no arbitrary limit to line lengths, and only
81
+ # internal newline characters and prettyprinting will
82
+ # determine newlines in the output.
65
83
  # helpers:: a helpers object containing utility methods. Usually this is a
66
84
  # Rails view object.
67
85
  # content_method_name:: in case you want to call a method other than
68
86
  # #content, pass its name in here.
69
87
  #
70
- def to_html(options = {})
71
- 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
88
+ def render(options = {})
72
89
  _render(options).to_s
73
90
  end
74
91
 
75
- # alias for #to_html
76
- # @deprecated Please use {#to_html} instead
92
+ # alias for #render
93
+ # @deprecated Please use {#render} instead
77
94
  def to_s(*args)
78
95
  unless defined? @@already_warned_to_s
79
96
  $stderr.puts "Erector::Widget#to_s is deprecated. Please use #to_html instead. Called from #{caller.first}"
@@ -82,36 +99,39 @@ module Erector
82
99
  to_html(*args)
83
100
  end
84
101
 
85
- # Entry point for rendering a widget (and all its children). Same as #to_html
86
- # only it returns an array, for theoretical performance improvements when using a
87
- # Rack server (like Sinatra or Rails Metal).
102
+ # Entry point for rendering a widget (and all its children). Same as
103
+ # #render / #to_html only it returns an array, for theoretical performance
104
+ # improvements when using a Rack server (like Sinatra or Rails Metal).
88
105
  #
89
- # # Options: see #to_html
106
+ # # Options: see #render
90
107
  def to_a(options = {})
91
108
  _render(options).to_a
92
109
  end
93
110
 
94
111
  # Template method which must be overridden by all widget subclasses.
95
112
  # Inside this method you call the magic #element methods which emit HTML
96
- # and text to the output string. If you call "super" (or don't override
113
+ # and text to the output string.
114
+ #
115
+ # If you call "super" (or don't override
97
116
  # +content+, or explicitly call "call_block") then your widget will
98
117
  # execute the block that was passed into its constructor. The semantics of
99
- # this block are confusing; make sure to read the rdoc for Erector#call_block
118
+ # this block are confusing; make sure to read the rdoc for
119
+ # Erector#call_block
100
120
  def content
101
121
  call_block
102
122
  end
103
-
104
- # When this method is executed, the default block that was passed in to
105
- # the widget's constructor will be executed. The semantics of this
123
+
124
+ # When this method is executed, the default block that was passed in to
125
+ # the widget's constructor will be executed. The semantics of this
106
126
  # block -- that is, what "self" is, and whether it has access to
107
127
  # Erector methods like "div" and "text", and the widget's instance
108
128
  # variables -- can be quite confusing. The rule is, most of the time the
109
129
  # block is evaluated using "call" or "yield", which means that its scope
110
130
  # is that of the caller. So if that caller is not an Erector widget, it
111
- # will *not* have access to the Erector methods, but it *will* have access
131
+ # will *not* have access to the Erector methods, but it *will* have access
112
132
  # to instance variables and methods of the calling object.
113
- #
114
- # If you want this block to have access to Erector methods then use
133
+ #
134
+ # If you want this block to have access to Erector methods then use
115
135
  # Erector::Inline#content or Erector#inline.
116
136
  def call_block
117
137
  @_block.call(self) if @_block
@@ -122,7 +142,8 @@ module Erector
122
142
  # the second argument is a hash used to populate its instance variables.
123
143
  # If the first argument is an instance then the hash must be unspecified
124
144
  # (or empty). If a block is passed to this method, then it gets set as the
125
- # rendered widget's block.
145
+ # rendered widget's block, and will be executed when that widget calls
146
+ # +call_block+ or calls +super+ from inside its +content+ method.
126
147
  #
127
148
  # This is the preferred way to call one widget from inside another. This
128
149
  # method assures that the same output string is used, which gives better
@@ -140,8 +161,8 @@ module Erector
140
161
 
141
162
  # Creates a whole new output string, executes the block, then converts the
142
163
  # output string to a string and returns it as raw text. If at all possible
143
- # you should avoid this method since it hurts performance, and use +widget+
144
- # instead.
164
+ # you should avoid this method since it hurts performance, and use
165
+ # +widget+ instead.
145
166
  def capture
146
167
  original, @_output = output, Output.new
147
168
  yield
@@ -152,22 +173,46 @@ module Erector
152
173
  end
153
174
 
154
175
  protected
176
+ # executes this widget's #content method, which emits stuff onto the
177
+ # output stream
155
178
  def _render(options = {}, &block)
156
179
  @_block = block if block
157
180
  @_parent = options[:parent] || parent
158
181
  @_helpers = options[:helpers] || parent
159
- @_output = options[:output]
160
- @_output = Output.new(options) unless output.is_a?(Output)
182
+ if options[:output]
183
+ # todo: document that either :buffer or :output can be used to specify an output buffer, and deprecate :output
184
+ if options[:output].is_a? Output
185
+ @_output = options[:output]
186
+ else
187
+ @_output = Output.new({:buffer => options[:output]}.merge(options))
188
+ end
189
+ else
190
+ @_output = Output.new(options)
191
+ end
161
192
 
162
193
  output.widgets << self.class
163
194
  send(options[:content_method_name] || :content)
164
195
  output
165
196
  end
166
197
 
198
+ # same as _render, but using a parent widget's output stream and helpers
167
199
  def _render_via(parent, options = {}, &block)
168
200
  _render(options.merge(:parent => parent,
169
201
  :output => parent.output,
170
202
  :helpers => parent.helpers), &block)
171
203
  end
204
+
205
+ protected
206
+
207
+ def sort_for_xml_declaration(attributes)
208
+ # correct order is "version, encoding, standalone" (XML 1.0 section 2.8).
209
+ # But we only try to put version before encoding for now.
210
+ stringized = []
211
+ attributes.each do |key, value|
212
+ stringized << [key.to_s, value]
213
+ end
214
+ stringized.sort{|a, b| b <=> a}
215
+ end
216
+
172
217
  end
173
218
  end
@@ -0,0 +1,34 @@
1
+ module Erector
2
+ module Attributes
3
+ def format_attributes(attributes)
4
+ if !attributes || attributes.empty?
5
+ ""
6
+ else
7
+ format_sorted(sort_attributes(attributes))
8
+ end
9
+ end
10
+
11
+ def format_sorted(sorted)
12
+ results = ['']
13
+ sorted.each do |key, value|
14
+ if value
15
+ if value.is_a?(Array)
16
+ value = value.flatten
17
+ next if value.empty?
18
+ value = value.join(' ')
19
+ end
20
+ results << "#{key}=\"#{h(value)}\""
21
+ end
22
+ end
23
+ results.join(' ')
24
+ end
25
+
26
+ def sort_attributes(attributes)
27
+ stringized = []
28
+ attributes.each do |key, value|
29
+ stringized << [key.to_s, value]
30
+ end
31
+ stringized.sort
32
+ end
33
+ end
34
+ end