perron 0.0.1 → 0.5.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 +17 -0
- data/Gemfile.lock +269 -0
- data/README.md +182 -0
- data/Rakefile +9 -0
- data/app/helpers/meta_tags_helper.rb +17 -0
- data/app/helpers/perron/markdown_helper.rb +9 -0
- data/bin/console +10 -0
- data/bin/rails +17 -0
- data/bin/release +19 -0
- data/bin/setup +8 -0
- data/lib/generators/content/content_generator.rb +45 -0
- data/lib/generators/content/templates/controller.rb.tt +17 -0
- data/lib/generators/content/templates/index.html.erb.tt +11 -0
- data/lib/generators/content/templates/model.rb.tt +2 -0
- data/lib/generators/content/templates/root.erb.tt +5 -0
- data/lib/generators/content/templates/show.html.erb.tt +7 -0
- data/lib/generators/perron/install_generator.rb +9 -0
- data/lib/generators/perron/templates/initializer.rb.tt +22 -0
- data/lib/perron/configuration.rb +63 -0
- data/lib/perron/engine.rb +13 -0
- data/lib/perron/errors.rb +9 -0
- data/lib/perron/html_processor/target_blank.rb +23 -0
- data/lib/perron/html_processor.rb +28 -0
- data/lib/perron/markdown.rb +54 -0
- data/lib/perron/metatags.rb +82 -0
- data/lib/perron/refinements/delete_suffixes.rb +12 -0
- data/lib/perron/root.rb +13 -0
- data/lib/perron/site/builder/assets.rb +67 -0
- data/lib/perron/site/builder/page.rb +51 -0
- data/lib/perron/site/builder/paths.rb +40 -0
- data/lib/perron/site/builder/public_files.rb +40 -0
- data/lib/perron/site/builder.rb +44 -0
- data/lib/perron/site/collection.rb +28 -0
- data/lib/perron/site/resource/class_methods.rb +43 -0
- data/lib/perron/site/resource/context.rb +13 -0
- data/lib/perron/site/resource/core.rb +15 -0
- data/lib/perron/site/resource/publishable.rb +51 -0
- data/lib/perron/site/resource/separator.rb +31 -0
- data/lib/perron/site/resource/slug.rb +20 -0
- data/lib/perron/site/resource.rb +62 -0
- data/lib/perron/site.rb +33 -0
- data/lib/perron/tasks/perron.rake +12 -0
- data/lib/perron/version.rb +3 -0
- data/lib/perron.rb +11 -1
- data/perron.gemspec +22 -0
- metadata +69 -6
@@ -0,0 +1,22 @@
|
|
1
|
+
Perron.configure do |config|
|
2
|
+
# config.output = "output"
|
3
|
+
|
4
|
+
# config.site_name = "AppRefresher"
|
5
|
+
|
6
|
+
# The build mode for Perron. Can be :standalone or :integrated.
|
7
|
+
# config.mode = :standalone
|
8
|
+
|
9
|
+
# In `integrated` mode, the root is skipped by default. Set to `true` to enable.
|
10
|
+
# config.include_root = false
|
11
|
+
|
12
|
+
# config.default_url_options = {host: "apprefresher.com", protocol: "https", trailing_slash: true}
|
13
|
+
|
14
|
+
# Override the defaults (meta) title suffix
|
15
|
+
# Default: `— Perron.configuration.site_name`
|
16
|
+
# config.title_suffix = nil
|
17
|
+
|
18
|
+
# Set default meta values
|
19
|
+
# Examples:
|
20
|
+
# - `config.metadata.description = "AI-powered tool to keep your knowledge base articles images/screenshots and content up-to-date"`
|
21
|
+
# - `config.metadata.author = "Rails Designer"`
|
22
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Perron
|
4
|
+
def self.configuration
|
5
|
+
@configuration ||= Configuration.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.configure
|
9
|
+
yield(configuration)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.reset_configuration!
|
13
|
+
@configuration = Configuration.new
|
14
|
+
end
|
15
|
+
|
16
|
+
class Configuration
|
17
|
+
def initialize
|
18
|
+
@config = ActiveSupport::OrderedOptions.new
|
19
|
+
|
20
|
+
@config.output = "output"
|
21
|
+
|
22
|
+
@config.mode = :standalone
|
23
|
+
@config.include_root = false
|
24
|
+
|
25
|
+
@config.site_name = nil
|
26
|
+
@config.title_suffix = nil
|
27
|
+
|
28
|
+
@config.allowed_extensions = [".erb", ".md"]
|
29
|
+
@config.exclude_from_public = %w[assets storage]
|
30
|
+
@config.excluded_assets = %w[action_cable actioncable actiontext activestorage rails-ujs trix turbo]
|
31
|
+
|
32
|
+
@config.default_url_options = {
|
33
|
+
host: ENV.fetch("PERRON_HOST", "localhost:3000"),
|
34
|
+
protocol: ENV.fetch("PERRON_PROTOCOL", "http"),
|
35
|
+
trailing_slash: ENV.fetch("PERRON_TRAILING_SLASH", "true") == "true"
|
36
|
+
}
|
37
|
+
|
38
|
+
@config.metadata = ActiveSupport::OrderedOptions.new
|
39
|
+
end
|
40
|
+
|
41
|
+
def input = "app/content"
|
42
|
+
|
43
|
+
def output
|
44
|
+
mode.integrated? ? "public" : @config.output
|
45
|
+
end
|
46
|
+
|
47
|
+
def mode = @config.mode.to_s.inquiry
|
48
|
+
|
49
|
+
def exclude_root? = !@config.include_root
|
50
|
+
|
51
|
+
def method_missing(method_name, ...)
|
52
|
+
if @config.respond_to?(method_name)
|
53
|
+
@config.send(method_name, ...)
|
54
|
+
else
|
55
|
+
super
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def respond_to_missing?(method_name)
|
60
|
+
@config.respond_to?(method_name) || super
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Perron
|
4
|
+
class Engine < Rails::Engine
|
5
|
+
initializer "perron.default_url_options" do |app|
|
6
|
+
app.config.action_controller.default_url_options = Perron.configuration.default_url_options
|
7
|
+
end
|
8
|
+
|
9
|
+
rake_tasks do
|
10
|
+
load File.expand_path("../tasks/perron.rake", __FILE__)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Perron
|
4
|
+
class HtmlProcessor
|
5
|
+
class TargetBlank
|
6
|
+
def initialize(html)
|
7
|
+
@html = html
|
8
|
+
end
|
9
|
+
|
10
|
+
def process
|
11
|
+
@html.css("a").each do |link|
|
12
|
+
href = link["href"]
|
13
|
+
|
14
|
+
next unless href
|
15
|
+
next if href.start_with?("/", "#", "mailto:")
|
16
|
+
|
17
|
+
link["target"] = "_blank"
|
18
|
+
link["rel"] = "noopener"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "perron/html_processor/target_blank"
|
4
|
+
|
5
|
+
module Perron
|
6
|
+
class HtmlProcessor
|
7
|
+
def initialize(html)
|
8
|
+
@html = html
|
9
|
+
end
|
10
|
+
|
11
|
+
def process
|
12
|
+
document = Nokogiri::HTML::DocumentFragment.parse(@html)
|
13
|
+
|
14
|
+
PROCESSORS.each do |processor|
|
15
|
+
processor.new(document).process
|
16
|
+
end
|
17
|
+
|
18
|
+
document.to_html
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# TODO: should be a configuration option
|
24
|
+
PROCESSORS = [
|
25
|
+
Perron::HtmlProcessor::TargetBlank
|
26
|
+
]
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "perron/html_processor"
|
4
|
+
|
5
|
+
module Perron
|
6
|
+
class Markdown
|
7
|
+
class << self
|
8
|
+
def render(text)
|
9
|
+
parser.parse(text)
|
10
|
+
.then { Perron::HtmlProcessor.new(it).process }
|
11
|
+
.html_safe
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def parser
|
17
|
+
@parser ||= markdown_parser
|
18
|
+
end
|
19
|
+
|
20
|
+
def markdown_parser
|
21
|
+
if defined?(::CommonMarker)
|
22
|
+
CommonMarkerParser.new
|
23
|
+
elsif defined?(::Kramdown)
|
24
|
+
KramdownParser.new
|
25
|
+
elsif defined?(::Redcarpet)
|
26
|
+
RedcarpetParser.new
|
27
|
+
else
|
28
|
+
PlainTextParser.new
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class CommonMarkerParser
|
34
|
+
def parse(text) = CommonMarker.render_html(text, :DEFAULT)
|
35
|
+
end
|
36
|
+
|
37
|
+
class KramdownParser
|
38
|
+
def parse(text) = Kramdown::Document.new(text).to_html
|
39
|
+
end
|
40
|
+
|
41
|
+
class RedcarpetParser
|
42
|
+
def parse(text)
|
43
|
+
renderer = Redcarpet::Render::HTML.new(filter_html: true)
|
44
|
+
markdown = Redcarpet::Markdown.new(renderer, autolink: true, tables: true)
|
45
|
+
|
46
|
+
markdown.render(text)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class PlainTextParser
|
51
|
+
def parse(text) = text.to_s
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Perron
|
4
|
+
class Metatags
|
5
|
+
include ActionView::Helpers::TagHelper
|
6
|
+
|
7
|
+
def initialize(resource)
|
8
|
+
@resource = resource
|
9
|
+
@config = Perron.configuration
|
10
|
+
end
|
11
|
+
|
12
|
+
def render(options = {})
|
13
|
+
keys = tags.keys
|
14
|
+
.then { options[:only] ? it & options[:only].map(&:to_sym) : it }
|
15
|
+
.then { options[:except] ? it - options[:except].map(&:to_sym) : it }
|
16
|
+
|
17
|
+
safe_join(keys.filter_map { tags[it].presence }, "\n")
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
FRONTMATTER_KEY_MAP = {
|
23
|
+
"image" => %w[og:image twitter:image],
|
24
|
+
"author" => %w[og:author]
|
25
|
+
}.freeze
|
26
|
+
|
27
|
+
def tags
|
28
|
+
@tags ||= begin
|
29
|
+
frontmatter = @resource&.metadata&.stringify_keys || {}
|
30
|
+
defaults = @config.metadata
|
31
|
+
|
32
|
+
title = frontmatter["title"] || defaults["title"] || @config.site_name
|
33
|
+
description = frontmatter["description"] || defaults["description"]
|
34
|
+
author = frontmatter["author"] || defaults["author"]
|
35
|
+
image = frontmatter["image"] || defaults["image"]
|
36
|
+
og_image = frontmatter["og:image"] || image
|
37
|
+
twitter_image = frontmatter["twitter:image"] || og_image
|
38
|
+
|
39
|
+
{
|
40
|
+
title: title_tag(title),
|
41
|
+
description: meta_tag(name: "description", content: description),
|
42
|
+
|
43
|
+
og_type: meta_tag(property: "og:type", content: frontmatter["og:type"] || "article"),
|
44
|
+
og_title: meta_tag(property: "og:title", content: frontmatter["og:title"] || title),
|
45
|
+
og_description: meta_tag(property: "og:description", content: frontmatter["og:description"] || description),
|
46
|
+
og_site_name: meta_tag(property: "og:site_name", content: @config.site_name),
|
47
|
+
og_image: meta_tag(property: "og:image", content: og_image),
|
48
|
+
og_author: meta_tag(property: "og:author", content: frontmatter["og:author"] || author),
|
49
|
+
|
50
|
+
twitter_card: meta_tag(name: "twitter:card", content: frontmatter["twitter:card"] || "summary_large_image"),
|
51
|
+
twitter_title: meta_tag(name: "twitter:title", content: frontmatter["twitter:title"] || title),
|
52
|
+
twitter_description: meta_tag(name: "twitter:description", content: frontmatter["twitter:description"] || description),
|
53
|
+
twitter_image: meta_tag(name: "twitter:image", content: twitter_image),
|
54
|
+
article_published: meta_tag(property: "article:published_time", content: @resource&.published_at),
|
55
|
+
|
56
|
+
og_url: meta_tag(property: "og:url", content: canonical_url)
|
57
|
+
}
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def title_tag(content)
|
62
|
+
tag.title(
|
63
|
+
content.then { (it == @config.site_name) ? it : "#{it} #{@config.title_suffix || "— #{@config.site_name}"}" }
|
64
|
+
)
|
65
|
+
end
|
66
|
+
|
67
|
+
def meta_tag(attributes)
|
68
|
+
return if attributes[:content].blank?
|
69
|
+
|
70
|
+
tag.meta(**attributes)
|
71
|
+
end
|
72
|
+
|
73
|
+
def canonical_url
|
74
|
+
url_options = @config.default_url_options
|
75
|
+
base_url = "#{url_options[:protocol]}://#{url_options[:host]}"
|
76
|
+
url = URI.join(base_url, @resource&.path).to_s
|
77
|
+
has_extension = URI(url).path.split("/").last&.include?(".")
|
78
|
+
|
79
|
+
url.then { (url_options[:trailing_slash] && !it.end_with?("/") && !has_extension) ? "#{it}/" : it }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/lib/perron/root.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Perron
|
4
|
+
module Site
|
5
|
+
class Builder
|
6
|
+
class Assets
|
7
|
+
def initialize
|
8
|
+
@output_path = Rails.root.join(Perron.configuration.output)
|
9
|
+
end
|
10
|
+
|
11
|
+
def prepare
|
12
|
+
puts "📦 Precompiling and copying assets…"
|
13
|
+
|
14
|
+
success = system("bundle exec rails assets:precompile", out: File::NULL, err: File::NULL)
|
15
|
+
|
16
|
+
unless success
|
17
|
+
puts "❌ ERROR: Asset precompilation failed"
|
18
|
+
|
19
|
+
exit(1)
|
20
|
+
end
|
21
|
+
|
22
|
+
source = Rails.root.join("public", "assets")
|
23
|
+
destination = @output_path.join("assets")
|
24
|
+
|
25
|
+
unless Dir.exist?(source)
|
26
|
+
puts "⚠️ WARNING: No assets found in `#{source}` to copy"
|
27
|
+
|
28
|
+
return
|
29
|
+
end
|
30
|
+
|
31
|
+
FileUtils.mkdir_p(destination)
|
32
|
+
FileUtils.cp_r(Dir.glob("#{source}/*"), destination)
|
33
|
+
|
34
|
+
puts " ✅ Copied assets to `#{destination.relative_path_from(Rails.root)}`"
|
35
|
+
|
36
|
+
prune_excluded_assets from: destination
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def prune_excluded_assets(from:)
|
42
|
+
return if exclusions.empty?
|
43
|
+
|
44
|
+
puts "Pruning excluded assets…"
|
45
|
+
|
46
|
+
pattern = /^(#{exclusions.join("|")})(\.esm|\.min)?-[a-f0-9]{8,}/
|
47
|
+
|
48
|
+
Dir.glob("#{from}/**/*").each do |path|
|
49
|
+
next if File.directory?(path)
|
50
|
+
|
51
|
+
filename = File.basename(path)
|
52
|
+
|
53
|
+
if filename.match?(pattern)
|
54
|
+
FileUtils.rm(path)
|
55
|
+
|
56
|
+
map_file = "#{path}.map"
|
57
|
+
|
58
|
+
FileUtils.rm(map_file) if File.exist?(map_file)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def exclusions = Perron.configuration.excluded_assets
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rack/mock"
|
4
|
+
|
5
|
+
module Perron
|
6
|
+
module Site
|
7
|
+
class Builder
|
8
|
+
class Page
|
9
|
+
def initialize(path)
|
10
|
+
@output_path, @path = Rails.root.join(Perron.configuration.output), path
|
11
|
+
end
|
12
|
+
|
13
|
+
def render
|
14
|
+
action = route_info[:action]
|
15
|
+
request = ActionDispatch::Request.new(env)
|
16
|
+
response = ActionDispatch::Response.new
|
17
|
+
|
18
|
+
request.path_parameters = route_info
|
19
|
+
|
20
|
+
controller.dispatch(action, request, response)
|
21
|
+
|
22
|
+
return puts " ❌ ERROR: Request failed for '#{@path}' (Status: #{response.status})" unless response.successful?
|
23
|
+
|
24
|
+
save_html(response.body)
|
25
|
+
rescue => error
|
26
|
+
puts " ❌ ERROR: Failed to generate page for '#{@path}'. Details: #{error.class} - #{error.message}\n#{error.backtrace.first(3).join("\n")}"
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def save_html(html)
|
32
|
+
directory_path = @output_path.join(@path.delete_prefix("/"))
|
33
|
+
file_path = directory_path.join("index.html")
|
34
|
+
|
35
|
+
FileUtils.mkdir_p(directory_path)
|
36
|
+
File.write(file_path, html)
|
37
|
+
|
38
|
+
puts "✅ Generated: #{@path} -> #{file_path.relative_path_from(@output_path)}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def route_info
|
42
|
+
@route_info ||= Rails.application.routes.recognize_path(@path)
|
43
|
+
end
|
44
|
+
|
45
|
+
def env = Rack::MockRequest.env_for(@path, "HTTP_HOST" => Perron.configuration.default_url_options[:host])
|
46
|
+
|
47
|
+
def controller = "#{route_info[:controller]}_controller".classify.constantize.new
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Perron
|
4
|
+
module Site
|
5
|
+
class Builder
|
6
|
+
class Paths
|
7
|
+
def initialize(collection, paths)
|
8
|
+
@collection, @paths = collection, paths
|
9
|
+
end
|
10
|
+
|
11
|
+
def get
|
12
|
+
@paths << routes.public_send(index_path) if routes.respond_to?(index_path)
|
13
|
+
|
14
|
+
if routes.respond_to?(show_path)
|
15
|
+
@collection.all.each do |resource|
|
16
|
+
root = resource.slug == "/"
|
17
|
+
|
18
|
+
next if skip? root
|
19
|
+
|
20
|
+
@paths << (root ? routes.root_path : routes.public_send(show_path, resource))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def skip?(root)
|
28
|
+
root &&
|
29
|
+
Perron.configuration.mode.integrated? && Perron.configuration.exclude_root?
|
30
|
+
end
|
31
|
+
|
32
|
+
def routes = Rails.application.routes.url_helpers
|
33
|
+
|
34
|
+
def index_path = "#{@collection.name}_path"
|
35
|
+
|
36
|
+
def show_path = "#{@collection.name.singularize}_path"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Perron
|
4
|
+
module Site
|
5
|
+
class Builder
|
6
|
+
class PublicFiles
|
7
|
+
def initialize
|
8
|
+
@output_path = Rails.root.join(Perron.configuration.output)
|
9
|
+
@public_dir = Rails.root.join("public")
|
10
|
+
end
|
11
|
+
|
12
|
+
def copy
|
13
|
+
puts "📂 Copying public files…"
|
14
|
+
|
15
|
+
return unless Dir.exist?(@public_dir)
|
16
|
+
|
17
|
+
if paths.empty?
|
18
|
+
puts " - No public files to copy"
|
19
|
+
|
20
|
+
return
|
21
|
+
end
|
22
|
+
|
23
|
+
paths.each do |path|
|
24
|
+
FileUtils.cp_r(path, @output_path)
|
25
|
+
|
26
|
+
puts " ✅ Copied: #{File.basename(path)}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def paths
|
33
|
+
@paths ||= Dir.glob("#{@public_dir}/*", File::FNM_DOTMATCH).reject do |path|
|
34
|
+
Set.new(Perron.configuration.exclude_from_public + %w[. ..]).include?(File.basename(path))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "perron/site/builder/assets"
|
4
|
+
require "perron/site/builder/public_files"
|
5
|
+
require "perron/site/builder/paths"
|
6
|
+
require "perron/site/builder/page"
|
7
|
+
|
8
|
+
module Perron
|
9
|
+
module Site
|
10
|
+
class Builder
|
11
|
+
def initialize
|
12
|
+
@output_path = Rails.root.join(Perron.configuration.output)
|
13
|
+
end
|
14
|
+
|
15
|
+
def build
|
16
|
+
if Perron.configuration.mode.standalone?
|
17
|
+
puts "🧹 Cleaning previous build…"
|
18
|
+
FileUtils.rm_rf(Dir.glob("#{@output_path}/*"))
|
19
|
+
|
20
|
+
Perron::Site::Builder::Assets.new.prepare
|
21
|
+
Perron::Site::Builder::PublicFiles.new.copy
|
22
|
+
end
|
23
|
+
|
24
|
+
puts "🚀 Starting site build…"
|
25
|
+
puts "-" * 15
|
26
|
+
|
27
|
+
paths.each { render_page(it) }
|
28
|
+
|
29
|
+
puts "-" * 15
|
30
|
+
puts "✅ Build complete"
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def paths
|
36
|
+
Set.new.tap do |paths|
|
37
|
+
Perron::Site.collections.each { Perron::Site::Builder::Paths.new(it, paths).get }
|
38
|
+
end.to_a
|
39
|
+
end
|
40
|
+
|
41
|
+
def render_page(path) = Perron::Site::Builder::Page.new(path).render
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Perron
|
4
|
+
class Collection
|
5
|
+
attr_reader :name
|
6
|
+
|
7
|
+
def initialize(name)
|
8
|
+
@name = name
|
9
|
+
@collection_path = File.join(Rails.root, Perron.configuration.input, name)
|
10
|
+
|
11
|
+
raise Errors::CollectionNotFoundError, "No such collection: #{name}" unless File.exist?(@collection_path) && File.directory?(@collection_path)
|
12
|
+
end
|
13
|
+
|
14
|
+
def all(resource_class = "Content::#{name.classify}".safe_constantize)
|
15
|
+
@all ||= Dir.glob("#{@collection_path}/**/*.*").map do |file_path|
|
16
|
+
resource_class.new(file_path)
|
17
|
+
end.select(&:published?)
|
18
|
+
end
|
19
|
+
|
20
|
+
def find(slug, resource_class = Resource)
|
21
|
+
resource = all(resource_class).find { it.slug == slug }
|
22
|
+
|
23
|
+
return resource if resource
|
24
|
+
|
25
|
+
raise Errors::ResourceNotFoundError, "Resource not found with slug: #{slug}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Perron
|
4
|
+
class Resource
|
5
|
+
module ClassMethods
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
class_methods do
|
9
|
+
def find(slug) = collection.find(slug, name.constantize)
|
10
|
+
|
11
|
+
def all = collection.all(self)
|
12
|
+
|
13
|
+
def count = all.size
|
14
|
+
|
15
|
+
def first = all[0]
|
16
|
+
|
17
|
+
def second = all[1]
|
18
|
+
|
19
|
+
def third = all[2]
|
20
|
+
|
21
|
+
def fourth = all[3]
|
22
|
+
|
23
|
+
def fifth = all[4]
|
24
|
+
|
25
|
+
def forty_two = all[41]
|
26
|
+
|
27
|
+
def last = all.last
|
28
|
+
|
29
|
+
def take(n) = all.first(n)
|
30
|
+
|
31
|
+
def collection = Collection.new(collection_name)
|
32
|
+
|
33
|
+
def model_name
|
34
|
+
@model_name ||= ActiveModel::Name.new(self, nil, name.demodulize.to_s)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def collection_name = name.demodulize.underscore.pluralize
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|