vanilla 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/README +52 -0
  2. data/Rakefile +118 -0
  3. data/bin/vanilla +9 -0
  4. data/config.example.yml +5 -0
  5. data/config.ru +9 -0
  6. data/lib/defensio.rb +59 -0
  7. data/lib/tasks/vanilla.rake +178 -0
  8. data/lib/vanilla/app.rb +87 -0
  9. data/lib/vanilla/console.rb +3 -0
  10. data/lib/vanilla/dynasnip.rb +110 -0
  11. data/lib/vanilla/dynasnips/comments.rb +108 -0
  12. data/lib/vanilla/dynasnips/current_snip.rb +32 -0
  13. data/lib/vanilla/dynasnips/debug.rb +13 -0
  14. data/lib/vanilla/dynasnips/edit.rb +63 -0
  15. data/lib/vanilla/dynasnips/edit_link.rb +24 -0
  16. data/lib/vanilla/dynasnips/index.rb +11 -0
  17. data/lib/vanilla/dynasnips/kind.rb +70 -0
  18. data/lib/vanilla/dynasnips/link_to.rb +14 -0
  19. data/lib/vanilla/dynasnips/link_to_current_snip.rb +16 -0
  20. data/lib/vanilla/dynasnips/login.rb +56 -0
  21. data/lib/vanilla/dynasnips/new.rb +14 -0
  22. data/lib/vanilla/dynasnips/notes.rb +42 -0
  23. data/lib/vanilla/dynasnips/pre.rb +19 -0
  24. data/lib/vanilla/dynasnips/rand.rb +27 -0
  25. data/lib/vanilla/dynasnips/raw.rb +19 -0
  26. data/lib/vanilla/dynasnips/url_to.rb +7 -0
  27. data/lib/vanilla/renderers/base.rb +78 -0
  28. data/lib/vanilla/renderers/bold.rb +9 -0
  29. data/lib/vanilla/renderers/erb.rb +16 -0
  30. data/lib/vanilla/renderers/markdown.rb +13 -0
  31. data/lib/vanilla/renderers/raw.rb +9 -0
  32. data/lib/vanilla/renderers/ruby.rb +35 -0
  33. data/lib/vanilla/renderers/textile.rb +13 -0
  34. data/lib/vanilla/request.rb +68 -0
  35. data/lib/vanilla/routes.rb +29 -0
  36. data/lib/vanilla/snip_handling.rb +33 -0
  37. data/lib/vanilla/snips/start.rb +18 -0
  38. data/lib/vanilla/snips/system.rb +76 -0
  39. data/lib/vanilla/snips/tutorial.rb +158 -0
  40. data/lib/vanilla/test_snips.rb +85 -0
  41. data/lib/vanilla.rb +20 -0
  42. data/spec/dynasnip_spec.rb +31 -0
  43. data/spec/renderers/base_renderer_spec.rb +40 -0
  44. data/spec/renderers/erb_renderer_spec.rb +27 -0
  45. data/spec/renderers/markdown_renderer_spec.rb +29 -0
  46. data/spec/renderers/raw_renderer_spec.rb +21 -0
  47. data/spec/renderers/ruby_renderer_spec.rb +42 -0
  48. data/spec/renderers/vanilla_app_detecting_renderer_spec.rb +35 -0
  49. data/spec/soup_test.db +0 -0
  50. data/spec/spec_helper.rb +64 -0
  51. data/spec/vanilla_app_spec.rb +38 -0
  52. data/spec/vanilla_presenting_spec.rb +84 -0
  53. data/spec/vanilla_request_spec.rb +73 -0
  54. data/spec/vanilla_snip_finding_spec.rb +28 -0
  55. metadata +122 -0
@@ -0,0 +1,108 @@
1
+ require 'vanilla/dynasnip'
2
+ require 'defensio'
3
+ require 'date'
4
+
5
+ class Comments < Dynasnip
6
+ usage %|
7
+ Embed comments within snips!
8
+
9
+ {comments <snip-name>}
10
+
11
+ This will embed a list of comments, and a comment form, in a snip
12
+ If the snip is being rendered within another snip, it will show a link to the snip,
13
+ with the number of comments.
14
+ |
15
+
16
+ def get(snip_name=nil, disable_new_comments=false)
17
+ snip_name = snip_name || app.request.params[:snip]
18
+ return usage if self.class.snip_name == snip_name
19
+ comments = Soup.sieve(:commenting_on => snip_name)
20
+ comments_html = if app.request.snip_name == snip_name
21
+ rendered_comments = render_comments(comments)
22
+ rendered_comments += comment_form.gsub('SNIP_NAME', snip_name) unless disable_new_comments
23
+ rendered_comments
24
+ else
25
+ %{<a href="#{Vanilla::Routes.url_to(snip_name)}">#{comments.length} comments for #{snip_name}</a>}
26
+ end
27
+ return comments_html
28
+ end
29
+
30
+ def post(*args)
31
+ snip_name = app.request.params[:snip]
32
+ existing_comments = Soup.sieve(:commenting_on => snip_name)
33
+ comment = app.request.params.reject { |k,v| ![:author, :email, :website, :content].include?(k) }
34
+
35
+ return "You need to add some details!" if comment.empty?
36
+
37
+ comment = check_for_spam(comment)
38
+
39
+ if comment[:spam]
40
+ "Sorry - your comment looks like spam, according to Defensio :("
41
+ else
42
+ return "No spam today, thanks anyway" unless app.request.params[:human] == 'human'
43
+ Soup << comment.merge({
44
+ :name => "#{snip_name}-comment-#{existing_comments.length + 1}",
45
+ :commenting_on => snip_name,
46
+ :created_at => Time.now
47
+ })
48
+ "Thanks for your comment! Back to {link_to #{snip_name}}"
49
+ end
50
+ end
51
+
52
+ def render_comments(comments)
53
+ "<h2>Comments</h2><ol class='comments'>" + comments.map do |comment|
54
+ rendered_comment = comment_template.gsub('COMMENT_CONTENT', app.render(comment)).
55
+ gsub('COMMENT_DATE', comment.created_at)
56
+ author = comment.author
57
+ author = "Anonymous" unless author && author != ""
58
+ if comment.website && comment.website != ""
59
+ rendered_comment.gsub!('COMMENT_AUTHOR', "<a href=\"#{comment.website}\">#{author}</a>")
60
+ else
61
+ rendered_comment.gsub!('COMMENT_AUTHOR', author)
62
+ end
63
+ rendered_comment
64
+ end.join + "</ol>"
65
+ end
66
+
67
+ def check_for_spam(comment)
68
+ snip_date = Date.parse(Soup[app.request.params[:snip]].updated_at)
69
+ Defensio.configure(app.config[:defensio])
70
+ defensio_params = {
71
+ :comment_author_email => comment[:email],
72
+ :comment_author => comment[:author],
73
+ :comment_author_url => comment[:website],
74
+ :comment_content => comment[:content],
75
+ :comment_type => "comment",
76
+ :user_ip => app.request.ip,
77
+ :article_date => snip_date.strftime("%Y/%m/%d")
78
+ }
79
+ audit = Defensio.audit_comment(defensio_params)
80
+
81
+ # Augment the comment hash
82
+ comment[:user_ip] = app.request.ip
83
+ comment[:spamminess] = audit["defensio_result"]["spaminess"]
84
+ comment[:spam] = audit["defensio_result"]["spam"]
85
+ comment[:defensio_signature] = audit["defensio_result"]["signature"]
86
+ comment[:defensio_message] = audit["defensio_result"]["message"] if audit["defensio_result"]["message"]
87
+ comment[:defensio_status] = audit["defensio_result"]["status"]
88
+ comment
89
+ end
90
+
91
+ attribute :comment_template, %{
92
+ <li>
93
+ <p>COMMENT_AUTHOR (COMMENT_DATE)</p>
94
+ <div>COMMENT_CONTENT</div>
95
+ </li>
96
+ }
97
+
98
+ attribute :comment_form, %{
99
+ <form class="comments" action="/#{snip_name}?snip=SNIP_NAME" method="POST">
100
+ <label>Name: <input type="text" name="author"></input></label>
101
+ <label>Email: <input type="text" name="email"></input></label>
102
+ <label>Website: <input type="text" name="website"></input></label>
103
+ <textarea name="content"></textarea>
104
+ <label class="human">Type 'human' if you are one: <input type="text" name="human"></input></label>
105
+ <button>Submit</button>
106
+ </form>
107
+ }
108
+ end
@@ -0,0 +1,32 @@
1
+ require 'vanilla/dynasnip'
2
+
3
+ class CurrentSnip < Dynasnip
4
+ usage %|
5
+ The current_snip dyna normally returns the result of rendering the snip named by the
6
+ 'snip' value in the parameters. This way, it can be used in templates to place the currently
7
+ requested snip, in its rendered form, within the page.
8
+
9
+ It can also be used to determine the name of the current snip in a consistent way:
10
+
11
+ {current_snip name}
12
+
13
+ will output the name of the current snip, or the name of the snip currently being edited.
14
+ |
15
+
16
+ def handle(*args)
17
+ if args[0] == 'name'
18
+ if app.request.snip_name == 'edit' # we're editing so don't use this name
19
+ app.request.params[:snip_to_edit]
20
+ else
21
+ app.request.snip_name
22
+ end
23
+ else
24
+ if app.request.snip
25
+ app.render(app.request.snip, app.request.part)
26
+ else
27
+ app.response.status = 404
28
+ "Couldn't find snip {link_to #{app.request.snip_name}}"
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,13 @@
1
+ require 'vanilla/dynasnip'
2
+
3
+ # If the dynasnip is a subclass of Dynasnip, it has access to the request hash
4
+ # (or whatever - access to some object outside of the snip itself.)
5
+ class Debug < Dynasnip
6
+ def get(*args)
7
+ app.request.inspect
8
+ end
9
+
10
+ def post(*args)
11
+ "You posted! " + app.request.inspect
12
+ end
13
+ end
@@ -0,0 +1,63 @@
1
+ require 'vanilla/dynasnip'
2
+ require 'vanilla/dynasnips/login'
3
+
4
+ # The edit dyna will load the snip given in the 'snip_to_edit' part of the
5
+ # params
6
+ class EditSnip < Dynasnip
7
+ include Login::Helper
8
+
9
+ snip_name "edit"
10
+
11
+ def get(snip_name=nil)
12
+ return login_required unless logged_in?
13
+ snip = Vanilla.snip(snip_name || app.request.params[:name])
14
+ edit(snip)
15
+ end
16
+
17
+ def post(*args)
18
+ return login_required unless logged_in?
19
+ snip_attributes = cleaned_params
20
+ snip_attributes.delete(:save_button)
21
+ return 'no params' if snip_attributes.empty?
22
+ snip = Vanilla.snip(snip_attributes[:name])
23
+ snip_attributes[:updated_at] = Time.now
24
+ snip_attributes.each do |name, value|
25
+ snip.__send__(:set_value, name, value)
26
+ end
27
+ snip.save
28
+ %{Saved snip #{Vanilla::Routes.link_to snip_attributes[:name]} ok}
29
+ rescue Exception => e
30
+ snip_attributes[:created_at] ||= Time.now
31
+ Soup << snip_attributes
32
+ %{Created snip #{Vanilla::Routes.link_to snip_attributes[:name]} ok}
33
+ end
34
+
35
+ def edit(snip)
36
+ renderer = Vanilla::Renderers::Erb.new(app)
37
+ renderer.instance_eval { @snip_to_edit = snip } # hacky!
38
+ snip_in_edit_template = renderer.render_without_including_snips(Vanilla.snip('edit'), :template)
39
+ prevent_snip_inclusion(snip_in_edit_template)
40
+ end
41
+
42
+ private
43
+
44
+ def prevent_snip_inclusion(content)
45
+ content.gsub("{", "&#123;").gsub("}" ,"&#125;")
46
+ end
47
+
48
+ attribute :template, %{
49
+ <form action="<%= Vanilla::Routes.url_to 'edit' %>" method="post">
50
+ <dl class="attributes">
51
+ <% @snip_to_edit.attributes.each do |name, value| %>
52
+ <dt><%= name %></dt>
53
+ <% num_rows = (value || "").split("\n").length + 1 %>
54
+ <dd><textarea name="<%= name %>" class="<%= name %>" rows="<%= num_rows %>"><%=h value %></textarea></dd>
55
+ <% end %>
56
+ <dt><input class="attribute_name" type="text"></input></dt>
57
+ <dd><textarea></textarea></dd>
58
+ </dl>
59
+ <a href="#" id="add">Add</a>
60
+ <button name='save_button'>Save</button>
61
+ </form>
62
+ }
63
+ end
@@ -0,0 +1,24 @@
1
+ require 'vanilla/dynasnip'
2
+
3
+ class EditLink < Dynasnip
4
+ usage %|
5
+ You can use the edit_link snip to insert links for editing other snips. For example:
6
+
7
+ &#123;edit_link blah&#125;
8
+
9
+ would insert a link to an editor for the blah snip.
10
+
11
+ You can also give a custom piece of text for the link, like this:
12
+
13
+ &#123;edit_link blah,link-name&#125;|
14
+
15
+ def handle(*args)
16
+ if args.length < 2
17
+ show_usage
18
+ else
19
+ snip_name = args[0]
20
+ link_text = args[1]
21
+ Vanilla::Routes.edit_link(snip_name, link_text)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,11 @@
1
+ require 'vanilla/dynasnip'
2
+
3
+ class Index < Dynasnip
4
+ def get(*args)
5
+ # TODO: figure out a way around calling Soup/AR methods directly.
6
+ list = Soup.tuple_class.find_all_by_name('name').map { |tuple|
7
+ "<li>#{Vanilla::Routes.link_to tuple.value}</li>"
8
+ }
9
+ "<ol>#{list}</ol>"
10
+ end
11
+ end
@@ -0,0 +1,70 @@
1
+ require 'vanilla/dynasnip'
2
+ require 'atom'
3
+ require 'date'
4
+
5
+ class Kind < Dynasnip
6
+ def handle(kind, limit=10, as=:html)
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|
10
+ render_entry_in_template(snip, as, kind)
11
+ end
12
+ render_entry_collection(snips, entries, as, kind)
13
+ end
14
+
15
+ def render_entry_in_template(snip, as, kind)
16
+ rendered_contents = prepare_snip_contents(snip)
17
+ case as
18
+ when :html
19
+ snip_template.
20
+ gsub('SNIP_KIND', kind).
21
+ gsub('SNIP_NAME', snip.name).
22
+ gsub('CREATED_AT', snip.created_at || '').
23
+ gsub('SNIP_CONTENT', rendered_contents)
24
+ when :xml
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)
28
+ e.content = Atom::Content::Html.new(rendered_contents)
29
+ e.title = snip.name
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}"
33
+ end
34
+ end
35
+ end
36
+
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}/")
41
+ end
42
+
43
+ def render_entry_collection(snips, entries, as, kind)
44
+ case as
45
+ when :html
46
+ entries.join
47
+ when :xml
48
+ Atom::Feed.new do |f|
49
+ f.title = feed_title
50
+ f.updated = DateTime.parse(snips[0].updated_at)
51
+ f.id = "tag:#{domain},2008-06-01:kind/#{kind}"
52
+ f.entries = entries
53
+ end.to_xml
54
+ end
55
+ end
56
+
57
+ attribute :feed_title, "Your Blog"
58
+ attribute :domain, "yourdomain.com"
59
+ attribute :snip_template, %{
60
+ <div class="snip SNIP_KIND">
61
+ <div class="details">
62
+ #{Vanilla::Routes.link_to '#', 'SNIP_NAME'}
63
+ <p class="created_at">CREATED_AT</p>
64
+ </div>
65
+ <div class="content">
66
+ SNIP_CONTENT
67
+ </div>
68
+ </div>
69
+ }
70
+ end
@@ -0,0 +1,14 @@
1
+ require 'vanilla/dynasnip'
2
+
3
+ class LinkTo < Dynasnip
4
+ usage %|
5
+ The link_to dyna lets you create links between snips:
6
+
7
+ {link_to blah}
8
+
9
+ would insert a link to the blah snip.|
10
+
11
+ def handle(snip_name)
12
+ Vanilla.snip_exists?(snip_name) ? Vanilla::Routes.link_to(snip_name) : Vanilla::Routes.new_link(snip_name)
13
+ end
14
+ end
@@ -0,0 +1,16 @@
1
+ require 'vanilla/dynasnip'
2
+
3
+ class LinkToCurrentSnip < Dynasnip
4
+ usage %|
5
+ Renders a link to the current snip, or the snip currently being edited
6
+ (if we're currently editing)
7
+ |
8
+
9
+ def handle(*args)
10
+ if app.request.snip_name == 'edit' # we're editing so don't use this name
11
+ Vanilla::Routes.link_to app.request.params[:snip_to_edit]
12
+ else
13
+ Vanilla::Routes.link_to app.request.snip_name
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,56 @@
1
+ require 'vanilla/dynasnip'
2
+ require 'yaml'
3
+ require 'md5'
4
+
5
+ class Login < Dynasnip
6
+ module Helper
7
+ def logged_in?
8
+ !current_user.nil?
9
+ end
10
+
11
+ def current_user
12
+ app.request.session['logged_in_as']
13
+ end
14
+
15
+ def login_required
16
+ "You need to <a href='/login'>login</a> to do that."
17
+ end
18
+ end
19
+ include Helper
20
+
21
+ def get(*args)
22
+ if logged_in?
23
+ login_controls
24
+ else
25
+ render(self, 'template')
26
+ end
27
+ end
28
+
29
+ def post(*args)
30
+ if app.config[:credentials][cleaned_params[:name]] == MD5.md5(cleaned_params[:password]).to_s
31
+ app.request.session['logged_in_as'] = cleaned_params[:name]
32
+ login_controls
33
+ else
34
+ "login fail!"
35
+ end
36
+ end
37
+
38
+ def delete(*args)
39
+ app.request.session['logged_in_as'] = nil
40
+ "Logged out"
41
+ end
42
+
43
+ attribute :template, <<-EHTML
44
+ <form action='/login' method='post'>
45
+ <label>Name: <input type="text" name="name"></input></label>
46
+ <label>Password: <input type="password" name="password"></input></label>
47
+ <button>login</button>
48
+ </form>
49
+ EHTML
50
+
51
+ private
52
+
53
+ def login_controls
54
+ "logged in as {link_to #{app.request.session['logged_in_as']}}; <a href='/login?_method=delete'>logout</a>"
55
+ end
56
+ end
@@ -0,0 +1,14 @@
1
+ require 'vanilla/dynasnip'
2
+ require 'vanilla/dynasnips/login'
3
+
4
+ class NewSnip < Dynasnip
5
+ include Login::Helper
6
+
7
+ snip_name :new
8
+
9
+ def handle(*arg)
10
+ return login_required unless logged_in?
11
+ base_params = {:render_as => '', :content => '', :author => current_user}.update(app.request.params)
12
+ editor = EditSnip.new(app).edit(Snip.new(base_params))
13
+ end
14
+ end
@@ -0,0 +1,42 @@
1
+ require 'vanilla/dynasnip'
2
+
3
+ class Notes < Dynasnip
4
+ def get(*args)
5
+ all_notes_content = all_notes.map { |snip|
6
+ render_note(snip)
7
+ }.join("")
8
+ snip.main_template.gsub('[notes]', all_notes_content)
9
+ end
10
+
11
+ def post(*args)
12
+ new_note = Snip.new(cleaned_params)
13
+ new_note.name = "note_#{snip.next_note_id}"
14
+ new_note.kind = "note"
15
+ new_note.save
16
+ increment_next_id
17
+ get(*args)
18
+ end
19
+
20
+ private
21
+
22
+ def all_notes
23
+ Snip.with(:kind, "= 'note'")
24
+ end
25
+
26
+ def increment_next_id
27
+ s = snip
28
+ s.next_note_id = s.next_note_id.to_i + 1
29
+ s.save
30
+ end
31
+
32
+ def render_note(note)
33
+ note_link = Vanilla::Routes.link_to(note.name)
34
+ note_content = Vanilla.render(note.name, nil, context, [])
35
+ snip.note_template.gsub('[note]', note_content).gsub('[link]', note_link)
36
+ end
37
+
38
+ attribute :next_note_id, 1
39
+
40
+ attribute :note_template, %{<dt>[link]</dt><dd>[note]</dd>}
41
+ attribute :main_template, %{<dl>[notes]</dl>}
42
+ end
@@ -0,0 +1,19 @@
1
+ require 'vanilla/dynasnip'
2
+
3
+ class ShowContentInPreTag < Dynasnip
4
+ snip_name "pre"
5
+
6
+ usage %|
7
+ Wraps the contents of the given snip in &lt;pre&gt; tags, e.g.
8
+
9
+ {pre my_snip}
10
+
11
+ You can specify a part to render in pre tags, should you wish:
12
+
13
+ {pre my_snip,specific_part}
14
+ |
15
+
16
+ def handle(snip_name, part=:content)
17
+ %{<pre>#{Vanilla.snip(snip_name).__send__(part || :content)}</pre>}
18
+ end
19
+ end
@@ -0,0 +1,27 @@
1
+ require 'vanilla/dynasnip'
2
+
3
+ class RandomNumber < Dynasnip
4
+ snip_name "rand"
5
+
6
+ usage %|
7
+ Returns a random number, normally between 1 and 100.
8
+ The range can be limited:
9
+
10
+ {rand 50} renders a random number between 1 and 50
11
+ {rand 2,19} renders a random number between 2 and 19
12
+ |
13
+
14
+ def handle(*args)
15
+ min = 1
16
+ max = 100
17
+ max = args[0] if args.length == 1
18
+ if args.length == 2
19
+ min = args[0]
20
+ max = args[1]
21
+ end
22
+ # arguments come in as strings, so we need to convert them.
23
+ min = min.to_i
24
+ max = max.to_i
25
+ (rand(max-min) + min)
26
+ end
27
+ end
@@ -0,0 +1,19 @@
1
+ require 'vanilla/dynasnip'
2
+
3
+ class ShowRawContent < Dynasnip
4
+ snip_name "raw"
5
+
6
+ usage %|
7
+ Displays the raw contents of a snip in &lt;pre&gt; tags, e.g.
8
+
9
+ {raw my_snip}
10
+
11
+ You can specify a part to show, should you wish:
12
+
13
+ {raw my_snip,specific_part}
14
+ |
15
+
16
+ def handle(snip_name, part=:content)
17
+ %{<pre>#{Dynasnip.escape_curly_braces(Vanilla.snip(snip_name).__send__(part || :content))}</pre>}
18
+ end
19
+ end
@@ -0,0 +1,7 @@
1
+ require 'vanilla/dynasnip'
2
+
3
+ class UrlTo < Dynasnip
4
+ def handle(snip_name)
5
+ Snip[snip_name] ? Vanilla::Routes.url_to(snip_name) : "[Snip '#{snip_name}' not found]"
6
+ end
7
+ end
@@ -0,0 +1,78 @@
1
+ require 'vanilla/app'
2
+
3
+ module Vanilla
4
+ module Renderers
5
+ class Base
6
+
7
+ # Render a snip.
8
+ def self.render(snip, part=:content)
9
+ new(app).render(snip, part)
10
+ end
11
+
12
+ def self.escape_curly_braces(str)
13
+ str.gsub("{", "&#123;").gsub("}", "&#125;")
14
+ end
15
+
16
+ attr_reader :app
17
+
18
+ def initialize(app)
19
+ @app = app
20
+ end
21
+
22
+ def self.snip_regexp
23
+ %r{ \{
24
+ ([\w\-]+) (?: \.([\w\-]+) )?
25
+ (?: \s+ ([\w\-,]+) )?
26
+ \} }x
27
+ end
28
+
29
+ # Default behaviour to include a snip's content
30
+ def include_snips(content)
31
+ content.gsub(Vanilla::Renderers::Base.snip_regexp) do
32
+ snip_name = $1
33
+ snip_attribute = $2
34
+ snip_args = $3 ? $3.split(',') : []
35
+
36
+ # Render the snip or snip part with the given args, and the current
37
+ # context, but with the default renderer for that snip. We dispatch
38
+ # *back* out to the root Vanilla.render method to do this.
39
+ snip = Vanilla.snip(snip_name)
40
+ if snip
41
+ app.render(snip, snip_attribute, snip_args)
42
+ else
43
+ app.render_missing_snip(snip_name)
44
+ end
45
+ end
46
+ end
47
+
48
+ # Default rendering behaviour. Subclasses shouldn't really need to touch this.
49
+ def render(snip, part=:content, args=[])
50
+ prepare(snip, part, args)
51
+ processed_text = render_without_including_snips(snip, part)
52
+ include_snips(processed_text)
53
+ end
54
+
55
+ # Subclasses should override this to perform any actions required before
56
+ # rendering
57
+ def prepare(snip, part, args)
58
+ # do nothing, by default
59
+ end
60
+
61
+ def render_without_including_snips(snip, part=:content)
62
+ process_text(raw_content(snip, part))
63
+ end
64
+
65
+ # Handles processing the text of the content.
66
+ # Subclasses should override this method to do fancy text processing
67
+ # like markdown, or loading the content as Ruby code.
68
+ def process_text(content)
69
+ content
70
+ end
71
+
72
+ # Returns the raw content for the selected part of the selected snip
73
+ def raw_content(snip, part)
74
+ snip.__send__((part || :content).to_sym)
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,9 @@
1
+ require 'vanilla/renderers/base'
2
+
3
+ module Vanilla::Renderers
4
+ class Bold < Base
5
+ def process_text(content)
6
+ "<b>#{content}</b>"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,16 @@
1
+ require 'vanilla/renderers/base'
2
+
3
+ require 'erb'
4
+ include ERB::Util
5
+
6
+ module Vanilla::Renderers
7
+ class Erb < Base
8
+ def prepare(snip, part=:content, args=[])
9
+ @snip = snip
10
+ end
11
+
12
+ def process_text(content)
13
+ ERB.new(content).result(binding)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ require 'vanilla/renderers/base'
2
+
3
+ require 'rubygems'
4
+ gem 'BlueCloth' # from http://www.deveiate.org/projects/BlueCloth
5
+ require 'bluecloth'
6
+
7
+ module Vanilla::Renderers
8
+ class Markdown < Base
9
+ def process_text(content)
10
+ BlueCloth.new(content).to_html
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ require 'vanilla/renderers/base'
2
+
3
+ module Vanilla::Renderers
4
+ class Raw < Base
5
+ def render(snip, part=:content)
6
+ raw_content(snip, part)
7
+ end
8
+ end
9
+ end