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