spontaneous 0.2.0.beta4 → 0.2.0.beta5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (205) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -0
  3. data/Gemfile +11 -6
  4. data/Readme.markdown +136 -69
  5. data/application/css/core.css.scss +27 -7
  6. data/application/css/editing.css.scss +4 -26
  7. data/application/css/schema_error.css.scss +22 -0
  8. data/application/js/content.js +11 -3
  9. data/application/js/edit_panel.js +1 -4
  10. data/application/js/field/file.js +17 -0
  11. data/application/js/field/image.js +30 -21
  12. data/application/js/field/string.js +4 -1
  13. data/application/js/field_preview.js +21 -16
  14. data/application/js/publish.js +6 -6
  15. data/application/js/types.js +5 -13
  16. data/application/js/views.js +2 -2
  17. data/application/js/views/box_view.js +3 -2
  18. data/application/js/views/page_piece_view.js +1 -1
  19. data/application/js/views/piece_view.js +1 -1
  20. data/application/views/schema_modification_error.html.erb +13 -3
  21. data/db/migrations/20131104101935_site_must_publish_all.rb +14 -0
  22. data/lib/spontaneous.rb +0 -1
  23. data/lib/spontaneous/box_style.rb +15 -9
  24. data/lib/spontaneous/capistrano/deploy.rb +13 -1
  25. data/lib/spontaneous/change.rb +11 -13
  26. data/lib/spontaneous/cli.rb +5 -2
  27. data/lib/spontaneous/cli/assets.rb +7 -1
  28. data/lib/spontaneous/cli/console.rb +7 -1
  29. data/lib/spontaneous/cli/content.rb +35 -0
  30. data/lib/spontaneous/cli/fields.rb +3 -2
  31. data/lib/spontaneous/cli/generate.rb +5 -2
  32. data/lib/spontaneous/cli/server.rb +12 -8
  33. data/lib/spontaneous/cli/site.rb +12 -12
  34. data/lib/spontaneous/cli/user.rb +28 -14
  35. data/lib/spontaneous/collections/box_set.rb +4 -4
  36. data/lib/spontaneous/collections/field_set.rb +4 -4
  37. data/lib/spontaneous/collections/prototype_set.rb +12 -4
  38. data/lib/spontaneous/data_mapper.rb +11 -7
  39. data/lib/spontaneous/data_mapper/content_model.rb +8 -0
  40. data/lib/spontaneous/data_mapper/content_model/associations.rb +1 -1
  41. data/lib/spontaneous/data_mapper/dataset.rb +14 -2
  42. data/lib/spontaneous/data_mapper/scope.rb +33 -13
  43. data/lib/spontaneous/facet.rb +4 -0
  44. data/lib/spontaneous/field.rb +12 -12
  45. data/lib/spontaneous/field/base.rb +27 -22
  46. data/lib/spontaneous/field/boolean.rb +4 -4
  47. data/lib/spontaneous/field/date.rb +2 -2
  48. data/lib/spontaneous/field/file.rb +24 -18
  49. data/lib/spontaneous/field/html.rb +1 -1
  50. data/lib/spontaneous/field/image.rb +6 -19
  51. data/lib/spontaneous/field/location.rb +1 -1
  52. data/lib/spontaneous/field/long_string.rb +3 -3
  53. data/lib/spontaneous/field/markdown.rb +3 -3
  54. data/lib/spontaneous/field/select.rb +2 -2
  55. data/lib/spontaneous/field/string.rb +2 -2
  56. data/lib/spontaneous/field/tags.rb +2 -2
  57. data/lib/spontaneous/field/update.rb +21 -20
  58. data/lib/spontaneous/field/webvideo.rb +6 -6
  59. data/lib/spontaneous/field/webvideo/fallback.rb +2 -2
  60. data/lib/spontaneous/field/webvideo/vimeo.rb +7 -7
  61. data/lib/spontaneous/generators/site.rb +2 -2
  62. data/lib/spontaneous/generators/site/Gemfile.tt +5 -1
  63. data/lib/spontaneous/layout.rb +2 -2
  64. data/lib/spontaneous/media.rb +1 -0
  65. data/lib/spontaneous/media/file.rb +6 -5
  66. data/lib/spontaneous/media/image/attributes.rb +4 -0
  67. data/lib/spontaneous/media/image/renderable.rb +4 -4
  68. data/lib/spontaneous/media/store.rb +22 -0
  69. data/lib/spontaneous/{storage → media/store}/backend.rb +1 -1
  70. data/lib/spontaneous/{storage → media/store}/cloud.rb +1 -1
  71. data/lib/spontaneous/{storage → media/store}/local.rb +1 -1
  72. data/lib/spontaneous/media/temp_file.rb +1 -1
  73. data/lib/spontaneous/model.rb +10 -7
  74. data/lib/spontaneous/model/action.rb +7 -0
  75. data/lib/spontaneous/model/action/clean.rb +87 -0
  76. data/lib/spontaneous/model/box/allowed_types.rb +15 -1
  77. data/lib/spontaneous/model/core.rb +10 -0
  78. data/lib/spontaneous/model/core/aliases.rb +1 -1
  79. data/lib/spontaneous/model/core/content_groups.rb +1 -1
  80. data/lib/spontaneous/model/core/fields.rb +1 -1
  81. data/lib/spontaneous/model/core/modifications.rb +2 -2
  82. data/lib/spontaneous/model/core/page_search.rb +4 -0
  83. data/lib/spontaneous/model/core/publishing.rb +4 -17
  84. data/lib/spontaneous/model/core/render.rb +4 -4
  85. data/lib/spontaneous/model/core/styles.rb +2 -2
  86. data/lib/spontaneous/model/core/visibility.rb +6 -2
  87. data/lib/spontaneous/model/page.rb +6 -2
  88. data/lib/spontaneous/model/page/controllers.rb +55 -17
  89. data/lib/spontaneous/model/page/formats.rb +12 -7
  90. data/lib/spontaneous/model/page/layouts.rb +2 -2
  91. data/lib/spontaneous/model/page/locks.rb +4 -1
  92. data/lib/spontaneous/model/page/page_tree.rb +40 -6
  93. data/lib/spontaneous/output.rb +14 -52
  94. data/lib/spontaneous/output/context.rb +11 -39
  95. data/lib/spontaneous/output/context/navigation.rb +31 -0
  96. data/lib/spontaneous/output/format.rb +15 -19
  97. data/lib/spontaneous/output/renderable.rb +99 -0
  98. data/lib/spontaneous/output/store.rb +24 -0
  99. data/lib/spontaneous/output/store/backend.rb +52 -0
  100. data/lib/spontaneous/output/store/file.rb +77 -0
  101. data/lib/spontaneous/output/store/moneta.rb +117 -0
  102. data/lib/spontaneous/output/store/revision.rb +34 -0
  103. data/lib/spontaneous/output/store/store.rb +15 -0
  104. data/lib/spontaneous/output/store/transaction.rb +44 -0
  105. data/lib/spontaneous/output/template/engine.rb +17 -7
  106. data/lib/spontaneous/output/template/renderer.rb +66 -40
  107. data/lib/spontaneous/page_lock.rb +5 -7
  108. data/lib/spontaneous/page_piece.rb +2 -2
  109. data/lib/spontaneous/permissions/user.rb +14 -7
  110. data/lib/spontaneous/plugins/application/features.rb +8 -4
  111. data/lib/spontaneous/plugins/application/state.rb +12 -6
  112. data/lib/spontaneous/prototypes/box_prototype.rb +9 -10
  113. data/lib/spontaneous/prototypes/field_prototype.rb +66 -15
  114. data/lib/spontaneous/publishing/immediate.rb +30 -26
  115. data/lib/spontaneous/rack.rb +12 -7
  116. data/lib/spontaneous/rack/back.rb +43 -37
  117. data/lib/spontaneous/rack/back/base.rb +4 -4
  118. data/lib/spontaneous/rack/back/changes.rb +2 -2
  119. data/lib/spontaneous/rack/back/file.rb +16 -24
  120. data/lib/spontaneous/rack/back/map.rb +5 -5
  121. data/lib/spontaneous/rack/back/preview.rb +3 -4
  122. data/lib/spontaneous/rack/back/schema.rb +1 -1
  123. data/lib/spontaneous/rack/back/site.rb +6 -7
  124. data/lib/spontaneous/rack/front.rb +19 -16
  125. data/lib/spontaneous/rack/middleware/authenticate.rb +3 -3
  126. data/lib/spontaneous/rack/middleware/reloader.rb +3 -2
  127. data/lib/spontaneous/rack/middleware/scope.rb +25 -19
  128. data/lib/spontaneous/rack/page_controller.rb +164 -13
  129. data/lib/spontaneous/rack/public.rb +23 -62
  130. data/lib/spontaneous/rack/static.rb +2 -3
  131. data/lib/spontaneous/schema.rb +27 -8
  132. data/lib/spontaneous/schema/schema_modification.rb +9 -1
  133. data/lib/spontaneous/schema/uid.rb +2 -2
  134. data/lib/spontaneous/schema/uid_map.rb +3 -2
  135. data/lib/spontaneous/search/database.rb +2 -2
  136. data/lib/spontaneous/search/field.rb +5 -3
  137. data/lib/spontaneous/search/index.rb +12 -7
  138. data/lib/spontaneous/search/results.rb +5 -3
  139. data/lib/spontaneous/server.rb +2 -2
  140. data/lib/spontaneous/site.rb +10 -3
  141. data/lib/spontaneous/site/features.rb +26 -6
  142. data/lib/spontaneous/site/helpers.rb +9 -12
  143. data/lib/spontaneous/site/level.rb +7 -9
  144. data/lib/spontaneous/site/map.rb +9 -11
  145. data/lib/spontaneous/site/paths.rb +5 -5
  146. data/lib/spontaneous/site/publishing.rb +83 -80
  147. data/lib/spontaneous/site/schema.rb +1 -7
  148. data/lib/spontaneous/site/search.rb +8 -18
  149. data/lib/spontaneous/site/selectors.rb +60 -54
  150. data/lib/spontaneous/site/state.rb +36 -30
  151. data/lib/spontaneous/site/storage.rb +10 -16
  152. data/lib/spontaneous/state.rb +8 -0
  153. data/lib/spontaneous/style.rb +32 -33
  154. data/lib/spontaneous/version.rb +1 -1
  155. data/spontaneous.gemspec +22 -21
  156. data/test/fixtures/public/templates/layouts/default.html.cut +1 -1
  157. data/test/fixtures/public/templates/layouts/default.pdf.cut +1 -1
  158. data/test/fixtures/public/templates/layouts/default.rss.cut +1 -1
  159. data/test/fixtures/search/config/indexes.rb +1 -1
  160. data/test/fixtures/serialisation/class_hash.yaml.erb +13 -1
  161. data/test/fixtures/serialisation/root_hash.yaml.erb +10 -0
  162. data/test/functional/test_application.rb +20 -24
  163. data/test/functional/test_back.rb +26 -27
  164. data/test/functional/test_cli.rb +146 -0
  165. data/test/functional/test_front.rb +287 -216
  166. data/test/functional/test_user_manager.rb +1 -1
  167. data/test/test_helper.rb +15 -11
  168. data/test/unit/test_alias.rb +32 -25
  169. data/test/unit/test_asset_bundler.rb +1 -1
  170. data/test/unit/test_assets.rb +34 -33
  171. data/test/unit/test_authentication.rb +1 -1
  172. data/test/unit/test_boxes.rb +16 -2
  173. data/test/unit/test_changesets.rb +23 -11
  174. data/test/unit/test_content.rb +15 -0
  175. data/test/unit/test_context.rb +139 -0
  176. data/test/unit/test_controllers.rb +374 -0
  177. data/test/{experimental → unit}/test_crypt.rb +0 -0
  178. data/test/unit/test_datamapper.rb +260 -237
  179. data/test/unit/test_datamapper_content.rb +42 -12
  180. data/test/{experimental → unit}/test_features.rb +85 -3
  181. data/test/unit/test_fields.rb +117 -42
  182. data/test/unit/test_formats.rb +11 -1
  183. data/test/unit/test_generators.rb +2 -2
  184. data/test/unit/test_helpers.rb +7 -8
  185. data/test/unit/test_images.rb +39 -2
  186. data/test/unit/test_layouts.rb +14 -12
  187. data/test/unit/test_media.rb +32 -23
  188. data/test/unit/test_output_store.rb +342 -0
  189. data/test/unit/test_page.rb +8 -1
  190. data/test/unit/test_permissions.rb +11 -7
  191. data/test/unit/test_plugins.rb +3 -3
  192. data/test/unit/test_prototype_set.rb +8 -1
  193. data/test/unit/test_publishing.rb +67 -54
  194. data/test/unit/test_render.rb +91 -38
  195. data/test/unit/test_revisions.rb +4 -4
  196. data/test/unit/test_schema.rb +109 -84
  197. data/test/unit/test_search.rb +42 -42
  198. data/test/unit/test_serialisation.rb +3 -2
  199. data/test/unit/test_site.rb +39 -27
  200. data/test/unit/test_storage.rb +9 -6
  201. data/test/unit/test_styles.rb +25 -32
  202. data/test/unit/test_templates.rb +8 -4
  203. metadata +89 -54
  204. data/lib/spontaneous/model/page/request.rb +0 -105
  205. data/lib/spontaneous/storage.rb +0 -22
@@ -1,32 +1,14 @@
1
1
 
2
2
  module Spontaneous::Output::Context
3
3
  autoload :RenderCache, 'spontaneous/output/context/render_cache'
4
+ autoload :Navigation, 'spontaneous/output/context/navigation'
4
5
 
5
6
  module ContextCore
6
7
  include RenderCache
7
- attr_accessor :_renderer
8
-
9
- def navigation(depth = 1, &block)
10
- case depth
11
- when 0, :root
12
- root
13
- when 1, :section
14
- navigation_at_depth(1, &block)
15
- else
16
- navigation_at_depth(depth, &block)
17
- end
18
- end
8
+ include Navigation
19
9
 
20
- def navigation_at_depth(depth = 1)
21
- current_page = __target.page
22
- __pages_at_depth(current_page, depth).each do |p|
23
- yield(p, current_page.active?(p))
24
- end
25
- end
10
+ attr_accessor :_renderer, :site
26
11
 
27
- def __pages_at_depth(origin_page, depth)
28
- origin_page.at_depth(depth)
29
- end
30
12
 
31
13
  def page
32
14
  __target.page
@@ -45,11 +27,11 @@ module Spontaneous::Output::Context
45
27
  end
46
28
 
47
29
  def root
48
- Spontaneous::Site.root
30
+ site.home
49
31
  end
50
32
 
51
33
  def site_page(path)
52
- Spontaneous::Site[path]
34
+ site[path]
53
35
  end
54
36
 
55
37
  def asset_environment
@@ -66,10 +48,6 @@ module Spontaneous::Output::Context
66
48
  "url(#{asset_path(path, options)})"
67
49
  end
68
50
 
69
- def site
70
- Spontaneous::Site.instance
71
- end
72
-
73
51
  def publishing?
74
52
  false
75
53
  end
@@ -136,15 +114,9 @@ module Spontaneous::Output::Context
136
114
  end
137
115
 
138
116
  def __decode_params(param)
139
- unless param.is_a?(String)
140
- @_render_method ||= "to_#{__loader.format}".to_sym
141
- if param.respond_to?(@_render_method)
142
- param = param.send(@_render_method)
143
- else
144
- if param.respond_to?(:render)
145
- param = __render_content(param) #render(param, param.template)
146
- end
147
- end
117
+ return param if param.is_a?(String)
118
+ if param.respond_to?(:render)
119
+ param = __render_content(param) #render(param, param.template)
148
120
  end
149
121
  param.to_s
150
122
  end
@@ -153,9 +125,9 @@ module Spontaneous::Output::Context
153
125
  # use of shared caches that are held by it.
154
126
  def __render_content(content)
155
127
  if content.respond_to?(:render_using)
156
- content.render_using(_renderer, __format, self)
128
+ content.render_using(_renderer, __format, {}, self)
157
129
  else
158
- content.render(__format, self)
130
+ content.render(__format, {}, self)
159
131
  end
160
132
  end
161
133
  end
@@ -186,7 +158,7 @@ module Spontaneous::Output::Context
186
158
  end
187
159
  end
188
160
 
189
- def __pages_at_depth(origin_page, depth)
161
+ def __pages_at_depth(origin_page, depth, opts = {})
190
162
  _with_render_cache("pages_at_depth.#{origin_page.id}.#{depth}") do
191
163
  super
192
164
  end
@@ -0,0 +1,31 @@
1
+ module Spontaneous::Output::Context
2
+ module Navigation
3
+ def navigation(opts = {}, &block)
4
+ depth = opts.delete(:depth) || 1
5
+ case depth
6
+ when 0, :root
7
+ root
8
+ when 1, :section
9
+ navigation_at_depth(1, opts, &block)
10
+ else
11
+ navigation_at_depth(depth, opts, &block)
12
+ end
13
+ end
14
+
15
+ def navigation_at_depth(depth, opts = {})
16
+ current_page = __target.page
17
+ pages = __pages_at_depth(current_page, depth, opts).map { |p| [p, current_page.active?(p)] }
18
+ if block_given?
19
+ pages.each do |p, active|
20
+ yield(p, active)
21
+ end
22
+ else
23
+ pages
24
+ end
25
+ end
26
+
27
+ def __pages_at_depth(origin_page, depth, opts = {})
28
+ origin_page.at_depth(depth, opts)
29
+ end
30
+ end
31
+ end
@@ -65,6 +65,10 @@ module Spontaneous::Output
65
65
  @name
66
66
  end
67
67
 
68
+ def to_s
69
+ to_sym.to_s
70
+ end
71
+
68
72
  def extension(is_dynamic = false, dynamic_extension = Spontaneous::Output::Template.extension)
69
73
  if (override = @options[:extension])
70
74
  return normalise_extension(override.to_s)
@@ -99,8 +103,8 @@ module Spontaneous::Output
99
103
  @options[:postprocess]
100
104
  end
101
105
 
102
- def context
103
- Spontaneous::Site.context (helper_formats + [name]).uniq.compact
106
+ def context(site = Spontaneous.instance)
107
+ site.context (helper_formats + [name]).uniq.compact
104
108
  end
105
109
  end
106
110
 
@@ -121,20 +125,20 @@ module Spontaneous::Output
121
125
  content.model
122
126
  end
123
127
 
124
- def render(params = {}, *args)
125
- render_using(default_renderer, params, *args)
128
+ def render(params = {}, parent_context = nil)
129
+ render_using(default_renderer, params, parent_context)
126
130
  end
127
131
 
128
- def render_using(renderer, params, *args)
132
+ def render_using(renderer, params = {}, parent_context = nil)
129
133
  before_render
130
- output = render_page(renderer, params, *args)
134
+ output = render_page(renderer, params, parent_context)
131
135
  output = postprocess(output)
132
136
  after_render(output)
133
137
  output
134
138
  end
135
139
 
136
140
  def default_renderer
137
- Spontaneous::Output.renderer
141
+ Spontaneous::Output.default_renderer
138
142
  end
139
143
 
140
144
  def before_render
@@ -150,21 +154,13 @@ module Spontaneous::Output
150
154
  output
151
155
  end
152
156
 
153
- def render_page(renderer, params = {}, *args)
154
- renderer.render(self, params, *args)
157
+ def render_page(renderer, params = {}, parent_context = nil)
158
+ renderer.render(self, params, parent_context)
155
159
  end
156
160
 
157
- def publish_page(renderer, revision)
161
+ def publish_page(renderer, revision, transaction)
158
162
  rendered = render_using(renderer, {:revision => revision})
159
- path = output_path(revision, rendered)
160
- File.open(path, 'w') do |file|
161
- case rendered
162
- when IO
163
- IO.copy_stream(rendered, file)
164
- else
165
- file.write(rendered.to_s)
166
- end
167
- end
163
+ transaction.store(self, renderer.is_dynamic_template?(rendered), rendered)
168
164
  end
169
165
 
170
166
  def output_path(revision, render)
@@ -0,0 +1,99 @@
1
+ # The Renderable class is wrapped around the targets of the render context in
2
+ # order to solve a particular problem around passing parameters/locals to
3
+ # field & content render calls from within templates.
4
+
5
+ # Say you wanted to render an image field but pass it some size parameters
6
+ # in order to insert it at a particular size. You would do this in the template
7
+ # like this:
8
+ #
9
+ # ${ image.thumbnail(width: 50, height: 50) }
10
+ #
11
+ # this renders the image into the template using the following call stack:
12
+ #
13
+ # context.__decode_params(context.image.thumbnail(width: 50, height: 50)).render(__format, {}, ...)
14
+ #
15
+ # The important thing to notice is that the params passed to the `thumbnail` method
16
+ # are not passed into the final render and so will not result in the image being inserted
17
+ # with the custom size params.
18
+ #
19
+ # Thie Renderable class solves this by intercepting all calls that return an object
20
+ # that responds to #render and wrapping them in a Renderable instance that maintains
21
+ # any params passed to them. So in the above method chain results gives:
22
+ #
23
+ # context.image #=> Renderable.new(page.image, {})
24
+ # context.image.thumbnail #=> Renderable.new(page.image.thumbnail, {width: 50, height: 50})
25
+ #
26
+ # and so the final render call will be made to the final Renderable instance which will
27
+ # invoke page.image.thumbnail#render with the :width & :height locals it's saved from the
28
+ # original template call, equivalent to:
29
+ #
30
+ # page.image.thumbnail.render(__format, {width: 50, height: 50}, ...)
31
+
32
+ module Spontaneous::Output
33
+ # A convenience method that tests to see if an object is 'renderable'
34
+ # that is it responds to #render
35
+ # and if so wraps it in a Renderable object with the given locals
36
+ def self.Renderable(obj, locals)
37
+ if obj.respond_to?(:render)
38
+ Renderable.new(obj, locals)
39
+ else
40
+ obj
41
+ end
42
+ end
43
+
44
+ class Renderable < BasicObject
45
+
46
+ def initialize(target, template_params = {})
47
+ @target, @template_params = target, (template_params || {})
48
+ end
49
+
50
+ def method_missing(name, *args)
51
+ result = nil
52
+ locals = __extract_locals__(args)
53
+
54
+ if ::Kernel.block_given?
55
+ # If we are passed a block then we want to make sure that any values
56
+ # yielded from that block are wrapped in Renderable instances
57
+ # if appropriate
58
+ block = ::Proc.new
59
+ result = @target.send(name, *args) do |*block_args|
60
+ renderable_args = block_args.map { |arg| ::Spontaneous::Output::Renderable(arg, locals) }
61
+ block.call(*renderable_args)
62
+ end
63
+ else
64
+ result = ::Spontaneous::Output::Renderable(@target.send(name, *args), locals)
65
+ end
66
+ result
67
+ end
68
+
69
+ def respond_to?(method_name, include_private = false)
70
+ @target.respond_to?(method_name, include_private) || super
71
+ end
72
+
73
+ alias_method :respond_to_missing?, :respond_to?
74
+
75
+ def render(format = :html, locals = {}, parent_context = nil)
76
+ @target.render(format, __render_locals__(locals), parent_context)
77
+ end
78
+
79
+ def render_using(renderer, format = :html, locals = {}, parent_context = nil)
80
+ @target.render_using(renderer, format, __render_locals__(locals), parent_context)
81
+ end
82
+
83
+ private
84
+
85
+ def __render_locals__(locals)
86
+ return @template_params if locals.nil?
87
+ locals.merge(@template_params || {})
88
+ end
89
+
90
+ # This is a non-destructive version of activesupport's Array#extract_options!
91
+ def __extract_locals__(args)
92
+ if args.last.is_a?(::Hash)
93
+ args.last
94
+ else
95
+ {}
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,24 @@
1
+ module Spontaneous::Output
2
+ module Store
3
+ autoload :Backend, 'spontaneous/output/store/backend'
4
+ autoload :File, 'spontaneous/output/store/file'
5
+ autoload :Moneta, 'spontaneous/output/store/moneta'
6
+ autoload :Revision, 'spontaneous/output/store/revision'
7
+ autoload :Store, 'spontaneous/output/store/store'
8
+ autoload :Transaction, 'spontaneous/output/store/transaction'
9
+
10
+ # Initializes a new store with the provided options
11
+ # Uses moneta as a unified interface to except in the special case of the :File
12
+ # store which needs to work in a way compatible with the use of
13
+ # an frontend HTTP proxy, e.g. Nginx
14
+ def self.new(backend_class, options = {})
15
+ backend = case backend_class
16
+ when :File
17
+ File.new(options[:root])
18
+ else
19
+ Moneta.new(backend_class, options)
20
+ end
21
+ Store.new(backend)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,52 @@
1
+ module Spontaneous::Output::Store
2
+ class Backend
3
+ unless defined?(STATIC_PATH)
4
+ STATIC_PATH, PROTECTED_PATH, DYNAMIC_PATH = %w(static protected dynamic).map(&:freeze)
5
+ end
6
+
7
+ def store_static(revision, key, template, transaction = nil)
8
+ store(revision, STATIC_PATH, key, template, transaction)
9
+ end
10
+
11
+ def store_protected(revision, key, template, transaction = nil)
12
+ store(revision, PROTECTED_PATH, key, template, transaction)
13
+ end
14
+
15
+ def store_dynamic(revision, key, template, transaction = nil)
16
+ store(revision, DYNAMIC_PATH, key, template, transaction)
17
+ end
18
+
19
+ def load_static(revision, key)
20
+ load(revision, STATIC_PATH, key)
21
+ end
22
+
23
+ def load_protected(revision, key)
24
+ load(revision, PROTECTED_PATH, key)
25
+ end
26
+
27
+ def load_dynamic(revision, key)
28
+ load(revision, DYNAMIC_PATH, key)
29
+ end
30
+
31
+ def output_key(output, dynamic = false)
32
+ path = output.page.path
33
+ ext = output.extension(dynamic)
34
+ case path
35
+ when Spontaneous::SLASH
36
+ "/index#{ext}"
37
+ else
38
+ "#{path}#{ext}"
39
+ end
40
+ end
41
+
42
+ protected
43
+
44
+ def store(revision, partition, path, template, transaction)
45
+ raise NotImplementedError
46
+ end
47
+
48
+ def load(revision, partition, path)
49
+ raise NotImplementedError
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,77 @@
1
+ require 'fileutils'
2
+
3
+ module Spontaneous::Output::Store
4
+ # The basic template storage backend.
5
+ #
6
+ # It stores static, protected & dynamic templates in different
7
+ # directories so that a reverse proxy conf can be pointed directly
8
+ # at the `static` area and (in theory) protected templates could
9
+ # be served using a "sendfile" header.
10
+ class File < Backend
11
+
12
+ def initialize(root)
13
+ @root = root
14
+ end
15
+
16
+ def add_revision(revision, keys)
17
+ ensure_dir revision_path(revision)
18
+ end
19
+
20
+ def revisions
21
+ dirs = ::Dir.entries(@root).select { |dir| /^[0-9]+$/ === dir }
22
+ dirs.map { |dir| dir.to_i(10) }.sort
23
+ end
24
+
25
+ def delete_revision(revision, keys = nil)
26
+ if (dir = revision_path(revision)) && ::File.exist?(dir)
27
+ ::FileUtils.rm_r(dir)
28
+ end
29
+ end
30
+
31
+ protected
32
+
33
+ def store(revision, partition, key, template, transaction)
34
+ ::File.open(path!(revision, partition, key, transaction), 'wb') { |f| f.write(template) }
35
+ end
36
+
37
+ def load(revision, partition, key)
38
+ read(path(revision, partition, key))
39
+ end
40
+
41
+ def read(path)
42
+ return nil unless ::File.exist?(path)
43
+ ::File.open(path, 'r:UTF-8')
44
+ end
45
+
46
+ def pad_revision(revision)
47
+ revision.to_s.rjust(5, "0")
48
+ end
49
+
50
+ def path!(revision, partition, key, transaction)
51
+ ensure_path path(revision, partition, key, transaction)
52
+ end
53
+
54
+ def path(revision, partition, path, transaction = nil)
55
+ transaction.push(key(revision, partition, path)) if transaction
56
+ ::File.join(revision_path(revision), partition, path)
57
+ end
58
+
59
+ def revision_path(revision)
60
+ ::File.join(@root, pad_revision(revision))
61
+ end
62
+
63
+ def ensure_path(path)
64
+ ensure_dir ::File.dirname(path)
65
+ path
66
+ end
67
+
68
+ def ensure_dir(dir)
69
+ FileUtils.mkdir_p(dir) unless ::File.exist?(dir)
70
+ dir
71
+ end
72
+
73
+ def key(revision, partition, path)
74
+ ::File.join(pad_revision(revision), partition, path)
75
+ end
76
+ end
77
+ end