perron 1.0.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.
- checksums.yaml +4 -4
- data/Gemfile.lock +11 -15
- data/app/controllers/perron/concierge_controller.rb +4 -1
- data/app/helpers/perron/markdown_helper.rb +3 -2
- data/app/helpers/perron/meta_tags_helper.rb +3 -9
- data/app/helpers/perron/paginate_helper.rb +40 -0
- data/app/views/perron/concierge/show.html.erb +1 -1
- data/lib/perron/collection.rb +2 -1
- data/lib/perron/configuration.rb +23 -2
- data/lib/perron/data_source.rb +6 -4
- data/lib/perron/engine.rb +11 -8
- data/lib/perron/install/deploy.yml +15 -0
- data/lib/perron/install.rb +3 -0
- data/lib/perron/paginate.rb +58 -0
- data/lib/perron/relation.rb +24 -6
- data/lib/perron/resource/class_methods.rb +6 -0
- data/lib/perron/resource/configuration.rb +3 -0
- data/lib/perron/resource/metadata.rb +9 -3
- data/lib/perron/resource/sourceable.rb +60 -8
- data/lib/perron/resource.rb +6 -0
- data/lib/perron/site/builder/feeds/atom.erb +1 -1
- data/lib/perron/site/builder/page.rb +19 -4
- data/lib/perron/site/builder/paths.rb +19 -1
- data/lib/perron/site/builder.rb +24 -0
- data/lib/perron/site/validate.rb +18 -5
- data/lib/perron/tasks/deploy.rake +58 -0
- data/lib/perron/version.rb +1 -1
- data/lib/perron.rb +1 -0
- data/perron.gemspec +1 -1
- metadata +8 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bdd54bac7e739c0199d3381c1f20aaf15dd5c4dfddec9f57635c0dcf41b66505
|
|
4
|
+
data.tar.gz: c04dc9b074c2a53803086ee6f9a5a7780a26f930661a0884513185323197ba03
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0fdac048810289509dc253a93839fd2df30358a90e463dac6acc3e0cad1171640698e973522d5321b53ca807c56f9a806aeb4bdc972f56773ee0b2693871009b
|
|
7
|
+
data.tar.gz: 8864e0a699f0cf04cc67c696dee53df33e70cd7abe9d57a34b75312613d332c09d0e675fc46a02a52870f30c0a556bb00c98fd7150c8769af5bd1a1ddb79429c
|
data/Gemfile.lock
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
perron (1.
|
|
4
|
+
perron (1.1.0)
|
|
5
5
|
csv
|
|
6
6
|
json
|
|
7
|
-
mata (~> 0.
|
|
7
|
+
mata (~> 0.10.0)
|
|
8
8
|
psych
|
|
9
9
|
rails (>= 7.2.0)
|
|
10
10
|
|
|
@@ -136,11 +136,10 @@ GEM
|
|
|
136
136
|
net-pop
|
|
137
137
|
net-smtp
|
|
138
138
|
marcel (1.0.4)
|
|
139
|
-
mata (0.
|
|
139
|
+
mata (0.10.0)
|
|
140
140
|
listen (~> 3.0)
|
|
141
141
|
rack (>= 3.0)
|
|
142
142
|
mini_mime (1.1.5)
|
|
143
|
-
mini_portile2 (2.8.9)
|
|
144
143
|
minitest (5.27.0)
|
|
145
144
|
net-imap (0.5.9)
|
|
146
145
|
date
|
|
@@ -152,24 +151,21 @@ GEM
|
|
|
152
151
|
net-smtp (0.5.1)
|
|
153
152
|
net-protocol
|
|
154
153
|
nio4r (2.7.4)
|
|
155
|
-
nokogiri (1.
|
|
156
|
-
mini_portile2 (~> 2.8.2)
|
|
154
|
+
nokogiri (1.19.3-aarch64-linux-gnu)
|
|
157
155
|
racc (~> 1.4)
|
|
158
|
-
nokogiri (1.
|
|
156
|
+
nokogiri (1.19.3-aarch64-linux-musl)
|
|
159
157
|
racc (~> 1.4)
|
|
160
|
-
nokogiri (1.
|
|
158
|
+
nokogiri (1.19.3-arm-linux-gnu)
|
|
161
159
|
racc (~> 1.4)
|
|
162
|
-
nokogiri (1.
|
|
160
|
+
nokogiri (1.19.3-arm-linux-musl)
|
|
163
161
|
racc (~> 1.4)
|
|
164
|
-
nokogiri (1.
|
|
162
|
+
nokogiri (1.19.3-arm64-darwin)
|
|
165
163
|
racc (~> 1.4)
|
|
166
|
-
nokogiri (1.
|
|
164
|
+
nokogiri (1.19.3-x86_64-darwin)
|
|
167
165
|
racc (~> 1.4)
|
|
168
|
-
nokogiri (1.
|
|
166
|
+
nokogiri (1.19.3-x86_64-linux-gnu)
|
|
169
167
|
racc (~> 1.4)
|
|
170
|
-
nokogiri (1.
|
|
171
|
-
racc (~> 1.4)
|
|
172
|
-
nokogiri (1.18.8-x86_64-linux-musl)
|
|
168
|
+
nokogiri (1.19.3-x86_64-linux-musl)
|
|
173
169
|
racc (~> 1.4)
|
|
174
170
|
parallel (1.27.0)
|
|
175
171
|
parser (3.3.8.0)
|
|
@@ -5,8 +5,11 @@ module Perron
|
|
|
5
5
|
end
|
|
6
6
|
|
|
7
7
|
def run_command
|
|
8
|
-
|
|
8
|
+
command = params[:command]
|
|
9
9
|
|
|
10
|
+
return redirect_back fallback_location: root_path unless command.start_with?("bin/rails generate content")
|
|
11
|
+
|
|
12
|
+
system(command)
|
|
10
13
|
redirect_back fallback_location: root_path
|
|
11
14
|
end
|
|
12
15
|
end
|
|
@@ -4,10 +4,11 @@ require "perron/markdown"
|
|
|
4
4
|
|
|
5
5
|
module Perron
|
|
6
6
|
module MarkdownHelper
|
|
7
|
-
def markdownify(content = nil, process:
|
|
7
|
+
def markdownify(content = nil, process: nil, resource: nil, &block)
|
|
8
8
|
text = block_given? ? capture(&block).strip_heredoc : content
|
|
9
|
+
processors = (process.nil? || process.empty?) ? Perron.configuration.default_processors : process
|
|
9
10
|
|
|
10
|
-
Perron::Markdown.render(text, processors:
|
|
11
|
+
Perron::Markdown.render(text, processors: processors, resource: resource || @resource)
|
|
11
12
|
end
|
|
12
13
|
end
|
|
13
14
|
end
|
|
@@ -2,16 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module Perron
|
|
4
4
|
module MetaTagsHelper
|
|
5
|
-
def meta_tags(options = {})
|
|
5
|
+
def meta_tags(options = {})
|
|
6
|
+
metadata = (@metadata || {}).merge(@resource&.metadata || {})
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Resource = Data.define(:path, :collection, :metadata, :published_at)
|
|
10
|
-
|
|
11
|
-
def resource
|
|
12
|
-
return Resource.new(request.path, nil, @metadata, nil) if @metadata.present?
|
|
13
|
-
|
|
14
|
-
@resource || Resource.new(request.path, nil, {}, nil)
|
|
8
|
+
Perron::Metatags.new(metadata).render(options)
|
|
15
9
|
end
|
|
16
10
|
end
|
|
17
11
|
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Perron
|
|
4
|
+
module PaginateHelper
|
|
5
|
+
def paginate(scope, page: nil, **options)
|
|
6
|
+
page ||= (params[:page] || 1).to_i
|
|
7
|
+
resource_class = scope.model_class
|
|
8
|
+
|
|
9
|
+
config = resource_class.configuration.pagination
|
|
10
|
+
per_page = options[:per_page] || config.per_page
|
|
11
|
+
page_path_template = options[:path_template] || config.path_template
|
|
12
|
+
|
|
13
|
+
route = find_index_route(resource_class)
|
|
14
|
+
base_path = route_path(route)
|
|
15
|
+
|
|
16
|
+
use_query_params = Rails.env.development? || Rails.env.local?
|
|
17
|
+
paginate = Paginate.new(scope, page: page, per_page: per_page, base_path: base_path, page_path_template: page_path_template, use_query_params: use_query_params)
|
|
18
|
+
|
|
19
|
+
[paginate, paginate.items]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def find_index_route(resource_class)
|
|
25
|
+
controller_name = resource_class.name.demodulize.sub("Controller", "").underscore.pluralize
|
|
26
|
+
|
|
27
|
+
Rails.application.routes.routes.find do |r|
|
|
28
|
+
r.defaults[:controller] == "content/#{controller_name}" &&
|
|
29
|
+
r.defaults[:action] == "index"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def route_path(route)
|
|
34
|
+
return "/#{route.name}/" unless route
|
|
35
|
+
|
|
36
|
+
path_spec = route.path.spec.to_s
|
|
37
|
+
path_spec.sub(/\(.*?\)/, "").gsub(/:[^\/]+/, "").sub(/\/$/, "") + "/"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -211,7 +211,7 @@
|
|
|
211
211
|
<code>bin/rails generate content Post --new "My first post"</code>
|
|
212
212
|
|
|
213
213
|
<%= form_with url: perron_run_command_path do |form| %>
|
|
214
|
-
<%= form.hidden_field :command, value: 'bin/rails generate content Post --new
|
|
214
|
+
<%= form.hidden_field :command, value: 'bin/rails generate content Post --new' %>
|
|
215
215
|
|
|
216
216
|
<% if File.file?("app/content/posts/untitled.md") %>
|
|
217
217
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm45.66,85.66-56,56a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35a8,8,0,0,1,11.32,11.32Z"/></svg>
|
data/lib/perron/collection.rb
CHANGED
|
@@ -16,7 +16,7 @@ module Perron
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def all(resource_class = "Content::#{name.classify}".safe_constantize)
|
|
19
|
-
Perron::Relation.new(load_resources(resource_class).select(&:published?))
|
|
19
|
+
Perron::Relation.new(load_resources(resource_class).select(&:published?), resource_class)
|
|
20
20
|
end
|
|
21
21
|
alias_method :resources, :all
|
|
22
22
|
|
|
@@ -52,6 +52,7 @@ module Perron
|
|
|
52
52
|
|
|
53
53
|
Dir.glob("#{@collection_path}/**/*.*")
|
|
54
54
|
.select { allowed_extensions.include?(File.extname(it)) }
|
|
55
|
+
.reject { File.basename(it, ".*").downcase == "readme" }
|
|
55
56
|
.map { resource_class.new(it) }
|
|
56
57
|
end
|
|
57
58
|
end
|
data/lib/perron/configuration.rb
CHANGED
|
@@ -37,6 +37,8 @@ module Perron
|
|
|
37
37
|
|
|
38
38
|
@config.markdown_options = {}
|
|
39
39
|
|
|
40
|
+
@config.default_processors = []
|
|
41
|
+
|
|
40
42
|
@config.search_scope = []
|
|
41
43
|
|
|
42
44
|
@config.cache_data_sources = false
|
|
@@ -51,6 +53,9 @@ module Perron
|
|
|
51
53
|
|
|
52
54
|
@config.metadata = ActiveSupport::OrderedOptions.new
|
|
53
55
|
@config.metadata.title_separator = " — "
|
|
56
|
+
|
|
57
|
+
@config.before_build = nil
|
|
58
|
+
@config.after_build = nil
|
|
54
59
|
end
|
|
55
60
|
|
|
56
61
|
def input = Rails.root.join("app", "content")
|
|
@@ -65,6 +70,22 @@ module Perron
|
|
|
65
70
|
@additional_routes || (mode.integrated? ? [] : %w[root_path])
|
|
66
71
|
end
|
|
67
72
|
|
|
73
|
+
def deploy
|
|
74
|
+
@deploy ||= ActiveSupport::OrderedOptions.new.tap do |config|
|
|
75
|
+
def config.method_missing(method_name, *args, &block)
|
|
76
|
+
if method_name.to_s.end_with?("=")
|
|
77
|
+
super
|
|
78
|
+
else
|
|
79
|
+
self[method_name] ||= ActiveSupport::OrderedOptions.new
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def config.respond_to_missing?(method_name, include_private = false)
|
|
84
|
+
!method_name.to_s.end_with?("=") || super
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
68
89
|
attr_writer :additional_routes
|
|
69
90
|
|
|
70
91
|
def url
|
|
@@ -82,8 +103,8 @@ module Perron
|
|
|
82
103
|
end
|
|
83
104
|
end
|
|
84
105
|
|
|
85
|
-
def respond_to_missing?(method_name)
|
|
86
|
-
@config.respond_to?(method_name) || super
|
|
106
|
+
def respond_to_missing?(method_name, ...)
|
|
107
|
+
@config.respond_to?(method_name, ...) || super
|
|
87
108
|
end
|
|
88
109
|
end
|
|
89
110
|
end
|
data/lib/perron/data_source.rb
CHANGED
|
@@ -66,11 +66,13 @@ module Perron
|
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
data.map.with_index do |item, index|
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
if item.is_a?(Hash)
|
|
70
|
+
Item.new(item, identifier: @identifier)
|
|
71
|
+
elsif item.is_a?(String)
|
|
72
|
+
item
|
|
73
|
+
else
|
|
74
|
+
raise Errors::DataParseError, "Item at index #{index} in `#{@file_path}` must be a hash/object or string, got #{item.class}"
|
|
71
75
|
end
|
|
72
|
-
|
|
73
|
-
Item.new(item, identifier: @identifier)
|
|
74
76
|
end
|
|
75
77
|
end
|
|
76
78
|
|
data/lib/perron/engine.rb
CHANGED
|
@@ -30,17 +30,19 @@ module Perron
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
initializer "perron.concierge", before: :add_builtin_route do |app|
|
|
33
|
-
|
|
34
|
-
app.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
if Rails.env.development?
|
|
34
|
+
app.config.after_initialize do
|
|
35
|
+
app.routes.append do
|
|
36
|
+
namespace :perron do
|
|
37
|
+
post :run_command, to: "concierge#run_command"
|
|
38
|
+
end
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
root to: "perron/concierge#show" unless app.routes.named_routes.key?(:root)
|
|
41
|
+
end
|
|
40
42
|
end
|
|
41
|
-
end
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
app.routes.finalize!
|
|
45
|
+
end
|
|
44
46
|
end
|
|
45
47
|
|
|
46
48
|
initializer "perron.inflections" do
|
|
@@ -57,6 +59,7 @@ module Perron
|
|
|
57
59
|
load File.expand_path("../tasks/install.rake", __FILE__)
|
|
58
60
|
load File.expand_path("../tasks/sync_sources.rake", __FILE__)
|
|
59
61
|
load File.expand_path("../tasks/validate.rake", __FILE__)
|
|
62
|
+
load File.expand_path("../tasks/deploy.rake", __FILE__)
|
|
60
63
|
end
|
|
61
64
|
end
|
|
62
65
|
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Deploy configuration for Perron
|
|
2
|
+
# Requires Beam Up (`bundle add beam_up`)
|
|
3
|
+
# See also: https://perron.railsdesigner.com/docs/deploy/
|
|
4
|
+
|
|
5
|
+
# Uncomment and configure your provider:
|
|
6
|
+
# provider: netlify
|
|
7
|
+
|
|
8
|
+
# netlify:
|
|
9
|
+
# api_token: your_token_here
|
|
10
|
+
# site_id: your_site_id
|
|
11
|
+
|
|
12
|
+
before_actions:
|
|
13
|
+
- RAILS_ENV=production bundle exec rails perron:build
|
|
14
|
+
after_actions:
|
|
15
|
+
- bundle exec rails perron:clobber
|
data/lib/perron/install.rb
CHANGED
|
@@ -6,6 +6,9 @@ copy_file "#{__dir__}/install/initializer.rb", "config/initializers/perron.rb"
|
|
|
6
6
|
say "Create content data directory"
|
|
7
7
|
copy_file "#{__dir__}/install/README.md", "app/content/data/README.md"
|
|
8
8
|
|
|
9
|
+
say "Create deploy configuration"
|
|
10
|
+
copy_file "#{__dir__}/install/deploy.yml", "config/deploy.yml"
|
|
11
|
+
|
|
9
12
|
say "Add Markdown gem options to Gemfile"
|
|
10
13
|
append_to_file "Gemfile", <<~RUBY
|
|
11
14
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Perron
|
|
4
|
+
class Paginate
|
|
5
|
+
def initialize(collection, page:, per_page:, base_path: nil, page_path_template: nil, use_query_params: false)
|
|
6
|
+
@collection = collection
|
|
7
|
+
@per_page = per_page
|
|
8
|
+
@base_path = base_path
|
|
9
|
+
@page_path_template = page_path_template || "/page/:page/"
|
|
10
|
+
@use_query_params = use_query_params
|
|
11
|
+
|
|
12
|
+
@total_items = collection.size
|
|
13
|
+
@total_pages = total_items.zero? ? 0 : (total_items.to_f / per_page).ceil
|
|
14
|
+
@current_page = page.clamp(1, total_pages.zero? ? 1 : total_pages)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
attr_reader :current_page, :total_pages, :total_items, :per_page
|
|
18
|
+
|
|
19
|
+
def items
|
|
20
|
+
offset = (@current_page - 1) * @per_page
|
|
21
|
+
|
|
22
|
+
@collection[offset, @per_page] || []
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def next? = @current_page < @total_pages
|
|
26
|
+
|
|
27
|
+
def previous? = @current_page > 1
|
|
28
|
+
|
|
29
|
+
def next
|
|
30
|
+
return unless next?
|
|
31
|
+
|
|
32
|
+
page_path(@current_page + 1)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def previous
|
|
36
|
+
return unless previous?
|
|
37
|
+
|
|
38
|
+
target = (@current_page == 2) ? 1 : @current_page - 1
|
|
39
|
+
|
|
40
|
+
page_path(target)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
attr_reader :use_query_params
|
|
46
|
+
|
|
47
|
+
def page_path(number)
|
|
48
|
+
return if number < 1
|
|
49
|
+
return if number > @total_pages && @total_pages > 0
|
|
50
|
+
|
|
51
|
+
return @base_path if number <= 1
|
|
52
|
+
|
|
53
|
+
return "#{@base_path}?page=#{number}" if @use_query_params
|
|
54
|
+
|
|
55
|
+
@base_path.sub(/\/$/, "") + @page_path_template.sub(":page", number.to_s)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
data/lib/perron/relation.rb
CHANGED
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
module Perron
|
|
4
4
|
class Relation < Array
|
|
5
|
-
def initialize(resources = [])
|
|
6
|
-
super
|
|
5
|
+
def initialize(resources = [], model_class = nil)
|
|
6
|
+
super(resources)
|
|
7
|
+
|
|
8
|
+
@model_class = model_class
|
|
7
9
|
end
|
|
10
|
+
attr_reader :model_class
|
|
8
11
|
|
|
9
12
|
def where(**conditions)
|
|
10
13
|
filtered = select do |resource|
|
|
@@ -19,12 +22,12 @@ module Perron
|
|
|
19
22
|
end
|
|
20
23
|
end
|
|
21
24
|
|
|
22
|
-
Relation.new(filtered)
|
|
25
|
+
Relation.new(filtered, @model_class)
|
|
23
26
|
end
|
|
24
27
|
|
|
25
|
-
def limit(count) = Relation.new(first(count))
|
|
28
|
+
def limit(count) = Relation.new(first(count), @model_class)
|
|
26
29
|
|
|
27
|
-
def offset(count) = Relation.new(drop(count))
|
|
30
|
+
def offset(count) = Relation.new(drop(count), @model_class)
|
|
28
31
|
|
|
29
32
|
def order(attribute, direction = :asc)
|
|
30
33
|
if attribute.is_a?(Hash)
|
|
@@ -33,7 +36,7 @@ module Perron
|
|
|
33
36
|
|
|
34
37
|
sorted = sort_by { it.public_send(attribute) }
|
|
35
38
|
|
|
36
|
-
Relation.new((direction == :desc) ? sorted.reverse : sorted)
|
|
39
|
+
Relation.new((direction == :desc) ? sorted.reverse : sorted, @model_class)
|
|
37
40
|
end
|
|
38
41
|
|
|
39
42
|
def pluck(*attributes)
|
|
@@ -47,5 +50,20 @@ module Perron
|
|
|
47
50
|
end
|
|
48
51
|
end
|
|
49
52
|
end
|
|
53
|
+
|
|
54
|
+
def in_order_of(attribute, values, filter: true)
|
|
55
|
+
return Relation.new([]) if values.empty?
|
|
56
|
+
|
|
57
|
+
indexed = values.each_with_index.to_h
|
|
58
|
+
|
|
59
|
+
resources = if filter
|
|
60
|
+
select { indexed.key?(it.public_send(attribute)) }
|
|
61
|
+
.sort_by { indexed[it.public_send(attribute)] }
|
|
62
|
+
else
|
|
63
|
+
sort_by { indexed[it.public_send(attribute)] || Float::INFINITY }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
Relation.new(resources)
|
|
67
|
+
end
|
|
50
68
|
end
|
|
51
69
|
end
|
|
@@ -22,6 +22,8 @@ module Perron
|
|
|
22
22
|
|
|
23
23
|
def order(attribute, direction = :asc) = all.order(attribute, direction)
|
|
24
24
|
|
|
25
|
+
def in_order_of(attribute, values, filter: true) = all.in_order_of(attribute, values, filter:)
|
|
26
|
+
|
|
25
27
|
def first(n = nil)
|
|
26
28
|
n ? all.first(n) : all[0]
|
|
27
29
|
end
|
|
@@ -44,6 +46,10 @@ module Perron
|
|
|
44
46
|
|
|
45
47
|
def root = all.find(&:root?)
|
|
46
48
|
|
|
49
|
+
def destroy_all
|
|
50
|
+
all.each(&:destroy)
|
|
51
|
+
end
|
|
52
|
+
|
|
47
53
|
def model_name
|
|
48
54
|
@model_name ||= ActiveModel::Name.new(self, nil, name.demodulize.to_s)
|
|
49
55
|
end
|
|
@@ -31,6 +31,9 @@ module Perron
|
|
|
31
31
|
config.related_posts.enabled = false
|
|
32
32
|
config.related_posts.max = 5
|
|
33
33
|
|
|
34
|
+
config.pagination = ActiveSupport::OrderedOptions.new
|
|
35
|
+
config.pagination.path_template = "/page/:page/"
|
|
36
|
+
|
|
34
37
|
config.sitemap = ActiveSupport::OrderedOptions.new
|
|
35
38
|
config.sitemap.exclude = false
|
|
36
39
|
end
|
|
@@ -3,22 +3,28 @@
|
|
|
3
3
|
module Perron
|
|
4
4
|
class Resource
|
|
5
5
|
class Metadata
|
|
6
|
-
def initialize(resource:, frontmatter:, collection:)
|
|
6
|
+
def initialize(resource:, frontmatter:, collection:, controller_metadata: {})
|
|
7
7
|
@resource = resource
|
|
8
8
|
@frontmatter = frontmatter&.deep_symbolize_keys || {}
|
|
9
9
|
@collection = collection
|
|
10
|
+
@controller_metadata = controller_metadata
|
|
10
11
|
@config = Perron.configuration
|
|
11
12
|
end
|
|
12
13
|
|
|
13
14
|
def data
|
|
14
15
|
@data ||= ActiveSupport::OrderedOptions
|
|
15
16
|
.new
|
|
16
|
-
.merge(apply_fallbacks_and_defaults(to:
|
|
17
|
+
.merge(apply_fallbacks_and_defaults(to: merged_metadata))
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
private
|
|
20
21
|
|
|
21
|
-
def
|
|
22
|
+
def merged_metadata
|
|
23
|
+
site_data
|
|
24
|
+
.merge(collection_data)
|
|
25
|
+
.merge(@controller_metadata)
|
|
26
|
+
.merge(@frontmatter)
|
|
27
|
+
end
|
|
22
28
|
|
|
23
29
|
def apply_fallbacks_and_defaults(to:)
|
|
24
30
|
to[:title] ||= @config.site_name || Rails.application.name.underscore.camelize
|
|
@@ -5,6 +5,11 @@ module Perron
|
|
|
5
5
|
module Sourceable
|
|
6
6
|
extend ActiveSupport::Concern
|
|
7
7
|
|
|
8
|
+
MODES = {
|
|
9
|
+
single: ->(dataset) { dataset.zip },
|
|
10
|
+
combinations: ->(dataset) { dataset.to_a.combination(2).to_a }
|
|
11
|
+
}
|
|
12
|
+
|
|
8
13
|
class_methods do
|
|
9
14
|
def sources(*arguments)
|
|
10
15
|
@source_definitions = parsed(*arguments)
|
|
@@ -32,7 +37,7 @@ module Perron
|
|
|
32
37
|
def generate_from_sources!
|
|
33
38
|
return unless source_backed?
|
|
34
39
|
|
|
35
|
-
|
|
40
|
+
derive.each do |combo|
|
|
36
41
|
content = content_with combo
|
|
37
42
|
filename = filename_with combo
|
|
38
43
|
|
|
@@ -48,7 +53,7 @@ module Perron
|
|
|
48
53
|
def parsed(*arguments)
|
|
49
54
|
return {} if arguments.empty?
|
|
50
55
|
|
|
51
|
-
arguments.flat_map do |argument|
|
|
56
|
+
definitions = arguments.flat_map do |argument|
|
|
52
57
|
case argument
|
|
53
58
|
when Hash
|
|
54
59
|
argument.to_a
|
|
@@ -58,11 +63,39 @@ module Perron
|
|
|
58
63
|
[[argument, {primary_key: :id}]]
|
|
59
64
|
end
|
|
60
65
|
end.to_h
|
|
66
|
+
|
|
67
|
+
if definitions.values.any? { it[:mode] }
|
|
68
|
+
raise ArgumentError, "mode is only supported for single-source definitions" if definitions.size > 1
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
definitions
|
|
61
72
|
end
|
|
62
73
|
|
|
63
|
-
def
|
|
74
|
+
def derive
|
|
64
75
|
datasets = source_names.map { resolve it }
|
|
65
76
|
|
|
77
|
+
if single_source_with_mode?
|
|
78
|
+
derive_from_single_source(datasets.first)
|
|
79
|
+
else
|
|
80
|
+
derive_from_multiple_sources(datasets)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def derive_from_single_source(dataset)
|
|
85
|
+
source_name = source_names.first
|
|
86
|
+
mode = source_definitions[source_name][:mode] || :single
|
|
87
|
+
method = MODES[mode.to_sym] || raise(ArgumentError, "Unknown mode: #{mode}")
|
|
88
|
+
|
|
89
|
+
method.call(dataset).each do |combo|
|
|
90
|
+
primary_key = source_definitions[source_name][:primary_key] || :id
|
|
91
|
+
|
|
92
|
+
combo.each do |item|
|
|
93
|
+
raise Errors::DataParseError, "Primary key `#{primary_key}` is nil for row" if item.public_send(primary_key).nil?
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def derive_from_multiple_sources(datasets)
|
|
66
99
|
datasets.first.product(*datasets[1..]).each do |combo|
|
|
67
100
|
combo.each_with_index do |item, index|
|
|
68
101
|
name = source_names[index]
|
|
@@ -74,18 +107,37 @@ module Perron
|
|
|
74
107
|
end
|
|
75
108
|
|
|
76
109
|
def content_with(combo)
|
|
77
|
-
data =
|
|
110
|
+
data = if single_source_with_mode?
|
|
111
|
+
source_name = source_names.first
|
|
112
|
+
names = source_definitions[source_name][:as]&.map(&:to_sym) || (1..combo.size).map { :"#{source_name}_#{it}" }
|
|
113
|
+
|
|
114
|
+
combo.each_with_index.to_h { |item, index| [names[index], item] }
|
|
115
|
+
else
|
|
116
|
+
source_names.each_with_index.to_h { |name, index| [name, combo[index]] }
|
|
117
|
+
end
|
|
118
|
+
|
|
78
119
|
source = Source.new(data)
|
|
79
120
|
|
|
80
121
|
source_template(source)
|
|
81
122
|
end
|
|
82
123
|
|
|
83
124
|
def filename_with(combo)
|
|
84
|
-
|
|
85
|
-
|
|
125
|
+
if single_source_with_mode?
|
|
126
|
+
source_name = source_names.first
|
|
127
|
+
primary_key = source_definitions[source_name][:primary_key] || :id
|
|
128
|
+
|
|
129
|
+
combo.map { it.public_send(primary_key) }.join("-")
|
|
130
|
+
else
|
|
131
|
+
source_names.each_with_index.map do |name, index|
|
|
132
|
+
primary_key = source_definitions[name][:primary_key]
|
|
133
|
+
|
|
134
|
+
combo[index].public_send(primary_key)
|
|
135
|
+
end.join("-")
|
|
136
|
+
end
|
|
137
|
+
end
|
|
86
138
|
|
|
87
|
-
|
|
88
|
-
|
|
139
|
+
def single_source_with_mode?
|
|
140
|
+
source_names.one? && source_definitions[source_names.first][:mode]
|
|
89
141
|
end
|
|
90
142
|
|
|
91
143
|
def output_dir = Perron.configuration.input.join(model_name.collection)
|
data/lib/perron/resource.rb
CHANGED
|
@@ -96,6 +96,10 @@ module Perron
|
|
|
96
96
|
slug == "/"
|
|
97
97
|
end
|
|
98
98
|
|
|
99
|
+
def destroy
|
|
100
|
+
File.delete(@file_path) and self
|
|
101
|
+
end
|
|
102
|
+
|
|
99
103
|
private
|
|
100
104
|
|
|
101
105
|
ID_LENGTH = 8
|
|
@@ -117,6 +121,8 @@ module Perron
|
|
|
117
121
|
end
|
|
118
122
|
|
|
119
123
|
def erb_processing?
|
|
124
|
+
return false if metadata.erb == false
|
|
125
|
+
|
|
120
126
|
@file_path.ends_with?(".erb") || metadata.erb == true
|
|
121
127
|
end
|
|
122
128
|
end
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
<% resources.each do |resource| %>
|
|
18
18
|
<entry>
|
|
19
|
-
<id><%= url_for_resource.call(resource)
|
|
19
|
+
<id><%= url_for_resource.call(resource) %></id>
|
|
20
20
|
<title><%= resource.metadata.title %></title>
|
|
21
21
|
<link href="<%= url_for_resource.call(resource) %>" rel="alternate" type="text/html"/>
|
|
22
22
|
<published><%= resource.published_at&.iso8601 %></published>
|
|
@@ -11,13 +11,15 @@ module Perron
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def render
|
|
14
|
-
|
|
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 =
|
|
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 ||=
|
|
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])
|
|
@@ -28,12 +28,30 @@ module Perron
|
|
|
28
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
29
|
end
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
base_path = routes.public_send("#{route.name}_path")
|
|
32
|
+
collection = collection_for(route)
|
|
33
|
+
return [base_path] unless collection
|
|
34
|
+
|
|
35
|
+
pagination = collection.configuration.pagination
|
|
36
|
+
return [base_path] unless pagination.per_page
|
|
37
|
+
|
|
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)
|
|
44
|
+
end
|
|
32
45
|
when "show" then show_paths_for(route)
|
|
33
46
|
else []
|
|
34
47
|
end
|
|
35
48
|
end
|
|
36
49
|
|
|
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
|
+
|
|
37
55
|
def show_paths_for(route)
|
|
38
56
|
resources_for(route).reject(&:root?).map do |resource|
|
|
39
57
|
routes.public_send("#{route.name}_path", resource)
|
data/lib/perron/site/builder.rb
CHANGED
|
@@ -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
|
|
@@ -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
|
data/lib/perron/site/validate.rb
CHANGED
|
@@ -36,7 +36,11 @@ module Perron
|
|
|
36
36
|
def validate_collection(collection)
|
|
37
37
|
collection.resources.each do |resource|
|
|
38
38
|
resource.validate ? success : failed(resource)
|
|
39
|
+
rescue Psych::SyntaxError => error
|
|
40
|
+
render_yaml error, resource.file_path
|
|
39
41
|
end
|
|
42
|
+
rescue Psych::SyntaxError => error
|
|
43
|
+
render_yaml error, "unknown"
|
|
40
44
|
end
|
|
41
45
|
|
|
42
46
|
def success = print "#{GREEN}.#{RESET}"
|
|
@@ -44,13 +48,22 @@ module Perron
|
|
|
44
48
|
def failed(resource)
|
|
45
49
|
print "#{RED}F#{RESET}"
|
|
46
50
|
|
|
47
|
-
|
|
51
|
+
@failures << Failure.new(
|
|
52
|
+
identifier: resource.file_path,
|
|
53
|
+
errors: resource.errors.respond_to?(:full_messages) ? resource.errors.full_messages : []
|
|
54
|
+
)
|
|
55
|
+
end
|
|
48
56
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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}" : ""
|
|
52
62
|
|
|
53
|
-
@failures << Failure.new(
|
|
63
|
+
@failures << Failure.new(
|
|
64
|
+
identifier: identifier,
|
|
65
|
+
errors: ["Invalid YAML#{line_info}#{column_info}: #{error.problem}"]
|
|
66
|
+
)
|
|
54
67
|
end
|
|
55
68
|
|
|
56
69
|
def failures_report
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
namespace :perron do
|
|
2
|
+
desc "Deploy static site using Beam Up"
|
|
3
|
+
task deploy: :environment do
|
|
4
|
+
begin
|
|
5
|
+
require "beam_up"
|
|
6
|
+
rescue LoadError
|
|
7
|
+
raise LoadError, <<~MSG
|
|
8
|
+
Beam Up is required for the deploy task to run.
|
|
9
|
+
|
|
10
|
+
Add it to your Gemfile:
|
|
11
|
+
gem "beam_up"
|
|
12
|
+
|
|
13
|
+
Read more: https://perron.railsdesigner.com/docs/deploy/
|
|
14
|
+
MSG
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
config = Rails.root.join("config/deploy.yml")
|
|
18
|
+
|
|
19
|
+
unless config.exist?
|
|
20
|
+
template = File.expand_path("../install/deploy.yml", __dir__)
|
|
21
|
+
FileUtils.cp(template, config)
|
|
22
|
+
|
|
23
|
+
puts "Created config/deploy.yml"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
beamed = BeamUp.with_progress do
|
|
27
|
+
BeamUp.deploy!(
|
|
28
|
+
Perron.configuration.output,
|
|
29
|
+
config_file: "config/deploy.yml"
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
puts beamed.message
|
|
34
|
+
puts "Deploy ID: #{beamed.deploy_id}" if beamed.deploy_id
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
namespace :deploy do
|
|
38
|
+
desc "Initialize deploy configuration with Beam Up"
|
|
39
|
+
task :init, [:provider] do |task, arguments|
|
|
40
|
+
begin
|
|
41
|
+
require "beam_up"
|
|
42
|
+
rescue LoadError
|
|
43
|
+
raise LoadError, <<~MSG
|
|
44
|
+
Beam Up is required for the deploy task to run.
|
|
45
|
+
|
|
46
|
+
Add it to your Gemfile:
|
|
47
|
+
gem "beam_up"
|
|
48
|
+
|
|
49
|
+
See for more: https://perron.railsdesigner.com/docs/deploy/
|
|
50
|
+
MSG
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
path = BeamUp.init!(arguments[:provider], config_file: "config/deploy.yml")
|
|
54
|
+
|
|
55
|
+
puts "Configured Beam Up in #{path}"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
data/lib/perron/version.rb
CHANGED
data/lib/perron.rb
CHANGED
data/perron.gemspec
CHANGED
|
@@ -19,7 +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.
|
|
22
|
+
spec.add_dependency "mata", "~> 0.10.0"
|
|
23
23
|
|
|
24
24
|
spec.add_runtime_dependency "csv"
|
|
25
25
|
spec.add_runtime_dependency "json"
|
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: 1.
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rails Designer Developers
|
|
@@ -29,14 +29,14 @@ dependencies:
|
|
|
29
29
|
requirements:
|
|
30
30
|
- - "~>"
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: 0.
|
|
32
|
+
version: 0.10.0
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
37
|
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: 0.
|
|
39
|
+
version: 0.10.0
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
41
|
name: csv
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -98,6 +98,7 @@ files:
|
|
|
98
98
|
- app/helpers/perron/feeds_helper.rb
|
|
99
99
|
- app/helpers/perron/markdown_helper.rb
|
|
100
100
|
- app/helpers/perron/meta_tags_helper.rb
|
|
101
|
+
- app/helpers/perron/paginate_helper.rb
|
|
101
102
|
- app/views/perron/concierge/show.html.erb
|
|
102
103
|
- bin/console
|
|
103
104
|
- bin/rails
|
|
@@ -133,10 +134,12 @@ files:
|
|
|
133
134
|
- lib/perron/html_processor/target_blank.rb
|
|
134
135
|
- lib/perron/install.rb
|
|
135
136
|
- lib/perron/install/README.md.tt
|
|
137
|
+
- lib/perron/install/deploy.yml
|
|
136
138
|
- lib/perron/install/initializer.rb.tt
|
|
137
139
|
- lib/perron/markdown.rb
|
|
138
140
|
- lib/perron/metatags.rb
|
|
139
141
|
- lib/perron/output_server.rb
|
|
142
|
+
- lib/perron/paginate.rb
|
|
140
143
|
- lib/perron/refinements/delete_suffixes.rb
|
|
141
144
|
- lib/perron/relation.rb
|
|
142
145
|
- lib/perron/resource.rb
|
|
@@ -180,6 +183,7 @@ files:
|
|
|
180
183
|
- lib/perron/site/validate.rb
|
|
181
184
|
- lib/perron/tasks/build.rake
|
|
182
185
|
- lib/perron/tasks/clobber.rake
|
|
186
|
+
- lib/perron/tasks/deploy.rake
|
|
183
187
|
- lib/perron/tasks/install.rake
|
|
184
188
|
- lib/perron/tasks/sync_sources.rake
|
|
185
189
|
- lib/perron/tasks/validate.rake
|
|
@@ -205,7 +209,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
205
209
|
- !ruby/object:Gem::Version
|
|
206
210
|
version: '0'
|
|
207
211
|
requirements: []
|
|
208
|
-
rubygems_version: 4.0.
|
|
212
|
+
rubygems_version: 4.0.14
|
|
209
213
|
specification_version: 4
|
|
210
214
|
summary: Rails-based static site generator
|
|
211
215
|
test_files: []
|