perron 0.11.0 → 0.12.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 +1 -1
- data/README.md +43 -9
- data/lib/generators/content/content_generator.rb +25 -0
- data/lib/generators/perron/install_generator.rb +4 -0
- data/lib/generators/perron/templates/README.md.tt +18 -1
- data/lib/perron/collection.rb +1 -1
- data/lib/perron/data.rb +2 -0
- data/lib/perron/feeds.rb +1 -0
- data/lib/perron/refinements/delete_suffixes.rb +11 -7
- data/lib/perron/resource/related.rb +1 -0
- data/lib/perron/resource/slug.rb +1 -1
- data/lib/perron/resource.rb +16 -16
- data/lib/perron/site/builder/assets.rb +2 -1
- data/lib/perron/site/builder/feeds/json.rb +6 -4
- data/lib/perron/site/builder/feeds/rss.rb +6 -4
- data/lib/perron/site/builder/feeds.rb +2 -0
- data/lib/perron/site/builder/page.rb +5 -3
- data/lib/perron/site/builder/sitemap.rb +1 -0
- data/lib/perron/site/builder.rb +1 -1
- data/lib/perron/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 70a9d142afefaac54496efbd93312cae38e14525b7f6310be576f74e7f9dfb4d
|
4
|
+
data.tar.gz: fd010ef141b72700120c926a104b8f33f72971d94e58ae01c8c793424d4c5319
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9ab5c1d522c8645f3470451e74c7a5a3475f1a683d6dc953e88fd8fb123e4477558ae99839f2db4742741663c2957eec9d94696d05d7936aa1df14880a5d59a4
|
7
|
+
data.tar.gz: fb40411ad56556967b82396d1bb540766550e73131b9358f870f441c75bd1538c9ef071b3fcb4a6c371f389ee7a1a031c101c4e24cb76b9875b4841756a68dc7
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -38,7 +38,7 @@ end
|
|
38
38
|
|
39
39
|
## Mode
|
40
40
|
|
41
|
-
Perron can operate in two modes, configured via `config.mode`. This allows
|
41
|
+
Perron can operate in two modes, configured via `config.mode`. This allows a build to be either a full static site or be integrated pages in a dynamic Rails application.
|
42
42
|
|
43
43
|
| **Mode** | `:standalone` (default) | `:integrated` |
|
44
44
|
| :--- | :--- | :--- |
|
@@ -54,7 +54,7 @@ Perron is, just like Rails, designed with convention over configuration in mind.
|
|
54
54
|
The controllers are located in `app/controllers/content/`. To make them available, create a route: `resources :posts, module: :content, only: %w[index show]`.
|
55
55
|
|
56
56
|
|
57
|
-
### Create
|
57
|
+
### Create a new collection
|
58
58
|
|
59
59
|
```bash
|
60
60
|
bin/rails generate content Post
|
@@ -66,12 +66,39 @@ This will create the following files:
|
|
66
66
|
* `app/controllers/content/posts_controller.rb`
|
67
67
|
* `app/views/content/posts/index.html.erb`
|
68
68
|
* `app/views/content/posts/show.html.erb`
|
69
|
-
|
69
|
+
|
70
|
+
And adds a route: `resources :posts, module: :content, only: %w[index show]`
|
71
|
+
|
72
|
+
|
73
|
+
### Routes
|
74
|
+
|
75
|
+
Perron uses standard Rails routing, allowing the use of familiar route helpers. For a typical “clean slug”, the filename without extensions serves as the `id` parameter (slug).
|
76
|
+
```ruby
|
77
|
+
<%# For app/content/pages/about.md %>
|
78
|
+
<%= link_to "About Us", page_path("about") # => <a href="/about/">About Us</a> %>
|
79
|
+
```
|
80
|
+
|
81
|
+
To create files with specific extensions directly (e.g., `pricing.html`), the route must first be configured to treat the entire filename as the ID. In `config/routes.rb`, modify the generated `resources` line by adding a `constraints` option:
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
# Change from…
|
85
|
+
resources :pages, path: "/", module: :content, only: %w[show]
|
86
|
+
|
87
|
+
# …to…
|
88
|
+
resources :pages, path: "/", module: :content, only: %w[show], constraints: { id: /[^\/]+/ }
|
89
|
+
```
|
90
|
+
|
91
|
+
With this change, a content file named `app/content/pages/pricing.html.erb` can be linked using the full filename. The builder will then create `pricing.html` in the output directory.
|
92
|
+
```ruby
|
93
|
+
<%= link_to "View Pricing", page_path("pricing", format: :html) # => <a href="/pricing.html">View Pricing</a> %>
|
94
|
+
```
|
70
95
|
|
71
96
|
|
72
97
|
### Setting a root page
|
73
98
|
|
74
|
-
To set a root page, include `Perron::Root` in your `Content::PagesController` and add a `app/content/pages/root.{md,erb,*}` file.
|
99
|
+
To set a root page, include `Perron::Root` in your `Content::PagesController` and add a `app/content/pages/root.{md,erb,*}` file. Then add `root to: "content/pages#root"` add the bottom of your `config/routes.erb`.
|
100
|
+
|
101
|
+
This is automatically added for you when you create a `Page` collection.
|
75
102
|
|
76
103
|
|
77
104
|
## Markdown support
|
@@ -244,9 +271,9 @@ You can render data collections directly using Rails-like partial rendering. Whe
|
|
244
271
|
<%= render Perron::Site.data.features %>
|
245
272
|
```
|
246
273
|
|
247
|
-
This expects a partial at `app/views/features/_feature.html.erb` that will be rendered once for each feature in your data file. The individual record is made available as a local variable matching the singular form of the collection name.
|
274
|
+
This expects a partial at `app/views/content/features/_feature.html.erb` that will be rendered once for each feature in your data file. The individual record is made available as a local variable matching the singular form of the collection name.
|
248
275
|
```erb
|
249
|
-
<!-- app/views/features/_feature.html.erb -->
|
276
|
+
<!-- app/views/content/features/_feature.html.erb -->
|
250
277
|
<div class="feature">
|
251
278
|
<h4><%= feature.name %></h4>
|
252
279
|
<p><%= feature.description %></p>
|
@@ -289,9 +316,14 @@ Feeds are configured within the `Resource` class corresponding to a collection:
|
|
289
316
|
class Content::Post < Perron::Resource
|
290
317
|
configure do |config|
|
291
318
|
config.feeds.rss.enabled = true
|
319
|
+
# config.feeds.rss.title = "My RSS feed" # defaults to configured site_name
|
320
|
+
# config.feeds.rss.description = "My RSS feed description" # defaults to configured site_description
|
292
321
|
# config.feeds.rss.path = "path-to-feed.xml"
|
293
322
|
# config.feeds.rss.max_items = 25
|
323
|
+
#
|
294
324
|
config.feeds.json.enabled = true
|
325
|
+
# config.feeds.json.title = "My JSON feed" # defaults to configured site_name
|
326
|
+
# config.feeds.json.description = "My JSON feed description" # defaults to configured site_description
|
295
327
|
# config.feeds.json.max_items = 15
|
296
328
|
# config.feeds.json.path = "path-to-feed.json"
|
297
329
|
end
|
@@ -362,10 +394,12 @@ Your content here…
|
|
362
394
|
Set collection defaults in the resource model:
|
363
395
|
```ruby
|
364
396
|
class Content::Post < Perron::Resource
|
365
|
-
|
397
|
+
Perron.configure do |config|
|
398
|
+
# …
|
366
399
|
|
367
|
-
|
368
|
-
|
400
|
+
config.metadata.description = "Put your routine tasks on autopilot"
|
401
|
+
config.metadata.author = "Helptail team"
|
402
|
+
end
|
369
403
|
end
|
370
404
|
```
|
371
405
|
|
@@ -5,6 +5,8 @@ require "rails/generators/base"
|
|
5
5
|
class ContentGenerator < Rails::Generators::NamedBase
|
6
6
|
source_root File.expand_path("templates", __dir__)
|
7
7
|
|
8
|
+
class_option :force_plural, type: :boolean, default: false, desc: "Forces the use of a plural model name and class"
|
9
|
+
|
8
10
|
argument :actions, type: :array, default: %w[index show], desc: "Specify which actions to generate (index/show)"
|
9
11
|
|
10
12
|
def create_model
|
@@ -35,8 +37,23 @@ class ContentGenerator < Rails::Generators::NamedBase
|
|
35
37
|
route "resources :#{plural_file_name}, module: :content, only: %w[#{actions.join(" ")}]"
|
36
38
|
end
|
37
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#show\"\n", before: /^\s*end\s*$/
|
45
|
+
end
|
46
|
+
|
38
47
|
private
|
39
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
|
+
|
40
57
|
def view_directory = Rails.root.join("app", "views", "content", plural_file_name)
|
41
58
|
|
42
59
|
def content_directory = Rails.root.join("app", "content", plural_file_name)
|
@@ -44,4 +61,12 @@ class ContentGenerator < Rails::Generators::NamedBase
|
|
44
61
|
def plural_class_name = plural_name.camelize
|
45
62
|
|
46
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
|
47
72
|
end
|
@@ -17,7 +17,7 @@ To use a data file, you can access it through the `Perron::Site.data` object fol
|
|
17
17
|
|
18
18
|
This is a convenient shorthand for `Perron::Data.new("features")`, which can also be used directly:
|
19
19
|
```ruby
|
20
|
-
|
20
|
+
|
21
21
|
<h4><%%= feature.name %></h4>
|
22
22
|
|
23
23
|
<p><%%= feature.description %></p>
|
@@ -38,3 +38,20 @@ The wrapper object provides flexible, read-only access to each record's attribut
|
|
38
38
|
feature.name
|
39
39
|
feature[:name]
|
40
40
|
```
|
41
|
+
|
42
|
+
|
43
|
+
## Rendering
|
44
|
+
|
45
|
+
You can render data collections directly using Rails-like partial rendering. When you call `render` on a data collection, Perron will automatically render a partial for each item.
|
46
|
+
```erb
|
47
|
+
<%= render Perron::Site.data.features %>
|
48
|
+
```
|
49
|
+
|
50
|
+
This expects a partial at `app/views/content/features/_feature.html.erb` that will be rendered once for each feature in your data file. The individual record is made available as a local variable matching the singular form of the collection name.
|
51
|
+
```erb
|
52
|
+
<!-- app/views/content/features/_feature.html.erb -->
|
53
|
+
<div class="feature">
|
54
|
+
<h4><%= feature.name %></h4>
|
55
|
+
<p><%= feature.description %></p>
|
56
|
+
</div>
|
57
|
+
```
|
data/lib/perron/collection.rb
CHANGED
data/lib/perron/data.rb
CHANGED
data/lib/perron/feeds.rb
CHANGED
@@ -13,6 +13,7 @@ module Perron
|
|
13
13
|
|
14
14
|
next if options[:only]&.map(&:to_s)&.exclude?(collection_name)
|
15
15
|
next if options[:except]&.map(&:to_s)&.include?(collection_name)
|
16
|
+
next if collection.configuration.blank?
|
16
17
|
|
17
18
|
collection.configuration.feeds.each do |type, feed|
|
18
19
|
next unless feed.enabled && feed.path && MIME_TYPES.key?(type)
|
@@ -1,11 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Perron
|
2
|
-
module
|
3
|
-
|
4
|
-
|
5
|
-
suffixes
|
6
|
-
|
7
|
-
|
8
|
-
|
4
|
+
module Refinements
|
5
|
+
module DeleteSuffixes
|
6
|
+
refine String do
|
7
|
+
def delete_suffixes(suffixes)
|
8
|
+
suffixes
|
9
|
+
.sort_by(&:length)
|
10
|
+
.reverse_each
|
11
|
+
.reduce(self, :delete_suffix)
|
12
|
+
end
|
9
13
|
end
|
10
14
|
end
|
11
15
|
end
|
@@ -70,6 +70,7 @@ module Perron
|
|
70
70
|
@tokenized_content ||= {}
|
71
71
|
|
72
72
|
return @tokenized_content[target_resource] if @tokenized_content.key?(target_resource)
|
73
|
+
return [] if target_resource.content.blank?
|
73
74
|
|
74
75
|
content = target_resource.content.gsub(/<[^>]*>/, " ")
|
75
76
|
tokens = content.downcase.scan(/\w+/).reject { StopWords.all.include?(it) || it.length < 3 }
|
data/lib/perron/resource/slug.rb
CHANGED
data/lib/perron/resource.rb
CHANGED
@@ -34,14 +34,6 @@ module Perron
|
|
34
34
|
alias_method :path, :slug
|
35
35
|
alias_method :to_param, :slug
|
36
36
|
|
37
|
-
def content
|
38
|
-
page_content = Perron::Resource::Separator.new(raw_content).content
|
39
|
-
|
40
|
-
return page_content unless erb_processing?
|
41
|
-
|
42
|
-
Perron::Resource::Renderer.erb(page_content, {resource: self})
|
43
|
-
end
|
44
|
-
|
45
37
|
def metadata
|
46
38
|
Perron::Resource::Metadata.new(
|
47
39
|
resource: self,
|
@@ -53,6 +45,14 @@ module Perron
|
|
53
45
|
def raw_content = File.read(@file_path)
|
54
46
|
alias_method :raw, :raw_content
|
55
47
|
|
48
|
+
def content
|
49
|
+
page_content = Perron::Resource::Separator.new(raw_content).content
|
50
|
+
|
51
|
+
return page_content unless erb_processing?
|
52
|
+
|
53
|
+
Perron::Resource::Renderer.erb(page_content, {resource: self})
|
54
|
+
end
|
55
|
+
|
56
56
|
def to_partial_path
|
57
57
|
@to_partial_path ||= begin
|
58
58
|
element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(self.class.model_name))
|
@@ -62,10 +62,6 @@ module Perron
|
|
62
62
|
end
|
63
63
|
end
|
64
64
|
|
65
|
-
def root?
|
66
|
-
collection.name.inquiry.pages? && File.basename(filename) == "root"
|
67
|
-
end
|
68
|
-
|
69
65
|
def collection = Collection.new(self.class.model_name.collection)
|
70
66
|
|
71
67
|
def related_resources(limit: 5) = Perron::Site::Resource::Related.new(self).find(limit:)
|
@@ -77,14 +73,18 @@ module Perron
|
|
77
73
|
@frontmatter ||= Perron::Resource::Separator.new(raw_content).frontmatter
|
78
74
|
end
|
79
75
|
|
80
|
-
def erb_processing?
|
81
|
-
@file_path.ends_with?(".erb") || metadata.erb == true
|
82
|
-
end
|
83
|
-
|
84
76
|
def generate_id
|
85
77
|
Digest::SHA1.hexdigest(
|
86
78
|
@file_path.delete_prefix(Perron.configuration.input.to_s).parameterize
|
87
79
|
).first(ID_LENGTH)
|
88
80
|
end
|
81
|
+
|
82
|
+
def erb_processing?
|
83
|
+
@file_path.ends_with?(".erb") || metadata.erb == true
|
84
|
+
end
|
85
|
+
|
86
|
+
def root?
|
87
|
+
collection.name.inquiry.pages? && File.basename(filename) == "root"
|
88
|
+
end
|
89
89
|
end
|
90
90
|
end
|
@@ -29,7 +29,8 @@ module Perron
|
|
29
29
|
end
|
30
30
|
|
31
31
|
FileUtils.mkdir_p(destination)
|
32
|
-
FileUtils.
|
32
|
+
FileUtils.move(Dir.glob("#{source}/*"), destination, force: true)
|
33
|
+
FileUtils.remove_dir(source)
|
33
34
|
|
34
35
|
puts " Copied assets to `#{destination.relative_path_from(Rails.root)}`"
|
35
36
|
|
@@ -18,14 +18,14 @@ module Perron
|
|
18
18
|
hash = Rails.application.routes.url_helpers.with_options(@configuration.default_url_options) do |url|
|
19
19
|
{
|
20
20
|
version: "https://jsonfeed.org/version/1.1",
|
21
|
-
title: @configuration.site_name,
|
22
21
|
home_page_url: @configuration.url,
|
23
|
-
|
22
|
+
title: feed_configuration.title.presence || @configuration.site_name,
|
23
|
+
description: feed_configuration.description.presence || @configuration.site_description,
|
24
24
|
items: resources.map do |resource|
|
25
25
|
{
|
26
26
|
id: resource.id,
|
27
27
|
url: url.polymorphic_url(resource),
|
28
|
-
date_published:
|
28
|
+
date_published: resource.published_at&.iso8601,
|
29
29
|
title: resource.metadata.title,
|
30
30
|
content_html: Perron::Markdown.render(resource.content)
|
31
31
|
}
|
@@ -43,8 +43,10 @@ module Perron
|
|
43
43
|
.reject { it.metadata.feed == false }
|
44
44
|
.sort_by { it.metadata.published_at || it.metadata.updated_at || Time.current }
|
45
45
|
.reverse
|
46
|
-
.take(
|
46
|
+
.take(feed_configuration.max_items)
|
47
47
|
end
|
48
|
+
|
49
|
+
def feed_configuration = @collection.configuration.feeds.json
|
48
50
|
end
|
49
51
|
end
|
50
52
|
end
|
@@ -18,8 +18,8 @@ module Perron
|
|
18
18
|
Nokogiri::XML::Builder.new(encoding: "UTF-8") do |xml|
|
19
19
|
xml.rss(:version => "2.0", "xmlns:atom" => "http://www.w3.org/2005/Atom") do
|
20
20
|
xml.channel do
|
21
|
-
xml.title @configuration.site_name
|
22
|
-
xml.description @configuration.site_description
|
21
|
+
xml.title feed_configuration.title.presence || @configuration.site_name
|
22
|
+
xml.description feed_configuration.description.presence || @configuration.site_description
|
23
23
|
xml.link @configuration.url
|
24
24
|
xml.generator "Perron (#{Perron::VERSION})"
|
25
25
|
|
@@ -28,7 +28,7 @@ module Perron
|
|
28
28
|
xml.item do
|
29
29
|
xml.guid resource.id
|
30
30
|
xml.link url.polymorphic_url(resource), isPermaLink: true
|
31
|
-
xml.pubDate(
|
31
|
+
xml.pubDate(resource.published_at&.rfc822)
|
32
32
|
xml.title resource.metadata.title
|
33
33
|
xml.description { xml.cdata(Perron::Markdown.render(resource.content)) }
|
34
34
|
end
|
@@ -46,8 +46,10 @@ module Perron
|
|
46
46
|
.reject { it.metadata.feed == false }
|
47
47
|
.sort_by { it.metadata.published_at || it.metadata.updated_at || Time.current }
|
48
48
|
.reverse
|
49
|
-
.take(
|
49
|
+
.take(feed_configuration.max_items)
|
50
50
|
end
|
51
|
+
|
52
|
+
def feed_configuration = @collection.configuration.feeds.rss
|
51
53
|
end
|
52
54
|
end
|
53
55
|
end
|
@@ -29,10 +29,12 @@ module Perron
|
|
29
29
|
private
|
30
30
|
|
31
31
|
def save_html(html)
|
32
|
-
|
33
|
-
file_path = directory_path.join("index.html")
|
32
|
+
prefixless_path = @path.delete_prefix("/")
|
34
33
|
|
35
|
-
|
34
|
+
file_path = @output_path.join(prefixless_path)
|
35
|
+
file_path = file_path.join("index.html") if File.extname(prefixless_path).empty?
|
36
|
+
|
37
|
+
FileUtils.mkdir_p(file_path.dirname)
|
36
38
|
File.write(file_path, html)
|
37
39
|
|
38
40
|
print "\e[32m.\e[0m"
|
data/lib/perron/site/builder.rb
CHANGED
data/lib/perron/version.rb
CHANGED