vanilla 1.2 → 1.9.9

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 (106) hide show
  1. data/Rakefile +61 -60
  2. data/bin/vanilla +6 -35
  3. data/config.example.yml +6 -0
  4. data/config.ru +10 -0
  5. data/lib/defensio.rb +59 -0
  6. data/lib/tasks/vanilla.rake +173 -0
  7. data/lib/vanilla.rb +3 -10
  8. data/lib/vanilla/app.rb +48 -104
  9. data/lib/vanilla/console.rb +5 -19
  10. data/lib/vanilla/dynasnips/comments.rb +108 -0
  11. data/lib/vanilla/dynasnips/current_snip.rb +32 -0
  12. data/{pristine_app/soups → lib/vanilla}/dynasnips/debug.rb +3 -5
  13. data/lib/vanilla/dynasnips/edit.rb +60 -0
  14. data/lib/vanilla/dynasnips/edit_link.rb +20 -0
  15. data/{pristine_app/soups → lib/vanilla}/dynasnips/index.rb +2 -4
  16. data/{pristine_app/soups/extras → lib/vanilla/dynasnips}/kind.rb +12 -14
  17. data/{pristine_app/soups → lib/vanilla}/dynasnips/link_to.rb +0 -2
  18. data/lib/vanilla/dynasnips/link_to_current_snip.rb +16 -0
  19. data/lib/vanilla/dynasnips/login.rb +56 -0
  20. data/lib/vanilla/dynasnips/new.rb +14 -0
  21. data/lib/vanilla/dynasnips/notes.rb +42 -0
  22. data/{pristine_app/soups → lib/vanilla}/dynasnips/pre.rb +4 -6
  23. data/{pristine_app/soups/extras → lib/vanilla/dynasnips}/rand.rb +0 -2
  24. data/{pristine_app/soups → lib/vanilla}/dynasnips/raw.rb +5 -8
  25. data/{pristine_app/soups/extras → lib/vanilla/dynasnips}/url_to.rb +0 -0
  26. data/lib/vanilla/renderers/base.rb +22 -32
  27. data/lib/vanilla/renderers/bold.rb +2 -0
  28. data/lib/vanilla/renderers/erb.rb +2 -0
  29. data/lib/vanilla/renderers/markdown.rb +2 -0
  30. data/lib/vanilla/renderers/raw.rb +2 -0
  31. data/lib/vanilla/renderers/ruby.rb +5 -9
  32. data/lib/vanilla/renderers/textile.rb +2 -0
  33. data/lib/vanilla/request.rb +15 -16
  34. data/lib/vanilla/routes.rb +18 -5
  35. data/lib/vanilla/snip_reference.rb +534 -0
  36. data/lib/vanilla/snip_reference.treetop +48 -0
  37. data/lib/vanilla/snip_reference_parser.rb +99 -82
  38. data/lib/vanilla/snips/start.rb +28 -0
  39. data/lib/vanilla/snips/system.rb +77 -0
  40. data/lib/vanilla/snips/tutorial.rb +244 -0
  41. data/lib/vanilla/soup_with_timestamps.rb +21 -0
  42. data/public/hatch.png +0 -0
  43. data/public/javascripts/jquery.autogrow-textarea.js +54 -0
  44. data/public/javascripts/jquery.js +4376 -0
  45. data/public/javascripts/vanilla.js +22 -0
  46. data/spec/dynasnip_spec.rb +28 -0
  47. data/spec/renderers/base_renderer_spec.rb +40 -0
  48. data/spec/renderers/erb_renderer_spec.rb +27 -0
  49. data/spec/renderers/markdown_renderer_spec.rb +29 -0
  50. data/spec/renderers/raw_renderer_spec.rb +21 -0
  51. data/spec/renderers/ruby_renderer_spec.rb +59 -0
  52. data/spec/renderers/vanilla_app_detecting_renderer_spec.rb +35 -0
  53. data/spec/spec_helper.rb +70 -0
  54. data/spec/tmp/config.yml +2 -0
  55. data/spec/tmp/soup/current_snip.yml +15 -0
  56. data/spec/tmp/soup/system.yml +5 -0
  57. data/spec/vanilla_app_spec.rb +38 -0
  58. data/spec/vanilla_presenting_spec.rb +84 -0
  59. data/spec/vanilla_request_spec.rb +73 -0
  60. metadata +79 -170
  61. data/lib/vanilla/renderers.rb +0 -12
  62. data/lib/vanilla/renderers/haml.rb +0 -13
  63. data/lib/vanilla/static.rb +0 -28
  64. data/pristine_app/Gemfile +0 -3
  65. data/pristine_app/Gemfile.lock +0 -32
  66. data/pristine_app/README +0 -47
  67. data/pristine_app/config.ru +0 -26
  68. data/pristine_app/public/vanilla.css +0 -15
  69. data/pristine_app/soups/base/layout.snip +0 -18
  70. data/pristine_app/soups/base/start.snip +0 -19
  71. data/pristine_app/soups/dynasnips/current_snip.rb +0 -29
  72. data/pristine_app/soups/dynasnips/link_to_current_snip.rb +0 -14
  73. data/pristine_app/soups/dynasnips/page_title.rb +0 -9
  74. data/pristine_app/soups/extras/comments.rb +0 -78
  75. data/pristine_app/soups/tutorial/bad_dynasnip.snip +0 -8
  76. data/pristine_app/soups/tutorial/hello_world.snip +0 -20
  77. data/pristine_app/soups/tutorial/markdown_example.snip +0 -13
  78. data/pristine_app/soups/tutorial/snip.snip +0 -9
  79. data/pristine_app/soups/tutorial/soup.snip +0 -3
  80. data/pristine_app/soups/tutorial/test.snip +0 -30
  81. data/pristine_app/soups/tutorial/textile_example.snip +0 -11
  82. data/pristine_app/soups/tutorial/tutorial-another-snip.snip +0 -1
  83. data/pristine_app/soups/tutorial/tutorial-basic-snip-inclusion.snip +0 -1
  84. data/pristine_app/soups/tutorial/tutorial-dynasnips.snip.markdown +0 -56
  85. data/pristine_app/soups/tutorial/tutorial-layout.snip +0 -56
  86. data/pristine_app/soups/tutorial/tutorial-links.snip +0 -4
  87. data/pristine_app/soups/tutorial/tutorial-renderers.snip.markdown +0 -77
  88. data/pristine_app/soups/tutorial/tutorial.snip.markdown +0 -69
  89. data/pristine_app/soups/tutorial/vanilla-rb.snip +0 -16
  90. data/pristine_app/soups/tutorial/vanilla.snip +0 -8
  91. data/test/dynasnip_test.rb +0 -42
  92. data/test/dynasnips/link_to_current_snip_test.rb +0 -19
  93. data/test/dynasnips/link_to_test.rb +0 -27
  94. data/test/dynasnips/page_title_test.rb +0 -19
  95. data/test/renderers/base_renderer_test.rb +0 -43
  96. data/test/renderers/erb_renderer_test.rb +0 -29
  97. data/test/renderers/haml_renderer_test.rb +0 -35
  98. data/test/renderers/markdown_renderer_test.rb +0 -31
  99. data/test/renderers/raw_renderer_test.rb +0 -23
  100. data/test/renderers/ruby_renderer_test.rb +0 -59
  101. data/test/snip_inclusion_test.rb +0 -56
  102. data/test/snip_reference_parser_test.rb +0 -123
  103. data/test/test_helper.rb +0 -75
  104. data/test/vanilla_app_test.rb +0 -83
  105. data/test/vanilla_presenting_test.rb +0 -125
  106. data/test/vanilla_request_test.rb +0 -87
data/lib/vanilla.rb CHANGED
@@ -1,14 +1,7 @@
1
- module Vanilla
2
- VERSION = "1.2"
1
+ require 'vanilla/app'
3
2
 
4
- autoload :Renderers, "vanilla/renderers"
5
- autoload :App, "vanilla/app"
6
- autoload :Dynasnip, "vanilla/dynasnip"
7
- autoload :Request, "vanilla/request"
8
- autoload :Routes, "vanilla/routes"
9
- autoload :Static, "vanilla/static"
10
- autoload :SnipReferenceParser, "vanilla/snip_reference_parser"
11
- end
3
+ # Load all the other renderer subclasses
4
+ Dir[File.join(File.dirname(__FILE__), 'vanilla', 'renderers', '*.rb')].each { |f| require f }
12
5
 
13
6
  # Load all the base dynasnip classes
14
7
  Dir[File.join(File.dirname(__FILE__), 'vanilla', 'dynasnips', '*.rb')].each do |dynasnip|
data/lib/vanilla/app.rb CHANGED
@@ -1,35 +1,26 @@
1
- require 'soup'
1
+ require 'vanilla/request'
2
+ require 'vanilla/routes'
3
+ require 'vanilla/soup_with_timestamps'
4
+
5
+ # Require the base set of renderers
6
+ require 'vanilla/renderers/base'
7
+ require 'vanilla/renderers/raw'
8
+ require 'vanilla/renderers/erb'
9
+
2
10
 
3
11
  module Vanilla
4
12
  class App
5
- include Vanilla::Routes
6
-
13
+ include Routes
14
+
7
15
  attr_reader :request, :response, :config, :soup
8
-
9
- # Create a new Vanilla application
10
- # Configuration options:
11
- #
12
- # :soup - provide the path to the soup data
13
- # :soups - provide an array of paths to soup data
14
- # :renderers - a hash of names to classes
15
- # :default_renderer - the class to use when no renderer is provided;
16
- # defaults to 'Vanilla::Renderers::Base'
17
- # :default_layout_snip - the snip to use as a layout when rendering to HTML;
18
- # defaults to 'layout'
19
- # :root_snip - the snip to load for the root ('/') url;
20
- # defaults to 'start'
21
- def initialize(config={})
22
- @config = config
23
- if @config[:soup].nil? && @config[:soups].nil?
24
- @config.merge!(:soup => File.expand_path("soup"))
25
- end
26
- @soup = prepare_soup(config)
27
- prepare_renderers(config[:renderers])
16
+
17
+ def initialize(config_file=nil)
18
+ prepare_configuration(config_file)
19
+ @soup = SoupWithTimestamps.new(config[:soup])
28
20
  end
29
-
21
+
30
22
  # Returns a Rack-appropriate 3-element array (via Rack::Response#finish)
31
23
  def call(env)
32
- env['vanilla.app'] = self
33
24
  @request = Vanilla::Request.new(env, self)
34
25
  @response = Rack::Response.new
35
26
 
@@ -37,9 +28,9 @@ module Vanilla
37
28
  output = formatted_render(request.snip, request.part, request.format)
38
29
  rescue => e
39
30
  @response.status = 500
40
- output = e.to_s + e.backtrace.join("\n")
31
+ output = e.to_s
41
32
  end
42
- response_format = request.format
33
+ response_format = request.format
43
34
  response_format = 'plain' if response_format == 'raw'
44
35
  @response['Content-Type'] = "text/#{response_format}"
45
36
  @response.write(output)
@@ -49,16 +40,11 @@ module Vanilla
49
40
  def formatted_render(snip, part=nil, format=nil)
50
41
  case format
51
42
  when 'html', nil
52
- layout = layout_for(snip)
53
- if layout == snip
54
- "Rendering of the current layout would result in infinite recursion."
55
- else
56
- render(layout)
57
- end
43
+ Renderers::Erb.new(self).render(soup['system'], :main_template)
58
44
  when 'raw', 'css', 'js'
59
- Renderers::Raw.new(self).render(snip, part)
45
+ Renderers::Raw.new(self).render(snip, part || :content)
60
46
  when 'text', 'atom', 'xml'
61
- render(snip, part)
47
+ render(snip, part || :content)
62
48
  else
63
49
  raise "Unknown format '#{format}'"
64
50
  end
@@ -73,82 +59,40 @@ module Vanilla
73
59
  end
74
60
  end
75
61
 
62
+ # Given the snip and parameters, yield an instance of the appropriate
63
+ # Vanilla::Render::Base subclass
64
+ def rendering(snip)
65
+ renderer_instance = renderer_for(snip).new(self)
66
+ yield renderer_instance
67
+ rescue Exception => e
68
+ "<pre>[Error rendering '#{snip.name}' - \"" +
69
+ e.message.gsub("<", "&lt;").gsub(">", "&gt;") + "\"]\n" +
70
+ e.backtrace.join("\n").gsub("<", "&lt;").gsub(">", "&gt;") + "</pre>"
71
+ end
72
+
76
73
  # Returns the renderer class for a given snip
77
74
  def renderer_for(snip)
78
- if snip
79
- find_renderer(snip.render_as || snip.extension)
80
- else
81
- default_renderer
82
- end
83
- end
84
-
85
- def default_layout_snip
86
- soup[config[:default_layout_snip] || 'layout']
75
+ return Renderers::Base unless snip.render_as && !snip.render_as.empty?
76
+ Vanilla::Renderers.const_get(snip.render_as)
87
77
  end
88
-
89
- def layout_for(snip)
90
- if snip
91
- renderer_for(snip).new(self).layout_for(snip)
92
- else
93
- default_layout_snip
94
- end
78
+
79
+ # Other things can call this when a snip cannot be loaded.
80
+ def render_missing_snip(snip_name)
81
+ "[snip '#{snip_name}' cannot be found]"
95
82
  end
96
-
83
+
97
84
  def snip(attributes)
98
- @soup << attributes
85
+ @soup.new_snip(attributes)
99
86
  end
100
-
101
- def register_renderer(klass, *types)
102
- types.each do |type|
103
- if klass.is_a?(String)
104
- klass = klass.split("::").inject(Object) { |o, name| o.const_get(name) }
105
- end
106
- @renderers[type.to_s] = klass
107
- end
108
- end
109
-
87
+
110
88
  private
111
-
112
- def prepare_renderers(additional_renderers={})
113
- @renderers = Hash.new(config[:default_renderer] || Vanilla::Renderers::Base)
114
- @renderers.merge!({
115
- "base" => Vanilla::Renderers::Base,
116
- "markdown" => Vanilla::Renderers::Markdown,
117
- "bold" => Vanilla::Renderers::Bold,
118
- "erb" => Vanilla::Renderers::Erb,
119
- "rb" => Vanilla::Renderers::Ruby,
120
- "ruby" => Vanilla::Renderers::Ruby,
121
- "haml" => Vanilla::Renderers::Haml,
122
- "raw" => Vanilla::Renderers::Raw,
123
- "textile" => Vanilla::Renderers::Textile
124
- })
125
- additional_renderers.each { |name, klass| register_renderer(klass, name) } if additional_renderers
126
- end
127
-
128
- def find_renderer(name)
129
- @renderers[(name ? name.downcase : nil)]
130
- end
131
-
132
- def default_renderer
133
- @renderers[nil]
134
- end
135
-
136
- def rendering(snip)
137
- renderer_instance = renderer_for(snip).new(self)
138
- yield renderer_instance
139
- rescue Exception => e
140
- snip_name = snip ? snip.name : nil
141
- "<pre>[Error rendering '#{snip_name}' - \"" +
142
- e.message.gsub("<", "&lt;").gsub(">", "&gt;") + "\"]\n" +
143
- e.backtrace.join("\n").gsub("<", "&lt;").gsub(">", "&gt;") + "</pre>"
144
- end
145
-
146
- def prepare_soup(config)
147
- if config[:soups]
148
- backends = [config[:soups]].flatten.map { |path| ::Soup::Backends::FileBackend.new(path) }
149
- ::Soup.new(::Soup::Backends::MultiSoup.new(*backends))
150
- else
151
- ::Soup.new(::Soup::Backends::FileBackend.new(config[:soup]))
89
+
90
+ def prepare_configuration(config_file)
91
+ config_file ||= "config.yml"
92
+ @config = YAML.load(File.open(config_file)) rescue {}
93
+ @config[:filename] = config_file
94
+ def @config.save!
95
+ File.open(self[:filename], 'w') { |f| f.puts self.to_yaml }
152
96
  end
153
97
  end
154
98
  end
@@ -1,23 +1,9 @@
1
1
  require 'vanilla'
2
+ require 'irb'
2
3
 
3
- module Vanilla
4
- class RackShim
5
- def run(app)
6
- app # return it
7
- end
8
- def use(*args)
9
- # ignore
10
- end
11
- def get_binding
12
- binding
13
- end
14
- end
4
+ def app(reload=false)
5
+ @__vanilla_console_app = nil if reload
6
+ @__vanilla_console_app ||= Vanilla::App.new(ENV['VANILLA_CONFIG'])
15
7
  end
16
8
 
17
- def app(reload=false)
18
- if !@__vanilla_console_app || reload
19
- shim_binding = Vanilla::RackShim.new.get_binding
20
- @__vanilla_console_app = eval File.read("config.ru"), shim_binding
21
- end
22
- @__vanilla_console_app
23
- end
9
+ puts "The Soup is simmering."
@@ -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 = app.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="#{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 = app.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
+ app.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(app.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
@@ -1,15 +1,13 @@
1
1
  require 'vanilla/dynasnip'
2
- require 'cgi'
3
2
 
4
3
  # If the dynasnip is a subclass of Dynasnip, it has access to the request hash
5
4
  # (or whatever - access to some object outside of the snip itself.)
6
5
  class Debug < Dynasnip
7
6
  def get(*args)
8
- CGI.escapeHTML(app.request.inspect)
7
+ app.request.inspect
9
8
  end
10
-
9
+
11
10
  def post(*args)
12
- "You posted! " + CGI.escapeHTML(app.request.inspect)
11
+ "You posted! " + app.request.inspect
13
12
  end
14
- self
15
13
  end
@@ -0,0 +1,60 @@
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 = app.soup[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 = app.soup[snip_attributes[:name]]
23
+ snip_attributes.each do |name, value|
24
+ snip.__send__(:set_value, name, value)
25
+ end
26
+ snip.save
27
+ %{Saved snip #{link_to snip_attributes[:name]} ok}
28
+ rescue Exception => e
29
+ app.soup << snip_attributes
30
+ %{Created snip #{link_to snip_attributes[:name]} ok}
31
+ end
32
+
33
+ def edit(snip)
34
+ renderer = Vanilla::Renderers::Erb.new(app)
35
+ renderer.instance_eval { @snip_to_edit = snip } # hacky!
36
+ snip_in_edit_template = renderer.render_without_including_snips(app.soup['edit'], :template)
37
+ prevent_snip_inclusion(snip_in_edit_template)
38
+ end
39
+
40
+ private
41
+
42
+ def prevent_snip_inclusion(content)
43
+ content.gsub("{", "&#123;").gsub("}" ,"&#125;")
44
+ end
45
+
46
+ attribute :template, %{
47
+ <form action="<%= url_to 'edit' %>" method="post">
48
+ <dl class="attributes">
49
+ <% @snip_to_edit.attributes.each do |name, value| %>
50
+ <dt><%= name %></dt>
51
+ <dd><textarea name="<%= name %>" class="<%= name %>"><%=h value %></textarea></dd>
52
+ <% end %>
53
+ <dt><input class="attribute_name" type="text"></dt>
54
+ <dd><textarea></textarea></dd>
55
+ </dl>
56
+ <a href="#" id="add">Add</a>
57
+ <button name='save_button'>Save</button>
58
+ </form>
59
+ }
60
+ end