sitepress-rails 2.0.0.beta6 → 2.0.0.beta10

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