vanilla 1.17 → 1.17.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/Rakefile +21 -7
  2. data/bin/vanilla +2 -2
  3. data/lib/vanilla.rb +10 -7
  4. data/lib/vanilla/app.rb +57 -97
  5. data/lib/vanilla/config.rb +46 -0
  6. data/lib/vanilla/console.rb +1 -1
  7. data/lib/vanilla/renderers/base.rb +12 -2
  8. data/lib/vanilla/request.rb +9 -34
  9. data/lib/vanilla/routing.rb +34 -0
  10. data/lib/vanilla/test_helper.rb +10 -7
  11. data/pristine_app/Gemfile.lock +35 -0
  12. data/pristine_app/application.rb +2 -4
  13. data/pristine_app/public/vanilla.css +299 -9
  14. data/pristine_app/soups/base/layout.snip +11 -7
  15. data/pristine_app/soups/base/start.snip +15 -14
  16. data/pristine_app/soups/system/current_snip.rb +2 -2
  17. data/pristine_app/soups/system/feed.rb +30 -0
  18. data/pristine_app/soups/system/index.rb +3 -3
  19. data/pristine_app/soups/system/link_to.rb +5 -1
  20. data/pristine_app/soups/system/link_to_current_snip.rb +2 -3
  21. data/pristine_app/soups/tutorial/tutorial-layout.snip +9 -2
  22. data/pristine_app/soups/tutorial/tutorial-links.snip +2 -1
  23. data/pristine_app/soups/tutorial/tutorial-removing.snip.markdown +8 -0
  24. data/pristine_app/soups/tutorial/tutorial-renderers.snip.markdown +10 -4
  25. data/pristine_app/soups/tutorial/tutorial.snip.markdown +0 -1
  26. data/pristine_app/soups/tutorial/vanilla-rb.snip +4 -6
  27. data/pristine_app/tmp/restart.txt +0 -0
  28. data/test/core/configuration_test.rb +89 -0
  29. data/test/{dynasnip_test.rb → core/dynasnip_test.rb} +0 -0
  30. data/test/{renderers → core/renderers}/base_renderer_test.rb +37 -0
  31. data/test/{renderers → core/renderers}/erb_renderer_test.rb +0 -0
  32. data/test/{renderers → core/renderers}/haml_renderer_test.rb +0 -0
  33. data/test/{renderers → core/renderers}/markdown_renderer_test.rb +0 -0
  34. data/test/{renderers → core/renderers}/raw_renderer_test.rb +0 -0
  35. data/test/{renderers → core/renderers}/ruby_renderer_test.rb +0 -0
  36. data/test/core/routing_test.rb +30 -0
  37. data/test/{snip_inclusion_test.rb → core/snip_inclusion_test.rb} +0 -0
  38. data/test/{snip_reference_parser_test.rb → core/snip_reference_parser_test.rb} +0 -0
  39. data/test/{test_helper.rb → core/test_helper.rb} +5 -4
  40. data/test/core/vanilla_app_test.rb +51 -0
  41. data/test/{vanilla_presenting_test.rb → core/vanilla_presenting_test.rb} +15 -1
  42. data/test/{vanilla_request_test.rb → core/vanilla_request_test.rb} +0 -0
  43. data/test/pristine_app/current_snip_test.rb +46 -0
  44. data/test/pristine_app/feed_test.rb +47 -0
  45. data/test/pristine_app/index_test.rb +34 -0
  46. data/test/pristine_app/link_to_current_snip_test.rb +11 -0
  47. data/test/pristine_app/link_to_test.rb +27 -0
  48. data/test/pristine_app/page_title_test.rb +15 -0
  49. data/test/pristine_app/raw_test.rb +24 -0
  50. data/test/pristine_app/test_helper.rb +25 -0
  51. metadata +83 -42
  52. data/lib/vanilla/routes.rb +0 -18
  53. data/test/dynasnips/link_to_current_snip_test.rb +0 -19
  54. data/test/dynasnips/link_to_test.rb +0 -27
  55. data/test/dynasnips/page_title_test.rb +0 -19
  56. data/test/vanilla_app_test.rb +0 -111
data/Rakefile CHANGED
@@ -7,12 +7,24 @@ require "vanilla"
7
7
 
8
8
  task :default => :test
9
9
 
10
- require "rake/testtask"
11
- Rake::TestTask.new do |t|
12
- t.libs << "test"
13
- t.ruby_opts << "-rubygems"
14
- t.test_files = FileList["test/**/*_test.rb"]
15
- t.verbose = true
10
+ task :test => ["test:core", "test:app"]
11
+
12
+ namespace :test do
13
+ require "rake/testtask"
14
+ Rake::TestTask.new(:core) do |t|
15
+ t.libs << "test/core"
16
+ t.ruby_opts << "-rubygems"
17
+ t.test_files = FileList["test/core/**/*_test.rb"]
18
+ t.verbose = true
19
+ end
20
+
21
+ require "rake/testtask"
22
+ Rake::TestTask.new(:app) do |t|
23
+ t.libs << "test/pristine_app"
24
+ t.ruby_opts << "-rubygems"
25
+ t.test_files = FileList["test/pristine_app/**/*_test.rb"]
26
+ t.verbose = true
27
+ end
16
28
  end
17
29
 
18
30
  if Object.const_defined?(:Gem)
@@ -42,7 +54,7 @@ if Object.const_defined?(:Gem)
42
54
 
43
55
  # All the other gems we need.
44
56
  s.add_dependency("rack", ">= 0.9.1")
45
- s.add_dependency("soup", ">= 1.0.6")
57
+ s.add_dependency("soup", ">= 1.0.8")
46
58
  s.add_dependency("ratom", ">= 0.3.5")
47
59
  s.add_dependency("RedCloth", ">= 4.1.1")
48
60
  s.add_dependency("BlueCloth", ">= 1.0.0")
@@ -52,6 +64,8 @@ if Object.const_defined?(:Gem)
52
64
 
53
65
  s.add_development_dependency("kintama", ">= 0.1.6") # add any other gems for testing/development
54
66
  s.add_development_dependency("mocha")
67
+ s.add_development_dependency("capybara")
68
+ s.add_development_dependency("launchy")
55
69
 
56
70
  # If you want to publish automatically to rubyforge, you'll may need
57
71
  # to tweak this, and the publishing task below too.
data/bin/vanilla CHANGED
@@ -15,10 +15,10 @@ end
15
15
  def upgrade
16
16
  require 'fileutils'
17
17
  soups_directory = File.join(Dir.pwd, "soups")
18
- if File.directory(soups_directory)
18
+ if File.directory?(soups_directory)
19
19
  confirm("Upgrade system snips?") do
20
20
  pristine_system = File.expand_path("../../pristine_app/soups/system", __FILE__)
21
- copy(pristine_system, system_directory)
21
+ copy(pristine_system, soups_directory)
22
22
  end
23
23
  confirm("Upgrade tutorial snips?") do
24
24
  pristine_system = File.expand_path("../../pristine_app/soups/tutorial", __FILE__)
data/lib/vanilla.rb CHANGED
@@ -1,17 +1,20 @@
1
1
  module Vanilla
2
- VERSION = "1.17"
2
+ VERSION = "1.17.1"
3
3
 
4
4
  autoload :Renderers, "vanilla/renderers"
5
5
  autoload :App, "vanilla/app"
6
+ autoload :Config, "vanilla/config"
6
7
  autoload :Dynasnip, "vanilla/dynasnip"
7
8
  autoload :Request, "vanilla/request"
8
- autoload :Routes, "vanilla/routes"
9
+ autoload :Routing, "vanilla/routing"
9
10
  autoload :Static, "vanilla/static"
10
11
  autoload :SnipReferenceParser, "vanilla/snip_reference_parser"
11
12
  autoload :TestHelper, "vanilla/test_helper"
12
- end
13
13
 
14
- # Load all the base dynasnip classes
15
- Dir[File.join(File.dirname(__FILE__), 'vanilla', 'dynasnips', '*.rb')].each do |dynasnip|
16
- require dynasnip
17
- end
14
+ class << self
15
+ # The set of currently loaded Vanilla::App subclasses
16
+ def apps
17
+ @apps ||= []
18
+ end
19
+ end
20
+ end
data/lib/vanilla/app.rb CHANGED
@@ -1,9 +1,11 @@
1
1
  require 'soup'
2
- require 'ostruct'
3
2
 
4
3
  module Vanilla
4
+
5
+ # This is the main App class for Vanilla applications; this should
6
+ # be subclassed for each instance of Vanilla that you want to run.
5
7
  class App
6
- include Vanilla::Routes
8
+ include Vanilla::Routing
7
9
 
8
10
  class << self
9
11
  attr_reader :config
@@ -13,48 +15,23 @@ module Vanilla
13
15
  self
14
16
  end
15
17
  def reset!
16
- @config = OpenStruct.new
18
+ @config = Vanilla::Config.new
19
+ end
20
+ def inherited(app)
21
+ Vanilla.apps << app
17
22
  end
18
23
  end
19
24
 
20
- attr_reader :request, :response, :config, :soup
21
-
22
- def class_config
23
- if self.class.config
24
- {:soup => self.class.config.soup,
25
- :soups => self.class.config.soups,
26
- :root => self.class.config.root,
27
- :root_snip => self.class.config.root_snip,
28
- :renderers => self.class.config.renderers,
29
- :default_layout_snip => self.class.config.default_layout_snip,
30
- :default_renderer => self.class.config.default_renderer}
31
- else
32
- {}
33
- end
25
+ attr_reader :request, :response, :soup
26
+
27
+ def initialize
28
+ @renderers = Hash.new { config.default_renderer }
29
+ @soup = prepare_soup
30
+ prepare_renderers
34
31
  end
35
32
 
36
- # Create a new Vanilla application
37
- # Configuration options:
38
- #
39
- # :soup - provide the path to the soup data
40
- # :soups - provide an array of paths to soup data
41
- # :root - the directory that the soup paths are relative to;
42
- # defaults to Dir.pwd
43
- # :renderers - a hash of names to classes
44
- # :default_renderer - the class to use when no renderer is provided;
45
- # defaults to 'Vanilla::Renderers::Base'
46
- # :default_layout_snip - the snip to use as a layout when rendering to HTML;
47
- # defaults to 'layout'
48
- # :root_snip - the snip to load for the root ('/') url;
49
- # defaults to 'start'
50
- def initialize(additional_configuration={})
51
- @config = class_config.merge(additional_configuration)
52
- @root_directory = @config[:root] || Dir.pwd
53
- if @config[:soup].nil? && @config[:soups].nil?
54
- @config[:soup] = File.expand_path("soup", @root_directory)
55
- end
56
- @soup = prepare_soup(config)
57
- prepare_renderers(config[:renderers])
33
+ def config
34
+ self.class.config
58
35
  end
59
36
 
60
37
  # Returns a Rack-appropriate 3-element array (via Rack::Response#finish)
@@ -64,7 +41,7 @@ module Vanilla
64
41
  @response = Rack::Response.new
65
42
 
66
43
  begin
67
- output = formatted_render(request.snip, request.part, request.format)
44
+ output = render_in_format(request.snip, request.part, request.format)
68
45
  rescue => e
69
46
  @response.status = 500
70
47
  output = e.to_s + e.backtrace.join("\n")
@@ -76,29 +53,14 @@ module Vanilla
76
53
  @response.finish # returns the array
77
54
  end
78
55
 
79
- def formatted_render(snip, part=nil, format=nil)
80
- case format
81
- when 'html', nil
82
- layout = layout_for(snip)
83
- if layout == snip
84
- "Rendering of the current layout would result in infinite recursion."
85
- else
86
- render(layout)
87
- end
88
- when 'raw', 'css', 'js'
89
- Renderers::Raw.new(self).render(snip, part)
90
- when 'text', 'atom', 'xml'
91
- render(snip, part)
92
- else
93
- raise "Unknown format '#{format}'"
94
- end
95
- end
96
-
97
56
  # render a snip using either the renderer given, or the renderer
98
57
  # specified by the snip's "render_as" property, or Render::Base
99
58
  # if nothing else is given.
59
+ #
60
+ # This method can be useful if a dynasnip or other part of the
61
+ # system needs to get a fully rendered version of a snip.
100
62
  def render(snip, part=:content, args=[], enclosing_snip=snip)
101
- rendering(snip) do |renderer|
63
+ rendering_and_handling_errors(snip) do |renderer|
102
64
  renderer.render(snip, part, args, enclosing_snip)
103
65
  end
104
66
  end
@@ -108,24 +70,12 @@ module Vanilla
108
70
  if snip
109
71
  find_renderer(snip.render_as || snip.extension)
110
72
  else
111
- default_renderer
73
+ config.default_renderer
112
74
  end
113
75
  end
114
76
 
115
77
  def default_layout_snip
116
- soup[config[:default_layout_snip] || 'layout']
117
- end
118
-
119
- def layout_for(snip)
120
- if snip
121
- renderer_for(snip).new(self).layout_for(snip)
122
- else
123
- default_layout_snip
124
- end
125
- end
126
-
127
- def snip(attributes)
128
- @soup << attributes
78
+ soup[config.default_layout_snip]
129
79
  end
130
80
 
131
81
  def register_renderer(klass, *types)
@@ -139,31 +89,15 @@ module Vanilla
139
89
 
140
90
  private
141
91
 
142
- def prepare_renderers(additional_renderers={})
143
- @renderers = Hash.new { config[:default_renderer] || Vanilla::Renderers::Base }
144
- @renderers.merge!({
145
- "base" => Vanilla::Renderers::Base,
146
- "markdown" => Vanilla::Renderers::Markdown,
147
- "bold" => Vanilla::Renderers::Bold,
148
- "erb" => Vanilla::Renderers::Erb,
149
- "rb" => Vanilla::Renderers::Ruby,
150
- "ruby" => Vanilla::Renderers::Ruby,
151
- "haml" => Vanilla::Renderers::Haml,
152
- "raw" => Vanilla::Renderers::Raw,
153
- "textile" => Vanilla::Renderers::Textile
154
- })
155
- additional_renderers.each { |name, klass| register_renderer(klass, name) } if additional_renderers
92
+ def prepare_renderers
93
+ config.renderers.each { |name, klass| register_renderer(klass, name) }
156
94
  end
157
95
 
158
96
  def find_renderer(name)
159
97
  @renderers[(name ? name.downcase : nil)]
160
98
  end
161
99
 
162
- def default_renderer
163
- @renderers[nil]
164
- end
165
-
166
- def rendering(snip)
100
+ def rendering_and_handling_errors(snip)
167
101
  renderer_instance = renderer_for(snip).new(self)
168
102
  yield renderer_instance
169
103
  rescue Exception => e
@@ -173,14 +107,40 @@ module Vanilla
173
107
  e.backtrace.join("\n").gsub("<", "&lt;").gsub(">", "&gt;") + "</pre>"
174
108
  end
175
109
 
176
- def prepare_soup(config)
177
- if config[:soups]
178
- backends = [config[:soups]].flatten.map do |path|
179
- ::Soup::Backends::FileBackend.new(File.expand_path(path, @root_directory))
110
+ def render_in_format(snip, part=nil, format=nil)
111
+ case format
112
+ when 'html', nil
113
+ layout = layout_for(snip)
114
+ if layout == snip
115
+ "Rendering of the current layout would result in infinite recursion."
116
+ else
117
+ render(layout)
118
+ end
119
+ when 'raw', 'css', 'js'
120
+ Renderers::Raw.new(self).render(snip, part)
121
+ when 'text', 'atom', 'xml'
122
+ render(snip, part)
123
+ else
124
+ raise "Unknown format '#{format}'"
125
+ end
126
+ end
127
+
128
+ def layout_for(snip)
129
+ if snip
130
+ renderer_for(snip).new(self).layout_for(snip)
131
+ else
132
+ default_layout_snip
133
+ end
134
+ end
135
+
136
+ def prepare_soup
137
+ if config.soups
138
+ backends = [config.soups].flatten.map do |path|
139
+ ::Soup::Backends::FileBackend.new(File.expand_path(path, config.root))
180
140
  end
181
141
  ::Soup.new(::Soup::Backends::MultiSoup.new(*backends))
182
142
  else
183
- ::Soup.new(::Soup::Backends::FileBackend.new(File.expand_path(config[:soup], @root_directory)))
143
+ raise "No soups defined!"
184
144
  end
185
145
  end
186
146
  end
@@ -0,0 +1,46 @@
1
+ require "vanilla"
2
+
3
+ module Vanilla
4
+
5
+ # Create a new Vanilla application
6
+ # Configuration options:
7
+ #
8
+ # :soup - provide the path to the soup data
9
+ # :soups - provide an array of paths to soup data
10
+ # :root - the directory that the soup paths are relative to;
11
+ # defaults to Dir.pwd
12
+ # :renderers - a hash of names to classes
13
+ # :default_renderer - the class to use when no renderer is provided;
14
+ # defaults to 'Vanilla::Renderers::Base'
15
+ # :default_layout_snip - the snip to use as a layout when rendering to HTML;
16
+ # defaults to 'layout'
17
+ # :root_snip - the snip to load for the root ('/') url;
18
+ # defaults to 'start'
19
+ class Config
20
+ attr_accessor :root,
21
+ :root_snip,
22
+ :soups,
23
+ :renderers,
24
+ :default_layout_snip,
25
+ :default_renderer
26
+
27
+ def initialize
28
+ @root = Dir.pwd
29
+ @root_snip = "start"
30
+ @soups = ["soups/base", "soups/system"]
31
+ @default_layout_snip = "layout"
32
+ @default_renderer = Vanilla::Renderers::Base
33
+ @renderers = {
34
+ "base" => Vanilla::Renderers::Base,
35
+ "markdown" => Vanilla::Renderers::Markdown,
36
+ "bold" => Vanilla::Renderers::Bold,
37
+ "erb" => Vanilla::Renderers::Erb,
38
+ "rb" => Vanilla::Renderers::Ruby,
39
+ "ruby" => Vanilla::Renderers::Ruby,
40
+ "haml" => Vanilla::Renderers::Haml,
41
+ "raw" => Vanilla::Renderers::Raw,
42
+ "textile" => Vanilla::Renderers::Textile
43
+ }
44
+ end
45
+ end
46
+ end
@@ -6,7 +6,7 @@ end
6
6
  def app(reload=false)
7
7
  if !@__vanilla_console_app || reload
8
8
  load "application.rb"
9
- @__vanilla_console_app = Application.new
9
+ @__vanilla_console_app = Vanilla.apps.first.new
10
10
  end
11
11
  @__vanilla_console_app
12
12
  end
@@ -3,7 +3,6 @@ require 'vanilla/snip_reference_parser'
3
3
  module Vanilla
4
4
  module Renderers
5
5
  class Base
6
- include Routes
7
6
 
8
7
  # Render a snip.
9
8
  def self.render(snip, part=:content)
@@ -20,11 +19,22 @@ module Vanilla
20
19
  @app = app
21
20
  end
22
21
 
23
- # defined for the routes
24
22
  def soup
25
23
  @app.soup
26
24
  end
27
25
 
26
+ def url_to(*args)
27
+ @app.url_to(*args)
28
+ end
29
+
30
+ def link_to(link_text, snip_name=link_text, part=nil)
31
+ if soup[snip_name]
32
+ %{<a href="#{url_to(snip_name, part)}">#{link_text}</a>}
33
+ else
34
+ %{<a class="missing" href="#{url_to(snip_name, part)}">#{link_text}</a>}
35
+ end
36
+ end
37
+
28
38
  def self.snip_regexp
29
39
  %r{(\{[\w\-_\d\.\"\' ]+( +[^\}.]+)?\})}
30
40
  end
@@ -13,6 +13,7 @@ module Vanilla
13
13
  determine_request_uri_parts
14
14
  end
15
15
 
16
+ # returns the parameters of the request, with every key as a symbol
16
17
  def params
17
18
  # Don't you just love how terse functional programming tends to look like maths?
18
19
  @symbolised_params ||= @rack_request.params.inject({}) { |p, (k,v)| p[k.to_sym] = v; p }
@@ -24,47 +25,21 @@ module Vanilla
24
25
  @app.soup[snip_name]
25
26
  end
26
27
 
27
- def cookies
28
- @rack_request.cookies
29
- end
30
-
31
- def ip
32
- @rack_request.env["REMOTE_ADDR"]
33
- end
34
-
35
- def session
36
- @rack_request.env["rack.session"]
28
+ def method_missing(name, *args)
29
+ if @rack_request.respond_to?(name)
30
+ @rack_request.send(name, *args)
31
+ else
32
+ super
33
+ end
37
34
  end
38
35
 
39
36
  private
40
37
 
41
38
  def determine_request_uri_parts
42
- @snip_name, @part, @format = request_uri_parts(@rack_request)
39
+ @snip_name, @part, @format = Vanilla::Routing.parse(@rack_request.path_info)
40
+ @snip_name ||= @app.config.root_snip
43
41
  @format ||= 'html'
44
42
  @method = (params.delete(:_method) || @rack_request.request_method).downcase
45
43
  end
46
-
47
- def uri_path(request)
48
- request.path_info
49
- end
50
-
51
- URL_ROOT = /\A\/?\Z/ # i.e. / or nothing
52
- URL_SNIP = /\A\/([\w\-\s]+)(\/|\.(\w+))?\Z/ # i.e. /start, /start.html
53
- URL_SNIP_AND_PART = /\A\/([\w\-\s]+)\/([\w\-\s]+)(\/|\.(\w+))?\Z/ # i.e. /blah/part, /blah/part.raw
54
-
55
- # Returns an array of the requested snip, part and format
56
- def request_uri_parts(request)
57
- case CGI.unescape(uri_path(request))
58
- when URL_ROOT
59
- [@app.config[:root_snip] || 'start', nil, 'html']
60
- when URL_SNIP
61
- [$1, nil, $3]
62
- when URL_SNIP_AND_PART
63
- [$1, $2, $4]
64
- else
65
- []
66
- end
67
- end
68
-
69
44
  end
70
45
  end