perron 0.18.0 → 1.0.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 +1 -1
- data/app/controllers/perron/concierge_controller.rb +13 -0
- data/app/helpers/perron/markdown_helper.rb +2 -2
- 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/configuration.rb +4 -0
- data/lib/perron/data_source/class_methods.rb +8 -0
- data/lib/perron/data_source.rb +14 -29
- data/lib/perron/development_feed_server.rb +69 -0
- data/lib/perron/engine.rb +29 -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.rb +23 -0
- data/lib/perron/markdown.rb +2 -2
- data/lib/perron/output_server.rb +9 -0
- data/lib/perron/resource/adjacency.rb +70 -0
- data/lib/perron/resource/associations.rb +1 -1
- data/lib/perron/resource/configuration.rb +9 -4
- data/lib/perron/resource/metadata.rb +10 -1
- data/lib/perron/resource/publishable.rb +2 -0
- data/lib/perron/resource/related.rb +32 -31
- data/lib/perron/resource/sourceable.rb +39 -9
- data/lib/perron/resource.rb +2 -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/paths.rb +58 -14
- 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 +1 -1
- data/lib/perron/site/validate.rb +1 -2
- data/lib/perron/site.rb +7 -0
- data/lib/perron/tasks/build.rake +6 -7
- data/lib/perron/tasks/install.rake +12 -0
- data/lib/perron/version.rb +1 -1
- metadata +18 -5
- 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
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<feed xmlns="http://www.w3.org/2005/Atom">
|
|
3
|
+
<generator uri="https://perron.railsdesigner.com/" version="<%= Perron::VERSION %>">Perron</generator>
|
|
4
|
+
<id><%= current_feed_url.call %></id>
|
|
5
|
+
<title><%= config.title.presence || @configuration.site_name %></title>
|
|
6
|
+
<subtitle><%= config.description.presence || @configuration.site_description %></subtitle>
|
|
7
|
+
<link href="<%= current_feed_url.call %>" rel="self" type="application/atom+xml"/>
|
|
8
|
+
<link href="<%= @configuration.url %>" rel="alternate" type="text/html"/>
|
|
9
|
+
<updated><%= resources.first&.published_at&.iso8601 || Time.current.iso8601 %></updated>
|
|
10
|
+
|
|
11
|
+
<% feed_author = config.author || { name: @configuration.site_name, email: "noreply@#{URI.parse(@configuration.url).host}" } %>
|
|
12
|
+
<author>
|
|
13
|
+
<% if feed_author[:name] %><name><%= feed_author[:name] %></name><% end %>
|
|
14
|
+
<% if feed_author[:email] %><email><%= feed_author[:email] %></email><% end %>
|
|
15
|
+
</author>
|
|
16
|
+
|
|
17
|
+
<% resources.each do |resource| %>
|
|
18
|
+
<entry>
|
|
19
|
+
<id><%= url_for_resource.call(resource) || "#{@configuration.url}/posts/#{resource.id}" %></id>
|
|
20
|
+
<title><%= resource.metadata.title %></title>
|
|
21
|
+
<link href="<%= url_for_resource.call(resource) %>" rel="alternate" type="text/html"/>
|
|
22
|
+
<published><%= resource.published_at&.iso8601 %></published>
|
|
23
|
+
<updated><%= (resource.metadata.updated_at || resource.published_at)&.iso8601 %></updated>
|
|
24
|
+
|
|
25
|
+
<% entry_author = author.call(resource); if entry_author %>
|
|
26
|
+
<author>
|
|
27
|
+
<% if entry_author.name %><name><%= entry_author.name %></name><% end %>
|
|
28
|
+
<% if entry_author.email %><email><%= entry_author.email %></email><% end %>
|
|
29
|
+
</author>
|
|
30
|
+
<% end %>
|
|
31
|
+
|
|
32
|
+
<% base_url = url_for_resource.call(resource) %>
|
|
33
|
+
<% if base_url %>
|
|
34
|
+
<content type="html" xml:base="<%= base_url %>"><![CDATA[<%= Perron::Markdown.render(resource.content, processors: ["absolute_urls"]) %>]]></content>
|
|
35
|
+
<% else %>
|
|
36
|
+
<content type="html"><![CDATA[<%= Perron::Markdown.render(resource.content, processors: ["absolute_urls"]) %>]]></content>
|
|
37
|
+
<% end %>
|
|
38
|
+
|
|
39
|
+
<% resource.metadata.tags&.each do |tag| %>
|
|
40
|
+
<category term="<%= tag %>"/>
|
|
41
|
+
<% end %>
|
|
42
|
+
</entry>
|
|
43
|
+
<% end %>
|
|
44
|
+
</feed>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "perron/site/builder/feeds/author"
|
|
4
|
+
require "perron/site/builder/feeds/template"
|
|
5
|
+
|
|
6
|
+
module Perron
|
|
7
|
+
module Site
|
|
8
|
+
class Builder
|
|
9
|
+
class Feeds
|
|
10
|
+
class Atom
|
|
11
|
+
include Feeds::Author
|
|
12
|
+
include Feeds::Template
|
|
13
|
+
|
|
14
|
+
def initialize(collection:)
|
|
15
|
+
@collection = collection
|
|
16
|
+
@configuration = Perron.configuration
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def generate
|
|
20
|
+
return if resources.empty?
|
|
21
|
+
|
|
22
|
+
template = find_template("atom")
|
|
23
|
+
return unless template
|
|
24
|
+
|
|
25
|
+
render(template, feed_configuration)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def resources
|
|
31
|
+
@resource ||= @collection.resources
|
|
32
|
+
.reject { it.metadata.feed == false }
|
|
33
|
+
.sort_by { it.metadata.published_at || it.metadata.updated_at || Time.current }
|
|
34
|
+
.reverse
|
|
35
|
+
.take(feed_configuration.max_items)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<%= {
|
|
2
|
+
generator: "Perron (#{Perron::VERSION})",
|
|
3
|
+
version: "https://jsonfeed.org/version/1.1",
|
|
4
|
+
home_page_url: @configuration.url,
|
|
5
|
+
title: config.title.presence || @configuration.site_name,
|
|
6
|
+
description: config.description.presence || @configuration.site_description,
|
|
7
|
+
|
|
8
|
+
items: resources.map { |resource|
|
|
9
|
+
item_author = author.call(resource)
|
|
10
|
+
{
|
|
11
|
+
id: resource.id,
|
|
12
|
+
url: url_for_resource.call(resource),
|
|
13
|
+
date_published: resource.published_at&.iso8601,
|
|
14
|
+
title: resource.metadata.title,
|
|
15
|
+
authors: (item_author && item_author.name ? [{ name: item_author.name, email: item_author.email, url: item_author.url, avatar: item_author.avatar }.compact] : nil),
|
|
16
|
+
content_html: Perron::Markdown.render(resource.content, processors: ["absolute_urls"])
|
|
17
|
+
}.compact
|
|
18
|
+
}
|
|
19
|
+
}.to_json %>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "json"
|
|
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 Json
|
|
11
11
|
include Feeds::Author
|
|
12
|
+
include Feeds::Template
|
|
12
13
|
|
|
13
14
|
def initialize(collection:)
|
|
14
15
|
@collection = collection
|
|
@@ -16,50 +17,23 @@ module Perron
|
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
def generate
|
|
19
|
-
return
|
|
20
|
+
return if resources.empty?
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
generator: "Perron (#{Perron::VERSION})",
|
|
24
|
-
version: "https://jsonfeed.org/version/1.1",
|
|
25
|
-
home_page_url: @configuration.url,
|
|
26
|
-
title: feed_configuration.title.presence || @configuration.site_name,
|
|
27
|
-
description: feed_configuration.description.presence || @configuration.site_description,
|
|
28
|
-
items: resources.map do |resource|
|
|
29
|
-
{
|
|
30
|
-
id: resource.id,
|
|
31
|
-
url: url.polymorphic_url(resource, ref: feed_configuration.ref).delete_suffix("?ref="),
|
|
32
|
-
date_published: resource.published_at&.iso8601,
|
|
33
|
-
authors: authors(resource),
|
|
34
|
-
title: resource.metadata.title,
|
|
35
|
-
content_html: Perron::Markdown.render(resource.content)
|
|
36
|
-
}
|
|
37
|
-
end
|
|
38
|
-
}
|
|
39
|
-
end
|
|
22
|
+
template = find_template("json")
|
|
23
|
+
return unless template
|
|
40
24
|
|
|
41
|
-
|
|
25
|
+
render(template, feed_configuration)
|
|
42
26
|
end
|
|
43
27
|
|
|
44
28
|
private
|
|
45
29
|
|
|
46
30
|
def resources
|
|
47
|
-
@
|
|
31
|
+
@resource ||= @collection.resources
|
|
48
32
|
.reject { it.metadata.feed == false }
|
|
49
33
|
.sort_by { it.metadata.published_at || it.metadata.updated_at || Time.current }
|
|
50
34
|
.reverse
|
|
51
35
|
.take(feed_configuration.max_items)
|
|
52
36
|
end
|
|
53
|
-
|
|
54
|
-
def feed_configuration = @collection.configuration.feeds.json
|
|
55
|
-
|
|
56
|
-
def authors(resource)
|
|
57
|
-
author = author(resource)
|
|
58
|
-
|
|
59
|
-
return nil unless author&.name
|
|
60
|
-
|
|
61
|
-
[{name: author.name, email: author.email, url: author.url, avatar: author.avatar}.compact].presence
|
|
62
|
-
end
|
|
63
37
|
end
|
|
64
38
|
end
|
|
65
39
|
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
|
3
|
+
<channel>
|
|
4
|
+
<generator>Perron (<%= Perron::VERSION %>)</generator>
|
|
5
|
+
<link><%= @configuration.url %></link>
|
|
6
|
+
<title><%= config.title.presence || @configuration.site_name %></title>
|
|
7
|
+
<description><%= config.description.presence || @configuration.site_description %></description>
|
|
8
|
+
|
|
9
|
+
<% resources.each do |resource| %>
|
|
10
|
+
<item>
|
|
11
|
+
<guid isPermaLink="false"><%= resource.id %></guid>
|
|
12
|
+
|
|
13
|
+
<% resource_url = url_for_resource.call(resource) %>
|
|
14
|
+
<% if resource_url %>
|
|
15
|
+
<link><%= resource_url %></link>
|
|
16
|
+
<% end %>
|
|
17
|
+
|
|
18
|
+
<pubDate><%= resource.published_at&.rfc822 %></pubDate>
|
|
19
|
+
<% entry_author = author.call(resource); if entry_author && entry_author.email %>
|
|
20
|
+
<author><%= entry_author.name ? "#{entry_author.email} (#{entry_author.name})" : entry_author.email %></author>
|
|
21
|
+
<% end %>
|
|
22
|
+
<title><%= resource.metadata.title %></title>
|
|
23
|
+
|
|
24
|
+
<description><![CDATA[<%= Perron::Markdown.render(resource.content, processors: ["absolute_urls"]) %>]]></description>
|
|
25
|
+
</item>
|
|
26
|
+
<% end %>
|
|
27
|
+
</channel>
|
|
28
|
+
</rss>
|
|
@@ -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
|
|
|
@@ -1,36 +1,80 @@
|
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
next if resource.root?
|
|
16
|
+
buildable_routes.each do |route|
|
|
17
|
+
paths_for(route).each { @paths << it }
|
|
18
|
+
end
|
|
19
|
+
end
|
|
17
20
|
|
|
18
|
-
|
|
21
|
+
private
|
|
19
22
|
|
|
20
|
-
|
|
21
|
-
|
|
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."
|
|
23
29
|
end
|
|
30
|
+
|
|
31
|
+
[routes.public_send("#{route.name}_path")]
|
|
32
|
+
when "show" then show_paths_for(route)
|
|
33
|
+
else []
|
|
24
34
|
end
|
|
25
35
|
end
|
|
26
36
|
|
|
27
|
-
|
|
37
|
+
def show_paths_for(route)
|
|
38
|
+
resources_for(route).reject(&:root?).map do |resource|
|
|
39
|
+
routes.public_send("#{route.name}_path", resource)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
28
42
|
|
|
29
43
|
def routes = Rails.application.routes.url_helpers
|
|
30
44
|
|
|
31
|
-
|
|
45
|
+
class ConstraintCollection
|
|
46
|
+
def initialize(values)
|
|
47
|
+
@values = values
|
|
48
|
+
end
|
|
32
49
|
|
|
33
|
-
|
|
50
|
+
def load_resources
|
|
51
|
+
@values.map { ConstraintResource.new(it) }
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
class ConstraintResource
|
|
56
|
+
def initialize(value)
|
|
57
|
+
@value = value
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def to_param = @value
|
|
61
|
+
|
|
62
|
+
def buildable? = true
|
|
63
|
+
|
|
64
|
+
def root? = false
|
|
65
|
+
|
|
66
|
+
def metadata = Metadata.new
|
|
67
|
+
|
|
68
|
+
class Metadata
|
|
69
|
+
def sitemap = nil
|
|
70
|
+
|
|
71
|
+
def sitemap_priority = nil
|
|
72
|
+
|
|
73
|
+
def sitemap_change_frequency = nil
|
|
74
|
+
|
|
75
|
+
def updated_at = nil
|
|
76
|
+
end
|
|
77
|
+
end
|
|
34
78
|
end
|
|
35
79
|
end
|
|
36
80
|
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