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
data/README ADDED
@@ -0,0 +1,52 @@
1
+ --~::{ Vanilla.rb }::~---
2
+ =========================
3
+
4
+ A wandering soul; a repo clone'd -
5
+ his meta_klass and methods honed.
6
+ But tarry he 'ponst what dark endeavour
7
+ with code of such unknowable terror?
8
+
9
+ - H.P. Gemcraft, 1914
10
+
11
+ A Preface; A Warning
12
+ ====================
13
+
14
+ The base flavour. The classic ice cream. Except riddled through with blood-red gemstones - Ruby gemstones.
15
+ This mad gelato will break your teeth.
16
+
17
+ ... and run the freakiest wiki-wonki-wiki-wonki-wiki-wonki-wickedy-wiki you've ever seen.
18
+
19
+ But you must not fear: fear is the mind-killer; fear is the little death that brings total oblivion;
20
+ fear is the chilling terror that makes you believe that *everything* is better when Model lays with
21
+ Controller and View in peace.
22
+
23
+ Vanilla.rb cries "HERESY!" upon that layered orgy of filth. Vanilla.rb says "EMBRACE CHAOS!". Vanilla.rb
24
+ says "EVERY SNIP FOR THEMSELVES!". Vanilla.rb waits quietly, in the dark corners.
25
+
26
+ Use at your own risk; really. I showed it to a cat, and the cat started talking in French.
27
+ It was MESSED. UP.
28
+
29
+
30
+ Thee Darke Invocation
31
+ =====================
32
+
33
+ $ gem install soup sqlite3-ruby rack ratom RedCloth BlueCloth
34
+
35
+ $ rake setup
36
+
37
+ ... a bunch of stuff gets created
38
+
39
+ $ rackup
40
+
41
+ ... the wonki starts. THE PAIN BEGINS.
42
+
43
+ (Try going to http://localhost:9292/vanilla-rb-tutorial to salve the chafing.)
44
+
45
+
46
+ Working with Vanilla
47
+ ====================
48
+
49
+ Currently unknowable; soon I hope to compile such a nercronomicon as to allow mortal kind to
50
+ conjure up Snips and Dynasnips and Renderers...
51
+
52
+ For the moment, see http://interblah.net/vanilla-rb-tutorial
data/Rakefile ADDED
@@ -0,0 +1,118 @@
1
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), 'lib')
2
+ require 'vanilla'
3
+
4
+ require 'spec'
5
+ require 'spec/rake/spectask'
6
+ Spec::Rake::SpecTask.new do |t|
7
+ t.spec_opts = %w(--format specdoc --colour)
8
+ t.libs = ["spec"]
9
+ end
10
+
11
+ task :default => :spec
12
+
13
+
14
+ require "rubygems"
15
+ require "rake/gempackagetask"
16
+ require "rake/rdoctask"
17
+
18
+ # This builds the actual gem. For details of what all these options
19
+ # mean, and other ones you can add, check the documentation here:
20
+ #
21
+ # http://rubygems.org/read/chapter/20
22
+ #
23
+ spec = Gem::Specification.new do |s|
24
+
25
+ # Change these as appropriate
26
+ s.name = "vanilla"
27
+ s.version = "1.0.0"
28
+ s.summary = "A bliki-type web content thing."
29
+ s.author = "James Adam"
30
+ s.email = "james@lazyatom.com.com"
31
+ s.homepage = "http://github.com/lazyatom/vanilla-rb"
32
+
33
+ s.has_rdoc = true
34
+ s.extra_rdoc_files = %w(README)
35
+ s.rdoc_options = %w(--main README)
36
+
37
+ # Add any extra files to include in the gem
38
+ s.files = %w(config.example.yml config.ru Rakefile README) + Dir.glob("{spec,lib,bin}/**/*")
39
+ s.executables = ['vanilla']
40
+ s.require_paths = ["lib"]
41
+
42
+ # If you want to depend on other gems, add them here, along with any
43
+ # relevant versions
44
+ # s.add_dependency("some_other_gem", "~> 0.1.0")
45
+
46
+ s.add_development_dependency("rspec") # add any other gems for testing/development
47
+
48
+ # If you want to publish automatically to rubyforge, you'll may need
49
+ # to tweak this, and the publishing task below too.
50
+ s.rubyforge_project = "vanilla"
51
+ end
52
+
53
+ # This task actually builds the gem. We also regenerate a static
54
+ # .gemspec file, which is useful if something (i.e. GitHub) will
55
+ # be automatically building a gem for this project. If you're not
56
+ # using GitHub, edit as appropriate.
57
+ Rake::GemPackageTask.new(spec) do |pkg|
58
+ pkg.gem_spec = spec
59
+
60
+ # Generate the gemspec file for github.
61
+ file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
62
+ File.open(file, "w") {|f| f << spec.to_ruby }
63
+ end
64
+
65
+ # Generate documentation
66
+ Rake::RDocTask.new do |rd|
67
+ rd.main = "README"
68
+ rd.rdoc_files.include("README", "lib/**/*.rb")
69
+ rd.rdoc_dir = "rdoc"
70
+ end
71
+
72
+ desc 'Clear out RDoc and generated packages'
73
+ task :clean => [:clobber_rdoc, :clobber_package] do
74
+ rm "#{spec.name}.gemspec"
75
+ end
76
+
77
+ # If you want to publish to RubyForge automatically, here's a simple
78
+ # task to help do that. If you don't, just get rid of this.
79
+ # Be sure to set up your Rubyforge account details with the Rubyforge
80
+ # gem; you'll need to run `rubyforge setup` and `rubyforge config` at
81
+ # the very least.
82
+ begin
83
+ require "rake/contrib/sshpublisher"
84
+ namespace :rubyforge do
85
+
86
+ desc "Release gem and RDoc documentation to RubyForge"
87
+ task :release => ["rubyforge:release:gem", "rubyforge:release:docs"]
88
+
89
+ namespace :release do
90
+ desc "Release a new version of this gem"
91
+ task :gem => [:package] do
92
+ require 'rubyforge'
93
+ rubyforge = RubyForge.new
94
+ rubyforge.configure
95
+ rubyforge.login
96
+ rubyforge.userconfig['release_notes'] = spec.summary
97
+ path_to_gem = File.join(File.dirname(__FILE__), "pkg", "#{spec.name}-#{spec.version}.gem")
98
+ puts "Publishing #{spec.name}-#{spec.version.to_s} to Rubyforge..."
99
+ rubyforge.add_release(spec.rubyforge_project, spec.name, spec.version.to_s, path_to_gem)
100
+ end
101
+
102
+ desc "Publish RDoc to RubyForge."
103
+ task :docs => [:rdoc] do
104
+ config = YAML.load(
105
+ File.read(File.expand_path('~/.rubyforge/user-config.yml'))
106
+ )
107
+
108
+ host = "#{config['username']}@rubyforge.org"
109
+ remote_dir = "/var/www/gforge-projects/vanilla-rb/" # Should be the same as the rubyforge project name
110
+ local_dir = 'rdoc'
111
+
112
+ Rake::SshDirPublisher.new(host, remote_dir, local_dir).upload
113
+ end
114
+ end
115
+ end
116
+ rescue LoadError
117
+ puts "Rake SshDirPublisher is unavailable or your rubyforge environment is not configured."
118
+ end
data/bin/vanilla ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'rake'
4
+ load File.join(File.dirname(__FILE__), *%w[.. lib tasks vanilla.rake])
5
+
6
+ mkdir(ARGV[0])
7
+ cd(ARGV[0])
8
+
9
+ Rake::Task['vanilla:setup'].invoke
@@ -0,0 +1,5 @@
1
+ ---
2
+ :filename: config.example.yml
3
+ :secret: a6bc097eff86cabd92ee72ba4687c3f057b7556deaa3e4a6b284460871056b87ba3e91548c37dcc44fbc10241cee5b386556e6bcc2946fd9b609dc3bc1b24488
4
+ :credentials:
5
+ admin: 5f4dcc3b5aa765d61d8327deb882cf99
data/config.ru ADDED
@@ -0,0 +1,9 @@
1
+ require 'vanilla'
2
+
3
+ app = Vanilla::App.new(ENV['VANILLA_CONFIG'])
4
+ use Rack::Session::Cookie, :key => 'vanilla.session',
5
+ :path => '/',
6
+ :expire_after => 2592000,
7
+ :secret => app.config[:secret]
8
+ use Rack::Static, :urls => ["/public"], :root => File.join(File.dirname(__FILE__))
9
+ run app
data/lib/defensio.rb ADDED
@@ -0,0 +1,59 @@
1
+ class Defensio
2
+ class << self
3
+ attr_accessor :format
4
+ attr_accessor :service_type
5
+ attr_accessor :api_version
6
+ attr_accessor :api_key
7
+ attr_accessor :owner_url
8
+ end
9
+
10
+ self.format = :xml
11
+ self.service_type = :app # Can be :blog
12
+ self.api_version = '1.2'
13
+
14
+ def self.configure(confhash)
15
+ if confhash['test']
16
+ @mock = true
17
+ self.owner_url = 'http://www.example.com'
18
+ return
19
+ else
20
+ confhash.each do |prop, val|
21
+ self.send("#{prop}=", val)
22
+ end
23
+ end
24
+ end
25
+
26
+ def self.method_missing(name, *args)
27
+ self.post(name.to_s.dasherize, *args)
28
+ end
29
+
30
+ private
31
+ def self.connection
32
+ uri = URI.parse('http://api.defensio.com/')
33
+ Net::HTTP.start(uri.host, uri.port)
34
+ end
35
+
36
+ def self.post(action, params = {})
37
+ resp = connection.post(real_path(action), params_from_hash(params))
38
+ raise "Problem with request: #{action}" unless resp.code == '200'
39
+ parse_response(resp.body)
40
+ end
41
+
42
+ def self.real_path(action)
43
+ "/#{service_type}/#{api_version}/#{action}/#{api_key}.#{format}"
44
+ end
45
+
46
+ def self.params_from_hash(params = {})
47
+ # Thanks Net::HTTPHeader
48
+ params.stringify_keys.merge('owner-url' => owner_url).map {|k,v| "#{CGI.escape(k.dasherize.to_s)}=#{CGI.escape(v.to_s)}" }.join('&')
49
+ end
50
+
51
+ def self.parse_response(body)
52
+ case format
53
+ when :yaml
54
+ YAML.load(body)
55
+ when :xml
56
+ Hash.from_xml(body)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,178 @@
1
+ $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..')))
2
+ require 'vanilla'
3
+
4
+ namespace :vanilla do
5
+ desc "Open an irb session preloaded with this library"
6
+ task :console do
7
+ sh "irb -Ilib -rubygems -rvanilla -rvanilla/console"
8
+ end
9
+
10
+ task :clean do
11
+ # TODO: get the database name from Soup
12
+ FileUtils.rm "soup.db" if File.exist?("soup.db")
13
+ end
14
+
15
+ task :prepare do
16
+ Soup.prepare
17
+ end
18
+
19
+ task :load_snips => :prepare do
20
+ Dynasnip.persist_all!(overwrite=true)
21
+
22
+ Dir[File.join(File.dirname(__FILE__), '..', 'vanilla', 'snips', '*.rb')].each do |f|
23
+ p "loading #{f}"
24
+ load f
25
+ end
26
+
27
+ load File.join(File.dirname(__FILE__), *%w[.. vanilla test_snips.rb])
28
+
29
+ puts "The soup is simmering. Loaded #{Soup.tuple_class.count} tuples"
30
+ end
31
+
32
+ desc 'Resets the soup to contain the base snips only. Dangerous!'
33
+ task :reset => [:clean, :load_snips]
34
+
35
+ namespace :upgrade do
36
+ desc 'Upgrade the dynasnips'
37
+ task :dynasnips => :prepare do
38
+ Dynasnip.all.each do |dynasnip|
39
+ print "Upgrading #{dynasnip.snip_name}... "
40
+ # TODO: our confused Soup interface might return an array.
41
+ snip = Soup[dynasnip.snip_name]
42
+ if snip.empty? || snip.nil?
43
+ # it's a new dyna
44
+ Soup << dynasnip.snip_attributes
45
+ puts "(new)"
46
+ elsif snip.created_at == snip.updated_at
47
+ # it's not been changed, let's upgrade
48
+ snip.destroy
49
+ Soup << dynasnip.snip_attributes
50
+ puts "(unedited)"
51
+ else
52
+ # the dyna exists and has been changed
53
+ dynasnip.snip_attributes.each do |name, value|
54
+ unless (existing_value = snip.get_value(name)) == value
55
+ puts "Conflict in attribute '#{name}':"
56
+ puts "> Your soup: '#{existing_value}'"
57
+ puts "> New soup: '#{value}"
58
+ print "Upgrade? [Y/n]: "
59
+ upgrade_value = ["Y", "y", ""].include? STDIN.gets.chomp.strip
60
+ snip.set_value(name, value) if upgrade_value
61
+ end
62
+ end
63
+ snip.save
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ desc 'Upgrade dynasnips and system snips'
70
+ task :upgrade => ["upgrade:dynasnips"]
71
+
72
+ desc 'Add a user (or change an existing password)'
73
+ task :add_user => :prepare do
74
+ puts "Adding a new user"
75
+ # config_file = ENV['VANILLA_CONFIG'] || 'config.yml'
76
+ # config_file = YAML.load(File.open(config_file)) rescue {}
77
+ app = Vanilla::App.new(ENV['VANILLA_CONFIG'])
78
+ print "Username: "
79
+ username = STDIN.gets.chomp.strip
80
+ print "Password: "
81
+ password = STDIN.gets.chomp.strip
82
+ print "Confirm password: "
83
+ confirm_password = STDIN.gets.chomp.strip
84
+ if password != confirm_password
85
+ raise "Passwords don't match!"
86
+ else
87
+ app.config[:credentials] ||= {}
88
+ app.config[:credentials][username] = MD5.md5(password).to_s
89
+ app.config.save!
90
+ puts "User '#{username}' added."
91
+ end
92
+ end
93
+
94
+ desc 'Generate file containing secret for cookie-based session storage'
95
+ task :generate_secret do
96
+ # Adapted from old rails secret generator.
97
+ require 'openssl'
98
+ if !File.exist?("/dev/urandom")
99
+ # OpenSSL transparently seeds the random number generator with
100
+ # data from /dev/urandom. On platforms where that is not
101
+ # available, such as Windows, we have to provide OpenSSL with
102
+ # our own seed. Unfortunately there's no way to provide a
103
+ # secure seed without OS support, so we'll have to do with
104
+ # rand() and Time.now.usec().
105
+ OpenSSL::Random.seed(rand(0).to_s + Time.now.usec.to_s)
106
+ end
107
+ data = OpenSSL::BN.rand(2048, -1, false).to_s
108
+ secret = OpenSSL::Digest::SHA512.new(data).hexdigest
109
+ app = Vanilla::App.new(ENV['VANILLA_CONFIG'])
110
+ app.config[:secret] = secret
111
+ app.config.save!
112
+ puts "Secret generated."
113
+ end
114
+
115
+ desc 'Prepare standard files to run Vanilla'
116
+ task :prepare_files do
117
+ cp File.join(File.dirname(__FILE__), *%w[.. .. config.ru]), 'config.ru'
118
+ cp_r File.join(File.dirname(__FILE__), *%w[.. .. public]), 'public'
119
+ mkdir 'tmp'
120
+ File.open("Rakefile", "w") do |f|
121
+ rakefile =<<-EOF
122
+ require 'vanilla'
123
+ load 'tasks/vanilla.rake'
124
+
125
+ # Add any other tasks here.
126
+ EOF
127
+ f.write rakefile.strip
128
+ end
129
+ end
130
+
131
+
132
+ desc 'Prepare a new vanilla.rb installation'
133
+ task :setup do
134
+ puts <<-EOM
135
+
136
+ ===================~ Vanilla.rb ~====================
137
+
138
+ Congratulations! You have elected to try out the weirdest web thing ever.
139
+ Lets get started. Firstly, I'm going to cook you some soup:
140
+
141
+
142
+ EOM
143
+ Rake::Task['vanilla:load_snips'].invoke
144
+
145
+ puts <<-EOM
146
+
147
+ Now I'm going to generate your configuration. This will be stored either in
148
+ 'config.yml' in the current directory, or in the path you provide via the
149
+ environment variable VANILLA_CONFIG.
150
+
151
+ Generating the secret for cookie-based session storage.
152
+ EOM
153
+ Rake::Task['vanilla:prepare_files'].invoke
154
+ Rake::Task['vanilla:generate_secret'].invoke
155
+
156
+ puts <<-EOM
157
+
158
+
159
+ Now that we've got our broth, you'll want to add a user, so you can edit stuff.
160
+ Lets do that now:
161
+
162
+
163
+ EOM
164
+ Rake::Task['vanilla:add_user'].invoke
165
+ puts <<-EOM
166
+
167
+
168
+ OK! You're ready to go. To start vanilla.rb, you'll want to get it running under
169
+ a webserver that supports Rack. The easiest way to do this locally is via 'rackup':
170
+
171
+ $ rackup
172
+
173
+ Then go to http://localhost:9292
174
+
175
+ I'm going now, Goodbye!
176
+ EOM
177
+ end
178
+ end
@@ -0,0 +1,87 @@
1
+ require 'vanilla'
2
+ require 'vanilla/request'
3
+
4
+ module Vanilla
5
+ class App
6
+
7
+ attr_reader :request, :response, :config
8
+
9
+ def initialize(config_file=nil)
10
+ prepare_configuration(config_file)
11
+ Soup.prepare
12
+ end
13
+
14
+ # Returns a Rack-appropriate 3-element array (via Rack::Response#finish)
15
+ def call(env)
16
+ @request = Vanilla::Request.new(env)
17
+ @response = Rack::Response.new
18
+
19
+ begin
20
+ output = formatted_render(request.snip, request.part, request.format)
21
+ rescue => e
22
+ @response.status = 500
23
+ output = e.to_s
24
+ end
25
+ response_format = request.format
26
+ response_format = 'plain' if response_format == 'raw'
27
+ @response['Content-Type'] = "text/#{response_format}"
28
+ @response.write(output)
29
+ @response.finish # returns the array
30
+ end
31
+
32
+ def formatted_render(snip, part=nil, format=nil)
33
+ case format
34
+ when 'html', nil
35
+ Renderers::Erb.new(self).render(Vanilla.snip('system'), :main_template)
36
+ when 'raw', 'css', 'js'
37
+ Renderers::Raw.new(self).render(snip, part || :content)
38
+ when 'text', 'atom', 'xml'
39
+ render(snip, part || :content)
40
+ else
41
+ raise "Unknown format '#{format}'"
42
+ end
43
+ end
44
+
45
+ # render a snip using either the renderer given, or the renderer
46
+ # specified by the snip's "render_as" property, or Render::Base
47
+ # if nothing else is given.
48
+ def render(snip, part=:content, args=[])
49
+ rendering(snip) do |renderer|
50
+ renderer.render(snip, part, args)
51
+ end
52
+ end
53
+
54
+ # Given the snip and parameters, yield an instance of the appropriate
55
+ # Vanilla::Render::Base subclass
56
+ def rendering(snip)
57
+ renderer_instance = renderer_for(snip).new(self)
58
+ yield renderer_instance
59
+ rescue Exception => e
60
+ "<pre>[Error rendering '#{snip.name}' - \"" +
61
+ e.message.gsub("<", "&lt;").gsub(">", "&gt;") + "\"]\n" +
62
+ e.backtrace.join("\n").gsub("<", "&lt;").gsub(">", "&gt;") + "</pre>"
63
+ end
64
+
65
+ # Returns the renderer class for a given snip
66
+ def renderer_for(snip)
67
+ return Renderers::Base unless snip.render_as && !snip.render_as.empty?
68
+ Vanilla::Renderers.const_get(snip.render_as)
69
+ end
70
+
71
+ # Other things can call this when a snip cannot be loaded.
72
+ def render_missing_snip(snip_name)
73
+ "[snip '#{snip_name}' cannot be found]"
74
+ end
75
+
76
+ private
77
+
78
+ def prepare_configuration(config_file)
79
+ config_file ||= "config.yml"
80
+ @config = YAML.load(File.open(config_file)) rescue {}
81
+ @config[:filename] = config_file
82
+ def @config.save!
83
+ File.open(self[:filename], 'w') { |f| f.puts self.to_yaml }
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,3 @@
1
+ require 'vanilla'
2
+ Soup.prepare
3
+ puts "The Soup is simmering."
@@ -0,0 +1,110 @@
1
+ require 'vanilla/renderers/base'
2
+ require 'enumerator'
3
+
4
+ class Dynasnip < Vanilla::Renderers::Base
5
+
6
+ def self.all
7
+ ObjectSpace.enum_for(:each_object, class << self; self; end).to_a - [self]
8
+ end
9
+
10
+ def self.snip_name(new_name=nil)
11
+ if new_name
12
+ @snip_name = new_name.to_s
13
+ else
14
+ # borrowed from ActiveSupport
15
+ @snip_name ||= self.name.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
16
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
17
+ tr("-", "_").
18
+ downcase
19
+ end
20
+ end
21
+
22
+ def self.attribute(attribute_name, attribute_value=nil)
23
+ @attributes ||= {}
24
+ @attributes[attribute_name.to_sym] = attribute_value if attribute_value
25
+ @attributes[attribute_name.to_sym]
26
+ end
27
+
28
+ def self.usage(str)
29
+ attribute :usage, escape_curly_braces(str).strip
30
+ end
31
+
32
+ def self.persist_all!(overwrite=false)
33
+ all.each do |dynasnip|
34
+ dynasnip.persist!(overwrite)
35
+ end
36
+ end
37
+
38
+ def self.build_snip
39
+ Snip.new(snip_attributes)
40
+ end
41
+
42
+ def self.snip_attributes
43
+ full_snip_attributes = {:name => snip_name, :content => self.name, :render_as => "Ruby"}
44
+ @attributes ? full_snip_attributes.merge!(@attributes) : full_snip_attributes
45
+ end
46
+
47
+ def self.persist!(overwrite=false)
48
+ if overwrite
49
+ snip = Soup[snip_name]
50
+ if snip
51
+ if snip.is_a?(Array)
52
+ snip.each { |s| s.destroy }
53
+ else
54
+ snip.destroy
55
+ end
56
+ end
57
+ end
58
+ snip = Soup[snip_name]
59
+ snip = snip[0] if snip.is_a?(Array)
60
+ if snip
61
+ snip_attributes.each do |name, value|
62
+ snip.set_value(name, value)
63
+ end
64
+ else
65
+ snip = build_snip
66
+ end
67
+ snip.save
68
+ snip
69
+ end
70
+
71
+ def method_missing(method, *args)
72
+ if snip = Vanilla.snip(snip_name)
73
+ snip.get_value(method)
74
+ elsif part = self.class.attribute(method)
75
+ part
76
+ else
77
+ super
78
+ end
79
+ end
80
+
81
+ # dynasnips gain access to the app in the same way as Render::Base
82
+ # subclasses
83
+
84
+ protected
85
+
86
+ def snip_name
87
+ self.class.snip_name
88
+ end
89
+
90
+ def snip
91
+ Snip[snip_name]
92
+ end
93
+
94
+ def show_usage
95
+ if snip.usage
96
+ Vanilla::Renderers::Markdown.render(snip_name, :usage)
97
+ else
98
+ "No usage information for #{snip_name}"
99
+ end
100
+ end
101
+
102
+ def cleaned_params
103
+ p = app.request.params.dup
104
+ p.delete(:snip)
105
+ p.delete(:format)
106
+ p.delete(:method)
107
+ p.delete(:part)
108
+ p
109
+ end
110
+ end