perron 0.18.0 → 1.1.0
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 +4 -4
- data/Gemfile.lock +11 -15
- data/app/controllers/perron/concierge_controller.rb +16 -0
- data/app/helpers/perron/markdown_helper.rb +3 -2
- data/app/helpers/perron/meta_tags_helper.rb +3 -9
- data/app/helpers/perron/paginate_helper.rb +40 -0
- data/app/views/perron/concierge/show.html.erb +271 -0
- data/lib/generators/rails/content/USAGE +21 -4
- data/lib/generators/rails/content/content_generator.rb +16 -12
- data/lib/generators/rails/content/templates/controller.rb.tt +6 -0
- data/lib/generators/rails/content/templates/model.rb.tt +1 -1
- data/lib/perron/assets/icon.png +0 -0
- data/lib/perron/assets/icon.svg +1 -0
- data/lib/perron/collection.rb +2 -1
- data/lib/perron/configuration.rb +27 -2
- data/lib/perron/data_source/class_methods.rb +8 -0
- data/lib/perron/data_source.rb +20 -33
- data/lib/perron/development_feed_server.rb +69 -0
- data/lib/perron/engine.rb +32 -1
- data/lib/perron/errors.rb +2 -0
- data/lib/perron/feeds.rb +4 -3
- data/lib/perron/html_processor/absolute_urls.rb +27 -0
- data/lib/perron/html_processor/base.rb +2 -2
- data/lib/perron/html_processor.rb +7 -11
- data/lib/{generators/perron/templates → perron/install}/README.md.tt +7 -9
- data/lib/perron/install/deploy.yml +15 -0
- data/lib/perron/install.rb +26 -0
- data/lib/perron/markdown.rb +2 -2
- data/lib/perron/output_server.rb +9 -0
- data/lib/perron/paginate.rb +58 -0
- data/lib/perron/relation.rb +24 -6
- data/lib/perron/resource/adjacency.rb +70 -0
- data/lib/perron/resource/associations.rb +1 -1
- data/lib/perron/resource/class_methods.rb +6 -0
- data/lib/perron/resource/configuration.rb +12 -4
- data/lib/perron/resource/metadata.rb +19 -4
- data/lib/perron/resource/publishable.rb +2 -0
- data/lib/perron/resource/related.rb +32 -31
- data/lib/perron/resource/sourceable.rb +98 -16
- data/lib/perron/resource.rb +8 -0
- data/lib/perron/site/builder/assets.rb +1 -1
- data/lib/perron/site/builder/feeds/atom.erb +44 -0
- data/lib/perron/site/builder/feeds/atom.rb +41 -0
- data/lib/perron/site/builder/feeds/json.erb +19 -0
- data/lib/perron/site/builder/feeds/json.rb +7 -33
- data/lib/perron/site/builder/feeds/rss.erb +28 -0
- data/lib/perron/site/builder/feeds/rss.rb +6 -28
- data/lib/perron/site/builder/feeds/template.rb +63 -0
- data/lib/perron/site/builder/feeds.rb +8 -3
- data/lib/perron/site/builder/page.rb +19 -4
- data/lib/perron/site/builder/paths.rb +75 -13
- data/lib/perron/site/builder/route_resources.rb +79 -0
- data/lib/perron/site/builder/sitemap.rb +71 -20
- data/lib/perron/site/builder.rb +25 -1
- data/lib/perron/site/validate.rb +19 -7
- data/lib/perron/site.rb +7 -0
- data/lib/perron/tasks/build.rake +6 -7
- data/lib/perron/tasks/deploy.rake +58 -0
- data/lib/perron/tasks/install.rake +12 -0
- data/lib/perron/version.rb +1 -1
- data/lib/perron.rb +1 -0
- data/perron.gemspec +1 -1
- metadata +25 -8
- data/lib/generators/perron/install_generator.rb +0 -32
- data/lib/perron/html_processor/syntax_highlight.rb +0 -32
- /data/lib/{generators/perron/templates → perron/install}/initializer.rb.tt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "nokogiri"
|
|
4
3
|
require "perron/site/builder/feeds/author"
|
|
4
|
+
require "perron/site/builder/feeds/template"
|
|
5
5
|
|
|
6
6
|
module Perron
|
|
7
7
|
module Site
|
|
@@ -9,6 +9,7 @@ module Perron
|
|
|
9
9
|
class Feeds
|
|
10
10
|
class Rss
|
|
11
11
|
include Feeds::Author
|
|
12
|
+
include Feeds::Template
|
|
12
13
|
|
|
13
14
|
def initialize(collection:)
|
|
14
15
|
@collection = collection
|
|
@@ -18,31 +19,10 @@ module Perron
|
|
|
18
19
|
def generate
|
|
19
20
|
return if resources.empty?
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
xml.title feed_configuration.title.presence || @configuration.site_name
|
|
26
|
-
xml.description feed_configuration.description.presence || @configuration.site_description
|
|
27
|
-
xml.link @configuration.url
|
|
28
|
-
|
|
29
|
-
Rails.application.routes.url_helpers.with_options(@configuration.default_url_options) do |url|
|
|
30
|
-
resources.each do |resource|
|
|
31
|
-
xml.item do
|
|
32
|
-
xml.guid resource.id
|
|
33
|
-
xml.link url.polymorphic_url(resource, ref: feed_configuration.ref).delete_suffix("?ref="), isPermaLink: true
|
|
34
|
-
xml.pubDate(resource.published_at&.rfc822)
|
|
35
|
-
if (author = author(resource)) && author.email
|
|
36
|
-
xml.author author.name ? "#{author.email} (#{author.name})" : author.email
|
|
37
|
-
end
|
|
38
|
-
xml.title resource.metadata.title
|
|
39
|
-
xml.description { xml.cdata(Perron::Markdown.render(resource.content)) }
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
end.to_xml
|
|
22
|
+
template = find_template("rss")
|
|
23
|
+
return unless template
|
|
24
|
+
|
|
25
|
+
render(template, feed_configuration)
|
|
46
26
|
end
|
|
47
27
|
|
|
48
28
|
private
|
|
@@ -54,8 +34,6 @@ module Perron
|
|
|
54
34
|
.reverse
|
|
55
35
|
.take(feed_configuration.max_items)
|
|
56
36
|
end
|
|
57
|
-
|
|
58
|
-
def feed_configuration = @collection.configuration.feeds.rss
|
|
59
37
|
end
|
|
60
38
|
end
|
|
61
39
|
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Perron
|
|
4
|
+
module Site
|
|
5
|
+
class Builder
|
|
6
|
+
class Feeds
|
|
7
|
+
module Template
|
|
8
|
+
def find_template(type)
|
|
9
|
+
collection_name = @collection.name.to_s.pluralize
|
|
10
|
+
|
|
11
|
+
user_path = Rails.root.join("app/views/content/#{collection_name}/#{type}.erb")
|
|
12
|
+
return user_path if File.exist?(user_path)
|
|
13
|
+
|
|
14
|
+
default_path = Pathname.new(__dir__).join("#{type}.erb")
|
|
15
|
+
return default_path if File.exist?(default_path)
|
|
16
|
+
|
|
17
|
+
nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def render(template_path, feed_config)
|
|
21
|
+
template = File.read(template_path)
|
|
22
|
+
b = binding
|
|
23
|
+
|
|
24
|
+
b.local_variable_set(:collection, @collection)
|
|
25
|
+
b.local_variable_set(:resources, resources)
|
|
26
|
+
b.local_variable_set(:config, feed_config)
|
|
27
|
+
b.local_variable_set(:routes, routes)
|
|
28
|
+
b.local_variable_set(:author, method(:author))
|
|
29
|
+
b.local_variable_set(:url_for_resource, method(:url_for_resource))
|
|
30
|
+
b.local_variable_set(:current_feed_url, method(:current_feed_url))
|
|
31
|
+
|
|
32
|
+
ERB.new(template).result(b)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def url_for_resource(resource)
|
|
36
|
+
routes
|
|
37
|
+
.polymorphic_url(resource, **@configuration.default_url_options.merge(ref: feed_configuration.ref))
|
|
38
|
+
.delete_suffix("?ref=")
|
|
39
|
+
rescue
|
|
40
|
+
nil
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def current_feed_url
|
|
44
|
+
path = feed_configuration.path || "feed.atom"
|
|
45
|
+
URI.join(@configuration.url, path).to_s
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def routes
|
|
49
|
+
Rails.application.routes.url_helpers
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def feed_configuration
|
|
53
|
+
case self.class.name.demodulize
|
|
54
|
+
when "Rss" then @collection.configuration.feeds.rss
|
|
55
|
+
when "Atom" then @collection.configuration.feeds.atom
|
|
56
|
+
when "Json" then @collection.configuration.feeds.json
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "perron/site/builder/feeds/
|
|
3
|
+
require "perron/site/builder/feeds/atom"
|
|
4
4
|
require "perron/site/builder/feeds/json"
|
|
5
|
+
require "perron/site/builder/feeds/rss"
|
|
5
6
|
|
|
6
7
|
module Perron
|
|
7
8
|
module Site
|
|
@@ -17,13 +18,17 @@ module Perron
|
|
|
17
18
|
|
|
18
19
|
config = collection.configuration.feeds
|
|
19
20
|
|
|
20
|
-
if config.
|
|
21
|
-
create_file at: config.
|
|
21
|
+
if config.atom.enabled
|
|
22
|
+
create_file at: config.atom.path, with: Atom.new(collection: collection).generate
|
|
22
23
|
end
|
|
23
24
|
|
|
24
25
|
if config.json.enabled
|
|
25
26
|
create_file at: config.json.path, with: Json.new(collection: collection).generate
|
|
26
27
|
end
|
|
28
|
+
|
|
29
|
+
if config.rss.enabled
|
|
30
|
+
create_file at: config.rss.path, with: Rss.new(collection: collection).generate
|
|
31
|
+
end
|
|
27
32
|
end
|
|
28
33
|
end
|
|
29
34
|
|
|
@@ -11,13 +11,15 @@ module Perron
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def render
|
|
14
|
-
|
|
14
|
+
info = route_info
|
|
15
|
+
return puts " ❌ ERROR: No route matches '#{@path}'" unless info
|
|
16
|
+
|
|
15
17
|
request = ActionDispatch::Request.new(env)
|
|
16
18
|
response = ActionDispatch::Response.new
|
|
17
19
|
|
|
18
|
-
request.path_parameters =
|
|
20
|
+
request.path_parameters = info
|
|
19
21
|
|
|
20
|
-
controller.dispatch(action, request, response)
|
|
22
|
+
controller.dispatch(info[:action], request, response)
|
|
21
23
|
|
|
22
24
|
return puts " ❌ ERROR: Request failed for '#{@path}' (Status: #{response.status})" unless response.successful?
|
|
23
25
|
|
|
@@ -41,7 +43,20 @@ module Perron
|
|
|
41
43
|
end
|
|
42
44
|
|
|
43
45
|
def route_info
|
|
44
|
-
@route_info ||=
|
|
46
|
+
@route_info ||= recognize_path_with_pagination_fallback(@path)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def recognize_path_with_pagination_fallback(path)
|
|
50
|
+
Rails.application.routes.recognize_path(path)
|
|
51
|
+
rescue ActionController::RoutingError
|
|
52
|
+
return nil unless (match = path.match(/\/(.+)\/page\/(\d+)\//))
|
|
53
|
+
|
|
54
|
+
base_path = "/#{match[1]}"
|
|
55
|
+
page_number = match[2].to_i
|
|
56
|
+
|
|
57
|
+
Rails.application.routes.recognize_path(base_path).tap do |route_info|
|
|
58
|
+
route_info[:page] = page_number
|
|
59
|
+
end
|
|
45
60
|
end
|
|
46
61
|
|
|
47
62
|
def env = Rack::MockRequest.env_for(@path, "HTTP_HOST" => Perron.configuration.default_url_options[:host])
|
|
@@ -1,36 +1,98 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "perron/site/builder/route_resources"
|
|
4
|
+
|
|
3
5
|
module Perron
|
|
4
6
|
module Site
|
|
5
7
|
class Builder
|
|
6
8
|
class Paths
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
include RouteResources
|
|
10
|
+
|
|
11
|
+
def initialize(paths)
|
|
12
|
+
@paths = paths
|
|
9
13
|
end
|
|
10
14
|
|
|
11
15
|
def get
|
|
12
|
-
|
|
16
|
+
buildable_routes.each do |route|
|
|
17
|
+
paths_for(route).each { @paths << it }
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def paths_for(route)
|
|
24
|
+
case route.defaults[:action]
|
|
25
|
+
when "index"
|
|
26
|
+
required_params = route.required_keys - [:controller, :action]
|
|
27
|
+
if required_params.any?
|
|
28
|
+
raise "Route `#{route.name}` (#{route.path.spec}) is an index route but requires parameters #{required_params}. Perron doesn't know how to generate these parameters."
|
|
29
|
+
end
|
|
13
30
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
31
|
+
base_path = routes.public_send("#{route.name}_path")
|
|
32
|
+
collection = collection_for(route)
|
|
33
|
+
return [base_path] unless collection
|
|
17
34
|
|
|
18
|
-
|
|
35
|
+
pagination = collection.configuration.pagination
|
|
36
|
+
return [base_path] unless pagination.per_page
|
|
19
37
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
38
|
+
total = collection.all.count
|
|
39
|
+
total_pages = (total.to_f / pagination.per_page).ceil
|
|
40
|
+
return [base_path] if total_pages <= 1
|
|
41
|
+
|
|
42
|
+
[base_path] + (2..total_pages).map do |page_number|
|
|
43
|
+
build_paginated_path(route, page_number, pagination.path_template)
|
|
23
44
|
end
|
|
45
|
+
when "show" then show_paths_for(route)
|
|
46
|
+
else []
|
|
24
47
|
end
|
|
25
48
|
end
|
|
26
49
|
|
|
27
|
-
|
|
50
|
+
def build_paginated_path(route, page_number, path_template)
|
|
51
|
+
routes.public_send("#{route.name}_path")
|
|
52
|
+
.sub(/\/$/, "") + path_template.sub(":page", page_number.to_s)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def show_paths_for(route)
|
|
56
|
+
resources_for(route).reject(&:root?).map do |resource|
|
|
57
|
+
routes.public_send("#{route.name}_path", resource)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
28
60
|
|
|
29
61
|
def routes = Rails.application.routes.url_helpers
|
|
30
62
|
|
|
31
|
-
|
|
63
|
+
class ConstraintCollection
|
|
64
|
+
def initialize(values)
|
|
65
|
+
@values = values
|
|
66
|
+
end
|
|
32
67
|
|
|
33
|
-
|
|
68
|
+
def load_resources
|
|
69
|
+
@values.map { ConstraintResource.new(it) }
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
class ConstraintResource
|
|
74
|
+
def initialize(value)
|
|
75
|
+
@value = value
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def to_param = @value
|
|
79
|
+
|
|
80
|
+
def buildable? = true
|
|
81
|
+
|
|
82
|
+
def root? = false
|
|
83
|
+
|
|
84
|
+
def metadata = Metadata.new
|
|
85
|
+
|
|
86
|
+
class Metadata
|
|
87
|
+
def sitemap = nil
|
|
88
|
+
|
|
89
|
+
def sitemap_priority = nil
|
|
90
|
+
|
|
91
|
+
def sitemap_change_frequency = nil
|
|
92
|
+
|
|
93
|
+
def updated_at = nil
|
|
94
|
+
end
|
|
95
|
+
end
|
|
34
96
|
end
|
|
35
97
|
end
|
|
36
98
|
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Perron
|
|
4
|
+
module Site
|
|
5
|
+
class Builder
|
|
6
|
+
module RouteResources
|
|
7
|
+
def buildable_routes
|
|
8
|
+
Rails.application.routes.routes.select do |route|
|
|
9
|
+
route.defaults[:controller]&.start_with?("content/") &&
|
|
10
|
+
%w[index show].include?(route.defaults[:action])
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def resources_for(route)
|
|
15
|
+
constraint_resources = constraint_resources_from(route)
|
|
16
|
+
return constraint_resources if constraint_resources.any?
|
|
17
|
+
|
|
18
|
+
collection = collection_for(route)
|
|
19
|
+
return [] unless collection
|
|
20
|
+
|
|
21
|
+
resources = collection.send(:load_resources).select(&:buildable?)
|
|
22
|
+
constraint = route.path.requirements[:id]
|
|
23
|
+
|
|
24
|
+
constraint.is_a?(Regexp) ? resources.select { constraint.match?(it.to_param) } : resources
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def collection_for(route)
|
|
28
|
+
collection = standard_collection(route)
|
|
29
|
+
return collection if collection
|
|
30
|
+
|
|
31
|
+
collection = parent_collection(route)
|
|
32
|
+
return collection if collection
|
|
33
|
+
|
|
34
|
+
constraint_collection(route)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def standard_collection(route)
|
|
40
|
+
controller_class = "#{route.defaults[:controller]}_controller".classify.constantize
|
|
41
|
+
collection_name = controller_class.respond_to?(:collection_name) ? controller_class.collection_name : route.defaults[:controller].split("/").last.chomp("_controller")
|
|
42
|
+
|
|
43
|
+
Perron::Site.find_collection(collection_name)
|
|
44
|
+
rescue NameError
|
|
45
|
+
Perron::Site.find_collection(route.defaults[:controller].split("/").last.chomp("_controller"))
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def parent_collection(route)
|
|
49
|
+
parent_controller = route.defaults[:controller].split("/")[-2]
|
|
50
|
+
return nil unless parent_controller
|
|
51
|
+
|
|
52
|
+
Perron::Site.find_collection(parent_controller.chomp("_controller"))
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def constraint_collection(route)
|
|
56
|
+
constraint = route.path.requirements[:id]
|
|
57
|
+
return nil unless constraint.is_a?(Regexp)
|
|
58
|
+
return nil unless constraint.source.include?("|")
|
|
59
|
+
|
|
60
|
+
values = constraint.source.split("|")
|
|
61
|
+
return nil unless values.all? { it.match?(/\A\w+\z/) }
|
|
62
|
+
|
|
63
|
+
Paths::ConstraintCollection.new(values)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def constraint_resources_from(route)
|
|
67
|
+
constraint = route.path.requirements[:id]
|
|
68
|
+
return [] unless constraint.is_a?(Regexp)
|
|
69
|
+
return [] unless constraint.source.include?("|")
|
|
70
|
+
|
|
71
|
+
values = constraint.source.split("|")
|
|
72
|
+
return [] unless values.all? { it.match?(/\A\w+\z/) }
|
|
73
|
+
|
|
74
|
+
values.map { Paths::ConstraintResource.new(it) }
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "perron/site/builder/route_resources"
|
|
4
|
+
|
|
3
5
|
module Perron
|
|
4
6
|
module Site
|
|
5
7
|
class Builder
|
|
6
8
|
class Sitemap
|
|
9
|
+
include RouteResources
|
|
10
|
+
|
|
7
11
|
def initialize(output_path)
|
|
8
12
|
@output_path = output_path
|
|
9
13
|
end
|
|
@@ -11,12 +15,27 @@ module Perron
|
|
|
11
15
|
def generate
|
|
12
16
|
return if !Perron.configuration.sitemap.enabled
|
|
13
17
|
|
|
18
|
+
added_urls = Set.new
|
|
19
|
+
|
|
14
20
|
xml = Nokogiri::XML::Builder.new(encoding: "UTF-8") do |builder|
|
|
15
21
|
builder.urlset(xmlns: "http://www.sitemaps.org/schemas/sitemap/0.9") do
|
|
16
22
|
add_additional_routes(with: builder)
|
|
17
23
|
|
|
18
|
-
|
|
19
|
-
|
|
24
|
+
buildable_routes.each do |route|
|
|
25
|
+
case route.defaults[:action]
|
|
26
|
+
when "index"
|
|
27
|
+
add_index_url(route, with: builder, added_urls: added_urls)
|
|
28
|
+
when "show"
|
|
29
|
+
if (route.required_keys - %i[controller action]).empty?
|
|
30
|
+
add_index_url(route, with: builder, added_urls: added_urls)
|
|
31
|
+
else
|
|
32
|
+
resources_for(route).reject(&:root?).each do |resource|
|
|
33
|
+
next if resource.metadata.sitemap == false
|
|
34
|
+
|
|
35
|
+
add_show_url(route, resource, with: builder, added_urls: added_urls)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
20
39
|
end
|
|
21
40
|
end
|
|
22
41
|
end.to_xml
|
|
@@ -41,31 +60,63 @@ module Perron
|
|
|
41
60
|
end
|
|
42
61
|
end
|
|
43
62
|
|
|
44
|
-
def
|
|
45
|
-
|
|
46
|
-
return if collection
|
|
63
|
+
def add_index_url(route, with:, added_urls:)
|
|
64
|
+
collection = collection_for(route)
|
|
65
|
+
return if collection&.configuration&.sitemap&.enabled == false
|
|
47
66
|
|
|
48
|
-
|
|
49
|
-
|
|
67
|
+
url_options = Perron.configuration.default_url_options
|
|
68
|
+
url_options = url_options.merge(trailing_slash: false) if route.path.spec.to_s.match?(/\.\w+/)
|
|
50
69
|
|
|
51
|
-
|
|
52
|
-
|
|
70
|
+
routes.with_options(url_options) do |url|
|
|
71
|
+
loc = url.public_send("#{route.name}_url")
|
|
72
|
+
next if added_urls.include?(loc)
|
|
73
|
+
added_urls << loc
|
|
53
74
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
75
|
+
lastmod = last_modified_for(collection)
|
|
76
|
+
|
|
77
|
+
with.url do
|
|
78
|
+
with.loc loc
|
|
79
|
+
with.priority Perron.configuration.sitemap.priority
|
|
80
|
+
with.changefreq Perron.configuration.sitemap.change_frequency
|
|
81
|
+
with.lastmod lastmod if lastmod
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def add_show_url(route, resource, with:, added_urls:)
|
|
87
|
+
collection = collection_for(route)
|
|
88
|
+
return if collection&.configuration&.sitemap&.enabled == false
|
|
89
|
+
|
|
90
|
+
priority = resource.metadata.sitemap_priority || collection&.configuration&.sitemap&.priority || Perron.configuration.sitemap.priority
|
|
91
|
+
change_frequency = resource.metadata.sitemap_change_frequency || collection&.configuration&.sitemap&.change_frequency || Perron.configuration.sitemap.change_frequency
|
|
92
|
+
|
|
93
|
+
url_options = Perron.configuration.default_url_options
|
|
94
|
+
url_options = url_options.merge(trailing_slash: false) if route.path.spec.to_s.match?(/\.\w+/)
|
|
95
|
+
|
|
96
|
+
loc = resource.root? ? routes.root_url(url_options) : routes.public_send("#{route.name}_url", resource, **url_options)
|
|
97
|
+
|
|
98
|
+
return if added_urls.include?(loc)
|
|
99
|
+
added_urls << loc
|
|
100
|
+
|
|
101
|
+
routes.with_options(Perron.configuration.default_url_options) do |url|
|
|
102
|
+
with.url do
|
|
103
|
+
with.loc loc
|
|
104
|
+
with.priority priority
|
|
105
|
+
with.changefreq change_frequency
|
|
106
|
+
with.lastmod resource.metadata.updated_at&.iso8601 if resource.metadata.updated_at.present?
|
|
65
107
|
end
|
|
66
108
|
end
|
|
67
109
|
end
|
|
68
110
|
|
|
111
|
+
def last_modified_for(collection)
|
|
112
|
+
return unless collection
|
|
113
|
+
|
|
114
|
+
resources = collection.send(:load_resources).select(&:buildable?)
|
|
115
|
+
dates = resources.filter_map { it.metadata.updated_at || it.metadata.publication_date }
|
|
116
|
+
|
|
117
|
+
dates.max&.iso8601
|
|
118
|
+
end
|
|
119
|
+
|
|
69
120
|
def routes = Rails.application.routes.url_helpers
|
|
70
121
|
end
|
|
71
122
|
end
|
data/lib/perron/site/builder.rb
CHANGED
|
@@ -16,6 +16,8 @@ module Perron
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def build
|
|
19
|
+
run_hook(:before_build)
|
|
20
|
+
|
|
19
21
|
if Perron.configuration.mode.standalone?
|
|
20
22
|
puts "🧹 Cleaning previous build…"
|
|
21
23
|
|
|
@@ -35,6 +37,8 @@ module Perron
|
|
|
35
37
|
output_preview_urls
|
|
36
38
|
|
|
37
39
|
puts "\n✅ Build complete"
|
|
40
|
+
ensure
|
|
41
|
+
run_hook(:after_build)
|
|
38
42
|
end
|
|
39
43
|
|
|
40
44
|
private
|
|
@@ -42,7 +46,7 @@ module Perron
|
|
|
42
46
|
def paths
|
|
43
47
|
Set.new.tap do |paths|
|
|
44
48
|
Perron::Site::Builder::AdditionalRoutes.new(paths).get
|
|
45
|
-
Perron::Site
|
|
49
|
+
Perron::Site::Builder::Paths.new(paths).get
|
|
46
50
|
end
|
|
47
51
|
end
|
|
48
52
|
|
|
@@ -58,6 +62,26 @@ module Perron
|
|
|
58
62
|
end
|
|
59
63
|
end
|
|
60
64
|
end
|
|
65
|
+
|
|
66
|
+
def run_hook(name)
|
|
67
|
+
return unless (hook = Perron.configuration.public_send(name))
|
|
68
|
+
|
|
69
|
+
context = Context.new(
|
|
70
|
+
output_path: @output_path.to_s,
|
|
71
|
+
mode: Perron.configuration.mode
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
hook.call(context)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
class Context
|
|
78
|
+
attr_reader :output_path, :mode
|
|
79
|
+
|
|
80
|
+
def initialize(output_path:, mode:)
|
|
81
|
+
@output_path = output_path
|
|
82
|
+
@mode = mode
|
|
83
|
+
end
|
|
84
|
+
end
|
|
61
85
|
end
|
|
62
86
|
end
|
|
63
87
|
end
|
data/lib/perron/site/validate.rb
CHANGED
|
@@ -19,8 +19,7 @@ module Perron
|
|
|
19
19
|
|
|
20
20
|
puts [
|
|
21
21
|
"Validation finished",
|
|
22
|
-
(" with #{@failures.count} failures" if failed?)
|
|
23
|
-
"."
|
|
22
|
+
(" with #{@failures.count} failures" if failed?)
|
|
24
23
|
].join
|
|
25
24
|
end
|
|
26
25
|
|
|
@@ -37,7 +36,11 @@ module Perron
|
|
|
37
36
|
def validate_collection(collection)
|
|
38
37
|
collection.resources.each do |resource|
|
|
39
38
|
resource.validate ? success : failed(resource)
|
|
39
|
+
rescue Psych::SyntaxError => error
|
|
40
|
+
render_yaml error, resource.file_path
|
|
40
41
|
end
|
|
42
|
+
rescue Psych::SyntaxError => error
|
|
43
|
+
render_yaml error, "unknown"
|
|
41
44
|
end
|
|
42
45
|
|
|
43
46
|
def success = print "#{GREEN}.#{RESET}"
|
|
@@ -45,13 +48,22 @@ module Perron
|
|
|
45
48
|
def failed(resource)
|
|
46
49
|
print "#{RED}F#{RESET}"
|
|
47
50
|
|
|
48
|
-
|
|
51
|
+
@failures << Failure.new(
|
|
52
|
+
identifier: resource.file_path,
|
|
53
|
+
errors: resource.errors.respond_to?(:full_messages) ? resource.errors.full_messages : []
|
|
54
|
+
)
|
|
55
|
+
end
|
|
49
56
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
57
|
+
def render_yaml(error, identifier)
|
|
58
|
+
print "#{RED}F#{RESET}"
|
|
59
|
+
|
|
60
|
+
line_info = error.line ? " at line #{error.line}" : ""
|
|
61
|
+
column_info = error.column ? ", column #{error.column}" : ""
|
|
53
62
|
|
|
54
|
-
@failures << Failure.new(
|
|
63
|
+
@failures << Failure.new(
|
|
64
|
+
identifier: identifier,
|
|
65
|
+
errors: ["Invalid YAML#{line_info}#{column_info}: #{error.problem}"]
|
|
66
|
+
)
|
|
55
67
|
end
|
|
56
68
|
|
|
57
69
|
def failures_report
|
data/lib/perron/site.rb
CHANGED
|
@@ -23,6 +23,13 @@ module Perron
|
|
|
23
23
|
|
|
24
24
|
def collection(name) = Collection.new(name)
|
|
25
25
|
|
|
26
|
+
def find_collection(name)
|
|
27
|
+
collection_path = File.join(Perron.configuration.input, name)
|
|
28
|
+
return nil unless File.exist?(collection_path) && File.directory?(collection_path)
|
|
29
|
+
|
|
30
|
+
Collection.new(name)
|
|
31
|
+
end
|
|
32
|
+
|
|
26
33
|
def data(name = nil)
|
|
27
34
|
Perron.deprecator.deprecation_warning(:data, "Use Content::Data::ClassName instead, e.g. `Content::Data::Users.all`")
|
|
28
35
|
|
data/lib/perron/tasks/build.rake
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
namespace :perron do
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
puts "
|
|
2
|
+
desc "Generate static HTML files from Perron collections"
|
|
3
|
+
task build: :environment do
|
|
4
|
+
unless Rails.env.production?
|
|
5
|
+
puts "⚠️ Not running in production mode. Unpublished content will be included in the build."
|
|
6
|
+
puts " └─> Run in production mode using: RAILS_ENV=production bin/rails perron:build"
|
|
7
|
+
puts ""
|
|
6
8
|
end
|
|
7
|
-
end
|
|
8
9
|
|
|
9
|
-
desc "Generate static HTML files from Perron collections"
|
|
10
|
-
task build: [:set_production_env, :environment] do
|
|
11
10
|
Perron::Site.build
|
|
12
11
|
end
|
|
13
12
|
end
|