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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +11 -15
  3. data/app/controllers/perron/concierge_controller.rb +16 -0
  4. data/app/helpers/perron/markdown_helper.rb +3 -2
  5. data/app/helpers/perron/meta_tags_helper.rb +3 -9
  6. data/app/helpers/perron/paginate_helper.rb +40 -0
  7. data/app/views/perron/concierge/show.html.erb +271 -0
  8. data/lib/generators/rails/content/USAGE +21 -4
  9. data/lib/generators/rails/content/content_generator.rb +16 -12
  10. data/lib/generators/rails/content/templates/controller.rb.tt +6 -0
  11. data/lib/generators/rails/content/templates/model.rb.tt +1 -1
  12. data/lib/perron/assets/icon.png +0 -0
  13. data/lib/perron/assets/icon.svg +1 -0
  14. data/lib/perron/collection.rb +2 -1
  15. data/lib/perron/configuration.rb +27 -2
  16. data/lib/perron/data_source/class_methods.rb +8 -0
  17. data/lib/perron/data_source.rb +20 -33
  18. data/lib/perron/development_feed_server.rb +69 -0
  19. data/lib/perron/engine.rb +32 -1
  20. data/lib/perron/errors.rb +2 -0
  21. data/lib/perron/feeds.rb +4 -3
  22. data/lib/perron/html_processor/absolute_urls.rb +27 -0
  23. data/lib/perron/html_processor/base.rb +2 -2
  24. data/lib/perron/html_processor.rb +7 -11
  25. data/lib/{generators/perron/templates → perron/install}/README.md.tt +7 -9
  26. data/lib/perron/install/deploy.yml +15 -0
  27. data/lib/perron/install.rb +26 -0
  28. data/lib/perron/markdown.rb +2 -2
  29. data/lib/perron/output_server.rb +9 -0
  30. data/lib/perron/paginate.rb +58 -0
  31. data/lib/perron/relation.rb +24 -6
  32. data/lib/perron/resource/adjacency.rb +70 -0
  33. data/lib/perron/resource/associations.rb +1 -1
  34. data/lib/perron/resource/class_methods.rb +6 -0
  35. data/lib/perron/resource/configuration.rb +12 -4
  36. data/lib/perron/resource/metadata.rb +19 -4
  37. data/lib/perron/resource/publishable.rb +2 -0
  38. data/lib/perron/resource/related.rb +32 -31
  39. data/lib/perron/resource/sourceable.rb +98 -16
  40. data/lib/perron/resource.rb +8 -0
  41. data/lib/perron/site/builder/assets.rb +1 -1
  42. data/lib/perron/site/builder/feeds/atom.erb +44 -0
  43. data/lib/perron/site/builder/feeds/atom.rb +41 -0
  44. data/lib/perron/site/builder/feeds/json.erb +19 -0
  45. data/lib/perron/site/builder/feeds/json.rb +7 -33
  46. data/lib/perron/site/builder/feeds/rss.erb +28 -0
  47. data/lib/perron/site/builder/feeds/rss.rb +6 -28
  48. data/lib/perron/site/builder/feeds/template.rb +63 -0
  49. data/lib/perron/site/builder/feeds.rb +8 -3
  50. data/lib/perron/site/builder/page.rb +19 -4
  51. data/lib/perron/site/builder/paths.rb +75 -13
  52. data/lib/perron/site/builder/route_resources.rb +79 -0
  53. data/lib/perron/site/builder/sitemap.rb +71 -20
  54. data/lib/perron/site/builder.rb +25 -1
  55. data/lib/perron/site/validate.rb +19 -7
  56. data/lib/perron/site.rb +7 -0
  57. data/lib/perron/tasks/build.rake +6 -7
  58. data/lib/perron/tasks/deploy.rake +58 -0
  59. data/lib/perron/tasks/install.rake +12 -0
  60. data/lib/perron/version.rb +1 -1
  61. data/lib/perron.rb +1 -0
  62. data/perron.gemspec +1 -1
  63. metadata +25 -8
  64. data/lib/generators/perron/install_generator.rb +0 -32
  65. data/lib/perron/html_processor/syntax_highlight.rb +0 -32
  66. /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
- Nokogiri::XML::Builder.new(encoding: "UTF-8") do |xml|
22
- xml.rss(:version => "2.0", "xmlns:atom" => "http://www.w3.org/2005/Atom") do
23
- xml.channel do
24
- xml.generator "Perron (#{Perron::VERSION})"
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/rss"
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.rss.enabled
21
- create_file at: config.rss.path, with: Rss.new(collection: collection).generate
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
- action = route_info[:action]
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 = route_info
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 ||= Rails.application.routes.recognize_path(@path)
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
- def initialize(collection, paths)
8
- @collection, @paths = collection, paths
9
+ include RouteResources
10
+
11
+ def initialize(paths)
12
+ @paths = paths
9
13
  end
10
14
 
11
15
  def get
12
- @paths << routes.public_send(index_path) if routes.respond_to?(index_path)
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
- if routes.respond_to?(show_path)
15
- @collection.send(:load_resources).select(&:buildable?).each do |resource|
16
- next if resource.root?
31
+ base_path = routes.public_send("#{route.name}_path")
32
+ collection = collection_for(route)
33
+ return [base_path] unless collection
17
34
 
18
- @paths << routes.public_send(show_path, resource)
35
+ pagination = collection.configuration.pagination
36
+ return [base_path] unless pagination.per_page
19
37
 
20
- (resource.class.try(:nested_routes) || []).each do |nested|
21
- @paths << routes.polymorphic_path([resource, nested])
22
- end
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
- private
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
- def index_path = "#{@collection.name}_path"
63
+ class ConstraintCollection
64
+ def initialize(values)
65
+ @values = values
66
+ end
32
67
 
33
- def show_path = "#{@collection.name.singularize}_path"
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
- Perron::Site.collections.each do |collection|
19
- add_urls_for(collection, with: builder)
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 add_urls_for(collection, with:)
45
- return if collection.configuration.blank?
46
- return if collection.configuration.sitemap.exclude == true
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
- collection.resources.each do |resource|
49
- next if resource.metadata.sitemap == false
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
- priority = resource.metadata.sitemap_priority || collection.configuration.sitemap.priority || Perron.configuration.sitemap.priority
52
- change_frequency = resource.metadata.sitemap_change_frequency || collection.configuration.sitemap.change_frequency || Perron.configuration.sitemap.change_frequency
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
- Rails.application.routes.url_helpers.with_options(Perron.configuration.default_url_options) do |url|
55
- with.url do
56
- with.loc resource.root? ? url.root_url : url.polymorphic_url(resource)
57
- with.priority priority
58
- with.changefreq change_frequency
59
- begin
60
- with.lastmod resource.metadata.updated_at.iso8601
61
- rescue
62
- Time.current.iso8601
63
- end
64
- end
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
@@ -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.collections.each { Perron::Site::Builder::Paths.new(it, paths).get }
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
@@ -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
- errors = []
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
- if resource.respond_to?(:errors) && resource.errors.respond_to?(:full_messages) && resource.errors.any?
51
- errors = resource.errors.full_messages
52
- end
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(identifier: resource.file_path, errors: errors)
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
 
@@ -1,13 +1,12 @@
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"
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