sitepress-rails 2.0.0.beta4 → 2.0.0.beta9

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