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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/Gemfile.lock +25 -2
  4. data/app/controllers/perron/searches_controller.rb +48 -0
  5. data/app/helpers/perron/feeds_helper.rb +7 -0
  6. data/app/helpers/perron/markdown_helper.rb +3 -3
  7. data/app/helpers/perron/meta_tags_helper.rb +17 -0
  8. data/bin/release +19 -4
  9. data/lib/generators/perron/templates/README.md.tt +31 -7
  10. data/lib/generators/perron/templates/initializer.rb.tt +11 -11
  11. data/lib/generators/rails/content/USAGE +40 -15
  12. data/lib/generators/rails/content/content_generator.rb +53 -15
  13. data/lib/generators/rails/content/templates/controller.rb.tt +1 -5
  14. data/lib/generators/rails/content/templates/model.rb.tt +11 -0
  15. data/lib/generators/rails/content/templates/root.erb.tt +1 -1
  16. data/lib/generators/rails/content/templates/show.html.erb.tt +1 -1
  17. data/lib/perron/collection.rb +10 -1
  18. data/lib/perron/configuration.rb +14 -6
  19. data/lib/perron/content/data.rb +6 -2
  20. data/lib/perron/data_source/class_methods.rb +58 -0
  21. data/lib/perron/data_source/helper_context.rb +20 -0
  22. data/lib/perron/data_source/item.rb +37 -0
  23. data/lib/perron/{data → data_source}/proxy.rb +1 -1
  24. data/lib/perron/data_source.rb +155 -0
  25. data/lib/perron/engine.rb +12 -0
  26. data/lib/perron/html_processor/syntax_highlight.rb +2 -0
  27. data/lib/perron/output_server.rb +7 -2
  28. data/lib/perron/relation.rb +51 -0
  29. data/lib/perron/resource/associations.rb +2 -2
  30. data/lib/perron/resource/class_methods.rb +14 -4
  31. data/lib/perron/resource/configuration.rb +8 -11
  32. data/lib/perron/resource/core.rb +11 -0
  33. data/lib/perron/resource/metadata.rb +1 -1
  34. data/lib/perron/resource/related/stop_words.rb +20 -20
  35. data/lib/perron/resource/related.rb +73 -52
  36. data/lib/perron/resource/scopes.rb +29 -0
  37. data/lib/perron/resource/searchable.rb +19 -0
  38. data/lib/perron/resource/sourceable.rb +2 -2
  39. data/lib/perron/resource/sweeper.rb +45 -0
  40. data/lib/perron/resource/table_of_content.rb +0 -18
  41. data/lib/perron/resource.rb +34 -24
  42. data/lib/perron/site/builder/additional_routes.rb +23 -0
  43. data/lib/perron/site/builder/feeds/author.rb +2 -1
  44. data/lib/perron/site/builder/paths.rb +2 -9
  45. data/lib/perron/site/builder/sitemap.rb +20 -2
  46. data/lib/perron/site/builder.rb +2 -0
  47. data/lib/perron/site.rb +3 -3
  48. data/lib/perron/tasks/build.rake +8 -1
  49. data/lib/perron/version.rb +1 -1
  50. data/lib/perron.rb +1 -1
  51. data/perron.gemspec +1 -0
  52. metadata +29 -7
  53. data/app/helpers/feeds_helper.rb +0 -5
  54. data/app/helpers/meta_tags_helper.rb +0 -15
  55. data/lib/perron/data.rb +0 -145
  56. 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
- @collection.resources
45
+ scores = similarity_matrix[@resource.slug] || {}
46
+
47
+ resources
16
48
  .reject { it.slug == @resource.slug }
17
- .map { [it, cosine_similarities_for(@resource, it)] }
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 cosine_similarities_for(resource_one, resource_two)
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
- return 0.0 if first_vector.empty? || second_vector.empty?
57
+ def similarity_matrix = @cache.similarity_matrix ||= build_similarity_matrix
30
58
 
31
- dot_product = 0.0
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
- first_vector.each_key { dot_product += first_vector[it] * second_vector[it] if second_vector.key?(it) }
63
+ slugs = vectors.keys
64
+ slugs.each_with_index do |slug_a, i|
65
+ next if vectors[slug_a].empty?
34
66
 
35
- first_magnitude = Math.sqrt(first_vector.values.sum { it**2 })
36
- second_magnitude = Math.sqrt(second_vector.values.sum { it**2 })
37
- denominator = first_magnitude * second_magnitude
67
+ slugs[(i + 1)..].each do |slug_b|
68
+ next if vectors[slug_b].empty?
38
69
 
39
- return 0.0 if denominator.zero?
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
- dot_product / denominator
76
+ matrix
42
77
  end
43
78
 
44
- def tfidf_vector_for(target_resource)
45
- @tfidf_vectors ||= {}
46
-
47
- return @tfidf_vectors[target_resource] if @tfidf_vectors.key?(target_resource)
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
- term_count = Hash.new(0)
85
+ def normalize(vector)
86
+ return {} if vector.empty?
55
87
 
56
- tokens.each { |token| term_count[token] += 1 }
88
+ magnitude = Math.sqrt(vector.values.sum { it**2 })
89
+ return {} if magnitude.zero?
57
90
 
58
- tfidf_vector = {}
91
+ vector.transform_values { it / magnitude }
92
+ end
59
93
 
60
- term_count.each do |term, count|
61
- terms = count.to_f / token_count
94
+ def tfidf_vector_for(resource)
95
+ tokens = tokenize(resource)
96
+ return {} if tokens.empty?
62
97
 
63
- tfidf_vector[term] = terms * inverse_document_frequency[term]
64
- end
98
+ token_count = tokens.size.to_f
65
99
 
66
- @tfidf_vectors[target_resource] = tfidf_vector
100
+ tokens.tally.to_h { |term, count| [term, (count / token_count) * inverse_document_frequency[term]] }
67
101
  end
68
102
 
69
- def tokenize_content(target_resource)
70
- @tokenized_content ||= {}
103
+ def tokenize(resource)
104
+ return [] if resource.content.blank?
71
105
 
72
- return @tokenized_content[target_resource] if @tokenized_content.key?(target_resource)
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
- resource_frequency = Hash.new(0)
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
- frequencies
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::Data.new(it.to_s) }
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::Data.new(name.to_s).find { it.public_send(primary_key).to_s == identifier.to_s }
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: []}}
@@ -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 Perron::Resource::Configuration
25
- include Perron::Resource::Core
26
- include Perron::Resource::ClassMethods
27
- include Perron::Resource::Associations
28
- include Perron::Resource::ReadingTime
29
- include Perron::Resource::Sourceable
30
- include Perron::Resource::Publishable
31
- include Perron::Resource::Previewable
32
- include Perron::Resource::TableOfContent
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 association_value(key) = metadata[key]
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
@@ -8,7 +8,8 @@ module Perron
8
8
  private
9
9
 
10
10
  def author(resource)
11
- author = resource.author || feed_configuration.author
11
+ author = (resource&.respond_to?(:author) && resource.author) ||
12
+ feed_configuration.author
12
13
 
13
14
  Author.new(author) if author
14
15
  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
- root = resource.slug == "/"
16
+ next if resource.root?
17
17
 
18
- next if skip? root
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
@@ -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/data"
5
- require "perron/data/proxy"
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::Data.new(name)) || Perron::Data::Proxy.new
29
+ (name && Perron::DataSource.new(name)) || Perron::DataSource::Proxy.new
30
30
  end
31
31
  end
32
32
  end
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Perron
2
- VERSION = "0.16.0"
2
+ VERSION = "0.18.0"
3
3
  end
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
@@ -19,6 +19,7 @@ Gem::Specification.new do |spec|
19
19
  spec.required_ruby_version = ">= 3.4.0"
20
20
 
21
21
  spec.add_dependency "rails", ">= 7.2.0"
22
+ spec.add_dependency "mata", "~> 0.8.0"
22
23
 
23
24
  spec.add_runtime_dependency "csv"
24
25
  spec.add_runtime_dependency "json"