perron 0.16.0 → 0.18.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 +1 -1
- data/Gemfile.lock +25 -2
- data/app/controllers/perron/searches_controller.rb +48 -0
- data/app/helpers/perron/feeds_helper.rb +7 -0
- data/app/helpers/perron/markdown_helper.rb +3 -3
- data/app/helpers/perron/meta_tags_helper.rb +17 -0
- data/bin/release +19 -4
- data/lib/generators/perron/templates/README.md.tt +31 -7
- data/lib/generators/perron/templates/initializer.rb.tt +11 -11
- data/lib/generators/rails/content/USAGE +40 -15
- data/lib/generators/rails/content/content_generator.rb +53 -15
- data/lib/generators/rails/content/templates/controller.rb.tt +1 -5
- data/lib/generators/rails/content/templates/model.rb.tt +11 -0
- data/lib/generators/rails/content/templates/root.erb.tt +1 -1
- data/lib/generators/rails/content/templates/show.html.erb.tt +1 -1
- data/lib/perron/collection.rb +10 -1
- data/lib/perron/configuration.rb +14 -6
- data/lib/perron/content/data.rb +6 -2
- data/lib/perron/data_source/class_methods.rb +58 -0
- data/lib/perron/data_source/helper_context.rb +20 -0
- data/lib/perron/data_source/item.rb +37 -0
- data/lib/perron/{data → data_source}/proxy.rb +1 -1
- data/lib/perron/data_source.rb +155 -0
- data/lib/perron/engine.rb +12 -0
- data/lib/perron/html_processor/syntax_highlight.rb +2 -0
- data/lib/perron/output_server.rb +7 -2
- data/lib/perron/relation.rb +51 -0
- data/lib/perron/resource/associations.rb +2 -2
- data/lib/perron/resource/class_methods.rb +14 -4
- data/lib/perron/resource/configuration.rb +8 -11
- data/lib/perron/resource/core.rb +11 -0
- data/lib/perron/resource/metadata.rb +1 -1
- data/lib/perron/resource/related/stop_words.rb +20 -20
- data/lib/perron/resource/related.rb +73 -52
- data/lib/perron/resource/scopes.rb +29 -0
- data/lib/perron/resource/searchable.rb +19 -0
- data/lib/perron/resource/sourceable.rb +2 -2
- data/lib/perron/resource/sweeper.rb +45 -0
- data/lib/perron/resource/table_of_content.rb +0 -18
- data/lib/perron/resource.rb +34 -24
- data/lib/perron/site/builder/additional_routes.rb +23 -0
- data/lib/perron/site/builder/feeds/author.rb +2 -1
- data/lib/perron/site/builder/paths.rb +2 -9
- data/lib/perron/site/builder/sitemap.rb +20 -2
- data/lib/perron/site/builder.rb +2 -0
- data/lib/perron/site.rb +3 -3
- data/lib/perron/tasks/build.rake +8 -1
- data/lib/perron/version.rb +1 -1
- data/lib/perron.rb +1 -1
- data/perron.gemspec +1 -0
- metadata +29 -7
- data/app/helpers/feeds_helper.rb +0 -5
- data/app/helpers/meta_tags_helper.rb +0 -15
- data/lib/perron/data.rb +0 -145
- data/lib/perron/root.rb +0 -13
|
@@ -5,93 +5,114 @@ require "perron/resource/related/stop_words"
|
|
|
5
5
|
module Perron
|
|
6
6
|
module Site
|
|
7
7
|
class Resource
|
|
8
|
+
# Finds related resources using TF-IDF cosine similarity.
|
|
9
|
+
#
|
|
10
|
+
# Pre-normalizes vectors so cosine similarity reduces to a dot product,
|
|
11
|
+
# then builds a symmetric similarity matrix once per collection.
|
|
12
|
+
# Results are cached at the class level so the O(n²) comparison
|
|
13
|
+
# is paid once, not once per resource.
|
|
8
14
|
class Related
|
|
15
|
+
Cache = Struct.new(:resources, :similarity_matrix, :fingerprint)
|
|
16
|
+
|
|
17
|
+
@collection_caches = {}
|
|
18
|
+
|
|
19
|
+
def self.cache_for(collection_name)
|
|
20
|
+
clear_cache!(collection_name) if stale?(collection_name)
|
|
21
|
+
@collection_caches[collection_name] ||= Cache.new(nil, nil, content_fingerprint(collection_name))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.clear_cache!(collection_name)
|
|
25
|
+
@collection_caches.delete(collection_name)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.stale?(collection_name)
|
|
29
|
+
@collection_caches[collection_name]&.fingerprint != content_fingerprint(collection_name)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.content_fingerprint(collection_name)
|
|
33
|
+
path = File.join(Perron.configuration.input, collection_name)
|
|
34
|
+
files = Dir.glob(File.join(path, "**", "*.*"))
|
|
35
|
+
[files.size, files.map { File.mtime(it) }.max]
|
|
36
|
+
end
|
|
37
|
+
|
|
9
38
|
def initialize(resource)
|
|
10
39
|
@resource = resource
|
|
11
40
|
@collection = resource.collection
|
|
41
|
+
@cache = self.class.cache_for(@collection.name)
|
|
12
42
|
end
|
|
13
43
|
|
|
14
44
|
def find(limit: 5)
|
|
15
|
-
@
|
|
45
|
+
scores = similarity_matrix[@resource.slug] || {}
|
|
46
|
+
|
|
47
|
+
resources
|
|
16
48
|
.reject { it.slug == @resource.slug }
|
|
17
|
-
.
|
|
18
|
-
.sort_by { |_, score| -score }
|
|
19
|
-
.map(&:first)
|
|
49
|
+
.sort_by { -(scores[it.slug] || 0.0) }
|
|
20
50
|
.first(limit)
|
|
21
51
|
end
|
|
22
52
|
|
|
23
53
|
private
|
|
24
54
|
|
|
25
|
-
def
|
|
26
|
-
first_vector = tfidf_vector_for(resource_one)
|
|
27
|
-
second_vector = tfidf_vector_for(resource_two)
|
|
55
|
+
def resources = @cache.resources ||= @collection.resources
|
|
28
56
|
|
|
29
|
-
|
|
57
|
+
def similarity_matrix = @cache.similarity_matrix ||= build_similarity_matrix
|
|
30
58
|
|
|
31
|
-
|
|
59
|
+
def build_similarity_matrix
|
|
60
|
+
vectors = resources.to_h { [it.slug, normalize(tfidf_vector_for(it))] }
|
|
61
|
+
matrix = Hash.new { |h, k| h[k] = {} }
|
|
32
62
|
|
|
33
|
-
|
|
63
|
+
slugs = vectors.keys
|
|
64
|
+
slugs.each_with_index do |slug_a, i|
|
|
65
|
+
next if vectors[slug_a].empty?
|
|
34
66
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
denominator = first_magnitude * second_magnitude
|
|
67
|
+
slugs[(i + 1)..].each do |slug_b|
|
|
68
|
+
next if vectors[slug_b].empty?
|
|
38
69
|
|
|
39
|
-
|
|
70
|
+
score = dot_product(vectors[slug_a], vectors[slug_b])
|
|
71
|
+
matrix[slug_a][slug_b] = score
|
|
72
|
+
matrix[slug_b][slug_a] = score
|
|
73
|
+
end
|
|
74
|
+
end
|
|
40
75
|
|
|
41
|
-
|
|
76
|
+
matrix
|
|
42
77
|
end
|
|
43
78
|
|
|
44
|
-
def
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
tokens = tokenize_content(target_resource)
|
|
50
|
-
token_count = tokens.size
|
|
51
|
-
|
|
52
|
-
return {} if token_count.zero?
|
|
79
|
+
def dot_product(vec_a, vec_b)
|
|
80
|
+
score = 0.0
|
|
81
|
+
vec_a.each_key { score += vec_a[it] * vec_b[it] if vec_b.key?(it) }
|
|
82
|
+
score
|
|
83
|
+
end
|
|
53
84
|
|
|
54
|
-
|
|
85
|
+
def normalize(vector)
|
|
86
|
+
return {} if vector.empty?
|
|
55
87
|
|
|
56
|
-
|
|
88
|
+
magnitude = Math.sqrt(vector.values.sum { it**2 })
|
|
89
|
+
return {} if magnitude.zero?
|
|
57
90
|
|
|
58
|
-
|
|
91
|
+
vector.transform_values { it / magnitude }
|
|
92
|
+
end
|
|
59
93
|
|
|
60
|
-
|
|
61
|
-
|
|
94
|
+
def tfidf_vector_for(resource)
|
|
95
|
+
tokens = tokenize(resource)
|
|
96
|
+
return {} if tokens.empty?
|
|
62
97
|
|
|
63
|
-
|
|
64
|
-
end
|
|
98
|
+
token_count = tokens.size.to_f
|
|
65
99
|
|
|
66
|
-
|
|
100
|
+
tokens.tally.to_h { |term, count| [term, (count / token_count) * inverse_document_frequency[term]] }
|
|
67
101
|
end
|
|
68
102
|
|
|
69
|
-
def
|
|
70
|
-
|
|
103
|
+
def tokenize(resource)
|
|
104
|
+
return [] if resource.content.blank?
|
|
71
105
|
|
|
72
|
-
|
|
73
|
-
return [] if target_resource.content.blank?
|
|
74
|
-
|
|
75
|
-
content = target_resource.content.gsub(/<[^>]*>/, " ")
|
|
76
|
-
tokens = content.downcase.scan(/\w+/).reject { StopWords.all.include?(it) || it.length < 3 }
|
|
77
|
-
|
|
78
|
-
@tokenized_content[target_resource] = tokens
|
|
106
|
+
resource.content.gsub(/<[^>]*>/, " ").downcase.scan(/\w+/).reject { StopWords.all.include?(it) || it.length < 3 }
|
|
79
107
|
end
|
|
80
108
|
|
|
81
109
|
def inverse_document_frequency
|
|
82
110
|
@inverse_document_frequency ||= begin
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
@collection.resources.each { tokenize_content(it).uniq.each { resource_frequency[it] += 1 } }
|
|
86
|
-
|
|
87
|
-
frequencies = {}
|
|
88
|
-
total_resources = @collection.resources.size
|
|
89
|
-
|
|
90
|
-
resource_frequency.each do |term, frequency|
|
|
91
|
-
frequencies[term] = Math.log(total_resources.to_f / (1 + frequency))
|
|
92
|
-
end
|
|
111
|
+
doc_frequency = Hash.new(0)
|
|
112
|
+
resources.each { tokenize(it).uniq.each { doc_frequency[it] += 1 } }
|
|
93
113
|
|
|
94
|
-
|
|
114
|
+
total = resources.size.to_f
|
|
115
|
+
doc_frequency.transform_values { Math.log(total / (1 + it)) }
|
|
95
116
|
end
|
|
96
117
|
end
|
|
97
118
|
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Perron
|
|
4
|
+
class Resource
|
|
5
|
+
module Scopes
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
class_methods do
|
|
9
|
+
def scope(name, body)
|
|
10
|
+
unless body.respond_to?(:call)
|
|
11
|
+
raise ArgumentError, "The scope body needs to be callable."
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
if respond_to?(name, true)
|
|
15
|
+
raise ArgumentError, "Cannot define scope :#{name} because it already exists."
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
singleton_class.define_method(name) do |*arguments|
|
|
19
|
+
instance_exec(*arguments, &body)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
Perron::Relation.define_method(name) do |*arguments|
|
|
23
|
+
instance_exec(*arguments, &body)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Perron
|
|
4
|
+
class Resource
|
|
5
|
+
module Searchable
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
class_attribute :search_fields_list, default: []
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class_methods do
|
|
13
|
+
def search_fields(*fields)
|
|
14
|
+
self.search_fields_list = fields
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -40,7 +40,7 @@ module Perron
|
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
def combinations
|
|
43
|
-
datasets = source_names.map { Perron::
|
|
43
|
+
datasets = source_names.map { Perron::DataSource.new(it.to_s) }
|
|
44
44
|
|
|
45
45
|
datasets.first.product(*datasets[1..])
|
|
46
46
|
end
|
|
@@ -72,7 +72,7 @@ module Perron
|
|
|
72
72
|
singular_name = name.to_s.singularize
|
|
73
73
|
identifier = frontmatter["#{singular_name}_#{primary_key}"]
|
|
74
74
|
|
|
75
|
-
hash[name] = Perron::
|
|
75
|
+
hash[name] = Perron::DataSource.new(name.to_s).find { it.public_send(primary_key).to_s == identifier.to_s }
|
|
76
76
|
end
|
|
77
77
|
|
|
78
78
|
Source.new(data)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Perron
|
|
4
|
+
class Resource
|
|
5
|
+
module Sweeper
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
def extracted_headings
|
|
9
|
+
extract_heading_texts(from: rendered_document)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def sweeped_content
|
|
13
|
+
ActionView::Base.full_sanitizer.sanitize(
|
|
14
|
+
rendered_document.text.gsub(/\s+/, " ").strip
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def rendered_document
|
|
21
|
+
@rendered_document ||= Nokogiri::HTML::DocumentFragment.parse(Markdown.render(content))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def extract_heading_texts(from:, levels: "h1, h2, h3, h4, h5, h6")
|
|
25
|
+
from.css(levels).map { it.text.strip }.compact_blank
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def extract_headings(from:, levels:)
|
|
29
|
+
from.css(levels).each_with_object([]) do |heading, headings|
|
|
30
|
+
heading_text = heading.text.strip
|
|
31
|
+
id = heading["id"] || heading.at("a")&.[]("id")
|
|
32
|
+
|
|
33
|
+
next if heading_text.empty? || id.blank?
|
|
34
|
+
|
|
35
|
+
headings << TableOfContent::Item.new(
|
|
36
|
+
id: id,
|
|
37
|
+
text: heading_text,
|
|
38
|
+
level: heading.name[1..].to_i,
|
|
39
|
+
children: []
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -20,24 +20,6 @@ module Perron
|
|
|
20
20
|
|
|
21
21
|
Item = ::Data.define(:id, :text, :level, :children)
|
|
22
22
|
|
|
23
|
-
def extract_headings(from:, levels:)
|
|
24
|
-
from.css(levels).each_with_object([]) do |heading, headings|
|
|
25
|
-
heading.tap do |node|
|
|
26
|
-
heading_text = node.text.strip
|
|
27
|
-
id = node["id"] || node.at("a")&.[]("id")
|
|
28
|
-
|
|
29
|
-
next if heading_text.empty? || id.blank?
|
|
30
|
-
|
|
31
|
-
headings << Item.new(
|
|
32
|
-
id: id,
|
|
33
|
-
text: heading_text,
|
|
34
|
-
level: node.name[1..].to_i,
|
|
35
|
-
children: []
|
|
36
|
-
)
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
|
|
41
23
|
class Builder
|
|
42
24
|
def build(headings)
|
|
43
25
|
parents = {0 => {children: []}}
|
data/lib/perron/resource.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "perron/relation"
|
|
3
4
|
require "perron/resource/configuration"
|
|
4
5
|
require "perron/resource/core"
|
|
5
6
|
require "perron/resource/class_methods"
|
|
@@ -10,26 +11,30 @@ require "perron/resource/publishable"
|
|
|
10
11
|
require "perron/resource/reading_time"
|
|
11
12
|
require "perron/resource/related"
|
|
12
13
|
require "perron/resource/renderer"
|
|
14
|
+
require "perron/resource/scopes"
|
|
13
15
|
require "perron/resource/slug"
|
|
16
|
+
require "perron/resource/searchable"
|
|
14
17
|
require "perron/resource/separator"
|
|
15
18
|
require "perron/resource/sourceable"
|
|
19
|
+
require "perron/resource/sweeper"
|
|
16
20
|
require "perron/resource/table_of_content"
|
|
17
21
|
|
|
18
22
|
module Perron
|
|
19
23
|
class Resource
|
|
20
|
-
ID_LENGTH = 8
|
|
21
|
-
|
|
22
24
|
include ActiveModel::Validations
|
|
23
25
|
|
|
24
|
-
include
|
|
25
|
-
include
|
|
26
|
-
include
|
|
27
|
-
include
|
|
28
|
-
include
|
|
29
|
-
include
|
|
30
|
-
include
|
|
31
|
-
include
|
|
32
|
-
include
|
|
26
|
+
include Configuration
|
|
27
|
+
include Core
|
|
28
|
+
include ClassMethods
|
|
29
|
+
include Associations
|
|
30
|
+
include ReadingTime
|
|
31
|
+
include Searchable
|
|
32
|
+
include Sourceable
|
|
33
|
+
include Publishable
|
|
34
|
+
include Previewable
|
|
35
|
+
include Scopes
|
|
36
|
+
include Sweeper
|
|
37
|
+
include TableOfContent
|
|
33
38
|
|
|
34
39
|
attr_reader :file_path, :id
|
|
35
40
|
|
|
@@ -41,6 +46,16 @@ module Perron
|
|
|
41
46
|
raise Errors::FileNotFoundError, "No such file: #{file_path}" unless File.exist?(file_path)
|
|
42
47
|
end
|
|
43
48
|
|
|
49
|
+
def pluck(*attributes)
|
|
50
|
+
raise ArgumentError, "wrong number of arguments (given 0, expected 1+)" if attributes.empty?
|
|
51
|
+
|
|
52
|
+
if attributes.size == 1
|
|
53
|
+
public_send(attributes.first)
|
|
54
|
+
else
|
|
55
|
+
attributes.map { |attr| public_send(attr) }
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
44
59
|
def filename = File.basename(@file_path)
|
|
45
60
|
|
|
46
61
|
def slug = Perron::Resource::Slug.new(self, frontmatter).create
|
|
@@ -66,15 +81,8 @@ module Perron
|
|
|
66
81
|
render_inline_erb using: page_content
|
|
67
82
|
end
|
|
68
83
|
|
|
69
|
-
def
|
|
70
|
-
|
|
71
|
-
def to_partial_path
|
|
72
|
-
@to_partial_path ||= begin
|
|
73
|
-
element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(self.class.model_name))
|
|
74
|
-
collection = ActiveSupport::Inflector.tableize(self.class.model_name)
|
|
75
|
-
|
|
76
|
-
File.join("content", collection, element)
|
|
77
|
-
end
|
|
84
|
+
def inline(layout: "application", **options)
|
|
85
|
+
{html: content, layout: layout}.merge(options)
|
|
78
86
|
end
|
|
79
87
|
|
|
80
88
|
def collection = Collection.new(self.class.model_name.collection)
|
|
@@ -82,8 +90,14 @@ module Perron
|
|
|
82
90
|
def related_resources(limit: 5) = Perron::Site::Resource::Related.new(self).find(limit:)
|
|
83
91
|
alias_method :related, :related_resources
|
|
84
92
|
|
|
93
|
+
def root?
|
|
94
|
+
slug == "/"
|
|
95
|
+
end
|
|
96
|
+
|
|
85
97
|
private
|
|
86
98
|
|
|
99
|
+
ID_LENGTH = 8
|
|
100
|
+
|
|
87
101
|
def frontmatter
|
|
88
102
|
@frontmatter ||= Perron::Resource::Separator.new(raw_content).frontmatter
|
|
89
103
|
end
|
|
@@ -103,9 +117,5 @@ module Perron
|
|
|
103
117
|
def erb_processing?
|
|
104
118
|
@file_path.ends_with?(".erb") || metadata.erb == true
|
|
105
119
|
end
|
|
106
|
-
|
|
107
|
-
def root?
|
|
108
|
-
collection.name.inquiry.pages? && File.basename(filename) == "root"
|
|
109
|
-
end
|
|
110
120
|
end
|
|
111
121
|
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Perron
|
|
4
|
+
module Site
|
|
5
|
+
class Builder
|
|
6
|
+
class AdditionalRoutes
|
|
7
|
+
def initialize(paths)
|
|
8
|
+
@paths = paths
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def get
|
|
12
|
+
Perron.configuration.additional_routes.each do |route_name|
|
|
13
|
+
@paths << routes.public_send(route_name) if routes.respond_to?(route_name)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def routes = Rails.application.routes.url_helpers
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -13,11 +13,9 @@ module Perron
|
|
|
13
13
|
|
|
14
14
|
if routes.respond_to?(show_path)
|
|
15
15
|
@collection.send(:load_resources).select(&:buildable?).each do |resource|
|
|
16
|
-
|
|
16
|
+
next if resource.root?
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
@paths << (root ? routes.root_path : routes.public_send(show_path, resource))
|
|
18
|
+
@paths << routes.public_send(show_path, resource)
|
|
21
19
|
|
|
22
20
|
(resource.class.try(:nested_routes) || []).each do |nested|
|
|
23
21
|
@paths << routes.polymorphic_path([resource, nested])
|
|
@@ -28,11 +26,6 @@ module Perron
|
|
|
28
26
|
|
|
29
27
|
private
|
|
30
28
|
|
|
31
|
-
def skip?(root)
|
|
32
|
-
root &&
|
|
33
|
-
Perron.configuration.mode.integrated? && Perron.configuration.exclude_root?
|
|
34
|
-
end
|
|
35
|
-
|
|
36
29
|
def routes = Rails.application.routes.url_helpers
|
|
37
30
|
|
|
38
31
|
def index_path = "#{@collection.name}_path"
|
|
@@ -13,6 +13,8 @@ module Perron
|
|
|
13
13
|
|
|
14
14
|
xml = Nokogiri::XML::Builder.new(encoding: "UTF-8") do |builder|
|
|
15
15
|
builder.urlset(xmlns: "http://www.sitemaps.org/schemas/sitemap/0.9") do
|
|
16
|
+
add_additional_routes(with: builder)
|
|
17
|
+
|
|
16
18
|
Perron::Site.collections.each do |collection|
|
|
17
19
|
add_urls_for(collection, with: builder)
|
|
18
20
|
end
|
|
@@ -24,6 +26,21 @@ module Perron
|
|
|
24
26
|
|
|
25
27
|
private
|
|
26
28
|
|
|
29
|
+
def add_additional_routes(with:)
|
|
30
|
+
(Perron.configuration.additional_routes || []).each do |route_name|
|
|
31
|
+
next unless routes.respond_to?(route_name)
|
|
32
|
+
|
|
33
|
+
routes.with_options(Perron.configuration.default_url_options) do |url|
|
|
34
|
+
with.url do
|
|
35
|
+
with.loc url.public_send(route_name.to_s.gsub("_path", "_url"))
|
|
36
|
+
with.priority Perron.configuration.sitemap.priority
|
|
37
|
+
with.changefreq Perron.configuration.sitemap.change_frequency
|
|
38
|
+
with.lastmod Time.current.iso8601
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
27
44
|
def add_urls_for(collection, with:)
|
|
28
45
|
return if collection.configuration.blank?
|
|
29
46
|
return if collection.configuration.sitemap.exclude == true
|
|
@@ -31,13 +48,12 @@ module Perron
|
|
|
31
48
|
collection.resources.each do |resource|
|
|
32
49
|
next if resource.metadata.sitemap == false
|
|
33
50
|
|
|
34
|
-
root = resource.slug == "/"
|
|
35
51
|
priority = resource.metadata.sitemap_priority || collection.configuration.sitemap.priority || Perron.configuration.sitemap.priority
|
|
36
52
|
change_frequency = resource.metadata.sitemap_change_frequency || collection.configuration.sitemap.change_frequency || Perron.configuration.sitemap.change_frequency
|
|
37
53
|
|
|
38
54
|
Rails.application.routes.url_helpers.with_options(Perron.configuration.default_url_options) do |url|
|
|
39
55
|
with.url do
|
|
40
|
-
with.loc root ? url.root_url : url.polymorphic_url(resource)
|
|
56
|
+
with.loc resource.root? ? url.root_url : url.polymorphic_url(resource)
|
|
41
57
|
with.priority priority
|
|
42
58
|
with.changefreq change_frequency
|
|
43
59
|
begin
|
|
@@ -49,6 +65,8 @@ module Perron
|
|
|
49
65
|
end
|
|
50
66
|
end
|
|
51
67
|
end
|
|
68
|
+
|
|
69
|
+
def routes = Rails.application.routes.url_helpers
|
|
52
70
|
end
|
|
53
71
|
end
|
|
54
72
|
end
|
data/lib/perron/site/builder.rb
CHANGED
|
@@ -5,6 +5,7 @@ require "perron/site/builder/sitemap"
|
|
|
5
5
|
require "perron/site/builder/feeds"
|
|
6
6
|
require "perron/site/builder/public_files"
|
|
7
7
|
require "perron/site/builder/paths"
|
|
8
|
+
require "perron/site/builder/additional_routes"
|
|
8
9
|
require "perron/site/builder/page"
|
|
9
10
|
|
|
10
11
|
module Perron
|
|
@@ -40,6 +41,7 @@ module Perron
|
|
|
40
41
|
|
|
41
42
|
def paths
|
|
42
43
|
Set.new.tap do |paths|
|
|
44
|
+
Perron::Site::Builder::AdditionalRoutes.new(paths).get
|
|
43
45
|
Perron::Site.collections.each { Perron::Site::Builder::Paths.new(it, paths).get }
|
|
44
46
|
end
|
|
45
47
|
end
|
data/lib/perron/site.rb
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "perron/collection"
|
|
4
|
-
require "perron/
|
|
5
|
-
require "perron/
|
|
4
|
+
require "perron/data_source"
|
|
5
|
+
require "perron/data_source/proxy"
|
|
6
6
|
require "perron/site/builder"
|
|
7
7
|
require "perron/site/validate"
|
|
8
8
|
|
|
@@ -26,7 +26,7 @@ module Perron
|
|
|
26
26
|
def data(name = nil)
|
|
27
27
|
Perron.deprecator.deprecation_warning(:data, "Use Content::Data::ClassName instead, e.g. `Content::Data::Users.all`")
|
|
28
28
|
|
|
29
|
-
(name && Perron::
|
|
29
|
+
(name && Perron::DataSource.new(name)) || Perron::DataSource::Proxy.new
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
32
|
end
|
data/lib/perron/tasks/build.rake
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
namespace :perron do
|
|
2
|
+
task :set_production_env do
|
|
3
|
+
unless ENV["RAILS_ENV"]
|
|
4
|
+
ENV["RAILS_ENV"] = "production"
|
|
5
|
+
puts "RAILS_ENV not set, defaulting to production"
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
2
9
|
desc "Generate static HTML files from Perron collections"
|
|
3
|
-
task build: :environment do
|
|
10
|
+
task build: [:set_production_env, :environment] do
|
|
4
11
|
Perron::Site.build
|
|
5
12
|
end
|
|
6
13
|
end
|
data/lib/perron/version.rb
CHANGED
data/lib/perron.rb
CHANGED
|
@@ -4,8 +4,8 @@ require "perron/version"
|
|
|
4
4
|
require "perron/configuration"
|
|
5
5
|
require "perron/deprecator"
|
|
6
6
|
require "perron/errors"
|
|
7
|
-
require "perron/root"
|
|
8
7
|
require "perron/site"
|
|
8
|
+
require "perron/relation"
|
|
9
9
|
require "perron/content/data"
|
|
10
10
|
require "perron/resource"
|
|
11
11
|
require "perron/markdown"
|
data/perron.gemspec
CHANGED