shaven 0.0.1

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.
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