vanilla 1.0.2 → 1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) hide show
  1. data/Rakefile +112 -109
  2. data/bin/vanilla +35 -6
  3. data/lib/vanilla.rb +10 -14
  4. data/lib/vanilla/app.rb +109 -41
  5. data/lib/vanilla/console.rb +22 -2
  6. data/lib/vanilla/dynasnip.rb +4 -36
  7. data/lib/vanilla/renderers.rb +12 -0
  8. data/lib/vanilla/renderers/base.rb +58 -34
  9. data/lib/vanilla/renderers/bold.rb +0 -2
  10. data/lib/vanilla/renderers/erb.rb +1 -3
  11. data/lib/vanilla/renderers/haml.rb +13 -0
  12. data/lib/vanilla/renderers/markdown.rb +0 -2
  13. data/lib/vanilla/renderers/raw.rb +0 -2
  14. data/lib/vanilla/renderers/ruby.rb +12 -6
  15. data/lib/vanilla/renderers/textile.rb +0 -2
  16. data/lib/vanilla/request.rb +19 -17
  17. data/lib/vanilla/routes.rb +9 -20
  18. data/lib/vanilla/snip_reference_parser.rb +94 -0
  19. data/lib/vanilla/static.rb +28 -0
  20. data/pristine_app/Gemfile +3 -0
  21. data/pristine_app/Gemfile.lock +32 -0
  22. data/pristine_app/README +47 -0
  23. data/pristine_app/config.ru +26 -0
  24. data/pristine_app/public/vanilla.css +15 -0
  25. data/pristine_app/soups/base/layout.snip +18 -0
  26. data/pristine_app/soups/base/start.snip +19 -0
  27. data/pristine_app/soups/dynasnips/current_snip.rb +29 -0
  28. data/{lib/vanilla → pristine_app/soups}/dynasnips/debug.rb +5 -3
  29. data/pristine_app/soups/dynasnips/index.rb +12 -0
  30. data/{lib/vanilla → pristine_app/soups}/dynasnips/link_to.rb +4 -2
  31. data/pristine_app/soups/dynasnips/link_to_current_snip.rb +14 -0
  32. data/pristine_app/soups/dynasnips/page_title.rb +9 -0
  33. data/{lib/vanilla → pristine_app/soups}/dynasnips/pre.rb +7 -5
  34. data/{lib/vanilla → pristine_app/soups}/dynasnips/raw.rb +8 -5
  35. data/pristine_app/soups/extras/comments.rb +78 -0
  36. data/{lib/vanilla/dynasnips → pristine_app/soups/extras}/kind.rb +19 -17
  37. data/{lib/vanilla/dynasnips → pristine_app/soups/extras}/rand.rb +2 -0
  38. data/pristine_app/soups/extras/url_to.rb +7 -0
  39. data/pristine_app/soups/tutorial/bad_dynasnip.snip +8 -0
  40. data/pristine_app/soups/tutorial/hello_world.snip +20 -0
  41. data/pristine_app/soups/tutorial/markdown_example.snip +13 -0
  42. data/pristine_app/soups/tutorial/snip.snip +9 -0
  43. data/pristine_app/soups/tutorial/soup.snip +3 -0
  44. data/pristine_app/soups/tutorial/test.snip +30 -0
  45. data/pristine_app/soups/tutorial/textile_example.snip +11 -0
  46. data/pristine_app/soups/tutorial/tutorial-another-snip.snip +1 -0
  47. data/pristine_app/soups/tutorial/tutorial-basic-snip-inclusion.snip +1 -0
  48. data/pristine_app/soups/tutorial/tutorial-dynasnips.snip.markdown +56 -0
  49. data/pristine_app/soups/tutorial/tutorial-layout.snip +56 -0
  50. data/pristine_app/soups/tutorial/tutorial-links.snip +4 -0
  51. data/pristine_app/soups/tutorial/tutorial-renderers.snip.markdown +77 -0
  52. data/pristine_app/soups/tutorial/tutorial.snip.markdown +69 -0
  53. data/pristine_app/soups/tutorial/vanilla-rb.snip +16 -0
  54. data/pristine_app/soups/tutorial/vanilla.snip +8 -0
  55. data/test/dynasnip_test.rb +42 -0
  56. data/test/dynasnips/link_to_current_snip_test.rb +19 -0
  57. data/test/dynasnips/link_to_test.rb +27 -0
  58. data/test/dynasnips/page_title_test.rb +19 -0
  59. data/test/renderers/base_renderer_test.rb +43 -0
  60. data/test/renderers/erb_renderer_test.rb +29 -0
  61. data/test/renderers/haml_renderer_test.rb +35 -0
  62. data/test/renderers/markdown_renderer_test.rb +31 -0
  63. data/test/renderers/raw_renderer_test.rb +23 -0
  64. data/test/renderers/ruby_renderer_test.rb +59 -0
  65. data/test/snip_inclusion_test.rb +56 -0
  66. data/test/snip_reference_parser_test.rb +123 -0
  67. data/test/test_helper.rb +75 -0
  68. data/test/vanilla_app_test.rb +83 -0
  69. data/test/vanilla_presenting_test.rb +125 -0
  70. data/test/vanilla_request_test.rb +87 -0
  71. metadata +179 -78
  72. data/config.example.yml +0 -5
  73. data/config.ru +0 -9
  74. data/lib/defensio.rb +0 -59
  75. data/lib/tasks/vanilla.rake +0 -177
  76. data/lib/vanilla/dynasnips/comments.rb +0 -108
  77. data/lib/vanilla/dynasnips/current_snip.rb +0 -32
  78. data/lib/vanilla/dynasnips/edit.rb +0 -63
  79. data/lib/vanilla/dynasnips/edit_link.rb +0 -24
  80. data/lib/vanilla/dynasnips/index.rb +0 -11
  81. data/lib/vanilla/dynasnips/link_to_current_snip.rb +0 -16
  82. data/lib/vanilla/dynasnips/login.rb +0 -56
  83. data/lib/vanilla/dynasnips/new.rb +0 -14
  84. data/lib/vanilla/dynasnips/notes.rb +0 -42
  85. data/lib/vanilla/dynasnips/url_to.rb +0 -7
  86. data/lib/vanilla/snip_handling.rb +0 -33
  87. data/lib/vanilla/snips/start.rb +0 -27
  88. data/lib/vanilla/snips/system.rb +0 -76
  89. data/lib/vanilla/snips/tutorial.rb +0 -157
  90. data/lib/vanilla/test_snips.rb +0 -85
  91. data/public/hatch.png +0 -0
  92. data/public/javascripts/jquery.js +0 -3549
  93. data/public/javascripts/vanilla.js +0 -21
  94. data/spec/dynasnip_spec.rb +0 -31
  95. data/spec/renderers/base_renderer_spec.rb +0 -40
  96. data/spec/renderers/erb_renderer_spec.rb +0 -27
  97. data/spec/renderers/markdown_renderer_spec.rb +0 -29
  98. data/spec/renderers/raw_renderer_spec.rb +0 -21
  99. data/spec/renderers/ruby_renderer_spec.rb +0 -42
  100. data/spec/renderers/vanilla_app_detecting_renderer_spec.rb +0 -35
  101. data/spec/spec_helper.rb +0 -64
  102. data/spec/vanilla_app_spec.rb +0 -38
  103. data/spec/vanilla_presenting_spec.rb +0 -84
  104. data/spec/vanilla_request_spec.rb +0 -73
  105. 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 = Soup.sieve(:kind => kind)
9
- entries = snips.sort_by { |s| s.created_at || '' }.reverse[0...limit.to_i].map do |snip|
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 = DateTime.parse(snip.created_at)
27
- e.updated = DateTime.parse(snip.updated_at || snip.created_at)
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}#{Vanilla::Routes.url_to(snip.name)}")
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
- rendered_snip = app.render(snip)
39
- # make all the links absolute
40
- rendered_snip.gsub(/href="\//, "href=\"http://#{domain}/")
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 = DateTime.parse(snips[0].updated_at)
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
- #{Vanilla::Routes.link_to '#', 'SNIP_NAME'}
64
+ {link_to_current_snip}
63
65
  <p class="created_at">CREATED_AT</p>
64
66
  </div>
65
67
  <div class="content">
@@ -24,4 +24,6 @@ class RandomNumber < Dynasnip
24
24
  max = max.to_i
25
25
  (rand(max-min) + min)
26
26
  end
27
+
28
+ self
27
29
  end
@@ -0,0 +1,7 @@
1
+ require 'vanilla/dynasnip'
2
+
3
+ class UrlTo < Dynasnip
4
+ def handle(snip_name)
5
+ app.soup[snip_name] ? url_to(snip_name) : "[Snip '#{snip_name}' not found]"
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ class BadDynasnip
2
+ def get(*args)
3
+ raise "Oh no"
4
+ end
5
+ end
6
+ BadDynasnip
7
+
8
+ :render_as: Ruby
@@ -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,13 @@
1
+
2
+ # testing header
3
+
4
+ so, how are you?
5
+
6
+ - item one
7
+ - item two
8
+ - item three
9
+
10
+
11
+ what the *hell* are looking at, [beyotch](http://example.com)?
12
+
13
+ :render_as: Markdown
@@ -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,3 @@
1
+ Soup is a data store supporting the {link_to snip}-space that {link_to vanilla-rb} expects.
2
+
3
+ It's hosted on github <a href="http://github.com/lazyatom/soup">here</a>.
@@ -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,11 @@
1
+
2
+ # testing lists
3
+ # because lists are common things
4
+
5
+ monkey
6
+
7
+ what the *hell* are __you__ looking at?
8
+
9
+ "Beyotch":http://example.com
10
+
11
+ :render_as: Textile
@@ -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
+ * &#123;dynasnip apple&#125;
17
+ * &#123;dynasnip apple, banana&#125;
18
+ * &#123;dynasnip apple, big banana, cherry&#125;
19
+ * &#123;dynasnip apple, big banana, "lovely lucious cherry"&#125;
20
+ * &#123;dynasnip apple => true, banana => false&#125;
21
+ * &#123;dynasnip apple: true, banana: false&#125;
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>&#123;hello\_world&#125;</tt>, gives:
38
+
39
+ > {hello_world}
40
+
41
+ Note that the `handle` method can take one (optional) argument. Lets try including it with <tt>&#123;hello\_world Dave&#125;</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,4 @@
1
+ {link_to tutorial}
2
+ {link_to tutorial-layout}
3
+ {link_to tutorial-renderers}
4
+ {link_to tutorial-dynasnips}
@@ -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}