perron 0.14.0 → 0.16.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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +7 -3
  3. data/lib/generators/perron/install_generator.rb +1 -4
  4. data/lib/generators/rails/content/USAGE +41 -0
  5. data/lib/generators/rails/content/content_generator.rb +126 -0
  6. data/lib/perron/collection.rb +12 -7
  7. data/lib/perron/configuration.rb +0 -2
  8. data/lib/perron/content/data.rb +13 -0
  9. data/lib/perron/data.rb +13 -0
  10. data/lib/perron/deprecator.rb +7 -0
  11. data/lib/perron/engine.rb +8 -1
  12. data/lib/perron/output_server.rb +49 -0
  13. data/lib/perron/resource/associations.rb +23 -9
  14. data/lib/perron/resource/previewable.rb +26 -0
  15. data/lib/perron/resource/publishable.rb +8 -0
  16. data/lib/perron/resource/slug.rb +3 -2
  17. data/lib/perron/resource/sourceable.rb +3 -2
  18. data/lib/perron/resource.rb +4 -0
  19. data/lib/perron/site/builder/feeds/author.rb +41 -0
  20. data/lib/perron/site/builder/feeds/json.rb +12 -0
  21. data/lib/perron/site/builder/feeds/rss.rb +6 -0
  22. data/lib/perron/site/builder/paths.rb +1 -1
  23. data/lib/perron/site/builder.rb +13 -0
  24. data/lib/perron/site.rb +2 -0
  25. data/lib/perron/tasks/clobber.rake +12 -0
  26. data/lib/perron/version.rb +1 -1
  27. data/lib/perron.rb +2 -0
  28. metadata +15 -9
  29. data/lib/generators/content/USAGE +0 -16
  30. data/lib/generators/content/content_generator.rb +0 -72
  31. /data/lib/generators/{content → rails/content}/templates/controller.rb.tt +0 -0
  32. /data/lib/generators/{content → rails/content}/templates/index.html.erb.tt +0 -0
  33. /data/lib/generators/{content → rails/content}/templates/model.rb.tt +0 -0
  34. /data/lib/generators/{content → rails/content}/templates/root.erb.tt +0 -0
  35. /data/lib/generators/{content → rails/content}/templates/show.html.erb.tt +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '080352c09efb475419f74de6ee7cf01a491d4cc349f6cf0d6e23d35132c83953'
4
- data.tar.gz: 156c0419afc4c3aa77411ac7f7508cbea56de584d164624be86b4d2615baf7f5
3
+ metadata.gz: 709c1a304332c522dd7e425824f2718b1a84994db9695b842cddab07fa2a6c2d
4
+ data.tar.gz: cd636faddbff65dd1cc27652fb9141a936ce7df7e5f4fca1e156fd7b1ff27522
5
5
  SHA512:
6
- metadata.gz: c75e1453c34bf8a8b8a4345d9167110745c1c47d8094aea3345e85fb46de51b0d326c904a7132c20e058ae89156c48dac5b9311121fc0359ad1d348ccde7d27f
7
- data.tar.gz: a3acd15ee6f0224c032b31dac7272539e6ed6e6c9451a4b22d47c83c108f8fa374f5b6b7ceab97ae7bf4d6cc1296bbac8cd9c8788b28c7187f50c29c4621ee01
6
+ metadata.gz: 40df8027fb5974b0670a80b4c61e918da1f3dd7cec833e5fab48cdb606ddb9d9e56480cb69ad3c3033c2fdf1deb09e0c105cb15730cb92581e1d57c3db57ce3f
7
+ data.tar.gz: af0d331538e600ad27d23f9377d6cdabcc1f3142ad7d07de517b002334c0414896edb45d6d0d5d6ab50a6af81be12f4b2bf667c15e8bc50e211a824b6701d1b2
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- perron (0.14.0)
4
+ perron (0.16.0)
5
5
  csv
6
6
  json
7
7
  psych
@@ -120,7 +120,8 @@ GEM
120
120
  net-smtp
121
121
  marcel (1.0.4)
122
122
  mini_mime (1.1.5)
123
- minitest (5.25.5)
123
+ mini_portile2 (2.8.9)
124
+ minitest (5.27.0)
124
125
  net-imap (0.5.9)
125
126
  date
126
127
  net-protocol
@@ -131,6 +132,9 @@ GEM
131
132
  net-smtp (0.5.1)
132
133
  net-protocol
133
134
  nio4r (2.7.4)
135
+ nokogiri (1.18.8)
136
+ mini_portile2 (~> 2.8.2)
137
+ racc (~> 1.4)
134
138
  nokogiri (1.18.8-aarch64-linux-gnu)
135
139
  racc (~> 1.4)
136
140
  nokogiri (1.18.8-aarch64-linux-musl)
@@ -244,7 +248,7 @@ GEM
244
248
  concurrent-ruby (~> 1.0)
245
249
  unicode-display_width (3.1.4)
246
250
  unicode-emoji (~> 4.0, >= 4.0.4)
247
- unicode-emoji (4.0.4)
251
+ unicode-emoji (4.2.0)
248
252
  useragent (0.16.11)
249
253
  websocket-driver (0.8.0)
250
254
  base64
@@ -11,10 +11,7 @@ module Perron
11
11
  end
12
12
 
13
13
  def create_data_directory
14
- data_directory = Rails.root.join("app", "content", "data")
15
- empty_directory data_directory
16
-
17
- template "README.md.tt", File.join(data_directory, "README.md")
14
+ template "README.md.tt", "app/content/data/README.md"
18
15
  end
19
16
 
20
17
  def add_markdown_gems
@@ -0,0 +1,41 @@
1
+ Description:
2
+ Generates content model scaffold (controller, views, routes) or creates
3
+ new content files from templates.
4
+
5
+ Examples:
6
+ Generate content scaffold:
7
+ rails generate content Post
8
+ rails generate content Post index
9
+ rails generate content Post index show
10
+
11
+ This will create:
12
+ app/content/posts/
13
+ app/models/content/post.rb
14
+ app/controllers/content/posts_controller.rb
15
+ app/views/content/posts/index.html.erb
16
+ app/views/content/posts/show.html.erb
17
+
18
+ And adds: resources :posts, module: :content, only: %w[index show]
19
+
20
+ Create new content file from template:
21
+ rails generate content Post --new
22
+ rails generate content Post --new "My First Post"
23
+
24
+ This will create a new content file in app/content/posts/ using:
25
+ - YYYY-MM-DD-template.*.tt (if exists, with date prefix)
26
+ - template.*.tt (if exists, without date prefix)
27
+ - Empty file with frontmatter dashes (if no template)
28
+
29
+ Template files support ERB:
30
+ ---
31
+ title: <%= @title %>
32
+ published_at: <%= Time.current %>
33
+ ---
34
+
35
+ Arguments:
36
+ NAME: Name of the content model (singular or plural)
37
+ actions: Actions to generate (default: index show)
38
+
39
+ Options:
40
+ --new [TITLE]: Create new content file instead of scaffold
41
+ --force-plural: Use plural form for model name and class
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/base"
4
+
5
+ module Rails
6
+ module Generators
7
+ class ContentGenerator < Rails::Generators::NamedBase
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ class_option :force_plural, type: :boolean, default: false, desc: "Forces the use of a plural model name and class"
11
+ class_option :new, type: :string, default: nil, banner: "TITLE",
12
+ desc: "Create a new content file from template instead of generating scaffold"
13
+
14
+ argument :actions, type: :array, default: %w[index show], banner: "actions", desc: "Specify which actions to generate (index/show)"
15
+
16
+ def initialize(*args)
17
+ super
18
+
19
+ @content_mode = !options[:new].nil?
20
+ @content_title = options[:new].presence
21
+ end
22
+
23
+ def create_content_file
24
+ return unless @content_mode
25
+
26
+ @title = @content_title
27
+
28
+ if template_file
29
+ create_file File.join(content_directory, filename_from_template), ERB.new(File.read(template_file)).result(binding)
30
+ else
31
+ create_file File.join(content_directory, filename_from_template), "---\n---\n"
32
+ end
33
+ end
34
+
35
+ def create_model
36
+ return if @content_mode
37
+
38
+ template "model.rb.tt", File.join("app/models/content", "#{file_name}.rb")
39
+ end
40
+
41
+ def create_controller
42
+ return if @content_mode
43
+
44
+ template "controller.rb.tt", File.join("app/controllers/content", "#{plural_file_name}_controller.rb")
45
+ end
46
+
47
+ def create_views
48
+ return if @content_mode
49
+
50
+ empty_directory view_directory
51
+
52
+ actions.each do |action|
53
+ template "#{action}.html.erb.tt", File.join(view_directory, "#{action}.html.erb")
54
+ end
55
+ end
56
+
57
+ def create_content_directory
58
+ return if @content_mode
59
+
60
+ FileUtils.mkdir_p(content_directory)
61
+ end
62
+
63
+ def create_pages_root
64
+ return if @content_mode
65
+ return unless pages_controller?
66
+
67
+ template "root.erb.tt", File.join(content_directory, "root.erb")
68
+ end
69
+
70
+ def add_content_route
71
+ return if @content_mode
72
+
73
+ route "resources :#{plural_file_name}, module: :content, only: %w[#{actions.join(" ")}]"
74
+ end
75
+
76
+ def add_root_route
77
+ return if @content_mode
78
+ return unless pages_controller?
79
+ return if root_route_exists?
80
+
81
+ inject_into_file "config/routes.rb", " root to: \"content/pages#root\"\n", before: /^\s*end\s*$/
82
+ end
83
+
84
+ private
85
+
86
+ def file_name
87
+ options[:force_plural] ? super.pluralize : super.singularize
88
+ end
89
+
90
+ def class_name
91
+ options[:force_plural] ? super.pluralize : super.singularize
92
+ end
93
+
94
+ def view_directory = File.join(destination_root, "app", "views", "content", plural_file_name)
95
+
96
+ def content_directory = File.join(destination_root, "app", "content", plural_file_name)
97
+
98
+ def plural_class_name = plural_name.camelize
99
+
100
+ def pages_controller? = plural_file_name == "pages"
101
+
102
+ def root_route_exists?
103
+ routes = File.join(destination_root, "config", "routes.rb")
104
+
105
+ return false unless File.exist?(routes)
106
+
107
+ File.read(routes).match?(/\broot\s+to:/)
108
+ end
109
+
110
+ def template_file
111
+ @template_file ||= Dir.glob(File.join(content_directory, "{YYYY-MM-DD-,}template.*.tt")).first
112
+ end
113
+
114
+ def filename_from_template
115
+ @filename_from_template ||= begin
116
+ return "untitled.md" unless template_file
117
+
118
+ File.basename(template_file, ".tt").tap do |name|
119
+ name.gsub!("YYYY-MM-DD", Time.current.strftime("%Y-%m-%d"))
120
+ name.sub!("template", @content_title ? @content_title.parameterize : "untitled")
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -16,17 +16,12 @@ module Perron
16
16
  end
17
17
 
18
18
  def all(resource_class = "Content::#{name.classify}".safe_constantize)
19
- allowed_extensions = Perron.configuration.allowed_extensions.map { ".#{it}" }.to_set
20
-
21
- Dir.glob("#{@collection_path}/**/*.*")
22
- .select { allowed_extensions.include?(File.extname(it)) }
23
- .map { resource_class.new(it) }
24
- .select(&:published?)
19
+ load_resources(resource_class).select(&:published?)
25
20
  end
26
21
  alias_method :resources, :all
27
22
 
28
23
  def find(slug, resource_class = Resource)
29
- resource = all(resource_class).find { it.slug == slug }
24
+ resource = load_resources(resource_class).find { it.slug == slug }
30
25
 
31
26
  return resource if resource
32
27
 
@@ -40,5 +35,15 @@ module Perron
40
35
  end
41
36
 
42
37
  def validate = Perron::Site::Validate.new(collections: [self]).validate
38
+
39
+ private
40
+
41
+ def load_resources(resource_class = "Content::#{name.classify}".safe_constantize)
42
+ allowed_extensions = Perron.configuration.allowed_extensions.map { ".#{it}" }.to_set
43
+
44
+ Dir.glob("#{@collection_path}/**/*.*")
45
+ .select { allowed_extensions.include?(File.extname(it)) }
46
+ .map { resource_class.new(it) }
47
+ end
43
48
  end
44
49
  end
@@ -16,8 +16,6 @@ module Perron
16
16
  @config.site_name = nil
17
17
  @config.site_description = nil
18
18
 
19
- @config.site_email = nil
20
-
21
19
  @config.output = "output"
22
20
 
23
21
  @config.mode = :standalone
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Content
4
+ module Data
5
+ def self.const_missing(name)
6
+ klass = Class.new(Perron::Data) do
7
+ def self.const_missing(nested_name) = const_set(nested_name, Class.new(Perron::Data))
8
+ end
9
+
10
+ const_set(name, klass)
11
+ end
12
+ end
13
+ end
data/lib/perron/data.rb CHANGED
@@ -13,6 +13,17 @@ module Perron
13
13
  end
14
14
 
15
15
  class << self
16
+ def all
17
+ parts = name.to_s.split("::").drop(2)
18
+ identifier = parts.empty? ? name.demodulize.underscore : parts.map(&:underscore).join("/")
19
+
20
+ new(identifier)
21
+ end
22
+
23
+ def find(id)
24
+ all.find { it[:id] == id || it["id"] == id }
25
+ end
26
+
16
27
  def path_for(identifier)
17
28
  path = Pathname.new(identifier)
18
29
 
@@ -107,6 +118,8 @@ module Perron
107
118
 
108
119
  def [](key) = @attributes[key.to_sym]
109
120
 
121
+ def association_value(key) = self[key]
122
+
110
123
  def to_partial_path
111
124
  @to_partial_path ||= begin
112
125
  identifier = @identifier.to_s
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Perron
4
+ def self.deprecator
5
+ @deprecator ||= ActiveSupport::Deprecation.new("1.0", "Perron")
6
+ end
7
+ end
data/lib/perron/engine.rb CHANGED
@@ -1,15 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "perron/output_server"
4
+
3
5
  module Perron
4
6
  class Engine < Rails::Engine
5
7
  initializer "perron.default_url_options" do |app|
6
8
  app.config.action_controller.default_url_options = Perron.configuration.default_url_options
7
9
  end
8
10
 
11
+ initializer "perron.output_server" do |app|
12
+ app.middleware.use Perron::OutputServer
13
+ end
14
+
9
15
  rake_tasks do
10
16
  load File.expand_path("../tasks/build.rake", __FILE__)
11
- load File.expand_path("../tasks/validate.rake", __FILE__)
17
+ load File.expand_path("../tasks/clobber.rake", __FILE__)
12
18
  load File.expand_path("../tasks/sync_sources.rake", __FILE__)
19
+ load File.expand_path("../tasks/validate.rake", __FILE__)
13
20
  end
14
21
  end
15
22
  end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Perron
4
+ class OutputServer
5
+ def initialize(app)
6
+ @app = app
7
+ end
8
+
9
+ def call(environment)
10
+ return @app.call(environment) if disabled?
11
+
12
+ static_file(environment).then do |file|
13
+ file ? serve(file) : @app.call(environment)
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def disabled? = !enabled?
20
+
21
+ def static_file(environment)
22
+ request_path = Rack::Request.new(environment).path_info
23
+ file_path = File.join(output_path, request_path, "index.html")
24
+
25
+ File.file?(file_path) ? file_path : nil
26
+ end
27
+
28
+ def serve(file_path)
29
+ content = File.read(file_path)
30
+
31
+ [
32
+ 200,
33
+
34
+ {
35
+ "Content-Type" => "text/html; charset=utf-8",
36
+ "Content-Length" => content.bytesize.to_s
37
+ },
38
+
39
+ [content]
40
+ ]
41
+ end
42
+
43
+ def enabled? = Dir.exist?(output_path)
44
+
45
+ def output_path
46
+ @output_path ||= Rails.root.join(Perron.configuration.output)
47
+ end
48
+ end
49
+ end
@@ -22,11 +22,11 @@ module Perron
22
22
  define_method(association_name) do
23
23
  cache_has_many_association(association_name) do
24
24
  associated_class = association_class_for(association_name, singularize: true, **options)
25
- foreign_key = foreign_key_for(inverse_association_name, **options)
26
- primary_key_method = options.fetch(:primary_key, :slug)
27
- lookup_value = public_send(primary_key_method)
25
+ ids_key = "#{association_name}_ids"
28
26
 
29
- associated_class.all.select { |record| record.metadata[foreign_key] == lookup_value }
27
+ metadata[ids_key] ?
28
+ records_for_ids(associated_class, metadata[ids_key]) :
29
+ records_for_foreign_key(associated_class, association_name, **options)
30
30
  end
31
31
  end
32
32
  end
@@ -41,6 +41,10 @@ module Perron
41
41
  @belongs_to_cache[name] = yield
42
42
  end
43
43
 
44
+ def foreign_key_for(base_name, **options)
45
+ (options[:foreign_key] || "#{base_name}_id").to_s
46
+ end
47
+
44
48
  def cache_has_many_association(name)
45
49
  @has_many_cache ||= {}
46
50
  return @has_many_cache[name] if @has_many_cache.key?(name)
@@ -49,18 +53,28 @@ module Perron
49
53
  end
50
54
 
51
55
  def association_class_for(association_name, singularize: false, **options)
52
- class_name = options[:class_name] || begin
56
+ if options[:class_name]
57
+ options[:class_name].to_s.constantize
58
+ else
53
59
  name = association_name.to_s
54
60
  name = name.singularize if singularize
55
61
 
56
- "Content::#{name.classify}"
62
+ "Content::#{name.classify}".constantize
57
63
  end
64
+ end
58
65
 
59
- class_name.constantize
66
+ def records_for_ids(associated_class, ids)
67
+ ids = Array(ids)
68
+
69
+ associated_class.all.select { ids.include?(it[:id]) || ids.include?(it["id"]) }
60
70
  end
61
71
 
62
- def foreign_key_for(base_name, **options)
63
- (options[:foreign_key] || "#{base_name}_id").to_s
72
+ def records_for_foreign_key(associated_class, association_name, **options)
73
+ foreign_key = foreign_key_for(inverse_association_name, **options)
74
+ primary_key_method = options.fetch(:primary_key, :slug)
75
+ lookup_value = public_send(primary_key_method)
76
+
77
+ associated_class.all.select { it.association_value(foreign_key) == lookup_value }
64
78
  end
65
79
 
66
80
  def inverse_association_name = self.class.name.demodulize.underscore
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Perron
4
+ class Resource
5
+ module Previewable
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ def previewable?
10
+ frontmatter.preview.present? && (draft? || scheduled?)
11
+ end
12
+ alias_method :preview?, :previewable?
13
+
14
+ def preview_token
15
+ return nil unless previewable?
16
+
17
+ @preview_token ||= if frontmatter.preview == true
18
+ Digest::SHA256.hexdigest(file_path)[0..11]
19
+ else
20
+ frontmatter.preview
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -16,8 +16,16 @@ module Perron
16
16
  true
17
17
  end
18
18
 
19
+ def buildable?
20
+ published? || previewable?
21
+ end
22
+
19
23
  def scheduled? = publication_date&.after?(Time.current)
20
24
 
25
+ def draft?
26
+ frontmatter.draft == true || frontmatter.published == false
27
+ end
28
+
21
29
  def publication_date
22
30
  @publication_date ||= begin
23
31
  from_meta = frontmatter.published_at.present? ? begin
@@ -15,8 +15,9 @@ module Perron
15
15
  def create
16
16
  return "/" if Perron.configuration.allowed_extensions.any? { @resource.filename == "root.#{it}" }
17
17
 
18
- @frontmatter.slug.presence ||
19
- @resource.filename.sub(/^[\d-]+-/, "").delete_suffixes(dot_prepended_allowed_extensions)
18
+ base_slug = @frontmatter.slug.presence || @resource.filename.sub(/^[\d-]+-/, "").delete_suffixes(dot_prepended_allowed_extensions)
19
+
20
+ [base_slug, @resource.preview_token].compact.join("-")
20
21
  end
21
22
 
22
23
  private
@@ -40,7 +40,7 @@ module Perron
40
40
  end
41
41
 
42
42
  def combinations
43
- datasets = source_names.map { Perron::Site.data.public_send(it) }
43
+ datasets = source_names.map { Perron::Data.new(it.to_s) }
44
44
 
45
45
  datasets.first.product(*datasets[1..])
46
46
  end
@@ -71,7 +71,8 @@ module Perron
71
71
  primary_key = options[:primary_key]
72
72
  singular_name = name.to_s.singularize
73
73
  identifier = frontmatter["#{singular_name}_#{primary_key}"]
74
- hash[name] = Perron::Site.data.public_send(name).find { it.public_send(primary_key).to_s == identifier.to_s }
74
+
75
+ hash[name] = Perron::Data.new(name.to_s).find { it.public_send(primary_key).to_s == identifier.to_s }
75
76
  end
76
77
 
77
78
  Source.new(data)
@@ -5,6 +5,7 @@ require "perron/resource/core"
5
5
  require "perron/resource/class_methods"
6
6
  require "perron/resource/associations"
7
7
  require "perron/resource/metadata"
8
+ require "perron/resource/previewable"
8
9
  require "perron/resource/publishable"
9
10
  require "perron/resource/reading_time"
10
11
  require "perron/resource/related"
@@ -27,6 +28,7 @@ module Perron
27
28
  include Perron::Resource::ReadingTime
28
29
  include Perron::Resource::Sourceable
29
30
  include Perron::Resource::Publishable
31
+ include Perron::Resource::Previewable
30
32
  include Perron::Resource::TableOfContent
31
33
 
32
34
  attr_reader :file_path, :id
@@ -64,6 +66,8 @@ module Perron
64
66
  render_inline_erb using: page_content
65
67
  end
66
68
 
69
+ def association_value(key) = metadata[key]
70
+
67
71
  def to_partial_path
68
72
  @to_partial_path ||= begin
69
73
  element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(self.class.model_name))
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Perron
4
+ module Site
5
+ class Builder
6
+ class Feeds
7
+ module Author
8
+ private
9
+
10
+ def author(resource)
11
+ author = resource.author || feed_configuration.author
12
+
13
+ Author.new(author) if author
14
+ end
15
+
16
+ class Author
17
+ def initialize(author)
18
+ @author = author
19
+ end
20
+
21
+ def name
22
+ @author.respond_to?(:metadata) ? @author.metadata.name : @author[:name]
23
+ end
24
+
25
+ def email
26
+ @author.respond_to?(:metadata) ? @author.metadata.email : @author[:email]
27
+ end
28
+
29
+ def url
30
+ @author.respond_to?(:metadata) ? @author.metadata.url : @author[:url]
31
+ end
32
+
33
+ def avatar
34
+ @author.respond_to?(:metadata) ? @author.metadata.avatar : @author[:avatar]
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -1,12 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "json"
4
+ require "perron/site/builder/feeds/author"
4
5
 
5
6
  module Perron
6
7
  module Site
7
8
  class Builder
8
9
  class Feeds
9
10
  class Json
11
+ include Feeds::Author
12
+
10
13
  def initialize(collection:)
11
14
  @collection = collection
12
15
  @configuration = Perron.configuration
@@ -27,6 +30,7 @@ module Perron
27
30
  id: resource.id,
28
31
  url: url.polymorphic_url(resource, ref: feed_configuration.ref).delete_suffix("?ref="),
29
32
  date_published: resource.published_at&.iso8601,
33
+ authors: authors(resource),
30
34
  title: resource.metadata.title,
31
35
  content_html: Perron::Markdown.render(resource.content)
32
36
  }
@@ -48,6 +52,14 @@ module Perron
48
52
  end
49
53
 
50
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
51
63
  end
52
64
  end
53
65
  end
@@ -1,12 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "nokogiri"
4
+ require "perron/site/builder/feeds/author"
4
5
 
5
6
  module Perron
6
7
  module Site
7
8
  class Builder
8
9
  class Feeds
9
10
  class Rss
11
+ include Feeds::Author
12
+
10
13
  def initialize(collection:)
11
14
  @collection = collection
12
15
  @configuration = Perron.configuration
@@ -29,6 +32,9 @@ module Perron
29
32
  xml.guid resource.id
30
33
  xml.link url.polymorphic_url(resource, ref: feed_configuration.ref).delete_suffix("?ref="), isPermaLink: true
31
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
32
38
  xml.title resource.metadata.title
33
39
  xml.description { xml.cdata(Perron::Markdown.render(resource.content)) }
34
40
  end
@@ -12,7 +12,7 @@ module Perron
12
12
  @paths << routes.public_send(index_path) if routes.respond_to?(index_path)
13
13
 
14
14
  if routes.respond_to?(show_path)
15
- @collection.all.each do |resource|
15
+ @collection.send(:load_resources).select(&:buildable?).each do |resource|
16
16
  root = resource.slug == "/"
17
17
 
18
18
  next if skip? root
@@ -31,6 +31,8 @@ module Perron
31
31
  Perron::Site::Builder::Sitemap.new(@output_path).generate
32
32
  Perron::Site::Builder::Feeds.new(@output_path).generate
33
33
 
34
+ output_preview_urls
35
+
34
36
  puts "\n✅ Build complete"
35
37
  end
36
38
 
@@ -43,6 +45,17 @@ module Perron
43
45
  end
44
46
 
45
47
  def render_page(path) = Perron::Site::Builder::Page.new(path).render
48
+
49
+ def output_preview_urls
50
+ previewable_resources = Perron::Site.collections.flat_map { it.send(:load_resources) }.select(&:previewable?)
51
+
52
+ if previewable_resources.any?
53
+ puts "\n🔒 Preview URLs:"
54
+ previewable_resources.each do |resource|
55
+ puts " #{Rails.application.routes.url_helpers.polymorphic_url(resource, **Perron.configuration.default_url_options)}"
56
+ end
57
+ end
58
+ end
46
59
  end
47
60
  end
48
61
  end
data/lib/perron/site.rb CHANGED
@@ -24,6 +24,8 @@ module Perron
24
24
  def collection(name) = Collection.new(name)
25
25
 
26
26
  def data(name = nil)
27
+ Perron.deprecator.deprecation_warning(:data, "Use Content::Data::ClassName instead, e.g. `Content::Data::Users.all`")
28
+
27
29
  (name && Perron::Data.new(name)) || Perron::Data::Proxy.new
28
30
  end
29
31
  end
@@ -0,0 +1,12 @@
1
+ namespace :perron do
2
+ desc "Remove compiled static output"
3
+ task clobber: :environment do
4
+ output_path = Rails.root.join(Perron.configuration.output)
5
+
6
+ if Dir.exist?(output_path)
7
+ FileUtils.rm_rf(output_path)
8
+
9
+ puts "Removed #{output_path}"
10
+ end
11
+ end
12
+ end
@@ -1,3 +1,3 @@
1
1
  module Perron
2
- VERSION = "0.14.0"
2
+ VERSION = "0.16.0"
3
3
  end
data/lib/perron.rb CHANGED
@@ -2,9 +2,11 @@
2
2
 
3
3
  require "perron/version"
4
4
  require "perron/configuration"
5
+ require "perron/deprecator"
5
6
  require "perron/errors"
6
7
  require "perron/root"
7
8
  require "perron/site"
9
+ require "perron/content/data"
8
10
  require "perron/resource"
9
11
  require "perron/markdown"
10
12
  require "perron/feeds"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: perron
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.0
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rails Designer Developers
@@ -86,21 +86,23 @@ files:
86
86
  - bin/rails
87
87
  - bin/release
88
88
  - bin/setup
89
- - lib/generators/content/USAGE
90
- - lib/generators/content/content_generator.rb
91
- - lib/generators/content/templates/controller.rb.tt
92
- - lib/generators/content/templates/index.html.erb.tt
93
- - lib/generators/content/templates/model.rb.tt
94
- - lib/generators/content/templates/root.erb.tt
95
- - lib/generators/content/templates/show.html.erb.tt
96
89
  - lib/generators/perron/install_generator.rb
97
90
  - lib/generators/perron/templates/README.md.tt
98
91
  - lib/generators/perron/templates/initializer.rb.tt
92
+ - lib/generators/rails/content/USAGE
93
+ - lib/generators/rails/content/content_generator.rb
94
+ - lib/generators/rails/content/templates/controller.rb.tt
95
+ - lib/generators/rails/content/templates/index.html.erb.tt
96
+ - lib/generators/rails/content/templates/model.rb.tt
97
+ - lib/generators/rails/content/templates/root.erb.tt
98
+ - lib/generators/rails/content/templates/show.html.erb.tt
99
99
  - lib/perron.rb
100
100
  - lib/perron/collection.rb
101
101
  - lib/perron/configuration.rb
102
+ - lib/perron/content/data.rb
102
103
  - lib/perron/data.rb
103
104
  - lib/perron/data/proxy.rb
105
+ - lib/perron/deprecator.rb
104
106
  - lib/perron/engine.rb
105
107
  - lib/perron/errors.rb
106
108
  - lib/perron/feeds.rb
@@ -111,6 +113,7 @@ files:
111
113
  - lib/perron/html_processor/target_blank.rb
112
114
  - lib/perron/markdown.rb
113
115
  - lib/perron/metatags.rb
116
+ - lib/perron/output_server.rb
114
117
  - lib/perron/refinements/delete_suffixes.rb
115
118
  - lib/perron/resource.rb
116
119
  - lib/perron/resource/associations.rb
@@ -118,6 +121,7 @@ files:
118
121
  - lib/perron/resource/configuration.rb
119
122
  - lib/perron/resource/core.rb
120
123
  - lib/perron/resource/metadata.rb
124
+ - lib/perron/resource/previewable.rb
121
125
  - lib/perron/resource/publishable.rb
122
126
  - lib/perron/resource/reading_time.rb
123
127
  - lib/perron/resource/related.rb
@@ -132,6 +136,7 @@ files:
132
136
  - lib/perron/site/builder.rb
133
137
  - lib/perron/site/builder/assets.rb
134
138
  - lib/perron/site/builder/feeds.rb
139
+ - lib/perron/site/builder/feeds/author.rb
135
140
  - lib/perron/site/builder/feeds/json.rb
136
141
  - lib/perron/site/builder/feeds/rss.rb
137
142
  - lib/perron/site/builder/page.rb
@@ -140,6 +145,7 @@ files:
140
145
  - lib/perron/site/builder/sitemap.rb
141
146
  - lib/perron/site/validate.rb
142
147
  - lib/perron/tasks/build.rake
148
+ - lib/perron/tasks/clobber.rake
143
149
  - lib/perron/tasks/sync_sources.rake
144
150
  - lib/perron/tasks/validate.rake
145
151
  - lib/perron/version.rb
@@ -164,7 +170,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
164
170
  - !ruby/object:Gem::Version
165
171
  version: '0'
166
172
  requirements: []
167
- rubygems_version: 3.6.9
173
+ rubygems_version: 4.0.3
168
174
  specification_version: 4
169
175
  summary: Rails-based static site generator
170
176
  test_files: []
@@ -1,16 +0,0 @@
1
- Description:
2
- Creates a new content model with specified actions
3
-
4
- Example:
5
- rails generate content post
6
-
7
- This will create:
8
- - app/models/content/post.rb
9
- - app/controllers/content/posts_controller.rb
10
- - app/views/content/posts/index.html.erb
11
- - app/views/content/posts/show.html.erb
12
- - …and add `resource :posts, module: :content, only: %w[index show]` to `config/routes.rb`
13
-
14
- Arguments:
15
- NAME: Name of the content model
16
- actions: List of actions to generate (index or show)
@@ -1,72 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "rails/generators/base"
4
-
5
- class ContentGenerator < Rails::Generators::NamedBase
6
- source_root File.expand_path("templates", __dir__)
7
-
8
- class_option :force_plural, type: :boolean, default: false, desc: "Forces the use of a plural model name and class"
9
-
10
- argument :actions, type: :array, default: %w[index show], banner: "actions", desc: "Specify which actions to generate (index/show)"
11
-
12
- def create_model
13
- template "model.rb.tt", File.join("app/models/content", "#{file_name}.rb")
14
- end
15
-
16
- def create_controller
17
- template "controller.rb.tt", File.join("app/controllers/content", "#{plural_file_name}_controller.rb")
18
- end
19
-
20
- def create_views
21
- empty_directory view_directory
22
-
23
- actions.each do |action|
24
- template "#{action}.html.erb.tt", File.join(view_directory, "#{action}.html.erb")
25
- end
26
- end
27
-
28
- def create_content_directory = FileUtils.mkdir_p(content_directory)
29
-
30
- def create_pages_root
31
- return unless pages_controller?
32
-
33
- template "root.erb.tt", File.join(content_directory, "root.erb")
34
- end
35
-
36
- def add_content_route
37
- route "resources :#{plural_file_name}, module: :content, only: %w[#{actions.join(" ")}]"
38
- end
39
-
40
- def add_root_route
41
- return unless pages_controller?
42
- return if root_route_exists?
43
-
44
- inject_into_file "config/routes.rb", " root to: \"content/pages#root\"\n", before: /^\s*end\s*$/
45
- end
46
-
47
- private
48
-
49
- def file_name
50
- options[:force_plural] ? super.pluralize : super.singularize
51
- end
52
-
53
- def class_name
54
- options[:force_plural] ? super.pluralize : super.singularize
55
- end
56
-
57
- def view_directory = Rails.root.join("app", "views", "content", plural_file_name)
58
-
59
- def content_directory = Rails.root.join("app", "content", plural_file_name)
60
-
61
- def plural_class_name = plural_name.camelize
62
-
63
- def pages_controller? = plural_file_name == "pages"
64
-
65
- def root_route_exists?
66
- routes = Rails.root.join("config", "routes.rb")
67
-
68
- return false unless File.exist?(routes)
69
-
70
- File.read(routes).match?(/\broot\s+to:/)
71
- end
72
- end