crafty 0.1.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/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2010-2011 Voormedia B.V.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,175 @@
1
+ Crafty – handcrafted HTML in pure Ruby
2
+ ======================================
3
+
4
+ Crafty allows you to easily and flexibly craft HTML output with pure Ruby
5
+ code. It is inspired by Builder and Markaby, but is simpler and more flexible.
6
+ Its goal is to provide simple helpers that allow you to build HTML markup in
7
+ any class.
8
+
9
+ Crafty can be used in builder classes that construct complex HTML markup, such
10
+ as form builders or pagination builders. Whenever you need to programmatically
11
+ construct HTML, consider including a Crafty helper module.
12
+
13
+
14
+ Features
15
+ --------
16
+
17
+ * Crafty is simple and fast.
18
+
19
+ * HTML element sets are predefined and provided as mix-ins.
20
+
21
+ * Automatic HTML output escaping, 100% compatible with Rails.
22
+
23
+ * No `instance_eval` or `method_missing` tricks, just helper methods.
24
+
25
+
26
+ Synopsis
27
+ --------
28
+
29
+ A very simple example:
30
+
31
+ require "crafty"
32
+
33
+ include Crafty::HTML::Basic
34
+
35
+ div class: "green" do
36
+ h1 "Crafty crafts HTML"
37
+ end
38
+ #=> "<div class=\"green\"><h1>Crafty crafts HTML</h1></div>"
39
+
40
+
41
+ Another example:
42
+
43
+ require "crafty"
44
+
45
+ class Widget
46
+ include Crafty::HTML::Basic
47
+
48
+ def initialize(target)
49
+ @target = target
50
+ end
51
+
52
+ def render
53
+ # The first helper method that is called will collect all HTML.
54
+ html do
55
+ head do
56
+ title "Hello"
57
+ end
58
+ body do
59
+ # Continue building in another method.
60
+ render_content
61
+ end
62
+ end
63
+ end
64
+
65
+ def render_content
66
+ div class: ["main", "content"] do
67
+ p "Hello, #{@target}!"
68
+ end
69
+ end
70
+ end
71
+
72
+ Widget.new("world").render
73
+ #=> "<html><head><title>Hello</title></head><body><div class=\"main content\"><p>Hello, world!</p></div></body></html>"
74
+
75
+
76
+ Helper modules
77
+ --------------
78
+
79
+ You can choose from one of the following helper modules:
80
+
81
+ * `Crafty::HTML::Basic`: A simple subset of all HTML elements, enough
82
+ for most HTML layouts.
83
+
84
+ * `Crafty::HTML::Forms`: All HTML elements that are related to forms. If you
85
+ use Rails, note that the names of some of these elements conflict with Rails
86
+ helpers (such as `label` and `input`).
87
+
88
+ * `Crafty::HTML::Semantic`: Juicy new HTML5 elements such as `header`,
89
+ `footer`, `nav`, etc.
90
+
91
+ * `Crafty::HTML::All`: All HTML5 elements that are defined in the HTML5 draft
92
+ spec. If you use Rails, note that some elements might conflict with Rails
93
+ helpers.
94
+
95
+ You can also choose to use `Crafty::HTML4::Basic`, `Crafty::HTML4::Forms` and
96
+ `Crafty::HTML4::All` instead. These modules provide helpers for HTML4/XHTML
97
+ elements. For a complete reference of which elements are included in which
98
+ module, [see the source](https://github.com/voormedia/crafty/tree/master/lib/crafty/toolsets).
99
+
100
+
101
+ Output streaming
102
+ ----------------
103
+
104
+ Crafty helpers return strings by default. However, if the object you use the
105
+ helpers in responds to `<<`, Crafty will push any output directly onto the
106
+ current object. This allows you to create builder classes that can stream
107
+ output. Observe how it works with this contrived example:
108
+
109
+ class StreamingWidget < Array # Subclass Array to demonstrate '<<'
110
+ include Crafty::HTML::Basic
111
+
112
+ def render(target)
113
+ html do
114
+ head do
115
+ title %Q(Hello "#{target}")
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ widget = StreamingWidget.new
122
+ widget.render("world")
123
+ #=> nil
124
+ widget
125
+ #=> ["<html>", "<head>", "<title>", "Hello &quot;world&quot;", "</title>", "</head>", "</html>"]
126
+
127
+
128
+ Benchmarks
129
+ ----------
130
+
131
+ Benchmarks do not necessarily give a complete picture of real-world
132
+ performance. Nevertheless, we wish to demonstrate that Crafty is fast enough
133
+ for daily use. These benchmarks were performed with Ruby 1.9.2.
134
+
135
+ Number of iterations = 50000
136
+ user system total real
137
+ crafty 7.630000 0.140000 7.770000 ( 7.750502)
138
+ builder 17.420000 0.110000 17.530000 ( 17.513536)
139
+ haml 17.450000 0.180000 17.630000 ( 17.600038)
140
+ erector 15.400000 0.110000 15.510000 ( 15.491134)
141
+ tagz 32.860000 0.650000 33.510000 ( 33.461828)
142
+ nokogiri 27.450000 0.210000 27.660000 ( 27.608287)
143
+
144
+
145
+ Requirements
146
+ ------------
147
+
148
+ Crafty has no requirements other than Ruby (tested with 1.8.7+).
149
+
150
+
151
+ Installation
152
+ ------------
153
+
154
+ Install as a gem:
155
+
156
+ gem install crafty
157
+
158
+ Or add to your project's `Gemfile`:
159
+
160
+ gem "crafty"
161
+
162
+
163
+ About Crafty
164
+ -------------
165
+
166
+ Crafty was created by Rolf Timmermans (r.timmermans *at* voormedia.com)
167
+
168
+ Copyright 2010-2011 Voormedia - [www.voormedia.com](http://www.voormedia.com/)
169
+
170
+
171
+ License
172
+ -------
173
+
174
+ Crafty is released under the MIT license.
175
+
data/Rakefile ADDED
@@ -0,0 +1,103 @@
1
+ # encoding: utf-8
2
+ require "jeweler"
3
+ require "rake/testtask"
4
+
5
+ Jeweler::Tasks.new do |spec|
6
+ spec.name = "crafty"
7
+ spec.summary = "Build HTML like a master craftsman."
8
+ spec.description = "Crafty provides you the tools to easily and flexibly create HTML output with pure Ruby."
9
+
10
+ spec.authors = ["Rolf Timmermans"]
11
+ spec.email = "r.timmermans@voormedia.com"
12
+
13
+ spec.files -= Dir["{benchmark,src}/**/*"]
14
+ end
15
+
16
+ Jeweler::GemcutterTasks.new
17
+
18
+ Rake::TestTask.new do |test|
19
+ test.pattern = "test/unit/**/*_test.rb"
20
+ end
21
+
22
+ task :default => [:generate, :test]
23
+
24
+ desc "Benchmark Crafty against various other HTML builders"
25
+ task :bench do
26
+ require File.expand_path("benchmark/bench", File.dirname(__FILE__))
27
+ end
28
+
29
+ desc "Profile Crafty"
30
+ task :profile do
31
+ require File.expand_path("benchmark/profile", File.dirname(__FILE__))
32
+ end
33
+
34
+ desc "Regenerate toolsets"
35
+ task :generate do
36
+ require File.expand_path("src/elements", File.dirname(__FILE__))
37
+
38
+ def simple_format(text, len = 73, indent = 6)
39
+ sentences = [[]]
40
+
41
+ text.split.each do |word|
42
+ if (sentences.last + [word]).join(' ').length > len
43
+ sentences << [word]
44
+ else
45
+ sentences.last << word
46
+ end
47
+ end
48
+
49
+ sentences.map { |sentence|
50
+ "#{" " * indent}#{sentence.join(' ')}"
51
+ }.join "\n"
52
+ end
53
+
54
+ def define(set, regular, empty)
55
+ simple_format("Toolset.define(self, %w{#{regular * " "}}" + (empty.any? ? ", %w{#{empty * " "}}" : "") + ")")
56
+ end
57
+
58
+ autoloading = [" # Generated HTML toolsets."]
59
+ Versions.each do |version|
60
+ path = "crafty/toolsets/#{version.to_s.downcase}"
61
+ file = File.open("lib/#{path}.rb", "w+")
62
+ file.puts "module Crafty"
63
+ file.puts " # This toolset has been automatically generated."
64
+ file.puts " module #{version}"
65
+
66
+ version_elements = Object.const_get(version)
67
+ Sets.each do |set|
68
+ set_elements = Object.const_get(set)
69
+
70
+ broken = set_elements - (HTML4 + HTML5)
71
+ raise "Incorrect elements in set: #{broken}" if broken.any?
72
+
73
+ all = version_elements & set_elements
74
+
75
+ file.puts " module #{set}"
76
+ file.puts define(set, all - Childless, all & Childless)
77
+ file.puts " end"
78
+ file.puts
79
+ end
80
+
81
+ file.puts " module All"
82
+ file.puts define(:All, version_elements - Childless, version_elements & Childless)
83
+ file.puts " end"
84
+ file.puts " end"
85
+
86
+ aliases = []
87
+ Aliases.each { |alt, orig| aliases << alt if orig == version }
88
+ file.puts "\n #{aliases * " = "} = #{version}" if aliases.any?
89
+ file.puts "end"
90
+ file.close
91
+
92
+ ([version] + aliases).each do |mod|
93
+ autoloading << %Q( autoload #{mod.inspect}, #{path.inspect})
94
+ end
95
+ autoloading << ""
96
+ end
97
+
98
+ lib = File.read("lib/crafty.rb")
99
+ lib.gsub!(/^\s*#\s*Generated.*\nend/m, "\n" + autoloading.join("\n") + "end")
100
+ File.open "lib/crafty.rb", "w+" do |file|
101
+ file.write lib
102
+ end
103
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/crafty.gemspec ADDED
@@ -0,0 +1,55 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{crafty}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Rolf Timmermans"]
12
+ s.date = %q{2011-04-01}
13
+ s.description = %q{Crafty provides you the tools to easily and flexibly create HTML output with pure Ruby.}
14
+ s.email = %q{r.timmermans@voormedia.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.md"
18
+ ]
19
+ s.files = [
20
+ "LICENSE",
21
+ "README.md",
22
+ "Rakefile",
23
+ "VERSION",
24
+ "crafty.gemspec",
25
+ "lib/crafty.rb",
26
+ "lib/crafty/safety.rb",
27
+ "lib/crafty/tools.rb",
28
+ "lib/crafty/toolset.rb",
29
+ "lib/crafty/toolsets/html4.rb",
30
+ "lib/crafty/toolsets/html5.rb",
31
+ "test/test_helper.rb",
32
+ "test/unit/html_test.rb",
33
+ "test/unit/tools_test.rb",
34
+ "test/unit/toolset_test.rb"
35
+ ]
36
+ s.require_paths = ["lib"]
37
+ s.rubygems_version = %q{1.5.2}
38
+ s.summary = %q{Build HTML like a master craftsman.}
39
+ s.test_files = [
40
+ "test/test_helper.rb",
41
+ "test/unit/html_test.rb",
42
+ "test/unit/tools_test.rb",
43
+ "test/unit/toolset_test.rb"
44
+ ]
45
+
46
+ if s.respond_to? :specification_version then
47
+ s.specification_version = 3
48
+
49
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
50
+ else
51
+ end
52
+ else
53
+ end
54
+ end
55
+
@@ -0,0 +1,34 @@
1
+ class Object
2
+ def html_safe?
3
+ false
4
+ end
5
+ end
6
+
7
+ module Crafty
8
+ class SafeString < String
9
+ def html_safe?
10
+ true
11
+ end
12
+
13
+ def html_safe
14
+ self
15
+ end
16
+
17
+ alias_method :to_s, :html_safe
18
+ alias_method :render, :html_safe
19
+ end
20
+
21
+ class SafeWrapper
22
+ def initialize(base)
23
+ @base = base
24
+ end
25
+
26
+ def <<(data)
27
+ @base << SafeString.new(data)
28
+ end
29
+
30
+ def render
31
+ nil
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,108 @@
1
+ require "crafty/safety"
2
+
3
+ module Crafty
4
+ module Tools
5
+ class << self
6
+ ESCAPE_SEQUENCE = { "&" => "&amp;", ">" => "&gt;", "<" => "&lt;", '"' => "&quot;" }
7
+
8
+ def escape(content)
9
+ return content if content.html_safe?
10
+ content.gsub(/[&><"]/) { |char| ESCAPE_SEQUENCE[char] }
11
+ end
12
+
13
+ def format_attributes(attributes)
14
+ return if attributes.nil?
15
+ attributes.collect do |name, value|
16
+ value = if value.kind_of? Array
17
+ value.flatten.compact * " "
18
+ else
19
+ value.to_s
20
+ end
21
+ next if value == ""
22
+ %Q{ #{name}="#{escape(value)}"}
23
+ end.join
24
+ end
25
+
26
+ def format_parameters(parameters)
27
+ return if parameters.nil?
28
+ parameters.collect do |name|
29
+ name = name.inspect if name.kind_of? String
30
+ %Q{ #{name}}
31
+ end.join
32
+ end
33
+
34
+ def create_stream(base)
35
+ if base.respond_to? :<<
36
+ SafeWrapper.new(base)
37
+ else
38
+ SafeString.new
39
+ end
40
+ end
41
+ end
42
+
43
+ def element!(name, content = nil, attributes = nil)
44
+ build! do
45
+ if content or block_given?
46
+ @_crafted << "<#{name}#{Tools.format_attributes(attributes)}>"
47
+ if block_given?
48
+ value = yield
49
+ content = value if !@_appended or value.kind_of? String
50
+ end
51
+ content = content.to_s
52
+ @_crafted << Tools.escape(content) if content != ""
53
+ @_crafted << "</#{name}>"
54
+ else
55
+ @_crafted << "<#{name}#{Tools.format_attributes(attributes)}/>"
56
+ end
57
+ end
58
+ end
59
+
60
+ def comment!(content)
61
+ build! do
62
+ @_crafted << "<!-- "
63
+ @_crafted << Tools.escape(content.to_s)
64
+ @_crafted << " -->"
65
+ end
66
+ end
67
+
68
+ def instruct!(name = nil, attributes = {})
69
+ unless name
70
+ name = "xml"
71
+ attributes = { :version => "1.0", :encoding => "UTF-8" }
72
+ end
73
+ build! do
74
+ @_crafted << "<?#{name}#{Tools.format_attributes(attributes)}?>"
75
+ end
76
+ end
77
+
78
+ def declare!(name, *parameters)
79
+ build! do
80
+ @_crafted << "<!#{name}#{Tools.format_parameters(parameters)}>"
81
+ end
82
+ end
83
+
84
+ def text!(content)
85
+ build! do
86
+ @_crafted << Tools.escape(content.to_s)
87
+ end
88
+ end
89
+ alias_method :write!, :text!
90
+
91
+ def build!
92
+ @_appended = false
93
+ if @_crafted
94
+ yield
95
+ @_appended = true
96
+ nil
97
+ else
98
+ begin
99
+ @_crafted = Tools.create_stream(self)
100
+ yield
101
+ @_crafted.render
102
+ ensure
103
+ @_crafted = nil
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,49 @@
1
+ module Crafty
2
+ module Toolset
3
+ class << self
4
+ def define(mod, elements = [], empty_elements = [])
5
+ define_elements(mod, elements)
6
+ define_empty_elements(mod, empty_elements)
7
+
8
+ mod.module_eval do
9
+ include Tools
10
+
11
+ def self.append_features(mod)
12
+ redefined = mod.instance_methods & self.instance_methods(false)
13
+ if redefined.any?
14
+ dup.tap do |safe|
15
+ redefined.each do |method|
16
+ safe.send :remove_method, method
17
+ end
18
+ end.append_features(mod)
19
+ else
20
+ super
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ def define_elements(mod, elements)
27
+ elements.each do |element|
28
+ mod.module_eval <<-RUBY, __FILE__, __LINE__ + 1
29
+ def #{element}(*arguments, &block)
30
+ attributes = arguments.pop if arguments.last.kind_of? Hash
31
+ content = arguments.first || ""
32
+ element!("#{element}", content, attributes, &block)
33
+ end
34
+ RUBY
35
+ end
36
+ end
37
+
38
+ def define_empty_elements(mod, elements)
39
+ elements.each do |element|
40
+ mod.module_eval <<-RUBY, __FILE__, __LINE__ + 1
41
+ def #{element}(attributes = nil)
42
+ element!("#{element}", nil, attributes)
43
+ end
44
+ RUBY
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,32 @@
1
+ module Crafty
2
+ # This toolset has been automatically generated.
3
+ module HTML4
4
+ module Basic
5
+ Toolset.define(self, %w{a blockquote body div em h1 h2 h3 h4 h5 h6 head
6
+ html li p pre small span strong table td th title tr ul}, %w{br img
7
+ link})
8
+ end
9
+
10
+ module Forms
11
+ Toolset.define(self, %w{button fieldset form label object option select
12
+ textarea}, %w{input})
13
+ end
14
+
15
+ module Semantic
16
+ Toolset.define(self, %w{abbr acronym address cite code legend menu})
17
+ end
18
+
19
+ module All
20
+ Toolset.define(self, %w{a abbr acronym address applet b bdo big
21
+ blockquote body button caption center cite code colgroup dd del dfn dir
22
+ div dl dt em fieldset font form frameset h1 h2 h3 h4 h5 h6 head html i
23
+ iframe ins kbd label legend li map menu noframes noscript object ol
24
+ optgroup option p pre q s samp script select small span strike strong
25
+ style sub sup table tbody td textarea tfoot th thead title tr tt u ul
26
+ var}, %w{area base basefont br col frame hr img input isindex link meta
27
+ param})
28
+ end
29
+ end
30
+
31
+ XHTML = HTML4
32
+ end
@@ -0,0 +1,34 @@
1
+ module Crafty
2
+ # This toolset has been automatically generated.
3
+ module HTML5
4
+ module Basic
5
+ Toolset.define(self, %w{a blockquote body div em h1 h2 h3 h4 h5 h6 head
6
+ html li p pre small span strong table td th title tr ul}, %w{br img
7
+ link})
8
+ end
9
+
10
+ module Forms
11
+ Toolset.define(self, %w{button datalist fieldset form label meter object
12
+ option output progress select textarea}, %w{input keygen})
13
+ end
14
+
15
+ module Semantic
16
+ Toolset.define(self, %w{abbr address article cite code details figcaption
17
+ figure footer header legend menu nav section}, %w{command})
18
+ end
19
+
20
+ module All
21
+ Toolset.define(self, %w{a abbr address article aside audio b bdi bdo
22
+ blockquote body button canvas caption cite code colgroup datalist dd del
23
+ details dfn div dl dt em fieldset figcaption figure footer form h1 h2 h3
24
+ h4 h5 h6 head header hgroup html i iframe ins kbd label legend li map
25
+ mark menu meter nav noscript object ol optgroup option output p pre
26
+ progress q rp rt ruby s samp script section select small span strong
27
+ style sub summary sup table tbody td textarea tfoot th thead time title
28
+ tr ul var video}, %w{area base br col command embed hr img input keygen
29
+ link meta param source track wbr})
30
+ end
31
+ end
32
+
33
+ HTML = HTML5
34
+ end
data/lib/crafty.rb ADDED
@@ -0,0 +1,11 @@
1
+ module Crafty
2
+ autoload :Tools, "crafty/tools"
3
+ autoload :Toolset, "crafty/toolset"
4
+
5
+ # Generated HTML toolsets.
6
+ autoload :HTML4, "crafty/toolsets/html4"
7
+ autoload :XHTML, "crafty/toolsets/html4"
8
+
9
+ autoload :HTML5, "crafty/toolsets/html5"
10
+ autoload :HTML, "crafty/toolsets/html5"
11
+ end
@@ -0,0 +1,21 @@
1
+ require "rubygems"
2
+ require "bundler/setup"
3
+ require "test/unit"
4
+ require "crafty"
5
+
6
+ class Test::Unit::TestCase
7
+ class << self
8
+ # Support declarative specification of test methods.
9
+ def test(name)
10
+ define_method "test_#{name.gsub(/\s+/,'_')}".to_sym, &Proc.new
11
+ end
12
+ end
13
+ end
14
+
15
+ if RUBY_VERSION < "1.9"
16
+ class Hash
17
+ def each
18
+ to_a.sort_by { |k, v| k.to_s }.each &Proc.new
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,126 @@
1
+ require File.expand_path("../test_helper", File.dirname(__FILE__))
2
+
3
+ class HTMLBase < Test::Unit::TestCase
4
+ def test_dummy; end
5
+
6
+ def self.behaves_as_basic_html
7
+ # Simple methods =========================================================
8
+ test "div should return content with given attributes" do
9
+ assert_equal %Q{<div class="green">Hello</div>}, @object.div("Hello", :class => "green")
10
+ end
11
+
12
+ test "div should return content in block with given attributes" do
13
+ assert_equal %Q{<div class="green">Hello</div>}, @object.div(:class => "green") { "Hello" }
14
+ end
15
+
16
+ test "div should return non string content" do
17
+ assert_equal %Q{<div>1234</div>}, @object.div(1234)
18
+ end
19
+
20
+ test "div should not be self closing" do
21
+ assert_equal %Q{<div></div>}, @object.div
22
+ end
23
+
24
+ test "div should not be self closing with nil block" do
25
+ assert_equal %Q{<div></div>}, @object.div { nil }
26
+ end
27
+
28
+ test "div should return closing tag without content" do
29
+ assert_equal %Q{<div class="green"></div>}, @object.div(:class => "green")
30
+ end
31
+
32
+ test "a should return anchor with given attributes" do
33
+ assert_equal %Q{<a href="http://example.org">link</a>}, @object.a("link", :href => "http://example.org")
34
+ end
35
+
36
+ test "title should return title with content" do
37
+ assert_equal %Q{<title>Hello</title>}, @object.title("Hello")
38
+ end
39
+
40
+ test "h1 should return h1 header" do
41
+ assert_equal %Q{<h1>Title</h1>}, @object.h1("Title")
42
+ end
43
+
44
+ test "br should be self closing" do
45
+ assert_equal %Q{<br/>}, @object.br
46
+ end
47
+
48
+ test "br should not accept block content" do
49
+ assert_equal %Q{<br/>}, @object.br { "foo" }
50
+ end
51
+
52
+ # Streaming ==============================================================
53
+ test "html should product stream of strings if object responds to arrows" do
54
+ @streaming_object.instance_eval do
55
+ html do
56
+ head do
57
+ title "Hi"
58
+ end
59
+ end
60
+ end
61
+ assert_equal ["<html>", "<head>", "<title>", "Hi", "</title>", "</head>", "</html>"], @streaming_object
62
+ end
63
+
64
+ # Examples ===============================================================
65
+ test "complex nested build calls should render correctly" do
66
+ assert_equal %Q{<html>} +
67
+ %Q{<head><title>my document</title><link href="style.css" rel="stylesheet" type="text/css"/></head>} +
68
+ %Q{<body class="awesome"><div><div><table cellspacing="0">} +
69
+ %Q{<tr><th>Col 1</th><th>Col 2</th></tr>} +
70
+ %Q{<tr><td>10000</td><td>content</td></tr>} +
71
+ %Q{</table></div></div></body>} +
72
+ %Q{</html>}, @object.instance_eval {
73
+ html do
74
+ head do
75
+ title "my document"
76
+ link :href => "style.css", :rel => :stylesheet, :type => "text/css"
77
+ end
78
+ body :class => :awesome do
79
+ div {
80
+ div {
81
+ table :cellspacing => 0 do
82
+ tr {
83
+ th "Col 1"
84
+ th "Col 2"
85
+ }
86
+ tr {
87
+ td 10_000
88
+ td "content"
89
+ }
90
+ end
91
+ }
92
+ }
93
+ end
94
+ end
95
+ }
96
+ end
97
+ end
98
+ end
99
+
100
+ class HTML5Test < HTMLBase
101
+ def setup
102
+ @object = Class.new { include Crafty::HTML5::Basic }.new
103
+ @streaming_object = Class.new(Array) { include Crafty::HTML5::Basic }.new
104
+ end
105
+
106
+ behaves_as_basic_html
107
+ end
108
+
109
+ class HTML4Test < HTMLBase
110
+ def setup
111
+ @object = Class.new { include Crafty::HTML4::Basic }.new
112
+ @streaming_object = Class.new(Array) { include Crafty::HTML4::Basic }.new
113
+ end
114
+
115
+ behaves_as_basic_html
116
+ end
117
+
118
+ class HTMLAliasTest < Test::Unit::TestCase
119
+ test "html should equal html5" do
120
+ assert_equal Crafty::HTML, Crafty::HTML5
121
+ end
122
+
123
+ test "xhtml should equal html4" do
124
+ assert_equal Crafty::XHTML, Crafty::HTML4
125
+ end
126
+ end
@@ -0,0 +1,239 @@
1
+ require File.expand_path("../test_helper", File.dirname(__FILE__))
2
+
3
+ class ToolsTest < Test::Unit::TestCase
4
+ def setup
5
+ @object = Class.new { include Crafty::Tools }.new
6
+ end
7
+
8
+ # Basic element functionality ==============================================
9
+ test "element should return element with given name" do
10
+ assert_equal %Q{<el/>}, @object.element!("el")
11
+ end
12
+
13
+ test "element should return element with given name and content" do
14
+ assert_equal %Q{<el>content</el>}, @object.element!("el", "content")
15
+ end
16
+
17
+ test "element should return element with given name and non string content" do
18
+ assert_equal %Q{<el>1234</el>}, @object.element!("el", 1234)
19
+ end
20
+
21
+ test "element should return element with given name and content in block" do
22
+ assert_equal %Q{<el>content</el>}, @object.element!("el") { "content" }
23
+ end
24
+
25
+ test "element should return element with given name and non string content in block" do
26
+ assert_equal %Q{<el>1234</el>}, @object.element!("el") { 1234 }
27
+ end
28
+
29
+ test "element should return element with given name and built content without non string block return value" do
30
+ assert_equal %Q{<el><el>content</el><el>content</el></el>}, @object.element!("el") {
31
+ 2.times { @object.element!("el") { "content" } }
32
+ }
33
+ end
34
+
35
+ test "element should return element with given name and built content with string block return value" do
36
+ assert_equal %Q{<el><el>content</el>foo</el>}, @object.element!("el") {
37
+ @object.element!("el") { "content" }
38
+ "foo"
39
+ }
40
+ end
41
+
42
+ test "element should return element with given name and no content" do
43
+ assert_equal %Q{<el></el>}, @object.element!("el") { nil }
44
+ end
45
+
46
+ test "element should return element with given name and blank content" do
47
+ assert_equal %Q{<el></el>}, @object.element!("el") { "" }
48
+ end
49
+
50
+ test "element should return element with given name and attributes" do
51
+ assert_equal %Q{<el attr="val" prop="value"/>},
52
+ @object.element!("el", nil, :attr => "val", :prop => "value")
53
+ end
54
+
55
+ test "element should return element with given name and attributes and content" do
56
+ assert_equal %Q{<el attr="val" prop="value">content</el>},
57
+ @object.element!("el", "content", :attr => "val", :prop => "value")
58
+ end
59
+
60
+ test "element should return element with given name and attributes and content in block" do
61
+ assert_equal %Q{<el attr="val" prop="value">content</el>},
62
+ @object.element!("el", nil, :attr => "val", :prop => "value") { "content" }
63
+ end
64
+
65
+ test "element should return element with given name and attributes with symbol values" do
66
+ assert_equal %Q{<el attr="val"/>}, @object.element!("el", nil, :attr => :val)
67
+ end
68
+
69
+ # Advanced functionality ===================================================
70
+ test "comment should return comment" do
71
+ assert_equal "<!-- commented -->", @object.comment!("commented")
72
+ end
73
+
74
+ test "instruct should return xml processing instruction" do
75
+ if RUBY_VERSION < "1.9"
76
+ assert_equal %Q{<?xml encoding="UTF-8" version="1.0"?>}, @object.instruct!
77
+ else
78
+ assert_equal %Q{<?xml version="1.0" encoding="UTF-8"?>}, @object.instruct!
79
+ end
80
+ end
81
+
82
+ test "instruct should return custom processing instruction" do
83
+ assert_equal %Q{<?foo attr="instr"?>}, @object.instruct!("foo", :attr => "instr")
84
+ end
85
+
86
+ test "declare should return declaration" do
87
+ assert_equal %Q{<!ENTITY greeting "Hello world">}, @object.declare!(:ENTITY, :greeting, "Hello world")
88
+ end
89
+
90
+ test "declare should return empty declaration if there are no parameters" do
91
+ assert_equal %Q{<!ENTITY>}, @object.declare!("ENTITY")
92
+ end
93
+
94
+ test "text should append directly to output stream" do
95
+ assert_equal %Q{foobar}, @object.text!("foobar")
96
+ end
97
+
98
+ test "write should append directly to output stream" do
99
+ assert_equal %Q{foobar}, @object.write!("foobar")
100
+ end
101
+
102
+ test "build should collect output" do
103
+ assert_equal %Q{<el/><el/>}, @object.build! {
104
+ @object.element!("el")
105
+ @object.element!("el")
106
+ }
107
+ end
108
+
109
+ test "attribute values as array should be joined with spaces" do
110
+ assert_equal %Q{<el attr="val1 val2 val3">content</el>},
111
+ @object.element!("el", "content", :attr => ["val1", "val2", "val3"])
112
+ end
113
+
114
+ test "attribute values as array should be flattened and compacted and joined with spaces" do
115
+ assert_equal %Q{<el attr="val1 val2 val3">content</el>},
116
+ @object.element!("el", "content", :attr => ["val1", [[nil, "val2"], "val3"]])
117
+ end
118
+
119
+ test "attribute values as empty array should be omitted" do
120
+ assert_equal %Q{<el>content</el>}, @object.element!("el", "content", :attr => [])
121
+ end
122
+
123
+ # Escaping =================================================================
124
+ test "element should return element with given name and escaped content" do
125
+ assert_equal %Q{<el>content &amp; &quot;info&quot; &lt; &gt;</el>},
126
+ @object.element!("el") { @object.write! %Q{content & "info" < >} }
127
+ end
128
+
129
+ test "element should return element with given name and escaped attributes" do
130
+ assert_equal %Q{<el attr="&quot;attrib&quot;" prop="a &gt; 1 &amp; b &lt; 4"/>},
131
+ @object.element!("el", nil, :attr => %Q{"attrib"}, :prop => "a > 1 & b < 4")
132
+ end
133
+
134
+ test "comment should return escaped comment" do
135
+ assert_equal "<!-- remarked &amp; commented -->", @object.comment!("remarked & commented")
136
+ end
137
+
138
+ test "instruct should return instruction with escaped attributes" do
139
+ assert_equal %Q{<?foo comment="1 &lt; 2"?>}, @object.instruct!("foo", :comment => "1 < 2")
140
+ end
141
+
142
+ test "write should escape output" do
143
+ assert_equal %Q{foo &amp; bar}, @object.write!("foo & bar")
144
+ end
145
+
146
+ test "element should not escape content that has been marked as html safe" do
147
+ html = Crafty::SafeString.new("<safe></safe>")
148
+ assert_equal %Q{<el><safe></safe></el>}, @object.element!("el") { html }
149
+ end
150
+
151
+ test "element should not escape attributes that have been marked as html safe" do
152
+ html = Crafty::SafeString.new("http://example.org/?q=example&amp;a=search")
153
+ assert_equal %Q{<el attr="http://example.org/?q=example&amp;a=search"/>}, @object.element!("el", nil, :attr => html)
154
+ end
155
+
156
+ test "write should not escape output that has been marked as html safe" do
157
+ assert_equal %Q{foo &amp; bar}, @object.write!(Crafty::SafeString.new("foo &amp; bar"))
158
+ end
159
+
160
+ test "element should return html safe string" do
161
+ assert_equal true, @object.element!("el").html_safe?
162
+ end
163
+
164
+ test "element should return string that reamins html safe when calling to_s" do
165
+ assert_equal true, @object.element!("el").to_s.html_safe?
166
+ end
167
+
168
+ # Building =================================================================
169
+ test "element should be nestable" do
170
+ assert_equal %Q{<el><nested>content</nested></el>},
171
+ @object.element!("el") { @object.element!("nested") { "content" } }
172
+ end
173
+
174
+ test "element should be nestable and chainable without concatenation" do
175
+ assert_equal %Q{<el><nest>content</nest><nested>more &amp; content</nested></el>},
176
+ @object.element!("el") {
177
+ @object.element!("nest") { "content" }
178
+ @object.element!("nested") { "more & content" }
179
+ }
180
+ end
181
+
182
+ test "element should be reset state of buffer after being called" do
183
+ assert_equal %Q{<el/><el/>}, @object.element!("el") + @object.element!("el")
184
+ end
185
+
186
+ test "element should append to object that responds to arrows" do
187
+ object = Class.new(Array) { include Crafty::Tools }.new
188
+ object.element!("el") {
189
+ object.element!("nest") { "content" }
190
+ object.element!("nested") { "more & content" }
191
+ }
192
+ assert_equal ["<el>", "<nest>", "content", "</nest>", "<nested>", "more &amp; content", "</nested>", "</el>"], object
193
+ end
194
+
195
+ test "element should escape content if appending to object that responds to arrows" do
196
+ object = Class.new(Array) { include Crafty::Tools }.new
197
+ content = "foo & bar"
198
+ object.element!("el", content)
199
+ assert_equal ["<el>", "foo &amp; bar", "</el>"], object
200
+ end
201
+
202
+ test "element should return nil if appending to object that responds to arrows" do
203
+ object = Class.new(Array) { include Crafty::Tools }.new
204
+ assert_nil object.element!("el")
205
+ end
206
+
207
+ test "element should append html safe strings to object that responds to arrows if html safe exists" do
208
+ begin
209
+ String.class_eval do
210
+ def html_safe
211
+ obj = dup
212
+ class << obj
213
+ def html_safe?; true end
214
+ end
215
+ obj
216
+ end
217
+ end
218
+ object = Class.new(Array) { include Crafty::Tools }.new
219
+ object.element!("el") {
220
+ object.element!("nest") { "content" }
221
+ object.element!("nested") { "more & content" }
222
+ }
223
+ assert_equal [true] * object.length, object.map(&:html_safe?)
224
+ ensure
225
+ String.class_eval do
226
+ undef_method :html_safe
227
+ end
228
+ end
229
+ end
230
+
231
+ test "element should append html safe strings to object that responds to arrows if html safe does not exist" do
232
+ object = Class.new(Array) { include Crafty::Tools }.new
233
+ object.element!("el") {
234
+ object.element!("nest") { "content" }
235
+ object.element!("nested") { "more & content" }
236
+ }
237
+ assert_equal [true] * object.length, object.map(&:html_safe?)
238
+ end
239
+ end
@@ -0,0 +1,52 @@
1
+ require File.expand_path("../test_helper", File.dirname(__FILE__))
2
+
3
+ class ToolsetTest < Test::Unit::TestCase
4
+ def setup
5
+ @toolset = Module.new { Crafty::Toolset.define(self, %w{a b c d}) }
6
+ end
7
+
8
+ # Basic functionality ======================================================
9
+ test "toolset should include methods" do
10
+ toolset = @toolset
11
+ object = Class.new { include toolset }.new
12
+ assert_equal "<a></a>", object.a
13
+ end
14
+
15
+ test "toolset should include elements" do
16
+ toolset = @toolset
17
+ object = Class.new { include toolset }.new
18
+ assert_equal "<x/>", object.element!("x")
19
+ end
20
+
21
+ # No conflicts =============================================================
22
+ test "toolset should not override existing method" do
23
+ toolset = @toolset
24
+ object = Class.new do
25
+ def b; end
26
+ include toolset
27
+ end.new
28
+ assert_equal nil, object.b {}
29
+ end
30
+
31
+ test "toolset should not override existing method when included twice" do
32
+ toolset = @toolset
33
+ object = Class.new do
34
+ def b; end
35
+ include toolset
36
+ include toolset
37
+ end.new
38
+ assert_equal nil, object.b {}
39
+ end
40
+
41
+ test "toolset should not override existing method from ancestor" do
42
+ toolset = @toolset
43
+ conflict = Module.new do
44
+ def b; end
45
+ end
46
+ object = Class.new do
47
+ include conflict
48
+ include toolset
49
+ end.new
50
+ assert_equal nil, object.b {}
51
+ end
52
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: crafty
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.0
6
+ platform: ruby
7
+ authors:
8
+ - Rolf Timmermans
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-04-01 00:00:00 +02:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ description: Crafty provides you the tools to easily and flexibly create HTML output with pure Ruby.
18
+ email: r.timmermans@voormedia.com
19
+ executables: []
20
+
21
+ extensions: []
22
+
23
+ extra_rdoc_files:
24
+ - LICENSE
25
+ - README.md
26
+ files:
27
+ - LICENSE
28
+ - README.md
29
+ - Rakefile
30
+ - VERSION
31
+ - crafty.gemspec
32
+ - lib/crafty.rb
33
+ - lib/crafty/safety.rb
34
+ - lib/crafty/tools.rb
35
+ - lib/crafty/toolset.rb
36
+ - lib/crafty/toolsets/html4.rb
37
+ - lib/crafty/toolsets/html5.rb
38
+ - test/test_helper.rb
39
+ - test/unit/html_test.rb
40
+ - test/unit/tools_test.rb
41
+ - test/unit/toolset_test.rb
42
+ has_rdoc: true
43
+ homepage:
44
+ licenses: []
45
+
46
+ post_install_message:
47
+ rdoc_options: []
48
+
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ requirements: []
64
+
65
+ rubyforge_project:
66
+ rubygems_version: 1.5.2
67
+ signing_key:
68
+ specification_version: 3
69
+ summary: Build HTML like a master craftsman.
70
+ test_files:
71
+ - test/test_helper.rb
72
+ - test/unit/html_test.rb
73
+ - test/unit/tools_test.rb
74
+ - test/unit/toolset_test.rb