vanilla 1.0.2 → 1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +112 -109
- data/bin/vanilla +35 -6
- data/lib/vanilla.rb +10 -14
- data/lib/vanilla/app.rb +109 -41
- data/lib/vanilla/console.rb +22 -2
- data/lib/vanilla/dynasnip.rb +4 -36
- data/lib/vanilla/renderers.rb +12 -0
- data/lib/vanilla/renderers/base.rb +58 -34
- data/lib/vanilla/renderers/bold.rb +0 -2
- data/lib/vanilla/renderers/erb.rb +1 -3
- data/lib/vanilla/renderers/haml.rb +13 -0
- data/lib/vanilla/renderers/markdown.rb +0 -2
- data/lib/vanilla/renderers/raw.rb +0 -2
- data/lib/vanilla/renderers/ruby.rb +12 -6
- data/lib/vanilla/renderers/textile.rb +0 -2
- data/lib/vanilla/request.rb +19 -17
- data/lib/vanilla/routes.rb +9 -20
- data/lib/vanilla/snip_reference_parser.rb +94 -0
- data/lib/vanilla/static.rb +28 -0
- data/pristine_app/Gemfile +3 -0
- data/pristine_app/Gemfile.lock +32 -0
- data/pristine_app/README +47 -0
- data/pristine_app/config.ru +26 -0
- data/pristine_app/public/vanilla.css +15 -0
- data/pristine_app/soups/base/layout.snip +18 -0
- data/pristine_app/soups/base/start.snip +19 -0
- data/pristine_app/soups/dynasnips/current_snip.rb +29 -0
- data/{lib/vanilla → pristine_app/soups}/dynasnips/debug.rb +5 -3
- data/pristine_app/soups/dynasnips/index.rb +12 -0
- data/{lib/vanilla → pristine_app/soups}/dynasnips/link_to.rb +4 -2
- data/pristine_app/soups/dynasnips/link_to_current_snip.rb +14 -0
- data/pristine_app/soups/dynasnips/page_title.rb +9 -0
- data/{lib/vanilla → pristine_app/soups}/dynasnips/pre.rb +7 -5
- data/{lib/vanilla → pristine_app/soups}/dynasnips/raw.rb +8 -5
- data/pristine_app/soups/extras/comments.rb +78 -0
- data/{lib/vanilla/dynasnips → pristine_app/soups/extras}/kind.rb +19 -17
- data/{lib/vanilla/dynasnips → pristine_app/soups/extras}/rand.rb +2 -0
- data/pristine_app/soups/extras/url_to.rb +7 -0
- data/pristine_app/soups/tutorial/bad_dynasnip.snip +8 -0
- data/pristine_app/soups/tutorial/hello_world.snip +20 -0
- data/pristine_app/soups/tutorial/markdown_example.snip +13 -0
- data/pristine_app/soups/tutorial/snip.snip +9 -0
- data/pristine_app/soups/tutorial/soup.snip +3 -0
- data/pristine_app/soups/tutorial/test.snip +30 -0
- data/pristine_app/soups/tutorial/textile_example.snip +11 -0
- data/pristine_app/soups/tutorial/tutorial-another-snip.snip +1 -0
- data/pristine_app/soups/tutorial/tutorial-basic-snip-inclusion.snip +1 -0
- data/pristine_app/soups/tutorial/tutorial-dynasnips.snip.markdown +56 -0
- data/pristine_app/soups/tutorial/tutorial-layout.snip +56 -0
- data/pristine_app/soups/tutorial/tutorial-links.snip +4 -0
- data/pristine_app/soups/tutorial/tutorial-renderers.snip.markdown +77 -0
- data/pristine_app/soups/tutorial/tutorial.snip.markdown +69 -0
- data/pristine_app/soups/tutorial/vanilla-rb.snip +16 -0
- data/pristine_app/soups/tutorial/vanilla.snip +8 -0
- data/test/dynasnip_test.rb +42 -0
- data/test/dynasnips/link_to_current_snip_test.rb +19 -0
- data/test/dynasnips/link_to_test.rb +27 -0
- data/test/dynasnips/page_title_test.rb +19 -0
- data/test/renderers/base_renderer_test.rb +43 -0
- data/test/renderers/erb_renderer_test.rb +29 -0
- data/test/renderers/haml_renderer_test.rb +35 -0
- data/test/renderers/markdown_renderer_test.rb +31 -0
- data/test/renderers/raw_renderer_test.rb +23 -0
- data/test/renderers/ruby_renderer_test.rb +59 -0
- data/test/snip_inclusion_test.rb +56 -0
- data/test/snip_reference_parser_test.rb +123 -0
- data/test/test_helper.rb +75 -0
- data/test/vanilla_app_test.rb +83 -0
- data/test/vanilla_presenting_test.rb +125 -0
- data/test/vanilla_request_test.rb +87 -0
- metadata +179 -78
- data/config.example.yml +0 -5
- data/config.ru +0 -9
- data/lib/defensio.rb +0 -59
- data/lib/tasks/vanilla.rake +0 -177
- data/lib/vanilla/dynasnips/comments.rb +0 -108
- data/lib/vanilla/dynasnips/current_snip.rb +0 -32
- data/lib/vanilla/dynasnips/edit.rb +0 -63
- data/lib/vanilla/dynasnips/edit_link.rb +0 -24
- data/lib/vanilla/dynasnips/index.rb +0 -11
- data/lib/vanilla/dynasnips/link_to_current_snip.rb +0 -16
- data/lib/vanilla/dynasnips/login.rb +0 -56
- data/lib/vanilla/dynasnips/new.rb +0 -14
- data/lib/vanilla/dynasnips/notes.rb +0 -42
- data/lib/vanilla/dynasnips/url_to.rb +0 -7
- data/lib/vanilla/snip_handling.rb +0 -33
- data/lib/vanilla/snips/start.rb +0 -27
- data/lib/vanilla/snips/system.rb +0 -76
- data/lib/vanilla/snips/tutorial.rb +0 -157
- data/lib/vanilla/test_snips.rb +0 -85
- data/public/hatch.png +0 -0
- data/public/javascripts/jquery.js +0 -3549
- data/public/javascripts/vanilla.js +0 -21
- data/spec/dynasnip_spec.rb +0 -31
- data/spec/renderers/base_renderer_spec.rb +0 -40
- data/spec/renderers/erb_renderer_spec.rb +0 -27
- data/spec/renderers/markdown_renderer_spec.rb +0 -29
- data/spec/renderers/raw_renderer_spec.rb +0 -21
- data/spec/renderers/ruby_renderer_spec.rb +0 -42
- data/spec/renderers/vanilla_app_detecting_renderer_spec.rb +0 -35
- data/spec/spec_helper.rb +0 -64
- data/spec/vanilla_app_spec.rb +0 -38
- data/spec/vanilla_presenting_spec.rb +0 -84
- data/spec/vanilla_request_spec.rb +0 -73
- data/spec/vanilla_snip_finding_spec.rb +0 -28
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'vanilla/dynasnip'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
class Comments < Dynasnip
|
5
|
+
usage %|
|
6
|
+
Embed comments within snips!
|
7
|
+
|
8
|
+
{comments <false>}
|
9
|
+
|
10
|
+
This will embed a list of comments, and a comment form, in a snip
|
11
|
+
If the snip is being rendered within another snip, it will show a link to the snip,
|
12
|
+
with the number of comments. Add a parameter to disable new comments.
|
13
|
+
|
|
14
|
+
|
15
|
+
def get(disable_new_comments=false)
|
16
|
+
return usage if self.class.snip_name == app.request.snip_name
|
17
|
+
comments = app.soup.with(:commenting_on => enclosing_snip.name)
|
18
|
+
comments_html = if app.request.snip_name == enclosing_snip.name
|
19
|
+
rendered_comments = render_comments(comments)
|
20
|
+
rendered_comments += comment_form.gsub('SNIP_NAME', enclosing_snip.name) unless disable_new_comments
|
21
|
+
rendered_comments
|
22
|
+
else
|
23
|
+
%{<a href="#{url_to(enclosing_snip.name)}">#{comments.length} comments for #{enclosing_snip.name}</a>}
|
24
|
+
end
|
25
|
+
return comments_html
|
26
|
+
end
|
27
|
+
|
28
|
+
def post(*args)
|
29
|
+
snip_name = app.request.params[:snip]
|
30
|
+
existing_comments = app.soup.with(:commenting_on => snip_name)
|
31
|
+
comment = app.request.params.reject { |k,v| ![:author, :email, :website, :content].include?(k) }
|
32
|
+
|
33
|
+
return "You need to add some details!" if comment.empty?
|
34
|
+
return "No spam today, thanks anyway" unless app.request.params[:human] == 'human'
|
35
|
+
|
36
|
+
app.soup << comment.merge({
|
37
|
+
:name => "#{snip_name}-comment-#{existing_comments.length + 1}",
|
38
|
+
:commenting_on => snip_name,
|
39
|
+
:created_at => Time.now
|
40
|
+
})
|
41
|
+
"Thanks for your comment! Back to {link_to #{snip_name}}"
|
42
|
+
end
|
43
|
+
|
44
|
+
def render_comments(comments)
|
45
|
+
"<h2>Comments</h2><ol class='comments'>" + comments.map do |comment|
|
46
|
+
rendered_comment = comment_template.gsub('COMMENT_CONTENT', app.render(comment)).
|
47
|
+
gsub('COMMENT_DATE', comment.created_at.to_s)
|
48
|
+
author = comment.author
|
49
|
+
author = "Anonymous" unless author && author != ""
|
50
|
+
if comment.website && comment.website != ""
|
51
|
+
rendered_comment.gsub!('COMMENT_AUTHOR', "<a href=\"#{comment.website}\">#{author}</a>")
|
52
|
+
else
|
53
|
+
rendered_comment.gsub!('COMMENT_AUTHOR', author)
|
54
|
+
end
|
55
|
+
rendered_comment
|
56
|
+
end.join + "</ol>"
|
57
|
+
end
|
58
|
+
|
59
|
+
attribute :comment_template, %{
|
60
|
+
<li>
|
61
|
+
<p>COMMENT_AUTHOR (COMMENT_DATE)</p>
|
62
|
+
<div>COMMENT_CONTENT</div>
|
63
|
+
</li>
|
64
|
+
}
|
65
|
+
|
66
|
+
attribute :comment_form, %{
|
67
|
+
<form class="comments" action="/comments?snip=SNIP_NAME" method="POST">
|
68
|
+
<label>Name: <input type="text" name="author"></input></label>
|
69
|
+
<label>Email: <input type="text" name="email"></input></label>
|
70
|
+
<label>Website: <input type="text" name="website"></input></label>
|
71
|
+
<textarea name="content"></textarea>
|
72
|
+
<label class="human">Type 'human' if you are one: <input type="text" name="human"></input></label>
|
73
|
+
<button>Submit</button>
|
74
|
+
</form>
|
75
|
+
}
|
76
|
+
|
77
|
+
self
|
78
|
+
end
|
@@ -5,41 +5,43 @@ require 'date'
|
|
5
5
|
class Kind < Dynasnip
|
6
6
|
def handle(kind, limit=10, as=:html)
|
7
7
|
as = as.to_sym
|
8
|
-
snips =
|
9
|
-
entries = snips.sort_by { |s| s.created_at ||
|
8
|
+
snips = app.soup.with(:kind => kind)
|
9
|
+
entries = snips.sort_by { |s| s.created_at || Time.at(0) }.reverse[0...limit.to_i].map do |snip|
|
10
10
|
render_entry_in_template(snip, as, kind)
|
11
11
|
end
|
12
12
|
render_entry_collection(snips, entries, as, kind)
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
def render_entry_in_template(snip, as, kind)
|
16
|
-
rendered_contents = prepare_snip_contents(snip)
|
16
|
+
rendered_contents = externalise_links(prepare_snip_contents(snip))
|
17
17
|
case as
|
18
18
|
when :html
|
19
19
|
snip_template.
|
20
20
|
gsub('SNIP_KIND', kind).
|
21
21
|
gsub('SNIP_NAME', snip.name).
|
22
|
-
gsub('CREATED_AT', snip.created_at || '').
|
22
|
+
gsub('CREATED_AT', snip.created_at.to_s || '').
|
23
23
|
gsub('SNIP_CONTENT', rendered_contents)
|
24
24
|
when :xml
|
25
25
|
Atom::Entry.new do |e|
|
26
|
-
e.published =
|
27
|
-
e.updated =
|
26
|
+
e.published = snip.created_at
|
27
|
+
e.updated = snip.updated_at || snip.created_at
|
28
28
|
e.content = Atom::Content::Html.new(rendered_contents)
|
29
29
|
e.title = snip.name
|
30
30
|
e.authors = [Atom::Person.new(:name => snip.author || domain)]
|
31
|
-
e.links << Atom::Link.new(:href => "http://#{domain}#{
|
32
|
-
e.id = "tag:#{domain},#{(snip.created_at || Date.today.to_s).split[0]}:/#{snip.name}"
|
31
|
+
e.links << Atom::Link.new(:href => "http://#{domain}#{url_to(snip.name)}")
|
32
|
+
e.id = "tag:#{domain},#{(snip.created_at.to_s || Date.today.to_s).split[0]}:/#{snip.name}"
|
33
33
|
end
|
34
34
|
end
|
35
35
|
end
|
36
|
-
|
36
|
+
|
37
37
|
def prepare_snip_contents(snip)
|
38
|
-
|
39
|
-
|
40
|
-
|
38
|
+
app.render(snip)
|
39
|
+
end
|
40
|
+
|
41
|
+
def externalise_links(content)
|
42
|
+
content.gsub(/href="\//, "href=\"http://#{domain}/").gsub(/src="\//, "src=\"http://#{domain}/")
|
41
43
|
end
|
42
|
-
|
44
|
+
|
43
45
|
def render_entry_collection(snips, entries, as, kind)
|
44
46
|
case as
|
45
47
|
when :html
|
@@ -47,19 +49,19 @@ class Kind < Dynasnip
|
|
47
49
|
when :xml
|
48
50
|
Atom::Feed.new do |f|
|
49
51
|
f.title = feed_title
|
50
|
-
f.updated =
|
52
|
+
f.updated = snips[0].updated_at
|
51
53
|
f.id = "tag:#{domain},2008-06-01:kind/#{kind}"
|
52
54
|
f.entries = entries
|
53
55
|
end.to_xml
|
54
56
|
end
|
55
57
|
end
|
56
|
-
|
58
|
+
|
57
59
|
attribute :feed_title, "Your Blog"
|
58
60
|
attribute :domain, "yourdomain.com"
|
59
61
|
attribute :snip_template, %{
|
60
62
|
<div class="snip SNIP_KIND">
|
61
63
|
<div class="details">
|
62
|
-
|
64
|
+
{link_to_current_snip}
|
63
65
|
<p class="created_at">CREATED_AT</p>
|
64
66
|
</div>
|
65
67
|
<div class="content">
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class HelloWorld
|
2
|
+
# although the name doesn't need to match the snip name,
|
3
|
+
# it's simple to follow that convention where appropriate
|
4
|
+
|
5
|
+
def handle(name=nil)
|
6
|
+
if name
|
7
|
+
"Hey #{name} - Hello World!"
|
8
|
+
else
|
9
|
+
"Hello World!"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# note that this code must evaluate to a class. One way of achieving that is by
|
14
|
+
# putting 'self' at the end of the class definition.
|
15
|
+
self
|
16
|
+
end
|
17
|
+
# Another way is by referring to the class at the end of the content. Either works fine.
|
18
|
+
HelloWorld
|
19
|
+
|
20
|
+
:render_as: Ruby
|
@@ -0,0 +1,9 @@
|
|
1
|
+
A snip is the basic building block of information for {link_to vanilla-rb}. Essentially, it is a piece of content with arbitrary attributes. Vanilla anticipates the presence of some key attributes:
|
2
|
+
|
3
|
+
* `name` - the name of the snip, which is how it will be referred to. The `name` of this snip is _snip_.
|
4
|
+
* `content` - the default part of the snip to render. You can see the `content` of this snip <a href="/snip/content.raw">here</a>.
|
5
|
+
* `render_as` - the name of the renderer to use when rendering the content. The `render_as` of this snip is {snip.render_as}.
|
6
|
+
|
7
|
+
One implementation of the snip store is {link_to soup}.
|
8
|
+
|
9
|
+
:render_as: Markdown
|
@@ -0,0 +1,30 @@
|
|
1
|
+
Linking is good: {link_to bold}
|
2
|
+
Here's a bold snip: {bold}
|
3
|
+
|
4
|
+
- Here's a random number between 5 and 15: {rand 5,15}
|
5
|
+
- Here's a random number between 1 and 90 (the default min): {rand 90}
|
6
|
+
- Here's a random number between 1 and 100 (the default range): {rand}
|
7
|
+
|
8
|
+
And lets include some textile:
|
9
|
+
|
10
|
+
{textile_example}
|
11
|
+
|
12
|
+
The source for that was
|
13
|
+
|
14
|
+
{pre textile_example}
|
15
|
+
|
16
|
+
And lets include some markdown!:
|
17
|
+
|
18
|
+
{markdown_example}
|
19
|
+
|
20
|
+
The source for that was
|
21
|
+
|
22
|
+
{pre markdown_example}
|
23
|
+
|
24
|
+
How about some {link_to debug} information: {debug}
|
25
|
+
|
26
|
+
What about a missing snip? Lets try to include one: {monkey}
|
27
|
+
|
28
|
+
And an error when running? {bad_dynasnip}
|
29
|
+
|
30
|
+
:render_as: Markdown
|
@@ -0,0 +1 @@
|
|
1
|
+
this is another snip!
|
@@ -0,0 +1 @@
|
|
1
|
+
This is a snip, which includes another {link_to snip}: {tutorial-another-snip}
|
@@ -0,0 +1,56 @@
|
|
1
|
+
{tutorial-links}
|
2
|
+
|
3
|
+
Dynasnips
|
4
|
+
=========
|
5
|
+
|
6
|
+
As mentioned in the general {link_to tutorial}, dynamic content is built in vanilla using "dynasnips". These are snips who content depends on more than just their content.
|
7
|
+
|
8
|
+
Typically they are rendered by the `Vanilla::Renderers::Ruby` renderer. Lets look again at the raw content of `link_to`:
|
9
|
+
|
10
|
+
{raw link_to}
|
11
|
+
|
12
|
+
As you can see, it simply refers to the Ruby class `LinkTo`, which is contained within the vanilla-rb codebase. When the Ruby renderer is called, expects the given code to evaulate to a Ruby class. It then instantiates the class, and calls a `handle` method on the instance, passing it any other arguments from the snip inclusion.
|
13
|
+
|
14
|
+
You can pass arguments to dynasnips in a number of ways. All of the following are valid:
|
15
|
+
|
16
|
+
* {dynasnip apple}
|
17
|
+
* {dynasnip apple, banana}
|
18
|
+
* {dynasnip apple, big banana, cherry}
|
19
|
+
* {dynasnip apple, big banana, "lovely lucious cherry"}
|
20
|
+
* {dynasnip apple => true, banana => false}
|
21
|
+
* {dynasnip apple: true, banana: false}
|
22
|
+
|
23
|
+
Where a simple list of arguments is given, these will be passed to the `handle` method as an array. If a ruby-hash-like syntax is used, a hash of these options will be passed.
|
24
|
+
|
25
|
+
Of course, it depends entirely on the implementation of the dynasnip what arguments it expects and accepts; some may require a flat list, while others may require hash-like named arguments.
|
26
|
+
|
27
|
+
|
28
|
+
Writing your own Dynasnips
|
29
|
+
--------------------------
|
30
|
+
|
31
|
+
While dynasnip classes can be provided as part of the vanilla codebase, it's envisioned that much of these will be created by end users in their own sites, either by refering to local classes, or defining the classes directly as the content. Here's an example of that, as the raw content of `hello_world`:
|
32
|
+
|
33
|
+
{raw hello_world}
|
34
|
+
|
35
|
+
It's important that the contents of the snip evaluate to a Ruby class; this is easy to achieve by placing `self` as the last statement in the class definition, or referencing the class at the end of the snip; both are shown above.
|
36
|
+
|
37
|
+
If we include the dynasnip here as <tt>{hello\_world}</tt>, gives:
|
38
|
+
|
39
|
+
> {hello_world}
|
40
|
+
|
41
|
+
Note that the `handle` method can take one (optional) argument. Lets try including it with <tt>{hello\_world Dave}</tt>:
|
42
|
+
|
43
|
+
> {hello_world Dave}
|
44
|
+
|
45
|
+
|
46
|
+
HTTP Verbs
|
47
|
+
----------
|
48
|
+
|
49
|
+
By default, the Ruby renderer will attempt to call `handle` on a dynasnip instance, but if the instance responds to methods corresponding to the HTTP Verbs - `get`, `post`, `put`, or `delete` - then these methods will be called instead.
|
50
|
+
|
51
|
+
This means you can have a single dynasnip which responds differently when receiving a `POST` request - quite useful if you want to write dynasnips that generate and respond to forms, like the `comments` dynasnip in your `extras` soup directory.
|
52
|
+
|
53
|
+
There's really no limit to what you can do with dynasnips - only what you can imagine.
|
54
|
+
|
55
|
+
|
56
|
+
{tutorial-links}
|
@@ -0,0 +1,56 @@
|
|
1
|
+
{tutorial-links}
|
2
|
+
|
3
|
+
Layouts
|
4
|
+
=======
|
5
|
+
|
6
|
+
Since you almost certainly want your site to look good, one of the first things you'll want to change in your vanilla site is the layout.
|
7
|
+
|
8
|
+
When the browser requests a snip, normally vanilla will present it within a _layout_ template. This would typically include a header, a footer, and any other peripheral markup that shouldn't be within the content of the snip itself. If you're familiar with the construction of web applications, this will be exactly as you expect.
|
9
|
+
|
10
|
+
Layouts are just like any other snip - they can be sent through a renderer, and include other snips. The default layout snip is called, predictable, `layout.snip`, and here's the content:
|
11
|
+
|
12
|
+
{raw layout}
|
13
|
+
|
14
|
+
When you request `/start`, this is the snip that's actually rendered first. If this snip was just text, that's all that would be returned; however, there are some dynasnip calls in here which help us actually return the content that the user requested.
|
15
|
+
|
16
|
+
|
17
|
+
`current_snip`
|
18
|
+
--------------
|
19
|
+
|
20
|
+
The most significant is the call to `current_snip`. This figures out what snip was actually requested (e.g. if the url is `/start`, it's the {link_to start} snip), and renders it in place.
|
21
|
+
|
22
|
+
Here's the source of `current_snip`:
|
23
|
+
|
24
|
+
{raw current_snip}
|
25
|
+
|
26
|
+
The default case, as in our layout, is `app.render(app.request.snip, app.request.part)` - it delegates rendering back to the application, which then takes care of processing `start` using the right renderer and so on. This method call returns the fully rendered string, and vanilla replaces the call to the dynasnip with that output, placing our snip in the appropriate place in the layout.
|
27
|
+
|
28
|
+
|
29
|
+
Other dynas
|
30
|
+
-----------
|
31
|
+
|
32
|
+
Of course, you can put other plain content in your layout, and other dynasnips too. In the provided layout there are calls to two other dynasnips.
|
33
|
+
|
34
|
+
The first is `page_title`, which simply places a (hopefully) meaningful string in the title element of the page. Snips can set the title to be used by defining a `:page_title` attribute. As usual, the source explains it more clearly:
|
35
|
+
|
36
|
+
{raw page_title}
|
37
|
+
|
38
|
+
The second dynasnip used is `link_to_current_snip`, which returns an HTML link to the snip that's currently being rendered. I'll let you figure out how to view the source yourself.
|
39
|
+
|
40
|
+
|
41
|
+
Other layouts
|
42
|
+
-------------
|
43
|
+
|
44
|
+
Vanilla looks for a snip called `layout` by default, but this can be changed by passing in a `:default_layout` option to `Vanilla::App.new`, e.g.
|
45
|
+
|
46
|
+
Vanilla::App.new(:default_layout => "my_layout")
|
47
|
+
|
48
|
+
|
49
|
+
You can also override the layout on a per-snip basis, simply by setting the `:layout` attribute of the snip to the name of the layout snip to use instead.
|
50
|
+
|
51
|
+
Finally, if you implement a custom renderer class (see {link_to tutorial-renderers, "the renderers tutorial"}), you can also specify a layout to be used when the requested snip invokes that renderer. This can be useful if you have a particular kind of content that requires a different layout entirely.
|
52
|
+
|
53
|
+
{tutorial-links}
|
54
|
+
|
55
|
+
:render_as: Markdown
|
56
|
+
:page_title: Tutorial - Layout
|
@@ -0,0 +1,77 @@
|
|
1
|
+
{tutorial-links}
|
2
|
+
|
3
|
+
Renderers
|
4
|
+
=========
|
5
|
+
|
6
|
+
As well as the flexibility to combine pieces of content, another reason {link_to vanilla-rb} is more *interesting* than simpler wiki-ish software is that each piece of content can be processed by arbitrarily complex software before it is composed.
|
7
|
+
|
8
|
+
In its simplest form, this means that some content can be written in one format (say, raw HTML) whereas other content can be written in different formats (like Textile or Markdown for richer content). You could write one blog post in Textile, and the next one in Markdown, without any issues, depending on what best suits your purpose.
|
9
|
+
|
10
|
+
This is one of the principle drivers behind {link_to vanilla-rb}; you shouldn't have to make an upfront decision about how best to structure your all of your content.
|
11
|
+
|
12
|
+
This has been covered generally in the {link_to tutorial, main tutorial}, but here we'll go into a bit more detail.
|
13
|
+
|
14
|
+
Defining a renderer for a snip
|
15
|
+
-------------------
|
16
|
+
|
17
|
+
The renderer used for a snip is determined in the following manner
|
18
|
+
|
19
|
+
1. Using the snip `extension` attribute
|
20
|
+
2. Using the snip `render_as` attribute
|
21
|
+
3. Using the default renderer
|
22
|
+
|
23
|
+
The snip `extension` is an attribute generated by the {link_to soup} library, which roughly corresponds to the file extension of the snip itself. So, if your snip is stored in a file called `my-schnip.markdown`, the `extension` property will be `markdown`. If the filename is `my-schnip.snip.markdown`, the `extension` is still just `markdown`.
|
24
|
+
|
25
|
+
To determine the actual renderer from this attributes, a lookup hash is used, mapping these strings onto Ruby classes. The default is something like this:
|
26
|
+
|
27
|
+
{
|
28
|
+
"base" => Vanilla::Renderers::Base,
|
29
|
+
"markdown" => Vanilla::Renderers::Markdown,
|
30
|
+
"bold" => Vanilla::Renderers::Bold,
|
31
|
+
"erb" => Vanilla::Renderers::Erb,
|
32
|
+
"rb" => Vanilla::Renderers::Ruby,
|
33
|
+
"ruby" => Vanilla::Renderers::Ruby,
|
34
|
+
"haml" => Vanilla::Renderers::Haml,
|
35
|
+
"raw" => Vanilla::Renderers::Raw,
|
36
|
+
"textile" => Vanilla::Renderers::Textile
|
37
|
+
}
|
38
|
+
|
39
|
+
|
40
|
+
Adding renderers
|
41
|
+
----------------
|
42
|
+
|
43
|
+
New renderers can be added as part of the application configuration:
|
44
|
+
|
45
|
+
Vanilla::App.new(:renderers => {
|
46
|
+
"rdoc" => MyRenderers::RDoc
|
47
|
+
})
|
48
|
+
|
49
|
+
These will be added to the lookup, and can also used to override the defaults (changing the renderer for "markdown" snips to use RDiscount, or Redcarpet, for example).
|
50
|
+
|
51
|
+
|
52
|
+
Writing new renderers
|
53
|
+
---------------------
|
54
|
+
|
55
|
+
The simplest renderer inherits from `Vanilla::Renderers::Base`, and reimplement the `process_text` method:
|
56
|
+
|
57
|
+
module Vanilla::Renderers
|
58
|
+
class Bold < Base
|
59
|
+
def process_text(content)
|
60
|
+
"<b>#{content}</b>"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
It is passed a string (either the selected snip's "content" attribute, or some other explicitly requested attribute), and should return that content in its rendered form. The `Vanilla::Renderers::Markdown` renderer shows this more clearly:
|
66
|
+
|
67
|
+
module Vanilla::Renderers
|
68
|
+
class Markdown < Base
|
69
|
+
def process_text(content)
|
70
|
+
BlueCloth.new(content).to_html
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
There are a number of other methods which can be overridden by custom renderers, but these are beyond the scope of this tutorial; the best way to learn is to look at the set of provided renderers (such as the Erb and Haml ones) and work from there.
|
76
|
+
|
77
|
+
{tutorial-links}
|