sitepress-rails 2.0.0.beta4 → 2.0.0.beta9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 135962bb8c744104c2119d592dc46d33d3bf86d1f0138f9cc8484416770f776d
4
- data.tar.gz: f9ec3cea5fb45637deef576515ecf039f943087d29c7d6255e2bf98070f7ede5
3
+ metadata.gz: 51637f7a749539e6a1fc3b23d4b295a477fc36231ff0ee9ad0bdfb4c00fadc6c
4
+ data.tar.gz: d30e8a536f64ce2b37ce4206907b2d18c60e974d82a540f444510fc0cee1b925
5
5
  SHA512:
6
- metadata.gz: 8daefdf6dde16ba78128f812aa1d8fa2e0fc5cf0feeb639844207236d9226231925b7915908d1f31895eb306eabeae54c2c37a5e3e2026cd078b4745754128f7
7
- data.tar.gz: af534c613a3d31f8dd7e61a7dd131bf01b71cd5915fe9b2ae2876b184ef9f68c715075d40d450fc7e07728c5779150bbbedf5224f07403859ca9fa8a48fb70e5
6
+ metadata.gz: d2f9e7f4270b2182d1a5a9347d1a4b4fce7db9cd088b5d0411e7dc0d33200daca1be262ba83a320cb6ce6eec1c50fe466d72856cad6d514473bcedae39ef3d8d
7
+ data.tar.gz: be42b1679592c410e1b30c05ca7118d568b6d4a8c4414b91dcbf984ffe105782ea9e28a9896eee4f5d25d81fe4e0c34d00faa85058a959f0e8caf4fdd8e381e3
@@ -4,7 +4,7 @@ module Sitepress
4
4
  # hosts like S3. To achieve this effect, we have to compile `pages/blah.html.haml`
5
5
  # to a folder with the filename `index.html`, so the final path would be `/blah/index.html`
6
6
  class DirectoryIndexPath < IndexPath
7
- def path_with_default_format
7
+ def filename_with_default_format
8
8
  File.join(node.name, "#{node.default_name}.#{format}")
9
9
  end
10
10
  end
@@ -4,16 +4,16 @@ module Sitepress
4
4
  # pages too, mainly grabbing the root, which doesn't have a name in the node, to the default_name
5
5
  # of the node, which is usually `index`.
6
6
  class IndexPath < RootPath
7
- def path_without_format
7
+ def filename_without_format
8
8
  node.name
9
9
  end
10
10
 
11
- def path_with_format
11
+ def filename_with_format
12
12
  "#{node.name}.#{format}"
13
13
  end
14
14
 
15
- def path_with_default_format
16
- path_with_format
15
+ def filename_with_default_format
16
+ filename_with_format
17
17
  end
18
18
  end
19
19
  end
@@ -13,27 +13,31 @@ module Sitepress
13
13
  @resource = resource
14
14
  end
15
15
 
16
- def path
16
+ def filename
17
17
  if format.nil?
18
- path_without_format
18
+ filename_without_format
19
19
  elsif format == node.default_format
20
- path_with_default_format
20
+ filename_with_default_format
21
21
  elsif format
22
- path_with_format
22
+ filename_with_format
23
23
  end
24
24
  end
25
25
 
26
+ def path
27
+ File.join(*resource.lineage, filename)
28
+ end
29
+
26
30
  protected
27
- def path_without_format
31
+ def filename_without_format
28
32
  node.default_name
29
33
  end
30
34
 
31
- def path_with_format
35
+ def filename_with_format
32
36
  "#{node.default_name}.#{format}"
33
37
  end
34
38
 
35
- def path_with_default_format
36
- path_with_format
39
+ def filename_with_default_format
40
+ filename_with_format
37
41
  end
38
42
  end
39
43
  end
@@ -6,63 +6,63 @@ module Sitepress
6
6
  class Compiler
7
7
  include FileUtils
8
8
 
9
- class ResourceCompiler
10
- attr_reader :resource
9
+ attr_reader :site, :root_path
11
10
 
12
- def initialize(resource)
13
- @resource = resource
14
- end
15
-
16
- def compilation_path
17
- File.join(*resource.lineage, compilation_filename)
18
- end
11
+ # If a resource can't render, it will raise an exception and stop the compiler. Sometimes
12
+ # its useful to turn off errors so you can get through a full compilation and see how many
13
+ # errors you encounter along the way. To do that, you'd set `raise_exception_on_error` to
14
+ # `false` and the compile will get through all the resources.
15
+ attr_accessor :raise_exception_on_error
19
16
 
20
- # Compiled assets have a slightly different filename for assets, especially the root node.
21
- def compilation_filename(path_builder: BuildPaths::DirectoryIndexPath, root_path_builder: BuildPaths::RootPath)
22
- path_builder = resource.node.root? ? root_path_builder : path_builder
23
- path_builder.new(resource).path
24
- end
25
-
26
- def render(page)
27
- Renderers::Server.new(resource).render
28
- end
29
- end
30
-
31
- attr_reader :site
32
-
33
- def initialize(site:, stdout: $stdout)
17
+ def initialize(site:, root_path:, stdout: $stdout, raise_exception_on_error: true)
34
18
  @site = site
35
19
  @stdout = stdout
20
+ @root_path = Pathname.new(root_path)
21
+ @raise_exception_on_error = raise_exception_on_error
36
22
  end
37
23
 
38
24
  # Iterates through all pages and writes them to disk
39
- def compile(target_path:)
40
- target_path = Pathname.new(target_path)
41
- mkdir_p target_path
42
- cache_resources = @site.cache_resources
43
- @stdout.puts "Compiling #{@site.root_path.expand_path}"
25
+ def compile
26
+ status "Building #{site.root_path.expand_path} to #{root_path.expand_path}"
27
+ resources.each do |resource, path|
28
+ if resource.renderable?
29
+ status "Rendering #{path}"
30
+ File.open(path.expand_path, "w"){ |f| f.write render resource }
31
+ else
32
+ status "Copying #{path}"
33
+ cp resource.asset.path, path.expand_path
34
+ end
35
+ rescue
36
+ status "Error building #{resource.inspect}"
37
+ raise if raise_exception_on_error
38
+ end
39
+ status "Successful build to #{root_path.expand_path}"
40
+ end
41
+
42
+ private
43
+ def resources
44
+ Enumerator.new do |y|
45
+ mkdir_p root_path
44
46
 
45
- begin
46
- @site.cache_resources = true
47
- @site.resources.each do |resource|
48
- compiler = ResourceCompiler.new(resource)
49
- path = target_path.join(compiler.compilation_path)
50
- mkdir_p path.dirname
51
- if resource.renderable?
52
- @stdout.puts " Rendering #{path}"
53
- File.open(path.expand_path, "w"){ |f| f.write compiler.render(resource) }
54
- else
55
- @stdout.puts " Copying #{path}"
56
- FileUtils.cp resource.asset.path, path.expand_path
47
+ site.resources.each do |resource|
48
+ path = build_path resource
49
+ mkdir_p path.dirname
50
+ y << [resource, path]
57
51
  end
58
- rescue => e
59
- @stdout.puts "Error compiling #{resource.inspect}"
60
- raise
61
52
  end
62
- @stdout.puts "Successful compilation to #{target_path.expand_path}"
63
- ensure
64
- @site.cache_resources = cache_resources
65
53
  end
66
- end
54
+
55
+ def build_path(resource)
56
+ path_builder = resource.node.root? ? BuildPaths::RootPath : BuildPaths::DirectoryIndexPath
57
+ root_path.join path_builder.new(resource).path
58
+ end
59
+
60
+ def render(resource)
61
+ Renderers::Server.new(resource).render
62
+ end
63
+
64
+ def status(message)
65
+ @stdout.puts message
66
+ end
67
67
  end
68
68
  end
@@ -20,26 +20,24 @@ module Sitepress
20
20
  # Load paths from `Sitepress#site` into rails so it can render views, helpers, etc. properly.
21
21
  initializer :set_sitepress_paths, before: :set_autoload_paths do |app|
22
22
  app.paths["app/helpers"].push site.helpers_path.expand_path
23
+ app.paths["app/assets"].push site.assets_path.expand_path
23
24
  app.paths["app/views"].push site.root_path.expand_path
24
25
  app.paths["app/views"].push site.pages_path.expand_path
25
26
  end
26
27
 
27
28
  # Configure sprockets paths for the site.
28
- initializer :set_asset_paths, before: :append_assets_path do |app|
29
+ initializer :set_manifest_file_path, before: :append_assets_path do |app|
29
30
  manifest_file = sitepress_configuration.manifest_file_path.expand_path
30
-
31
- if manifest_file.exist?
32
- app.paths["app/assets"].push site.assets_path.expand_path
33
- app.config.assets.precompile << manifest_file.to_s
34
- else
35
- Rails.logger.warn "WARNING: Sitepress could not enable Sprockets because it could not find a manifest file at #{manifest_file.to_s.inspect}."
36
- end
31
+ app.config.assets.precompile << manifest_file.to_s if manifest_file.exist?
37
32
  end
38
33
 
39
34
  # Configure Sitepress with Rails settings.
40
35
  initializer :configure_sitepress do |app|
41
36
  sitepress_configuration.parent_engine = app
37
+ # Reloads entire site between requests for development environments
42
38
  sitepress_configuration.cache_resources = app.config.cache_classes
39
+ # Set the templates extensions (e.g. erb, haml) so that Sitepress can better parse paths.
40
+ Sitepress::Path.handler_extensions = ActionView::Template::Handlers.extensions
43
41
  end
44
42
 
45
43
  private
@@ -3,11 +3,12 @@ require "sitepress-core"
3
3
  module Sitepress
4
4
  autoload :Compiler, "sitepress/compiler"
5
5
  autoload :RailsConfiguration, "sitepress/rails_configuration"
6
- autoload :RouteConstraint, "sitepress/route_constraint"
7
6
  module Renderers
8
7
  autoload :Controller, "sitepress/renderers/controller"
9
8
  autoload :Server, "sitepress/renderers/server"
10
9
  end
10
+ autoload :Rendition, "sitepress/rendition"
11
+ autoload :RouteConstraint, "sitepress/route_constraint"
11
12
  module BuildPaths
12
13
  autoload :RootPath, "sitepress/build_paths/root_path"
13
14
  autoload :IndexPath, "sitepress/build_paths/index_path"
@@ -15,7 +16,10 @@ module Sitepress
15
16
  end
16
17
 
17
18
  # Rescued by ActionController to display page not found error.
18
- PageNotFoundError = Class.new(StandardError)
19
+ ResourceNotFound = Class.new(StandardError)
20
+
21
+ # Raised when any of the Render subclasses can't render a page.
22
+ RenderingError = Class.new(RuntimeError)
19
23
 
20
24
  # Make site available via Sitepress.site from Rails app.
21
25
  def self.site
@@ -6,15 +6,15 @@ module Sitepress
6
6
  # Store in ./app/content by default.
7
7
  DEFAULT_SITE_ROOT = "app/content".freeze
8
8
 
9
- attr_accessor :routes
9
+ attr_accessor :routes, :cache_resources
10
10
  attr_writer :site, :parent_engine
11
11
 
12
- # Delegates configuration points into the Sitepress site.
13
- extend Forwardable
14
- def_delegators :site, :cache_resources, :cache_resources=, :cache_resources?
15
-
16
12
  def initialize
13
+ # Injects routes into parent apps routes when set to true. Set to false
14
+ # to inject routes manually.
17
15
  self.routes = true
16
+ # Caches sites between requests. Set to `false` for development environments.
17
+ self.cache_resources = true
18
18
  end
19
19
 
20
20
  def parent_engine
@@ -1,18 +1,20 @@
1
1
  module Sitepress
2
2
  module Renderers
3
+ # This would be the ideal way to render Sitepress resources, but there's a lot
4
+ # of hackery involved in getting it to work properly.
3
5
  class Controller
4
- attr_reader :controller, :page
6
+ attr_reader :controller, :resource
5
7
 
6
- def initialize(page, controller = SiteController)
8
+ def initialize(resource, controller = SiteController)
7
9
  @controller = controller
8
- @page = page
10
+ @resource = resource
9
11
  end
10
12
 
11
13
  def render
12
- renderer.render inline: page.body,
13
- type: page.asset.template_extensions.last,
14
+ renderer.render inline: resource.body,
15
+ type: resource.handler,
14
16
  layout: resolve_layout,
15
- content_type: page.mime_type.to_s
17
+ content_type: resource.mime_type.to_s
16
18
  end
17
19
 
18
20
  private
@@ -29,15 +31,15 @@ module Sitepress
29
31
  end
30
32
 
31
33
  def renderer
32
- controller.renderer.new("PATH_INFO" => page.request_path)
34
+ controller.renderer.new("PATH_INFO" => resource.request_path)
33
35
  end
34
36
 
35
37
  def resolve_layout
36
- return page.data.fetch("layout") if page.data.key? "layout"
38
+ return resource.data.fetch("layout") if resource.data.key? "layout"
37
39
  return layout unless has_layout_conditions?
38
40
 
39
41
  clause, formats = layout_conditions.first
40
- format = page.format.to_s
42
+ format = resource.format.to_s
41
43
 
42
44
  case clause
43
45
  when :only
@@ -1,21 +1,29 @@
1
1
  module Sitepress
2
2
  module Renderers
3
+ # Renders resources by invoking a rack call to the Rails application. From my
4
+ # experiments rendering as of 2021, this is the most reliable way to render
5
+ # resources. Rendering via `Renderers::Controller` has lots of various subtle issues
6
+ # that are surprising. People don't like surprises, so I opted to render through a
7
+ # slightly heavier stack.
3
8
  class Server
4
- attr_reader :rails_app, :page
9
+ attr_reader :rails_app, :resource
5
10
 
6
- def initialize(page, rails_app = Rails.application)
11
+ def initialize(resource, rails_app = Rails.application)
7
12
  @rails_app = rails_app
8
- @page = page
13
+ @resource = resource
9
14
  end
10
15
 
11
16
  def render
12
17
  code, headers, response = rails_app.routes.call env
13
18
  response.body
19
+ rescue => e
20
+ raise RenderingError.new "Error rendering #{resource.request_path.inspect} at #{resource.asset.path.expand_path.to_s.inspect}:\n#{e.message}"
14
21
  end
15
22
 
23
+ private
16
24
  def env
17
25
  {
18
- "PATH_INFO"=> page.request_path,
26
+ "PATH_INFO"=> resource.request_path,
19
27
  "REQUEST_METHOD"=>"GET",
20
28
  "rack.input" => "GET"
21
29
  }
@@ -0,0 +1,28 @@
1
+ module Sitepress
2
+ # Encapsulates the data needed to render a resource from a controller. This
3
+ # lets us keep the functions in the controller more functional, which makes them
4
+ # easier to override by the end users.
5
+ class Rendition
6
+ attr_accessor :resource, :output, :controller_layout
7
+
8
+ def initialize(resource)
9
+ @resource = resource
10
+ end
11
+
12
+ def mime_type
13
+ resource.mime_type.to_s
14
+ end
15
+
16
+ def handler
17
+ resource.handler
18
+ end
19
+
20
+ def source
21
+ resource.body
22
+ end
23
+
24
+ def layout
25
+ resource.data.fetch("layout", controller_layout)
26
+ end
27
+ end
28
+ end
@@ -13,49 +13,92 @@ module Sitepress
13
13
  extend ActiveSupport::Concern
14
14
 
15
15
  included do
16
- rescue_from Sitepress::PageNotFoundError, with: :page_not_found
16
+ rescue_from Sitepress::ResourceNotFound, with: :resource_not_found
17
17
  helper Sitepress::Engine.helpers
18
18
  helper_method :current_page, :site
19
19
  before_action :append_relative_partial_path, only: :show
20
+ around_action :ensure_site_reload, only: :show
20
21
  end
21
22
 
23
+ # Public method that is primarily called by Rails to display the page. This should
24
+ # be hooked up to the Rails routes file.
22
25
  def show
23
- render_page current_page
26
+ render_resource current_resource
24
27
  end
25
28
 
26
29
  protected
27
- def render_page(page)
28
- if page.renderable?
29
- render_text_resource page
30
+
31
+ # If a resource has a handler, (e.g. erb, haml, etc.) we say its "renderable" and
32
+ # process it. If it doesn't have a handler, we treat it like its just a plain ol'
33
+ # file and serve it up.
34
+ def render_resource(resource)
35
+ if resource.renderable?
36
+ render_resource_with_handler resource
30
37
  else
31
- send_binary_resource page
38
+ send_binary_resource resource
32
39
  end
33
40
  end
34
41
 
35
- def current_page
36
- @current_page ||= find_resource
42
+ # If a resource has a handler (e.g. erb, haml, etc.) we use the Rails renderer to
43
+ # process templates, layouts, partials, etc. To keep the whole rendering process
44
+ # contained in a way that the end user can override, we coupled the resource, source
45
+ # and output within a `Rendition` object so that it may be processed via hooks.
46
+ def render_resource_with_handler(resource)
47
+ rendition = Rendition.new(resource)
48
+ rendition.controller_layout = controller_layout
49
+
50
+ pre_render rendition
51
+ process_rendition rendition
52
+ post_render rendition
53
+ end
54
+
55
+ # This is where the actual rendering happens for the page source in Rails.
56
+ def pre_render(rendition)
57
+ rendition.output = render_to_string inline: rendition.source,
58
+ type: rendition.handler,
59
+ layout: rendition.layout
60
+ end
61
+
62
+ # This is to be used by end users if they need to do any post-processing on the rendering page.
63
+ # For example, the user may use Nokogiri to parse static HTML pages and hook it into the asset pipeline.
64
+ # They may also use tools like `HTMLPipeline` to process links from a markdown renderer.
65
+ def process_rendition(rendition)
66
+ # Do nothing unless the user extends this method.
37
67
  end
38
68
 
69
+ # Send the inline rendered, post-processed string into the Rails rendering method that actually sends
70
+ # the output to the end-user as a web response.
71
+ def post_render(rendition)
72
+ render inline: rendition.output, content_type: rendition.mime_type
73
+ end
74
+
75
+ # A reference to the current resource that's being requested.
76
+ def current_resource
77
+ @current_resource ||= find_resource
78
+ end
79
+ # In templates resources are more naturally thought of as pages, so we call it `current_page` from
80
+ # there and from the controller.
81
+ alias :current_page :current_resource
82
+
83
+ # References the singleton Site from the Sitepress::Configuration object. If you try to make this a class
84
+ # variable and let Rails have multiple Sitepress sites, you might run into issues with respect to the asset
85
+ # pipeline and various path configurations. To make this possible, a new object should be introduced to
86
+ # Sitepress that manages a many-sites to one-rails instance so there's no path issues.
39
87
  def site
40
88
  Sitepress.site
41
89
  end
42
90
 
43
- def page_not_found(e)
91
+ # Raises a routing error for Rails to deal with in a more "standard" way if the user doesn't
92
+ # override this method.
93
+ def resource_not_found(e)
44
94
  raise ActionController::RoutingError, e.message
45
95
  end
46
96
 
47
97
  private
98
+ # This makes it possible to render partials from the current resource with relative
99
+ # paths. Without this the paths would have to be absolute.
48
100
  def append_relative_partial_path
49
- append_view_path current_page.asset.path.dirname
50
- end
51
-
52
- def render_text_resource(resource)
53
- with_sitepress_render_cache do
54
- render inline: resource.body,
55
- type: resource.asset.template_extensions.last,
56
- layout: resource.data.fetch("layout", controller_layout),
57
- content_type: resource.mime_type.to_s
58
- end
101
+ append_view_path current_resource.asset.path.dirname
59
102
  end
60
103
 
61
104
  def send_binary_resource(resource)
@@ -64,13 +107,12 @@ module Sitepress
64
107
  type: resource.mime_type.to_s
65
108
  end
66
109
 
67
- # Sitepress::PageNotFoundError is handled in the default Sitepress::SiteController
68
- # with an execption that Rails can use to display a 404 error.
110
+ # Sitepress::ResourceNotFound is handled in the default Sitepress::SiteController
111
+ # with an exception that Rails can use to display a 404 error.
69
112
  def get(path)
70
113
  resource = site.resources.get(path)
71
114
  if resource.nil?
72
- # TODO: Display error in context of Reources class root.
73
- raise Sitepress::PageNotFoundError, "No such page: #{path}"
115
+ raise Sitepress::ResourceNotFound, "No such page: #{path}"
74
116
  else
75
117
  resource
76
118
  end
@@ -79,20 +121,7 @@ module Sitepress
79
121
  # Default finder of the resource for the current controller context. If the :resource_path
80
122
  # isn't present, then its probably the root path so grab that.
81
123
  def find_resource
82
- get params.fetch(:resource_path, ROOT_RESOURCE_PATH)
83
- end
84
-
85
- # When development environments disable the cache, we still want to turn it
86
- # on during rendering so that view doesn't rebuild the site on each call.
87
- def with_sitepress_render_cache(&block)
88
- cache_resources = site.cache_resources
89
- begin
90
- site.cache_resources = true
91
- yield
92
- ensure
93
- site.cache_resources = cache_resources
94
- site.clear_resources_cache unless site.cache_resources
95
- end
124
+ get request.path
96
125
  end
97
126
 
98
127
  # Returns the current layout for the inline Sitepress renderer. This is
@@ -102,9 +131,9 @@ module Sitepress
102
131
  private_layout_method = self.method(:_layout)
103
132
  layout =
104
133
  if Rails.version >= "6"
105
- private_layout_method.call lookup_context, current_page_rails_formats
134
+ private_layout_method.call lookup_context, current_resource_rails_formats
106
135
  elsif Rails.version >= "5"
107
- private_layout_method.call current_page_rails_formats
136
+ private_layout_method.call current_resource_rails_formats
108
137
  else
109
138
  private_layout_method.call
110
139
  end
@@ -124,8 +153,8 @@ module Sitepress
124
153
  # method returns the intersection of the formats Rails supports from Mime::Types
125
154
  # and the current page's node formats. If nothing intersects, HTML is returned
126
155
  # as a default.
127
- def current_page_rails_formats
128
- extensions = current_page.node.formats.extensions
156
+ def current_resource_rails_formats
157
+ extensions = current_resource.node.formats.extensions
129
158
  supported_extensions = extensions & Mime::EXTENSION_LOOKUP.keys
130
159
 
131
160
  if supported_extensions.empty?
@@ -134,5 +163,24 @@ module Sitepress
134
163
  supported_extensions.map?(&:to_sym)
135
164
  end
136
165
  end
166
+
167
+ # When in development mode, the site is reloaded and rebuilt between each request so
168
+ # that users can see changes to content and site structure. These rebuilds are unnecessary and
169
+ # slow per-request in a production environment, so they should not be reloaded.
170
+ def ensure_site_reload
171
+ yield
172
+ ensure
173
+ reload_site
174
+ end
175
+
176
+ # Drops the website cache so that it's rebuilt when called again.
177
+ def reload_site
178
+ site.reload! if reload_site?
179
+ end
180
+
181
+ # Looks at the configuration to see if the site should be reloaded between requests.
182
+ def reload_site?
183
+ !Sitepress.configuration.cache_resources
184
+ end
137
185
  end
138
186
  end