effigy 0.1 → 0.2.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 +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
|