effigy 0.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +19 -0
- data/README.textile +114 -0
- data/Rakefile +72 -16
- data/VERSION +1 -0
- data/lib/effigy.rb +2 -0
- data/lib/effigy/class_list.rb +30 -0
- data/lib/effigy/errors.rb +14 -0
- data/lib/effigy/rails.rb +6 -0
- data/lib/effigy/rails/template_handler.rb +45 -0
- data/lib/effigy/rails/view.rb +18 -0
- data/lib/effigy/view.rb +51 -16
- data/spec/effigy/class_list_spec.rb +45 -0
- data/spec/effigy/errors_spec.rb +10 -0
- data/spec/effigy/rails/template_handler_spec.rb +89 -0
- data/spec/effigy/view_spec.rb +106 -4
- data/spec/rails/generators/effigy_view_spec.rb +70 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/support/have_selector.rb +3 -3
- metadata +25 -8
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2009 Joe Ferris
|
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.textile
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
h1. Effigy
|
2
|
+
|
3
|
+
Create usable views in Ruby with HTML and CSS selectors.
|
4
|
+
|
5
|
+
h2. Synopsis
|
6
|
+
|
7
|
+
In Effigy, your view is a Ruby class that performs transformation on an HTML template. The template is passed to #render, which calls a private #transform method to apply the transformations. The transformed template is then returned as a string of HTML.
|
8
|
+
|
9
|
+
<pre>
|
10
|
+
template = %{
|
11
|
+
<html>
|
12
|
+
<head>
|
13
|
+
<title></title>
|
14
|
+
</head>
|
15
|
+
<body>
|
16
|
+
<h1></h1>
|
17
|
+
<p class="body"></p>
|
18
|
+
<div class="comment">
|
19
|
+
<h2></h2>
|
20
|
+
<p></p>
|
21
|
+
<a>View more</a>
|
22
|
+
</div>
|
23
|
+
<p id="no-comments">There aren't any comments for this post.</p>
|
24
|
+
</body>
|
25
|
+
</html>
|
26
|
+
}
|
27
|
+
|
28
|
+
class PostView < Effigy::View
|
29
|
+
attr_reader :post
|
30
|
+
|
31
|
+
def initialize(post)
|
32
|
+
@post = post
|
33
|
+
end
|
34
|
+
|
35
|
+
def transform
|
36
|
+
text('h1', post.title)
|
37
|
+
text('title', "#{post.title} - Site title")
|
38
|
+
text('p.body', post.body)
|
39
|
+
replace_with_each('.comment', post.comments) do |comment|
|
40
|
+
text('h2', comment.title)
|
41
|
+
text('p', comment.summary)
|
42
|
+
attributes('a', :href => url_for(comment))
|
43
|
+
end
|
44
|
+
remove('#no-comments') if post.comments.empty?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
view = PostView.new(post)
|
49
|
+
document = view.render(template)
|
50
|
+
|
51
|
+
# Result document:
|
52
|
+
# <html>
|
53
|
+
# <head>
|
54
|
+
# <title>Post title - Site title</title>
|
55
|
+
# </head>
|
56
|
+
# <body>
|
57
|
+
# <h1>Post title</h1>
|
58
|
+
# <p class="body">Post body</p>
|
59
|
+
# <div class="comment">
|
60
|
+
# <h2>First comment title</h2>
|
61
|
+
# <p>First comment body</p>
|
62
|
+
# <a href="/comments/1">View more</a>
|
63
|
+
# </div>
|
64
|
+
# <div class="comment">
|
65
|
+
# <h2>Second comment title</h2>
|
66
|
+
# <p>Second comment body</p>
|
67
|
+
# <a href="/comments/2">View more</a>
|
68
|
+
# </div>
|
69
|
+
# </body>
|
70
|
+
# </html>
|
71
|
+
</pre>
|
72
|
+
|
73
|
+
See the documentation for more information on available transformations.
|
74
|
+
|
75
|
+
h2. Rails
|
76
|
+
|
77
|
+
Effigy integrates with Rails. It provides a view subclass that copies instance variables from the controller, a template handler to find Effigy views and templates, and a generator to create skeleton view files.
|
78
|
+
|
79
|
+
Example:
|
80
|
+
|
81
|
+
<pre>
|
82
|
+
# app/controllers/magic_controller.rb
|
83
|
+
class MagicController < ApplicationController
|
84
|
+
def index
|
85
|
+
@spell = 'hocus pocus'
|
86
|
+
end
|
87
|
+
end
|
88
|
+
</pre>
|
89
|
+
|
90
|
+
<pre>
|
91
|
+
# app/views/magic/index.html.effigy
|
92
|
+
class MagicIndexView < Effigy::Rails::View
|
93
|
+
def transform
|
94
|
+
text('h1', @spell)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
</pre>
|
98
|
+
|
99
|
+
<pre>
|
100
|
+
# app/templates/magic/index.html
|
101
|
+
<h1>Spell name goes here</h1>
|
102
|
+
</pre>
|
103
|
+
|
104
|
+
View this example in your browser and you'll see "hocus pocus."
|
105
|
+
|
106
|
+
h2. Why?
|
107
|
+
|
108
|
+
Effigy is based on the idea that putting behavior in your templates is confusing and makes them difficult to maintain, and that the closer an ERB template gets to 50% Ruby, 50% HTML, the closer it gets to total chaos. Complicated views require unintuitive concepts (ERB buffers, capture blocks, etc). ERB also has the constant threat of unescaped user input slipping into a view.
|
109
|
+
|
110
|
+
Effigy was created because I have never liked interpolation-based templating languages like ERB and because XSLT is a new language (and I like Ruby just fine).
|
111
|
+
|
112
|
+
h2. Author
|
113
|
+
|
114
|
+
Effigy was written by Joe Ferris. See LICENSE for license info.
|
data/Rakefile
CHANGED
@@ -4,8 +4,8 @@ require 'rake/gempackagetask'
|
|
4
4
|
|
5
5
|
require 'spec/rake/spectask'
|
6
6
|
|
7
|
-
desc 'Default: run the specs.'
|
8
|
-
task :default => [:spec]
|
7
|
+
desc 'Default: run the specs and metrics.'
|
8
|
+
task :default => [:spec, :metrics]
|
9
9
|
|
10
10
|
Spec::Rake::SpecTask.new do |t|
|
11
11
|
t.spec_opts = ['--color', '--format', 'progress']
|
@@ -13,23 +13,79 @@ Spec::Rake::SpecTask.new do |t|
|
|
13
13
|
t.ruby_opts = ['-rrubygems']
|
14
14
|
end
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
16
|
+
task :rails_root do
|
17
|
+
rails_root = File.join('tmp', 'rails_root')
|
18
|
+
unless File.exist?(rails_root)
|
19
|
+
FileUtils.mkdir_p(File.dirname(rails_root))
|
20
|
+
command = "rails #{rails_root}"
|
21
|
+
output = `#{command} 2>&1`
|
22
|
+
if $? == 0
|
23
|
+
FileUtils.ln_s(FileUtils.pwd, File.join(rails_root, 'vendor', 'plugins'))
|
24
|
+
else
|
25
|
+
$stderr.puts "Command failed with status #{$?}:"
|
26
|
+
$stderr.puts command
|
27
|
+
$stderr.puts output
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
task :spec => :rails_root
|
34
|
+
|
35
|
+
desc "Remove build files"
|
36
|
+
task :clean do
|
37
|
+
%w(tmp pkg doc).each do |path|
|
38
|
+
FileUtils.rm_rf(path)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
begin
|
43
|
+
require 'jeweler'
|
44
|
+
Jeweler::Tasks.new do |gem|
|
45
|
+
gem.name = %q{effigy}
|
46
|
+
gem.version = "0.1"
|
47
|
+
gem.summary = %q{Effigy provides a view and template framework without a templating language.}
|
48
|
+
gem.description = %q{Define views in Ruby and templates in HTML. Avoid code interpolation or ugly templating languages. Use ids, class names, and semantic structures already present in your documents to produce content.}
|
21
49
|
|
22
|
-
|
23
|
-
|
24
|
-
|
50
|
+
gem.files = FileList['[A-Z]*', 'lib/**/*.rb', 'spec/**/*.rb']
|
51
|
+
gem.require_path = 'lib'
|
52
|
+
gem.test_files = Dir[*['spec/**/*_spec.rb']]
|
25
53
|
|
26
|
-
|
27
|
-
|
54
|
+
gem.authors = ["Joe Ferris"]
|
55
|
+
gem.email = %q{jferris@thoughtbot.com}
|
28
56
|
|
29
|
-
|
57
|
+
gem.platform = Gem::Platform::RUBY
|
58
|
+
end
|
59
|
+
Jeweler::GemcutterTasks.new
|
60
|
+
rescue LoadError
|
61
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
30
62
|
end
|
31
63
|
|
32
|
-
|
33
|
-
|
34
|
-
|
64
|
+
begin
|
65
|
+
require 'reek/adapters/rake_task'
|
66
|
+
|
67
|
+
namespace :metrics do
|
68
|
+
desc "Run reek"
|
69
|
+
Reek::RakeTask.new do |t|
|
70
|
+
t.source_files = FileList['lib/**/*.rb', 'rails/**/*.rb']
|
71
|
+
t.fail_on_error = false
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
desc "Run all metrics"
|
76
|
+
task :metrics => ['metrics:reek']
|
77
|
+
rescue LoadError => e
|
78
|
+
puts e.inspect
|
79
|
+
puts "Missing dependencies for metrics."
|
80
|
+
end
|
81
|
+
|
82
|
+
begin
|
83
|
+
require 'yard'
|
84
|
+
|
85
|
+
YARD::Rake::YardocTask.new do |t|
|
86
|
+
t.files = ['lib/**/*.rb', 'rails/**/*.rb']
|
87
|
+
end
|
88
|
+
rescue LoadError => e
|
89
|
+
puts e.inspect
|
90
|
+
puts "Missing dependencies for yard."
|
35
91
|
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.0
|
data/lib/effigy.rb
CHANGED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
module Effigy
|
4
|
+
class ClassList
|
5
|
+
def initialize(element)
|
6
|
+
@element = element
|
7
|
+
read_class_names
|
8
|
+
end
|
9
|
+
|
10
|
+
def <<(class_name)
|
11
|
+
@class_names << class_name
|
12
|
+
write_class_names
|
13
|
+
end
|
14
|
+
|
15
|
+
def remove(class_name)
|
16
|
+
@class_names.delete(class_name)
|
17
|
+
write_class_names
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def read_class_names
|
23
|
+
@class_names = (@element['class'] || '').split(' ')
|
24
|
+
end
|
25
|
+
|
26
|
+
def write_class_names
|
27
|
+
@element['class'] = @class_names.join(' ')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/effigy/rails.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
module Effigy
|
2
|
+
module Rails
|
3
|
+
class TemplateHandler < ActionView::TemplateHandler
|
4
|
+
include ActionView::TemplateHandlers::Compilable
|
5
|
+
|
6
|
+
def compile(view)
|
7
|
+
@view = view
|
8
|
+
load_view_class
|
9
|
+
return <<-RUBY
|
10
|
+
if @controller
|
11
|
+
variables = @controller.instance_variable_names
|
12
|
+
variables -= @controller.protected_instance_variables if @controller.respond_to?(:protected_instance_variables)
|
13
|
+
assigns = variables.inject({}) do |hash, name|
|
14
|
+
hash.update(name => @controller.instance_variable_get(name))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
view = #{view_class_name}.new(assigns) { |*names| yield(*names) }
|
18
|
+
view.render(#{template_source.inspect})
|
19
|
+
RUBY
|
20
|
+
end
|
21
|
+
|
22
|
+
def view_name
|
23
|
+
@view.name
|
24
|
+
end
|
25
|
+
|
26
|
+
def base_path
|
27
|
+
@view.base_path
|
28
|
+
end
|
29
|
+
|
30
|
+
def load_view_class
|
31
|
+
load(@view.filename)
|
32
|
+
end
|
33
|
+
|
34
|
+
def view_class_name
|
35
|
+
[base_path, view_name, 'view'].join('_').camelize
|
36
|
+
end
|
37
|
+
|
38
|
+
def template_source
|
39
|
+
template_path = @view.load_path.path.sub(/\/views$/, '/templates')
|
40
|
+
template_file_name = File.join(template_path, base_path, "#{view_name}.#{@view.format}")
|
41
|
+
IO.read(template_file_name)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Effigy
|
2
|
+
module Rails
|
3
|
+
class View < ::Effigy::View
|
4
|
+
def initialize(assigns, &layout_block)
|
5
|
+
assigns.each do |name, value|
|
6
|
+
instance_variable_set(name, value)
|
7
|
+
end
|
8
|
+
@layout_block = layout_block
|
9
|
+
end
|
10
|
+
|
11
|
+
protected
|
12
|
+
|
13
|
+
def content_for(capture)
|
14
|
+
@layout_block.call(capture)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/effigy/view.rb
CHANGED
@@ -1,12 +1,9 @@
|
|
1
1
|
require 'nokogiri'
|
2
|
+
require 'effigy/class_list'
|
3
|
+
require 'effigy/errors'
|
2
4
|
|
3
5
|
module Effigy
|
4
6
|
class View
|
5
|
-
def initialize
|
6
|
-
@css_assignments = {}
|
7
|
-
@xpath_assignments = {}
|
8
|
-
end
|
9
|
-
|
10
7
|
def text(selector, content)
|
11
8
|
select(selector).content = content
|
12
9
|
end
|
@@ -18,14 +15,11 @@ module Effigy
|
|
18
15
|
end
|
19
16
|
end
|
20
17
|
|
21
|
-
def
|
22
|
-
original_element =
|
23
|
-
sibling
|
24
|
-
|
25
|
-
item_element = original_element.dup
|
26
|
-
context(item_element) { yield(item) }
|
18
|
+
def replace_with_each(selector, collection, &block)
|
19
|
+
original_element = select(selector)
|
20
|
+
collection.inject(original_element) do |sibling, item|
|
21
|
+
item_element = clone_element_with_item(original_element, item, &block)
|
27
22
|
sibling.add_next_sibling(item_element)
|
28
|
-
sibling = item_element
|
29
23
|
end
|
30
24
|
original_element.unlink
|
31
25
|
end
|
@@ -33,8 +27,8 @@ module Effigy
|
|
33
27
|
def render(template)
|
34
28
|
@current_context = Nokogiri::XML.parse(template)
|
35
29
|
yield if block_given?
|
36
|
-
|
37
|
-
|
30
|
+
transform
|
31
|
+
output
|
38
32
|
end
|
39
33
|
|
40
34
|
def context(new_context)
|
@@ -44,19 +38,60 @@ module Effigy
|
|
44
38
|
@current_context = old_context
|
45
39
|
end
|
46
40
|
|
41
|
+
def remove(selector)
|
42
|
+
select_all(selector).each { |element| element.unlink }
|
43
|
+
end
|
44
|
+
|
45
|
+
def add_class_names(selector, *class_names)
|
46
|
+
element = select(selector)
|
47
|
+
class_list = ClassList.new(element)
|
48
|
+
class_names.each { |class_name| class_list << class_name }
|
49
|
+
end
|
50
|
+
|
51
|
+
def remove_class_names(selector, *class_names)
|
52
|
+
element = select(selector)
|
53
|
+
class_list = ClassList.new(element)
|
54
|
+
class_names.each { |class_name| class_list.remove(class_name) }
|
55
|
+
end
|
56
|
+
|
57
|
+
def inner(selector, xml)
|
58
|
+
select(selector).inner_html = xml
|
59
|
+
end
|
60
|
+
|
61
|
+
def outer(selector, xml)
|
62
|
+
select(selector).after(xml).unlink
|
63
|
+
end
|
64
|
+
|
47
65
|
private
|
48
66
|
|
67
|
+
def transform
|
68
|
+
end
|
69
|
+
|
49
70
|
attr_reader :current_context
|
50
71
|
|
51
72
|
def select(nodes)
|
52
73
|
if nodes.respond_to?(:search)
|
53
74
|
nodes
|
54
75
|
else
|
55
|
-
current_context.at(nodes)
|
76
|
+
current_context.at(nodes) or
|
77
|
+
raise ElementNotFound, nodes
|
56
78
|
end
|
57
79
|
end
|
58
80
|
|
59
|
-
def
|
81
|
+
def select_all(selector)
|
82
|
+
result = current_context.search(selector)
|
83
|
+
raise ElementNotFound, selector if result.empty?
|
84
|
+
result
|
85
|
+
end
|
86
|
+
|
87
|
+
def clone_element_with_item(original_element, item, &block)
|
88
|
+
item_element = original_element.dup
|
89
|
+
context(item_element) { yield(item) }
|
90
|
+
item_element
|
91
|
+
end
|
92
|
+
|
93
|
+
def output
|
94
|
+
current_context.to_xhtml
|
60
95
|
end
|
61
96
|
|
62
97
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'effigy/class_list'
|
3
|
+
|
4
|
+
describe Effigy::ClassList do
|
5
|
+
|
6
|
+
it "should add a class name to an element with one existing class name" do
|
7
|
+
element = Nokogiri::XML.parse(%{<test class="original"/>}).at("//test")
|
8
|
+
class_list = Effigy::ClassList.new(element)
|
9
|
+
class_list << 'another'
|
10
|
+
element.should have_selector('test.original')
|
11
|
+
element.should have_selector('test.another')
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should add a class name to an element with two existing class names" do
|
15
|
+
element = Nokogiri::XML.parse(%{<test class="one two"/>}).at("//test")
|
16
|
+
class_list = Effigy::ClassList.new(element)
|
17
|
+
class_list << 'another'
|
18
|
+
element.should have_selector('test.one')
|
19
|
+
element.should have_selector('test.two')
|
20
|
+
element.should have_selector('test.another')
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should add a class name to an element without a class attribute" do
|
24
|
+
element = Nokogiri::XML.parse(%{<test/>}).at("//test")
|
25
|
+
class_list = Effigy::ClassList.new(element)
|
26
|
+
class_list << 'another'
|
27
|
+
element.should have_selector('test.another')
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should remove a class name from an element with one existing class name" do
|
31
|
+
element = Nokogiri::XML.parse(%{<test class="original"/>}).at("//test")
|
32
|
+
class_list = Effigy::ClassList.new(element)
|
33
|
+
class_list.remove 'original'
|
34
|
+
element.should_not have_selector('test.original')
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should remove a class name from an element with two existing class names" do
|
38
|
+
element = Nokogiri::XML.parse(%{<test class="one two"/>}).at("//test")
|
39
|
+
class_list = Effigy::ClassList.new(element)
|
40
|
+
class_list.remove 'one'
|
41
|
+
element.should have_selector('test.two')
|
42
|
+
element.should_not have_selector('test.one')
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'effigy/errors'
|
3
|
+
|
4
|
+
describe Effigy::ElementNotFound do
|
5
|
+
it "should accept a selector and use it in the message" do
|
6
|
+
error = Effigy::ElementNotFound.new('user.selected')
|
7
|
+
error.selector.should == 'user.selected'
|
8
|
+
error.message.should include('user.selected')
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require File.join(RAILS_ROOT, 'config', 'environment')
|
4
|
+
require File.join(PROJECT_ROOT, 'rails', 'init')
|
5
|
+
require 'action_view/test_case'
|
6
|
+
require 'nokogiri'
|
7
|
+
|
8
|
+
describe "a controller with an effigy view and template" do
|
9
|
+
before do
|
10
|
+
@files = []
|
11
|
+
create_rails_source_file 'app/controllers/magic_controller.rb', <<-RUBY
|
12
|
+
class MagicController < ApplicationController
|
13
|
+
layout 'application'
|
14
|
+
include ActionController::TestCase::RaiseActionExceptions
|
15
|
+
def index
|
16
|
+
@spell = 'hocus pocus'
|
17
|
+
render
|
18
|
+
end
|
19
|
+
end
|
20
|
+
RUBY
|
21
|
+
|
22
|
+
create_rails_file 'app/views/magic/index.html.effigy', <<-RUBY
|
23
|
+
class MagicIndexView < Effigy::Rails::View
|
24
|
+
def transform
|
25
|
+
text('h1', @spell)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
RUBY
|
29
|
+
|
30
|
+
create_rails_file 'app/templates/magic/index.html', <<-HTML
|
31
|
+
<h1 class="success">placeholder title</h1>
|
32
|
+
HTML
|
33
|
+
|
34
|
+
create_rails_file 'app/views/layouts/application.html.effigy', <<-RUBY
|
35
|
+
class LayoutsApplicationView < Effigy::Rails::View
|
36
|
+
def transform
|
37
|
+
inner('body', content_for(:layout))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
RUBY
|
41
|
+
|
42
|
+
create_rails_file 'app/templates/layouts/application.html', <<-HTML
|
43
|
+
<html><body></body></html>
|
44
|
+
HTML
|
45
|
+
|
46
|
+
@controller = MagicController.new
|
47
|
+
@request = ActionController::TestRequest.new
|
48
|
+
@response = ActionController::TestResponse.new
|
49
|
+
end
|
50
|
+
|
51
|
+
after do
|
52
|
+
@files.each do |file|
|
53
|
+
FileUtils.rm(file)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def create_rails_file(relative_path, contents)
|
58
|
+
absolute_path = File.join(RAILS_ROOT, relative_path)
|
59
|
+
FileUtils.mkdir_p(File.dirname(absolute_path))
|
60
|
+
File.open(absolute_path, 'w') { |file| file.write(contents) }
|
61
|
+
@files << absolute_path
|
62
|
+
absolute_path
|
63
|
+
end
|
64
|
+
|
65
|
+
def create_rails_source_file(relative_path, contents)
|
66
|
+
load create_rails_file(relative_path, contents)
|
67
|
+
end
|
68
|
+
|
69
|
+
def render
|
70
|
+
get :index
|
71
|
+
end
|
72
|
+
|
73
|
+
include ActionController::TestProcess
|
74
|
+
|
75
|
+
it "should use the view to render the template" do
|
76
|
+
render
|
77
|
+
@response.should be_success
|
78
|
+
@response.rendered[:template].to_s.should == 'magic/index.html.effigy'
|
79
|
+
assigns(:spell).should_not be_nil
|
80
|
+
@response.body.should have_selector('h1.success', :contents => assigns(:spell))
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should render an effigy layout" do
|
84
|
+
render
|
85
|
+
|
86
|
+
@response.should be_success
|
87
|
+
@response.body.should have_selector('html body h1.success')
|
88
|
+
end
|
89
|
+
end
|
data/spec/effigy/view_spec.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'effigy/view'
|
3
|
+
require 'effigy/errors'
|
3
4
|
|
4
5
|
module Effigy
|
5
6
|
describe View do
|
@@ -25,12 +26,12 @@ module Effigy
|
|
25
26
|
xml.should have_selector(:element, :contents => 'something', :one => '123', :two => '234')
|
26
27
|
end
|
27
28
|
|
28
|
-
it "should replace
|
29
|
+
it "should replace an element with a clone for each item in a collection" do
|
29
30
|
template = %{<test><element><value>original</value></element></test>}
|
30
31
|
|
31
32
|
view = Effigy::View.new
|
32
33
|
xml = view.render(template) do
|
33
|
-
view.
|
34
|
+
view.replace_with_each('element', %w(one two)) do |value|
|
34
35
|
view.text('value', value)
|
35
36
|
end
|
36
37
|
end
|
@@ -53,6 +54,107 @@ module Effigy
|
|
53
54
|
|
54
55
|
xml.should have_selector('element value', :contents => 'expected')
|
55
56
|
end
|
57
|
+
|
58
|
+
it "should remove all matching elements" do
|
59
|
+
template = %{<test><first class="yes"/><other class="yes"/><last class="no"/></test>}
|
60
|
+
|
61
|
+
view = Effigy::View.new
|
62
|
+
xml = view.render(template) do
|
63
|
+
view.remove('.yes')
|
64
|
+
end
|
65
|
+
|
66
|
+
xml.should have_selector('.no')
|
67
|
+
xml.should_not have_selector('.yes')
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should add the given class names" do
|
71
|
+
template = %{<test class="original"/>}
|
72
|
+
|
73
|
+
view = Effigy::View.new
|
74
|
+
xml = view.render(template) do
|
75
|
+
view.add_class_names('test', 'one', 'two')
|
76
|
+
end
|
77
|
+
|
78
|
+
xml.should have_selector('test.original')
|
79
|
+
xml.should have_selector('test.one')
|
80
|
+
xml.should have_selector('test.two')
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should remove the given class names" do
|
84
|
+
template = %{<test class="one two three"/>}
|
85
|
+
|
86
|
+
view = Effigy::View.new
|
87
|
+
xml = view.render(template) do
|
88
|
+
view.remove_class_names('test', 'one', 'two')
|
89
|
+
end
|
90
|
+
|
91
|
+
xml.should have_selector('test.three')
|
92
|
+
xml.should_not have_selector('test.one')
|
93
|
+
xml.should_not have_selector('test.two')
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should replace an element's inner markup" do
|
97
|
+
template = %{<test><original>contents</original></test>}
|
98
|
+
|
99
|
+
view = Effigy::View.new
|
100
|
+
xml = view.render(template) do
|
101
|
+
view.inner 'test', '<new>replaced</new>'
|
102
|
+
end
|
103
|
+
|
104
|
+
xml.should have_selector('test new', :contents => 'replaced')
|
105
|
+
xml.should_not have_selector('original')
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should replace an element's outer markup" do
|
109
|
+
template = %{<test><original>contents</original></test>}
|
110
|
+
|
111
|
+
view = Effigy::View.new
|
112
|
+
xml = view.render(template) do
|
113
|
+
view.outer 'test', '<new>replaced</new>'
|
114
|
+
end
|
115
|
+
|
116
|
+
xml.should have_selector('new', :contents => 'replaced')
|
117
|
+
xml.should_not have_selector('test')
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should render xhtml by default" do
|
121
|
+
template = %{<html/>}
|
122
|
+
xml = Effigy::View.new.render(template)
|
123
|
+
xml.should_not include('xml')
|
124
|
+
end
|
125
|
+
|
126
|
+
describe "given a template without .find" do
|
127
|
+
def render(&block)
|
128
|
+
lambda do
|
129
|
+
view = Effigy::View.new
|
130
|
+
view.render('<test/>') { block.call(view) }
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should raise when updating text content for .find" do
|
135
|
+
render { |view| view.text('.find', 'value') }.should raise_error(Effigy::ElementNotFound)
|
136
|
+
end
|
137
|
+
|
138
|
+
it "should raise when updating attributes for .find" do
|
139
|
+
render { |view| view.attributes('.find', :attr => 'value') }.
|
140
|
+
should raise_error(Effigy::ElementNotFound)
|
141
|
+
end
|
142
|
+
|
143
|
+
it "should raise when replacing an element matching .find" do
|
144
|
+
render { |view| view.replace_with_each('.find', []) }.
|
145
|
+
should raise_error(Effigy::ElementNotFound)
|
146
|
+
end
|
147
|
+
|
148
|
+
it "should raise when removing elements matching .find" do
|
149
|
+
render { |view| view.remove('.find') }.
|
150
|
+
should raise_error(Effigy::ElementNotFound)
|
151
|
+
end
|
152
|
+
|
153
|
+
it "should raise when setting the context to .find" do
|
154
|
+
render { |view| view.context('.find') }.
|
155
|
+
should raise_error(Effigy::ElementNotFound)
|
156
|
+
end
|
157
|
+
end
|
56
158
|
end
|
57
159
|
|
58
160
|
describe View, "subclass" do
|
@@ -63,13 +165,13 @@ module Effigy
|
|
63
165
|
@value = value
|
64
166
|
end
|
65
167
|
|
66
|
-
def
|
168
|
+
def transform
|
67
169
|
text('element', @value)
|
68
170
|
end
|
69
171
|
end
|
70
172
|
end
|
71
173
|
|
72
|
-
it "should run #
|
174
|
+
it "should run #transform when rendering" do
|
73
175
|
template = %{<test><element>original</element></test>}
|
74
176
|
view = @subclass.new('expected')
|
75
177
|
view.render(template).should have_selector('element', :contents => 'expected')
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "script/generate effigy_view users create" do
|
4
|
+
before do
|
5
|
+
@controller_name = 'users'
|
6
|
+
@view_name = 'create'
|
7
|
+
@view_class_name = 'UsersCreateView'
|
8
|
+
FileUtils.cd RAILS_ROOT do
|
9
|
+
command = "script/generate effigy_view --backtrace #{@controller_name} #{@view_name} 2>&1"
|
10
|
+
output = `#{command}`
|
11
|
+
unless $? == 0
|
12
|
+
violated "Command failed: #{command}\n#{output}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
after do
|
18
|
+
FileUtils.rm_f(view_path)
|
19
|
+
FileUtils.rm_f(template_path)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should create a view file" do
|
23
|
+
view_path.should contain("class #{@view_class_name} < Rails::Effigy::View")
|
24
|
+
view_path.should contain("private")
|
25
|
+
view_path.should contain("def transform")
|
26
|
+
view_path.should contain(relative_template_path)
|
27
|
+
view_path.should contain("end\nend")
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should create a template file" do
|
31
|
+
template_path.should contain("<h1>#{@view_class_name}</h1>")
|
32
|
+
template_path.should contain("<p>Edit me at #{relative_template_path}</p>")
|
33
|
+
template_path.should contain("<p>Edit my view at #{relative_view_path}</p>")
|
34
|
+
end
|
35
|
+
|
36
|
+
def view_path
|
37
|
+
File.join(RAILS_ROOT, relative_view_path)
|
38
|
+
end
|
39
|
+
|
40
|
+
def relative_view_path
|
41
|
+
File.join('app', 'views', @controller_name, "#{@view_name}.html.effigy")
|
42
|
+
end
|
43
|
+
|
44
|
+
def template_path
|
45
|
+
File.join(RAILS_ROOT, relative_template_path)
|
46
|
+
end
|
47
|
+
|
48
|
+
def relative_template_path
|
49
|
+
File.join('app', 'templates', @controller_name, "#{@view_name}.html")
|
50
|
+
end
|
51
|
+
|
52
|
+
def contain(expected_text)
|
53
|
+
simple_matcher("contain the following lines:\n#{expected_text}") do |path, matcher|
|
54
|
+
if File.exist?(path)
|
55
|
+
actual_text = IO.read(path)
|
56
|
+
if actual_text.include?(expected_text)
|
57
|
+
true
|
58
|
+
else
|
59
|
+
matcher.failure_message =
|
60
|
+
"Expected to get the following text:\n#{expected_text}\nBut got:\n#{actual_text}"
|
61
|
+
false
|
62
|
+
end
|
63
|
+
else
|
64
|
+
matcher.failure_message = "File does not exist"
|
65
|
+
false
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -7,7 +7,7 @@ module Matchers
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def matches?(xml)
|
10
|
-
@xml = xml
|
10
|
+
@xml = xml.to_s
|
11
11
|
|
12
12
|
verify_element_present! &&
|
13
13
|
verify_contents! &&
|
@@ -15,11 +15,11 @@ module Matchers
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def failure_message
|
18
|
-
"Expected #{@missing},\nGot: #{
|
18
|
+
"Expected #{@missing},\nGot: #{@xml}"
|
19
19
|
end
|
20
20
|
|
21
21
|
def negative_failure_message
|
22
|
-
"Did not expect #{selector}"
|
22
|
+
"Did not expect #{selector},\nGot: #{@xml}"
|
23
23
|
end
|
24
24
|
|
25
25
|
private
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: effigy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joe Ferris
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-11-03 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -19,13 +19,26 @@ executables: []
|
|
19
19
|
|
20
20
|
extensions: []
|
21
21
|
|
22
|
-
extra_rdoc_files:
|
23
|
-
|
22
|
+
extra_rdoc_files:
|
23
|
+
- LICENSE
|
24
|
+
- README.textile
|
24
25
|
files:
|
26
|
+
- LICENSE
|
27
|
+
- README.textile
|
25
28
|
- Rakefile
|
26
|
-
-
|
29
|
+
- VERSION
|
27
30
|
- lib/effigy.rb
|
31
|
+
- lib/effigy/class_list.rb
|
32
|
+
- lib/effigy/errors.rb
|
33
|
+
- lib/effigy/rails.rb
|
34
|
+
- lib/effigy/rails/template_handler.rb
|
35
|
+
- lib/effigy/rails/view.rb
|
36
|
+
- lib/effigy/view.rb
|
37
|
+
- spec/effigy/class_list_spec.rb
|
38
|
+
- spec/effigy/errors_spec.rb
|
39
|
+
- spec/effigy/rails/template_handler_spec.rb
|
28
40
|
- spec/effigy/view_spec.rb
|
41
|
+
- spec/rails/generators/effigy_view_spec.rb
|
29
42
|
- spec/spec_helper.rb
|
30
43
|
- spec/support/have_selector.rb
|
31
44
|
has_rdoc: true
|
@@ -33,8 +46,8 @@ homepage:
|
|
33
46
|
licenses: []
|
34
47
|
|
35
48
|
post_install_message:
|
36
|
-
rdoc_options:
|
37
|
-
|
49
|
+
rdoc_options:
|
50
|
+
- --charset=UTF-8
|
38
51
|
require_paths:
|
39
52
|
- lib
|
40
53
|
required_ruby_version: !ruby/object:Gem::Requirement
|
@@ -52,9 +65,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
52
65
|
requirements: []
|
53
66
|
|
54
67
|
rubyforge_project:
|
55
|
-
rubygems_version: 1.3.
|
68
|
+
rubygems_version: 1.3.5
|
56
69
|
signing_key:
|
57
70
|
specification_version: 3
|
58
71
|
summary: Effigy provides a view and template framework without a templating language.
|
59
72
|
test_files:
|
73
|
+
- spec/effigy/class_list_spec.rb
|
74
|
+
- spec/effigy/errors_spec.rb
|
75
|
+
- spec/effigy/rails/template_handler_spec.rb
|
60
76
|
- spec/effigy/view_spec.rb
|
77
|
+
- spec/rails/generators/effigy_view_spec.rb
|