vanilla 1.0.0

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