shaven 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/.gitignore +12 -0
  2. data/.rspec +1 -0
  3. data/Isolate +12 -0
  4. data/README.md +183 -0
  5. data/Rakefile +47 -0
  6. data/lib/shaven.rb +27 -0
  7. data/lib/shaven/core_ext/hash.rb +22 -0
  8. data/lib/shaven/core_ext/object.rb +17 -0
  9. data/lib/shaven/document.rb +9 -0
  10. data/lib/shaven/helpers/html.rb +68 -0
  11. data/lib/shaven/nokogiri_ext/node.rb +86 -0
  12. data/lib/shaven/presenter.rb +90 -0
  13. data/lib/shaven/scope.rb +50 -0
  14. data/lib/shaven/transformer.rb +109 -0
  15. data/lib/shaven/transformers/auto.rb +17 -0
  16. data/lib/shaven/transformers/condition.rb +25 -0
  17. data/lib/shaven/transformers/context.rb +35 -0
  18. data/lib/shaven/transformers/dummy.rb +22 -0
  19. data/lib/shaven/transformers/list.rb +51 -0
  20. data/lib/shaven/transformers/reverse_condition.rb +25 -0
  21. data/lib/shaven/transformers/text_or_node.rb +42 -0
  22. data/lib/shaven/version.rb +13 -0
  23. data/shaven.gemspec +20 -0
  24. data/spec/benchmarks.rb +189 -0
  25. data/spec/fixtures/condition.html +5 -0
  26. data/spec/fixtures/context.html +7 -0
  27. data/spec/fixtures/dummy.html +4 -0
  28. data/spec/fixtures/list.html +6 -0
  29. data/spec/fixtures/list_of_contexts.html +6 -0
  30. data/spec/fixtures/reverse_condition.html +5 -0
  31. data/spec/fixtures/text_or_node.html +4 -0
  32. data/spec/helpers_html_spec.rb +54 -0
  33. data/spec/nokogiri_node_spec.rb +55 -0
  34. data/spec/presenter_spec.rb +36 -0
  35. data/spec/spec_helper.rb +11 -0
  36. data/spec/transformers/auto_spec.rb +34 -0
  37. data/spec/transformers/condition_spec.rb +28 -0
  38. data/spec/transformers/context_spec.rb +25 -0
  39. data/spec/transformers/dummy_spec.rb +18 -0
  40. data/spec/transformers/list_spec.rb +41 -0
  41. data/spec/transformers/reverse_condition_spec.rb +28 -0
  42. data/spec/transformers/text_or_node_spec.rb +65 -0
  43. metadata +123 -0
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ *.log
2
+ \#*
3
+ .\#*
4
+ build
5
+ pkg
6
+ tmp
7
+ eproject.cfg
8
+ .eproject
9
+ *.bundle
10
+ .DS_Store
11
+ *.gem
12
+ *.rbc
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Isolate ADDED
@@ -0,0 +1,12 @@
1
+ # -*- ruby -*-
2
+
3
+ gem "nokogiri"
4
+
5
+ env :development do
6
+ gem "rspec", "~> 2.5"
7
+ gem "mocha", "~> 0.9"
8
+
9
+ # For benchmarks...
10
+ gem "mustache"
11
+ gem "haml"
12
+ end
data/README.md ADDED
@@ -0,0 +1,183 @@
1
+ # Shaven - Templating without mustaches!
2
+
3
+ Hey guys, look at present fasion... mustaches are not fashionable anymore =P.
4
+ Take a look how nice looking are shaven templates.
5
+
6
+ ## Motivation
7
+
8
+ I'm not a designer, usualy all templates in my work are prepared by external
9
+ design studios or freelancers... But of course they are always pure xhtml.
10
+ So still we have to deal with them, convert to haml, fill in with mustaches or
11
+ erb sh**t! Now, my patience is over. Shaven will readmit some MVPC's fresh
12
+ air to your web apps and allow you to get rid of stupid logic from your views.
13
+
14
+ ## Installation
15
+
16
+ Installation with rubygems should go without quirks. Shaven depends on Nokogiri - if
17
+ you don't have it installed yet then i recommend you to check out its documentation
18
+ to avoid problems.
19
+
20
+ $ gem install shaven
21
+
22
+ ## How it works?
23
+
24
+ Shaven views are splited into two layers (similar to defunk's mustache) - Template
25
+ and Presenter. Templates are pure html files, Presenters are ruby classes
26
+ which provides data for templates. Depending on the data type provided by
27
+ presenter's methods you can freely and easily manipulate all contents within
28
+ your templates. Ok, lets finish talking and take a look at examples...
29
+
30
+ ### Simple usage
31
+
32
+ class SimplePresenter < Shaven::Presenter
33
+ def title
34
+ "Hello world!"
35
+ end
36
+
37
+ def description
38
+ "Yeah, hello beautiful code..."
39
+ end
40
+ end
41
+
42
+ html = <<-HTML
43
+ <!DOCTYPE html>
44
+ <html>
45
+ <head>
46
+ <title rb="title">Example title!</title>
47
+ </head>
48
+ <body>
49
+ <h1 rb="title">Example title</h1>
50
+ <p rb="description">Example description...</p>
51
+ </body>
52
+ </html>
53
+ HTML
54
+
55
+ SimplePresenter.feed(html).to_html
56
+
57
+ This code produces following html:
58
+
59
+ <!DOCTYPE html>
60
+ <html>
61
+ <head>
62
+ <title>Hello World!</title>
63
+ </head>
64
+ <body>
65
+ <h1>Hello World!</h1>
66
+ <p>Yeah, hello beautiful code...</p>
67
+ </body>
68
+ </html>
69
+
70
+ ### DOM manipulation
71
+
72
+ class ManipulationPresenter < Shaven::Presenter
73
+ # If you add parameter to the presenter method, original node will
74
+ # passed to it. Given element is an Nokogiri::XML::Node with some
75
+ # extra helpers for content manipulation.
76
+ def login_link(orig)
77
+ orig.update!(:method => "delete", :href => logout_path)
78
+ end
79
+
80
+ # You can use extra html helpers to create new dom elements...
81
+ def home_page_link
82
+ a(:href => root_path, :class => "home-page-link") { "Go home!" }
83
+ end
84
+
85
+ # ... or to replace current.
86
+ def title
87
+ tag(:h1, :id => "header") { "This is Sparta! "}.replace!
88
+ end
89
+ end
90
+
91
+ html = <<-HTML
92
+ <!DOCTYPE html>
93
+ <html>
94
+ <body>
95
+ <div rb="title">Example title</div>
96
+ <a href="#" rb="logout_link">Logout!</a>
97
+ <div rb="home_page_link">Home page link will go here...</div>
98
+ </body>
99
+ </html>
100
+ HTML
101
+
102
+ ManipulationPresenter.feed(html).to_html
103
+
104
+ Result:
105
+
106
+ <!DOCTYPE html>
107
+ <html>
108
+ <body>
109
+ <h1 id="header">This is Sparta!</h1>
110
+ <a href="/logout" data-method="delete">Logout!</a>
111
+ <div><a href="/" class="home-page-link">Go Home!</a></div>
112
+ </body>
113
+ </html>
114
+
115
+ ### Hash scopes and lists
116
+
117
+ Now, the true power of Shaven. Suport for lists and scopes.
118
+
119
+ class ComplexPresenter < Shaven::Presenter
120
+ # As scopes are treaded all hashes and objects responding to `#to_shaven`
121
+ # method (which returns hash with attributes).
122
+ def user
123
+ { :name => "John Doe",
124
+ :email => "john@doe.com",
125
+ }
126
+ end
127
+
128
+ def users_list
129
+ [ { :name => tag(:strong) { "Emmet Brown" }, :email => "emmet@brown.com"},
130
+ { :name => proc { |orig| orig.update!(:class => "marty") { "Marty Macfly" }, :email => "marty@macfly.com" },
131
+ { :name => "Biff Tannen", :email => "biff@tannen.com" }
132
+ ]
133
+ end
134
+ end
135
+
136
+ html = <<-HTML
137
+ <!DOCTYPE html>
138
+ <html>
139
+ <body>
140
+ <h1>Single user here!</h1>
141
+ <div rb="user">
142
+ <h2 rb="name">Sample name</h2>
143
+ <p rb="email">sapmle@email.com</p>
144
+ </div>
145
+ <h1>More users</h1>
146
+ <ul id="users">
147
+ <li rb="users_list">
148
+ <span rb="name">Sample name</span>
149
+ <span rb="email">sample@email.com</span>
150
+ <li>
151
+ </ul>
152
+ </body>
153
+ </html>
154
+ HTML
155
+
156
+ And the awesome result is:
157
+
158
+ <!DOCTYPE html>
159
+ <html>
160
+ <body>
161
+ <h1>Single user here!</h1>
162
+ <div rb="user">
163
+ <h2>Adam Smith</h2>
164
+ <p>adam@smith.com</p>
165
+ </div>
166
+ <h1>More users</h1>
167
+ <ul id="users">
168
+ <li>
169
+ <span><strong>Emmet Brown</strong></span>
170
+ <span>brown@brown.com</span>
171
+ <li>
172
+ <li class="marty">
173
+ <span>Marty Macfly</span>
174
+ <span>marty@macfly.com</span>
175
+ <li>
176
+ <li>
177
+ <span>Biff Tannen</span>
178
+ <span>biff@tannen.com</span>
179
+ <li>
180
+ </ul>
181
+ </body>
182
+ </html>
183
+
data/Rakefile ADDED
@@ -0,0 +1,47 @@
1
+ # -*- ruby -*-
2
+
3
+ begin
4
+ require 'isolate/now'
5
+ rescue LoadError
6
+ STDERR.puts "Run `gem install isolate` to install 'isolate'."
7
+ end
8
+
9
+ begin
10
+ require 'rspec/core/rake_task'
11
+ RSpec::Core::RakeTask.new(:spec) do |t|
12
+ t.rspec_opts = ENV['RSPEC_OPTS']
13
+ end
14
+ rescue LoadError
15
+ task :spec do
16
+ abort 'Run `gem install rspec` to install RSpec'
17
+ end
18
+ end
19
+
20
+ task :test => :spec
21
+ task :default => :test
22
+
23
+ begin
24
+ require 'metric_fu'
25
+ rescue LoadError
26
+ end
27
+
28
+ require 'rake/rdoctask'
29
+ Rake::RDocTask.new do |rdoc|
30
+ rdoc.rdoc_dir = 'rdoc'
31
+ rdoc.title = "Shaven - ruby templating without mustaches"
32
+ rdoc.rdoc_files.include('README*')
33
+ rdoc.rdoc_files.include('lib/**/*.rb')
34
+ end
35
+
36
+ desc "Opens console with loaded RAE env."
37
+ task :console do
38
+ require 'shaven'
39
+ require 'irb'
40
+ ARGV.clear
41
+ IRB.start
42
+ end
43
+
44
+ desc "Run benchmarks to compare Shaven's speed with other templating systems."
45
+ task :benchmark do
46
+ require File.dirname(__FILE__) + "/spec/benchmarks"
47
+ end
data/lib/shaven.rb ADDED
@@ -0,0 +1,27 @@
1
+ require "nokogiri"
2
+
3
+ module Shaven
4
+ unless {}.respond_to?(:stringify_keys)
5
+ require 'shaven/core_ext/hash'
6
+ end
7
+
8
+ require 'shaven/core_ext/object'
9
+ require 'shaven/nokogiri_ext/node'
10
+ require 'shaven/presenter'
11
+ require 'shaven/version'
12
+
13
+ if defined?(Rails)
14
+ require 'shaven/railtie'
15
+ end
16
+
17
+ class << self
18
+ # You can specify what caller attribute names should be used in your
19
+ # templates to define transformations. By default <tt>rb</tt>/<tt>rb:*</tt>
20
+ # attributes are in use.
21
+ attr_writer :caller_key
22
+
23
+ def caller_key
24
+ @template_key ||= 'rb'
25
+ end
26
+ end # self
27
+ end # Shaven
@@ -0,0 +1,22 @@
1
+ module Shaven
2
+ module CoreExt
3
+ # Contains helpers for keys stringifying. Implementation borrowed from activesupport.
4
+ module Hash
5
+ def stringify_keys
6
+ inject({}) { |options, (key, value)|
7
+ options[key.to_s] = value
8
+ options
9
+ }
10
+ end
11
+
12
+ def stringify_keys!
13
+ keys.each { |key| self[key.to_s] = delete(key) }
14
+ self
15
+ end
16
+ end # Hash
17
+ end # CoreExt
18
+ end # Shaven
19
+
20
+ class Hash
21
+ include Shaven::CoreExt::Hash
22
+ end
@@ -0,0 +1,17 @@
1
+ module Shaven
2
+ module CoreExt
3
+ module Object
4
+ def nokogiri_node?
5
+ kind_of?(Nokogiri::XML::Node)
6
+ end
7
+
8
+ def to_shaven
9
+ self
10
+ end
11
+ end # Object
12
+ end # CoreExt
13
+ end # Shaven
14
+
15
+ class Object
16
+ include Shaven::CoreExt::Object
17
+ end
@@ -0,0 +1,9 @@
1
+ module Shaven
2
+ class Document < Nokogiri::HTML::Document
3
+ class << self
4
+ def new(thing, encoding=nil, &block)
5
+ parse(thing, nil, encoding, Nokogiri::XML::ParseOptions::DEFAULT_HTML, &block)
6
+ end
7
+ end # self
8
+ end # Document
9
+ end # Shaven
@@ -0,0 +1,68 @@
1
+ module Shaven
2
+ module Helpers
3
+ module HTML
4
+ # Generates document's element node with given attributes and content.
5
+ #
6
+ # ==== Example
7
+ #
8
+ # tag(:h1, :id => "title") { "Hello world!" }
9
+ # tag(:p, :class => "description") { "Lorem ipsum dolor..." }
10
+ #
11
+ # produces:
12
+ #
13
+ # <h1 id="title">Hello world!</h1>
14
+ # <p class="description">Lorem ipsum dolor...</p>
15
+ #
16
+ def tag(tag, attrs={}, &content)
17
+ node = Nokogiri::XML::Node.new(tag.to_s, @document)
18
+ node.update!(attrs, &content)
19
+ end
20
+
21
+ # Shortcut for generating link node.
22
+ #
23
+ # ==== Example
24
+ #
25
+ # a(:href => "/users", :id => "users_link") { "Users" }
26
+ # a(:href => "/users/1", :method => "delete", :remote => true, :confirm => "Are you sure?") { "Delete" }
27
+ #
28
+ # produces:
29
+ #
30
+ # <a href="/users" id="users_link">Users</a>
31
+ # <a href="/users/1" data-method="delete" data-remote="data-remote" data-confirm="Are you sure?">Delete</a>
32
+ #
33
+ def a(attrs={}, &label)
34
+ tag(:a, attrs, &label)
35
+ end
36
+
37
+ # Shortcut for generating image node.
38
+ #
39
+ # ==== Example
40
+ #
41
+ # img(:src => "avatar.jpg" :class => "avatar")
42
+ #
43
+ # produces:
44
+ #
45
+ # <img src="avatar.jpg" class="avatar" />
46
+ #
47
+ def img(attrs={})
48
+ tag(:img, attrs)
49
+ end
50
+
51
+ # Shortcut for generating div element node.
52
+ #
53
+ # ==== Example
54
+ #
55
+ # div(:id => "name") { "Marty Macfly" }
56
+ # div(:id => "email") { "marty@macfly.com" }
57
+ #
58
+ # produces:
59
+ #
60
+ # <div id="name">Marty Macfly</div>
61
+ # <div id="email">marty@macfly.com</div>
62
+ #
63
+ def div(attrs={}, &content)
64
+ tag(:div, attrs, &content)
65
+ end
66
+ end # HTML
67
+ end # Helpers
68
+ end # Shaven
@@ -0,0 +1,86 @@
1
+ module Shaven
2
+ module NokogiriExt
3
+ module Node
4
+ # List of extra <tt>data-*</tt> attributes for dealing with unobtrusive javascript
5
+ # stuff. Following attributes will be converted to html compilant attrs. The <tt>true</tt>
6
+ # and <tt>false</tt> values here means if attribute is boolean or not.
7
+ UJS_DATA_ATTRIBUTES = {
8
+ 'remote' => true,
9
+ 'confirm' => false,
10
+ 'method' => false
11
+ }
12
+
13
+ # Helper for easy updating node's attributes and content.
14
+ #
15
+ # ==== Example
16
+ #
17
+ # node.update!(:id => "hello-world").to_html # => <div id="hello-world"></div>
18
+ # node.update!(nil, "Foo!").to_html # => <div id="hello-world">Foo!</div>
19
+ # node.update! { "Foobar!" } # => <div id="hello-world">Foobar!</div>
20
+ # node.update!(:id => false) # => <div>Foobar!</div>
21
+ #
22
+ def update!(attrs={}, content=nil, &block)
23
+ attrs = {} unless attrs
24
+ attrs = apply_ujs_data_attributes(attrs.stringify_keys)
25
+
26
+ # Applying attributes...
27
+ attrs.each { |key,value|
28
+ if value === false
29
+ delete(key.to_s)
30
+ else
31
+ self[key.to_s] = value.to_s
32
+ end
33
+ }
34
+
35
+ content = block.call if block_given?
36
+
37
+ # Applying content...
38
+ if content.is_a?(Nokogiri::XML::Node)
39
+ self.inner_html = content
40
+ elsif !content.nil?
41
+ self.content = content.to_s
42
+ end
43
+
44
+ return self
45
+ end
46
+
47
+ # Helper for replacing current node with other text or node.
48
+ #
49
+ # ==== Example
50
+ #
51
+ # node.replace!(other_node)
52
+ # node.replace!("Text")
53
+ # node.replace! { "Something new" }
54
+ #
55
+ # XXX: replace/replace_node method failed with segfault so this is some workaround...
56
+ #
57
+ def replace!(text_or_node=nil, &block)
58
+ text_or_node = block.call if block_given?
59
+
60
+ unless text_or_node.nokogiri_node?
61
+ content, text_or_node = text_or_node.to_s, Nokogiri::XML::Text.new("dummy", document)
62
+ text_or_node.content = content
63
+ end
64
+
65
+ node = add_previous_sibling(text_or_node)
66
+ remove
67
+ end
68
+
69
+ private
70
+
71
+ def apply_ujs_data_attributes(attrs)
72
+ UJS_DATA_ATTRIBUTES.each { |key, bool|
73
+ if attrs.key?(key)
74
+ value = attrs.delete(key)
75
+ attrs["data-#{key}"] = value ? (bool ? "data-#{key}" : value) : false
76
+ end
77
+ }
78
+ return attrs
79
+ end
80
+ end # Node
81
+ end # NokogiriExt
82
+ end # Shaven
83
+
84
+ class Nokogiri::XML::Node
85
+ include Shaven::NokogiriExt::Node
86
+ end