crafty 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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