sitepress-rails 2.0.0.beta6 → 2.0.0.beta10

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: 56f5e9a3099ff4ed2482dc64bb279b4d6bae6490f9730160b6aca70ce49fdab2
4
- data.tar.gz: dc5b8207d25bee40911b6e03443efcd9a7f0c447a8835072f5427846580c617e
3
+ metadata.gz: 954c272d004e17af69ac7c4464526b27d11d79078a85dfb0b1208f4f9888939f
4
+ data.tar.gz: 44ba6cb9ad8b3b0282666e9f4af9ec4612a6af0b47db77e632500e76e5a11330
5
5
  SHA512:
6
- metadata.gz: 9efc2c72960749d48d3efbea750eafa2aac4ddd6127f19f8f2a71ad0f8b149b1a901d35ea7830b4860b4d1950524e9786b21f915655b7526af8caa20c7c03a36
7
- data.tar.gz: 4479ffc29e860ff98b6eb06ad6ff04bbd580a92d2fe74ec286cf98ac421d6bb6d723b07fc5a95db9fd5bf64d6e08340b7da4e6ff6a73b4344650f9bb3fee6bd2
6
+ metadata.gz: 0f28845879556aa074c5feea01dcec9772a65f671bbd88c2c5568769848179cc55ec5ab37b90299faa5e45e85926c2d2868e9a12ebec58337c789eb5007d0e15
7
+ data.tar.gz: 8ae5baa05ad779f83c1eb734e19c9321a19da438993f1315b748b68b1d8a0212c46a8b49f9d204dfdcdafffb515c39194e6320281fd208f65fd0e56dd2eaec69
@@ -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,65 +6,63 @@ module Sitepress
6
6
  class Compiler
7
7
  include FileUtils
8
8
 
9
- RenderingError = Class.new(RuntimeError)
9
+ attr_reader :site, :root_path
10
10
 
11
- class ResourceCompiler
12
- attr_reader :resource
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
13
16
 
14
- def initialize(resource)
15
- @resource = resource
17
+ def initialize(site:, root_path:, stdout: $stdout, raise_exception_on_error: true)
18
+ @site = site
19
+ @stdout = stdout
20
+ @root_path = Pathname.new(root_path)
21
+ @raise_exception_on_error = raise_exception_on_error
22
+ end
23
+
24
+ # Iterates through all pages and writes them to disk
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
16
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
17
46
 
18
- def compilation_path
19
- File.join(*resource.lineage, compilation_filename)
47
+ site.resources.each do |resource|
48
+ path = build_path resource
49
+ mkdir_p path.dirname
50
+ y << [resource, path]
51
+ end
52
+ end
20
53
  end
21
54
 
22
- # Compiled assets have a slightly different filename for assets, especially the root node.
23
- def compilation_filename(path_builder: BuildPaths::DirectoryIndexPath, root_path_builder: BuildPaths::RootPath)
24
- path_builder = resource.node.root? ? root_path_builder : path_builder
25
- path_builder.new(resource).path
55
+ def build_path(resource)
56
+ path_builder = resource.node.root? ? BuildPaths::RootPath : BuildPaths::DirectoryIndexPath
57
+ root_path.join path_builder.new(resource).path
26
58
  end
27
59
 
28
- def render(page)
60
+ def render(resource)
29
61
  Renderers::Server.new(resource).render
30
62
  end
31
- end
32
63
 
33
- attr_reader :site
34
-
35
- def initialize(site:, stdout: $stdout)
36
- @site = site
37
- @stdout = stdout
38
- end
39
-
40
- # Iterates through all pages and writes them to disk
41
- def compile(target_path:)
42
- target_path = Pathname.new(target_path)
43
- mkdir_p target_path
44
- cache_resources = @site.cache_resources
45
- @stdout.puts "Compiling #{@site.root_path.expand_path}"
46
-
47
- begin
48
- @site.cache_resources = true
49
- @site.resources.each do |resource|
50
- compiler = ResourceCompiler.new(resource)
51
- path = target_path.join(compiler.compilation_path)
52
- mkdir_p path.dirname
53
- if resource.renderable?
54
- @stdout.puts " Rendering #{path}"
55
- File.open(path.expand_path, "w"){ |f| f.write compiler.render(resource) }
56
- else
57
- @stdout.puts " Copying #{path}"
58
- FileUtils.cp resource.asset.path, path.expand_path
59
- end
60
- rescue => e
61
- @stdout.puts "Error compiling #{resource.inspect}"
62
- raise
63
- end
64
- @stdout.puts "Successful compilation to #{target_path.expand_path}"
65
- ensure
66
- @site.cache_resources = cache_resources
64
+ def status(message)
65
+ @stdout.puts message
67
66
  end
68
- end
69
67
  end
70
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,20 +1,20 @@
1
1
  module Sitepress
2
2
  module Renderers
3
- # This would be the ideal way to render Sitepress pages, but there's a lot
3
+ # This would be the ideal way to render Sitepress resources, but there's a lot
4
4
  # of hackery involved in getting it to work properly.
5
5
  class Controller
6
- attr_reader :controller, :page
6
+ attr_reader :controller, :resource
7
7
 
8
- def initialize(page, controller = SiteController)
8
+ def initialize(resource, controller = SiteController)
9
9
  @controller = controller
10
- @page = page
10
+ @resource = resource
11
11
  end
12
12
 
13
13
  def render
14
- renderer.render inline: page.body,
15
- type: page.asset.template_extensions.last,
14
+ renderer.render inline: resource.body,
15
+ type: resource.handler,
16
16
  layout: resolve_layout,
17
- content_type: page.mime_type.to_s
17
+ content_type: resource.mime_type.to_s
18
18
  end
19
19
 
20
20
  private
@@ -31,15 +31,15 @@ module Sitepress
31
31
  end
32
32
 
33
33
  def renderer
34
- controller.renderer.new("PATH_INFO" => page.request_path)
34
+ controller.renderer.new("PATH_INFO" => resource.request_path)
35
35
  end
36
36
 
37
37
  def resolve_layout
38
- return page.data.fetch("layout") if page.data.key? "layout"
38
+ return resource.data.fetch("layout") if resource.data.key? "layout"
39
39
  return layout unless has_layout_conditions?
40
40
 
41
41
  clause, formats = layout_conditions.first
42
- format = page.format.to_s
42
+ format = resource.format.to_s
43
43
 
44
44
  case clause
45
45
  when :only
@@ -1,29 +1,29 @@
1
1
  module Sitepress
2
2
  module Renderers
3
- # Renders pages by invoking a rack call to the Rails application. From my
3
+ # Renders resources by invoking a rack call to the Rails application. From my
4
4
  # experiments rendering as of 2021, this is the most reliable way to render
5
- # pages. Rendering via `Renderers::Controller` has lots of various subtle issues
5
+ # resources. Rendering via `Renderers::Controller` has lots of various subtle issues
6
6
  # that are surprising. People don't like surprises, so I opted to render through a
7
7
  # slightly heavier stack.
8
8
  class Server
9
- attr_reader :rails_app, :page
9
+ attr_reader :rails_app, :resource
10
10
 
11
- def initialize(page, rails_app = Rails.application)
11
+ def initialize(resource, rails_app = Rails.application)
12
12
  @rails_app = rails_app
13
- @page = page
13
+ @resource = resource
14
14
  end
15
15
 
16
16
  def render
17
17
  code, headers, response = rails_app.routes.call env
18
18
  response.body
19
19
  rescue => e
20
- raise Compiler::RenderingError.new "Error rendering #{page.request_path.inspect} at #{page.asset.path.expand_path.to_s.inspect}:\n#{e.message}"
20
+ raise RenderingError.new "Error rendering #{resource.request_path.inspect} at #{resource.asset.path.expand_path.to_s.inspect}:\n#{e.message}"
21
21
  end
22
22
 
23
23
  private
24
24
  def env
25
25
  {
26
- "PATH_INFO"=> page.request_path,
26
+ "PATH_INFO"=> resource.request_path,
27
27
  "REQUEST_METHOD"=>"GET",
28
28
  "rack.input" => "GET"
29
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
@@ -1,3 +1,5 @@
1
+ require "cgi"
2
+
1
3
  module Sitepress
2
4
  # Serves up Sitepress site pages in a rails application. This is mixed into the
3
5
  # Sitepress::SiteController, but may be included into other controllers for static
@@ -13,49 +15,92 @@ module Sitepress
13
15
  extend ActiveSupport::Concern
14
16
 
15
17
  included do
16
- rescue_from Sitepress::PageNotFoundError, with: :page_not_found
18
+ rescue_from Sitepress::ResourceNotFound, with: :resource_not_found
17
19
  helper Sitepress::Engine.helpers
18
20
  helper_method :current_page, :site
19
21
  before_action :append_relative_partial_path, only: :show
22
+ around_action :ensure_site_reload, only: :show
20
23
  end
21
24
 
25
+ # Public method that is primarily called by Rails to display the page. This should
26
+ # be hooked up to the Rails routes file.
22
27
  def show
23
- render_page current_page
28
+ render_resource current_resource
24
29
  end
25
30
 
26
31
  protected
27
- def render_page(page)
28
- if page.renderable?
29
- render_text_resource page
32
+
33
+ # If a resource has a handler, (e.g. erb, haml, etc.) we say its "renderable" and
34
+ # process it. If it doesn't have a handler, we treat it like its just a plain ol'
35
+ # file and serve it up.
36
+ def render_resource(resource)
37
+ if resource.renderable?
38
+ render_resource_with_handler resource
30
39
  else
31
- send_binary_resource page
40
+ send_binary_resource resource
32
41
  end
33
42
  end
34
43
 
35
- def current_page
36
- @current_page ||= find_resource
44
+ # If a resource has a handler (e.g. erb, haml, etc.) we use the Rails renderer to
45
+ # process templates, layouts, partials, etc. To keep the whole rendering process
46
+ # contained in a way that the end user can override, we coupled the resource, source
47
+ # and output within a `Rendition` object so that it may be processed via hooks.
48
+ def render_resource_with_handler(resource)
49
+ rendition = Rendition.new(resource)
50
+ rendition.controller_layout = controller_layout
51
+
52
+ pre_render rendition
53
+ process_rendition rendition
54
+ post_render rendition
55
+ end
56
+
57
+ # This is where the actual rendering happens for the page source in Rails.
58
+ def pre_render(rendition)
59
+ rendition.output = render_to_string inline: rendition.source,
60
+ type: rendition.handler,
61
+ layout: rendition.layout
62
+ end
63
+
64
+ # This is to be used by end users if they need to do any post-processing on the rendering page.
65
+ # For example, the user may use Nokogiri to parse static HTML pages and hook it into the asset pipeline.
66
+ # They may also use tools like `HTMLPipeline` to process links from a markdown renderer.
67
+ def process_rendition(rendition)
68
+ # Do nothing unless the user extends this method.
37
69
  end
38
70
 
71
+ # Send the inline rendered, post-processed string into the Rails rendering method that actually sends
72
+ # the output to the end-user as a web response.
73
+ def post_render(rendition)
74
+ render inline: rendition.output, content_type: rendition.mime_type
75
+ end
76
+
77
+ # A reference to the current resource that's being requested.
78
+ def current_resource
79
+ @current_resource ||= find_resource
80
+ end
81
+ # In templates resources are more naturally thought of as pages, so we call it `current_page` from
82
+ # there and from the controller.
83
+ alias :current_page :current_resource
84
+
85
+ # References the singleton Site from the Sitepress::Configuration object. If you try to make this a class
86
+ # variable and let Rails have multiple Sitepress sites, you might run into issues with respect to the asset
87
+ # pipeline and various path configurations. To make this possible, a new object should be introduced to
88
+ # Sitepress that manages a many-sites to one-rails instance so there's no path issues.
39
89
  def site
40
90
  Sitepress.site
41
91
  end
42
92
 
43
- def page_not_found(e)
93
+ # Raises a routing error for Rails to deal with in a more "standard" way if the user doesn't
94
+ # override this method.
95
+ def resource_not_found(e)
44
96
  raise ActionController::RoutingError, e.message
45
97
  end
46
98
 
47
99
  private
100
+ # This makes it possible to render partials from the current resource with relative
101
+ # paths. Without this the paths would have to be absolute.
48
102
  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
103
+ append_view_path current_resource.asset.path.dirname
59
104
  end
60
105
 
61
106
  def send_binary_resource(resource)
@@ -64,13 +109,12 @@ module Sitepress
64
109
  type: resource.mime_type.to_s
65
110
  end
66
111
 
67
- # Sitepress::PageNotFoundError is handled in the default Sitepress::SiteController
68
- # with an execption that Rails can use to display a 404 error.
112
+ # Sitepress::ResourceNotFound is handled in the default Sitepress::SiteController
113
+ # with an exception that Rails can use to display a 404 error.
69
114
  def get(path)
70
115
  resource = site.resources.get(path)
71
116
  if resource.nil?
72
- # TODO: Display error in context of Reources class root.
73
- raise Sitepress::PageNotFoundError, "No such page: #{path}"
117
+ raise Sitepress::ResourceNotFound, "No such page: #{path}"
74
118
  else
75
119
  resource
76
120
  end
@@ -79,20 +123,12 @@ module Sitepress
79
123
  # Default finder of the resource for the current controller context. If the :resource_path
80
124
  # isn't present, then its probably the root path so grab that.
81
125
  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
126
+ get resource_request_path
127
+ end
128
+
129
+ # Returns the path of the resource in a way thats properly escape.
130
+ def resource_request_path
131
+ CGI.unescape request.path
96
132
  end
97
133
 
98
134
  # Returns the current layout for the inline Sitepress renderer. This is
@@ -102,9 +138,9 @@ module Sitepress
102
138
  private_layout_method = self.method(:_layout)
103
139
  layout =
104
140
  if Rails.version >= "6"
105
- private_layout_method.call lookup_context, current_page_rails_formats
141
+ private_layout_method.call lookup_context, current_resource_rails_formats
106
142
  elsif Rails.version >= "5"
107
- private_layout_method.call current_page_rails_formats
143
+ private_layout_method.call current_resource_rails_formats
108
144
  else
109
145
  private_layout_method.call
110
146
  end
@@ -124,8 +160,8 @@ module Sitepress
124
160
  # method returns the intersection of the formats Rails supports from Mime::Types
125
161
  # and the current page's node formats. If nothing intersects, HTML is returned
126
162
  # as a default.
127
- def current_page_rails_formats
128
- extensions = current_page.node.formats.extensions
163
+ def current_resource_rails_formats
164
+ extensions = current_resource.node.formats.extensions
129
165
  supported_extensions = extensions & Mime::EXTENSION_LOOKUP.keys
130
166
 
131
167
  if supported_extensions.empty?
@@ -134,5 +170,24 @@ module Sitepress
134
170
  supported_extensions.map?(&:to_sym)
135
171
  end
136
172
  end
173
+
174
+ # When in development mode, the site is reloaded and rebuilt between each request so
175
+ # that users can see changes to content and site structure. These rebuilds are unnecessary and
176
+ # slow per-request in a production environment, so they should not be reloaded.
177
+ def ensure_site_reload
178
+ yield
179
+ ensure
180
+ reload_site
181
+ end
182
+
183
+ # Drops the website cache so that it's rebuilt when called again.
184
+ def reload_site
185
+ site.reload! if reload_site?
186
+ end
187
+
188
+ # Looks at the configuration to see if the site should be reloaded between requests.
189
+ def reload_site?
190
+ !Sitepress.configuration.cache_resources
191
+ end
137
192
  end
138
193
  end