perron 0.10.0 → 0.11.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 +1 -0
- data/Gemfile.lock +3 -1
- data/README.md +70 -42
- data/app/helpers/meta_tags_helper.rb +1 -1
- data/lib/generators/content/templates/root.erb.tt +0 -1
- data/lib/generators/perron/install_generator.rb +2 -1
- data/lib/generators/perron/templates/initializer.rb.tt +5 -2
- data/lib/perron/{site/collection.rb → collection.rb} +7 -1
- data/lib/perron/configuration.rb +4 -0
- data/lib/perron/{site/data.rb → data.rb} +14 -2
- data/lib/perron/html_processor/syntax_highlight.rb +30 -0
- data/lib/perron/html_processor.rb +12 -8
- data/lib/perron/markdown.rb +6 -5
- data/lib/perron/metatags.rb +33 -59
- data/lib/perron/{site/resource → resource}/class_methods.rb +5 -1
- data/lib/perron/resource/metadata.rb +67 -0
- data/lib/perron/{site/resource → resource}/publishable.rb +5 -5
- data/lib/perron/{site/resource → resource}/related.rb +1 -1
- data/lib/perron/{site/resource → resource}/separator.rb +5 -5
- data/lib/perron/{site/resource → resource}/slug.rb +6 -3
- data/lib/perron/{site/resource.rb → resource.rb} +34 -10
- data/lib/perron/root.rb +1 -1
- data/lib/perron/site.rb +3 -4
- data/lib/perron/version.rb +1 -1
- data/lib/perron.rb +1 -0
- data/perron.gemspec +1 -1
- metadata +18 -16
- /data/lib/perron/{site/data → data}/proxy.rb +0 -0
- /data/lib/perron/{site/resource → resource}/configuration.rb +0 -0
- /data/lib/perron/{site/resource → resource}/core.rb +0 -0
- /data/lib/perron/{site/resource → resource}/related/stop_words.rb +0 -0
- /data/lib/perron/{site/resource → resource}/renderer.rb +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1757232a5830a600f00f1517b0aa26c0f95cc55005c32cdb7865282b522cefec
|
4
|
+
data.tar.gz: ba9a09ab06d80c8cc266c210d51770c5577d079dbfab6226f058c1f39a974505
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 82e81dbe7b346d47d539ab50b098c1b2542241d8864e3ed0cddf3e565b39785ae6710718a3db158cacaa27e67ea436960b88b012851ec5e85d7c91d3d0baa718
|
7
|
+
data.tar.gz: bdc619b82756afede00259a7f89970bac482a2e7204f2b8a66ad80b798467604b934462fe69d93e9d70e359abc298b0f72fcbf6842dde0258b7aaabe14a5042c
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
perron (0.
|
4
|
+
perron (0.11.0)
|
5
5
|
csv
|
6
6
|
json
|
7
7
|
psych
|
@@ -204,6 +204,7 @@ GEM
|
|
204
204
|
regexp_parser (2.10.0)
|
205
205
|
reline (0.6.1)
|
206
206
|
io-console (~> 0.5)
|
207
|
+
rouge (4.6.0)
|
207
208
|
rubocop (1.75.8)
|
208
209
|
json (~> 2.3)
|
209
210
|
language_server-protocol (~> 3.17.0.2)
|
@@ -267,6 +268,7 @@ DEPENDENCIES
|
|
267
268
|
perron!
|
268
269
|
rails (~> 7.2.0)
|
269
270
|
rake (~> 13.3.0)
|
271
|
+
rouge (~> 4.6.0)
|
270
272
|
standard (~> 1.50.0)
|
271
273
|
|
272
274
|
BUNDLED WITH
|
data/README.md
CHANGED
@@ -13,7 +13,7 @@ A Rails-based static site generator.
|
|
13
13
|
</a>
|
14
14
|
|
15
15
|
|
16
|
-
## Getting
|
16
|
+
## Getting started
|
17
17
|
|
18
18
|
### Installation
|
19
19
|
|
@@ -47,14 +47,14 @@ Perron can operate in two modes, configured via `config.mode`. This allows you t
|
|
47
47
|
| **Asset Handling** | Via Perron | Via Asset Pipeline |
|
48
48
|
|
49
49
|
|
50
|
-
##
|
50
|
+
## Collections
|
51
51
|
|
52
|
-
Perron is, just like Rails, designed with convention over configuration in mind. Content is stored in `app/content/*/*.{erb,md}
|
52
|
+
Perron is, just like Rails, designed with convention over configuration in mind. Content is stored in `app/content/*/*.{erb,md,*}` and backed by a class, located in `app/models/content/` that inherits from `Perron::Resource`.
|
53
53
|
|
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
|
-
###
|
57
|
+
### Create content
|
58
58
|
|
59
59
|
```bash
|
60
60
|
bin/rails generate content Post
|
@@ -69,29 +69,52 @@ This will create the following files:
|
|
69
69
|
* Adds route: `resources :posts, module: :content, only: %w[index show]`
|
70
70
|
|
71
71
|
|
72
|
-
### Setting a
|
72
|
+
### Setting a root page
|
73
73
|
|
74
|
-
To set a root page, include `Perron::Root` in your `Content::PagesController` and add a `app/content/pages/root.
|
74
|
+
To set a root page, include `Perron::Root` in your `Content::PagesController` and add a `app/content/pages/root.{md,erb,*}` file. This is automatically added for you when you create a `Page` collection.
|
75
75
|
|
76
|
-
This is automatically added when you create a `Page` collection.
|
77
76
|
|
78
|
-
|
79
|
-
## Markdown Support
|
77
|
+
## Markdown support
|
80
78
|
|
81
79
|
Perron supports markdown with the `markdownify` helper.
|
82
80
|
|
83
|
-
There are no markdown gems bundled by default, so you'll need to add one of these
|
81
|
+
There are no markdown gems bundled by default, so you'll need to add one of these to your `Gemfile`:
|
84
82
|
|
85
|
-
-
|
86
|
-
-
|
87
|
-
-
|
83
|
+
- `commonmarker`
|
84
|
+
- `kramdown`
|
85
|
+
- `redcarpet`
|
88
86
|
|
89
87
|
```bash
|
90
88
|
bundle add {commonmarker,kramdown,redcarpet}
|
91
89
|
```
|
92
90
|
|
91
|
+
### Configuration
|
92
|
+
|
93
|
+
To pass options to the parser, set `markdown_options` in `config/initializers/perron.rb`. The options hash is passed directly to the chosen library.
|
94
|
+
|
95
|
+
**Commonmarker**
|
96
|
+
```ruby
|
97
|
+
# Options are passed as keyword arguments.
|
98
|
+
Perron.configuration.markdown_options = { options: [:HARDBREAKS], extensions: [:table] }
|
99
|
+
```
|
100
|
+
|
101
|
+
**Kramdown**
|
102
|
+
```ruby
|
103
|
+
# Options are passed as a standard hash.
|
104
|
+
Perron.configuration.markdown_options = { input: "GFM", smart_quotes: "apos,quot" }
|
105
|
+
```
|
106
|
+
|
107
|
+
**Redcarpet**
|
108
|
+
```ruby
|
109
|
+
# Options are nested under :renderer_options and :markdown_options.
|
110
|
+
Perron.configuration.markdown_options = {
|
111
|
+
renderer_options: { hard_wrap: true },
|
112
|
+
markdown_options: { tables: true, autolink: true }
|
113
|
+
}
|
114
|
+
```
|
115
|
+
|
93
116
|
|
94
|
-
## HTML
|
117
|
+
## HTML transformations
|
95
118
|
|
96
119
|
Perron can post-process the HTML generated from your Markdown content.
|
97
120
|
|
@@ -100,19 +123,20 @@ Perron can post-process the HTML generated from your Markdown content.
|
|
100
123
|
|
101
124
|
Apply transformations by passing an array of processor names or classes to the `markdownify` helper via the `process` option.
|
102
125
|
```erb
|
103
|
-
<%= markdownify @resource.content, process: %w[target_blank
|
126
|
+
<%= markdownify @resource.content, process: %w[lazy_load_images syntax_highlight target_blank] %>
|
104
127
|
```
|
105
128
|
|
106
129
|
|
107
|
-
### Available
|
130
|
+
### Available processors
|
108
131
|
|
109
132
|
The following processors are built-in and can be activated by passing their string name:
|
110
133
|
|
111
134
|
- `target_blank`: Adds `target="_blank"` to all external links;
|
112
135
|
- `lazy_load_images`: Adds `loading="lazy"` to all `<img>` tags.
|
136
|
+
- `syntax_highlight`: Applies syntax highlighting to fenced code blocks (e.g., \`\`\`ruby). This requires adding the `rouge` gem to your Gemfile (`bundle add rouge`). You will also need to include a Rouge CSS theme for colors to appear.
|
113
137
|
|
114
138
|
|
115
|
-
### Creating
|
139
|
+
### Creating your own processors
|
116
140
|
|
117
141
|
You can create your own processor by defining a class that inherits from `Perron::HtmlProcessor::Base` and implements a `process` method.
|
118
142
|
Then, pass the class constant directly in the `process` array.
|
@@ -184,19 +208,8 @@ Check out our amazing features:
|
|
184
208
|
<% end %>
|
185
209
|
```
|
186
210
|
|
187
|
-
**Result:**
|
188
|
-
```html
|
189
|
-
<p>Check out our amazing features:</p>
|
190
|
-
<ul>
|
191
|
-
<li>Rails based</li>
|
192
|
-
<li>SEO friendly</li>
|
193
|
-
<li>Markdown first</li>
|
194
|
-
<li>ERB support</li>
|
195
|
-
</ul>
|
196
|
-
```
|
197
|
-
|
198
211
|
|
199
|
-
## Data
|
212
|
+
## Data files
|
200
213
|
|
201
214
|
Perron can consume structured data from YML, JSON, or CSV files, making them available within your templates.
|
202
215
|
This is useful for populating features, team members, or any other repeated data structure.
|
@@ -211,12 +224,12 @@ To use a data file, instantiate `Perron::Site.data` with the basename of the fil
|
|
211
224
|
<% end %>
|
212
225
|
```
|
213
226
|
|
214
|
-
### File
|
227
|
+
### File location and formats
|
215
228
|
|
216
229
|
By default, Perron looks up `app/content/data/` for files with a `.yml`, `.json`, or `.csv` extension.
|
217
230
|
For a `features` call, it would find `features.yml`, `features.json`, or `features.csv`. You can also provide a path to any data file, via `Perron::Data.new("path/to/data.json")`.
|
218
231
|
|
219
|
-
### Accessing
|
232
|
+
### Accessing data
|
220
233
|
|
221
234
|
The wrapper object provides flexible, read-only access to each record's attributes. Both dot notation and hash-like key access are supported.
|
222
235
|
```ruby
|
@@ -224,6 +237,22 @@ feature.name
|
|
224
237
|
feature[:name]
|
225
238
|
```
|
226
239
|
|
240
|
+
### Rendering
|
241
|
+
|
242
|
+
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.
|
243
|
+
```erb
|
244
|
+
<%= render Perron::Site.data.features %>
|
245
|
+
```
|
246
|
+
|
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.
|
248
|
+
```erb
|
249
|
+
<!-- app/views/features/_feature.html.erb -->
|
250
|
+
<div class="feature">
|
251
|
+
<h4><%= feature.name %></h4>
|
252
|
+
<p><%= feature.description %></p>
|
253
|
+
</div>
|
254
|
+
```
|
255
|
+
|
227
256
|
|
228
257
|
## Feeds
|
229
258
|
|
@@ -299,7 +328,7 @@ Or exclude certain tags:
|
|
299
328
|
|
300
329
|
Values are determined with the following precedence, from highest to lowest:
|
301
330
|
|
302
|
-
#### 1. Controller
|
331
|
+
#### 1. Controller action
|
303
332
|
|
304
333
|
Define a `@metadata` instance variable in your controller:
|
305
334
|
```ruby
|
@@ -314,10 +343,9 @@ class Content::PostsController < ApplicationController
|
|
314
343
|
end
|
315
344
|
```
|
316
345
|
|
317
|
-
#### 2. Page
|
346
|
+
#### 2. Page frontmatter
|
318
347
|
|
319
348
|
Add values to the YAML frontmatter in content files:
|
320
|
-
|
321
349
|
```yaml
|
322
350
|
---
|
323
351
|
title: My Awesome Post
|
@@ -331,7 +359,7 @@ Your content here…
|
|
331
359
|
|
332
360
|
#### 3. Collection configuration
|
333
361
|
|
334
|
-
Set
|
362
|
+
Set collection defaults in the resource model:
|
335
363
|
```ruby
|
336
364
|
class Content::Post < Perron::Resource
|
337
365
|
# …
|
@@ -341,7 +369,7 @@ class Content::Post < Perron::Resource
|
|
341
369
|
end
|
342
370
|
```
|
343
371
|
|
344
|
-
#### 4. Default
|
372
|
+
#### 4. Default values
|
345
373
|
|
346
374
|
Set site-wide defaults in the initializer:
|
347
375
|
```ruby
|
@@ -354,13 +382,13 @@ end
|
|
354
382
|
```
|
355
383
|
|
356
384
|
|
357
|
-
## Related
|
385
|
+
## Related resources
|
358
386
|
|
359
387
|
The `related_resources` method allows to find and display a list of similar resources
|
360
|
-
from the
|
388
|
+
from the sme collection. Similarity is calculated using the **[TF-IDF](https://en.wikipedia.org/wiki/Tf%E2%80%93idf)** algorithm on the content of each resource.
|
361
389
|
|
362
390
|
|
363
|
-
### Basic
|
391
|
+
### Basic usage
|
364
392
|
|
365
393
|
To get a list of the 5 most similar resources, call the method on any resource instance.
|
366
394
|
```ruby
|
@@ -372,9 +400,9 @@ To get a list of the 5 most similar resources, call the method on any resource i
|
|
372
400
|
```
|
373
401
|
|
374
402
|
|
375
|
-
## XML
|
403
|
+
## XML sitemap
|
376
404
|
|
377
|
-
A sitemap is
|
405
|
+
A sitemap is a XML file that lists all the pages of a website to help search engines discover and index content more efficiently, typically containing URLs, last modification dates, change frequency, and priority values.
|
378
406
|
|
379
407
|
Enable it with the following line in the Perron configuration:
|
380
408
|
```ruby
|
@@ -409,7 +437,7 @@ sitemap_change_frequency: :daily
|
|
409
437
|
```
|
410
438
|
|
411
439
|
|
412
|
-
## Building
|
440
|
+
## Building your static site
|
413
441
|
|
414
442
|
When in `standalone` mode and you're ready to generate your static site, run:
|
415
443
|
```bash
|
@@ -18,7 +18,8 @@ module Perron
|
|
18
18
|
def add_markdown_gems
|
19
19
|
append_to_file "Gemfile", <<~RUBY
|
20
20
|
|
21
|
-
# Perron
|
21
|
+
# Perron supports Markdown rendering using one of the following gems.
|
22
|
+
# Uncomment your preferred choice and run `bundle install`
|
22
23
|
# gem "commonmarker"
|
23
24
|
# gem "kramdown"
|
24
25
|
# gem "redcarpet"
|
@@ -3,14 +3,17 @@ Perron.configure do |config|
|
|
3
3
|
|
4
4
|
# config.site_name = "Helptail"
|
5
5
|
|
6
|
-
# The build mode for Perron. Can be :standalone or :integrated
|
6
|
+
# The build mode for Perron. Can be :standalone or :integrated
|
7
7
|
# config.mode = :standalone
|
8
8
|
|
9
|
-
# In `integrated` mode, the root is skipped by default. Set to `true` to enable
|
9
|
+
# In `integrated` mode, the root is skipped by default. Set to `true` to enable
|
10
10
|
# config.include_root = false
|
11
11
|
|
12
12
|
# config.default_url_options = {host: "helptail.com", protocol: "https", trailing_slash: true}
|
13
13
|
|
14
|
+
# The options hash is passed directly to the chosen library
|
15
|
+
# config.markdown_options = {}
|
16
|
+
|
14
17
|
# Set default meta values
|
15
18
|
# Examples:
|
16
19
|
# - `config.metadata.description = "Put your routine tasks on autopilot"`
|
@@ -5,7 +5,7 @@ module Perron
|
|
5
5
|
attr_reader :name
|
6
6
|
|
7
7
|
def initialize(name)
|
8
|
-
@name = name
|
8
|
+
@name = name.inquiry
|
9
9
|
@collection_path = File.join(Perron.configuration.input, name)
|
10
10
|
|
11
11
|
raise Errors::CollectionNotFoundError, "No such collection: #{name}" unless File.exist?(@collection_path) && File.directory?(@collection_path)
|
@@ -29,5 +29,11 @@ module Perron
|
|
29
29
|
|
30
30
|
raise Errors::ResourceNotFoundError, "Resource not found with slug: #{slug}"
|
31
31
|
end
|
32
|
+
|
33
|
+
def find_by_file_name(file_name, resource_class = Resource)
|
34
|
+
resource_class.new(
|
35
|
+
Perron.configuration.allowed_extensions.lazy.map { File.join(@collection_path, [file_name, it].join(".")) }.find { File.exist?(it) }
|
36
|
+
)
|
37
|
+
end
|
32
38
|
end
|
33
39
|
end
|
data/lib/perron/configuration.rb
CHANGED
@@ -28,12 +28,16 @@ module Perron
|
|
28
28
|
@config.exclude_from_public = %w[assets storage]
|
29
29
|
@config.excluded_assets = %w[action_cable actioncable actiontext activestorage rails-ujs trix turbo]
|
30
30
|
|
31
|
+
@config.view_unpublished = Rails.env.development?
|
32
|
+
|
31
33
|
@config.default_url_options = {
|
32
34
|
host: ENV.fetch("PERRON_HOST", "localhost:3000"),
|
33
35
|
protocol: ENV.fetch("PERRON_PROTOCOL", "http"),
|
34
36
|
trailing_slash: ENV.fetch("PERRON_TRAILING_SLASH", "true") == "true"
|
35
37
|
}
|
36
38
|
|
39
|
+
@config.markdown_options = {}
|
40
|
+
|
37
41
|
@config.sitemap = ActiveSupport::OrderedOptions.new
|
38
42
|
@config.sitemap.enabled = false
|
39
43
|
@config.sitemap.priority = 0.5
|
@@ -5,6 +5,7 @@ require "csv"
|
|
5
5
|
module Perron
|
6
6
|
class Data < SimpleDelegator
|
7
7
|
def initialize(identifier)
|
8
|
+
@identifier = identifier
|
8
9
|
@file_path = self.class.path_for!(identifier)
|
9
10
|
@records = records
|
10
11
|
|
@@ -47,7 +48,7 @@ module Perron
|
|
47
48
|
raise Errors::DataParseError, "Data in `#{@file_path}` must be an array of objects."
|
48
49
|
end
|
49
50
|
|
50
|
-
data.map { Item.new(it) }
|
51
|
+
data.map { Item.new(it, identifier: @identifier) }
|
51
52
|
end
|
52
53
|
|
53
54
|
def rendered_from(path)
|
@@ -97,12 +98,23 @@ module Perron
|
|
97
98
|
private_constant :HelperContext
|
98
99
|
|
99
100
|
class Item
|
100
|
-
def initialize(attributes)
|
101
|
+
def initialize(attributes, identifier:)
|
101
102
|
@attributes = attributes.transform_keys(&:to_sym)
|
103
|
+
@identifier = identifier
|
102
104
|
end
|
103
105
|
|
104
106
|
def [](key) = @attributes[key.to_sym]
|
105
107
|
|
108
|
+
def to_partial_path
|
109
|
+
@to_partial_path ||= begin
|
110
|
+
identifier = @identifier.to_s
|
111
|
+
collection = File.extname(identifier).present? ? File.basename(identifier, ".*") : identifier
|
112
|
+
element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.singularize(File.basename(collection)))
|
113
|
+
|
114
|
+
File.join("content", collection, element)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
106
118
|
def method_missing(method_name, *arguments, &block)
|
107
119
|
return super if !@attributes.key?(method_name) || arguments.any? || block
|
108
120
|
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rouge"
|
4
|
+
require "perron/html_processor/base"
|
5
|
+
|
6
|
+
module Perron
|
7
|
+
class HtmlProcessor
|
8
|
+
class SyntaxHighlight < HtmlProcessor::Base
|
9
|
+
def process
|
10
|
+
@html.css('pre > code[class*="language-"]').each do |code_block|
|
11
|
+
language = code_block[:class][/(?<=language-)\S+/]
|
12
|
+
|
13
|
+
next if language.blank?
|
14
|
+
|
15
|
+
code_block.parent.replace(
|
16
|
+
highlight(code_block.text, with: language)
|
17
|
+
)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def highlight(code_block, with:)
|
24
|
+
lexer = Rouge::Lexer.find(with) || Rouge::Lexers::PlainText.new
|
25
|
+
|
26
|
+
Rouge::Formatters::HTMLPygments.new(::Rouge::Formatters::HTML.new).format(lexer.lex(code_block))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -11,11 +11,9 @@ module Perron
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def process
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
document.to_html
|
14
|
+
Nokogiri::HTML::DocumentFragment.parse(@html).tap do |document|
|
15
|
+
@processors.each { it.new(document).process }
|
16
|
+
end.to_html
|
19
17
|
end
|
20
18
|
|
21
19
|
private
|
@@ -23,7 +21,13 @@ module Perron
|
|
23
21
|
BUILT_IN = {
|
24
22
|
"target_blank" => Perron::HtmlProcessor::TargetBlank,
|
25
23
|
"lazy_load_images" => Perron::HtmlProcessor::LazyLoadImages
|
26
|
-
}
|
24
|
+
}.tap do |processors|
|
25
|
+
require "rouge"
|
26
|
+
require "perron/html_processor/syntax_highlight"
|
27
|
+
|
28
|
+
processors["syntax_highlight"] = Perron::HtmlProcessor::SyntaxHighlight
|
29
|
+
rescue LoadError
|
30
|
+
end
|
27
31
|
|
28
32
|
def find_by(identifier)
|
29
33
|
case identifier
|
@@ -43,8 +47,8 @@ module Perron
|
|
43
47
|
|
44
48
|
return processor if processor
|
45
49
|
|
46
|
-
raise Perron::Errors::ProcessorNotFoundError,
|
47
|
-
|
50
|
+
raise Perron::Errors::ProcessorNotFoundError, "The `syntax_highlight` processor requires `rouge`. Run `bundle add rouge` to add it to your Gemfile." if name.inquiry.syntax_highlight?
|
51
|
+
raise Perron::Errors::ProcessorNotFoundError, "Could not find processor `#{name}`. It is not a Perron-included processor and the constant `#{name.camelize}` could not be found."
|
48
52
|
end
|
49
53
|
end
|
50
54
|
end
|
data/lib/perron/markdown.rb
CHANGED
@@ -18,7 +18,7 @@ module Perron
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def markdown_parser
|
21
|
-
if defined?(::
|
21
|
+
if defined?(::Commonmarker)
|
22
22
|
CommonMarkerParser.new
|
23
23
|
elsif defined?(::Kramdown)
|
24
24
|
KramdownParser.new
|
@@ -31,17 +31,18 @@ module Perron
|
|
31
31
|
end
|
32
32
|
|
33
33
|
class CommonMarkerParser
|
34
|
-
def parse(text) =
|
34
|
+
def parse(text) = Commonmarker.to_html(text, **Perron.configuration.markdown_options)
|
35
35
|
end
|
36
36
|
|
37
37
|
class KramdownParser
|
38
|
-
def parse(text) = Kramdown::Document.new(text).to_html
|
38
|
+
def parse(text) = Kramdown::Document.new(text, Perron.configuration.markdown_options).to_html
|
39
39
|
end
|
40
40
|
|
41
41
|
class RedcarpetParser
|
42
42
|
def parse(text)
|
43
|
-
|
44
|
-
|
43
|
+
options = Perron.configuration.markdown_options
|
44
|
+
renderer = Redcarpet::Render::HTML.new(options.fetch(:renderer_options, {}))
|
45
|
+
markdown = Redcarpet::Markdown.new(renderer, options.fetch(:markdown_options, {}))
|
45
46
|
|
46
47
|
markdown.render(text)
|
47
48
|
end
|
data/lib/perron/metatags.rb
CHANGED
@@ -4,9 +4,8 @@ module Perron
|
|
4
4
|
class Metatags
|
5
5
|
include ActionView::Helpers::TagHelper
|
6
6
|
|
7
|
-
def initialize(
|
8
|
-
@
|
9
|
-
@config = Perron.configuration
|
7
|
+
def initialize(data)
|
8
|
+
@data = data
|
10
9
|
end
|
11
10
|
|
12
11
|
def render(options = {})
|
@@ -19,75 +18,50 @@ module Perron
|
|
19
18
|
|
20
19
|
private
|
21
20
|
|
22
|
-
FRONTMATTER_KEY_MAP = {
|
23
|
-
"locale" => %w[og:locale],
|
24
|
-
"image" => %w[og:image twitter:image],
|
25
|
-
"author" => %w[og:author]
|
26
|
-
}.freeze
|
27
|
-
|
28
21
|
def tags
|
29
|
-
@tags ||=
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
og_type: meta_tag(property: "og:type", content: frontmatter["og:type"] || type),
|
52
|
-
og_url: meta_tag(property: "og:url", content: canonical_url),
|
53
|
-
og_image: meta_tag(property: "og:image", content: og_image),
|
54
|
-
|
55
|
-
og_description: meta_tag(property: "og:description", content: frontmatter["og:description"] || description),
|
56
|
-
og_site_name: meta_tag(property: "og:site_name", content: @config.site_name),
|
57
|
-
og_logo: meta_tag(property: "og:logo", content: frontmatter["og:logo"] || logo),
|
58
|
-
og_author: meta_tag(property: "og:author", content: frontmatter["og:author"] || author),
|
59
|
-
og_locale: meta_tag(property: "og:locale", content: frontmatter["og:locale"] || locale),
|
60
|
-
|
61
|
-
twitter_card: meta_tag(name: "twitter:card", content: frontmatter["twitter:card"] || "summary_large_image"),
|
62
|
-
twitter_title: meta_tag(name: "twitter:title", content: frontmatter["twitter:title"] || title),
|
63
|
-
twitter_description: meta_tag(name: "twitter:description", content: frontmatter["twitter:description"] || description),
|
64
|
-
twitter_image: meta_tag(name: "twitter:image", content: twitter_image)
|
65
|
-
}
|
66
|
-
end
|
22
|
+
@tags ||= {
|
23
|
+
title: title_tag(@data[:title]),
|
24
|
+
canonical: link_tag(rel: "canonical", href: @data[:canonical_url]),
|
25
|
+
|
26
|
+
description: meta_tag(name: "description", content: @data[:description]),
|
27
|
+
article_published: meta_tag(property: "article:published_time", content: @data[:article_published_time]),
|
28
|
+
|
29
|
+
og_title: meta_tag(property: "og:title", content: @data[:og_title]),
|
30
|
+
og_type: meta_tag(property: "og:type", content: @data[:og_type]),
|
31
|
+
og_url: meta_tag(property: "og:url", content: @data[:og_url]),
|
32
|
+
og_image: meta_tag(property: "og:image", content: @data[:og_image]),
|
33
|
+
og_description: meta_tag(property: "og:description", content: @data[:og_description]),
|
34
|
+
og_site_name: meta_tag(property: "og:site_name", content: @data[:og_site_name]),
|
35
|
+
og_logo: meta_tag(property: "og:logo", content: @data[:og_logo]),
|
36
|
+
og_author: meta_tag(property: "og:author", content: @data[:og_author]),
|
37
|
+
og_locale: meta_tag(property: "og:locale", content: @data[:og_locale]),
|
38
|
+
|
39
|
+
twitter_card: meta_tag(name: "twitter:card", content: @data[:twitter_card]),
|
40
|
+
twitter_title: meta_tag(name: "twitter:title", content: @data[:twitter_title]),
|
41
|
+
twitter_description: meta_tag(name: "twitter:description", content: @data[:twitter_description]),
|
42
|
+
twitter_image: meta_tag(name: "twitter:image", content: @data[:twitter_image])
|
43
|
+
}
|
67
44
|
end
|
68
45
|
|
69
46
|
def title_tag(content)
|
47
|
+
config = Perron.configuration
|
70
48
|
resource_title = content.to_s.strip
|
71
|
-
title_suffix =
|
72
|
-
|
49
|
+
title_suffix = config.metadata.title_suffix&.strip
|
73
50
|
suffix = (title_suffix if title_suffix.present? && resource_title != title_suffix)
|
74
51
|
|
75
|
-
tag.title([resource_title, suffix].compact.join(
|
52
|
+
tag.title([resource_title, suffix].compact.join(config.metadata.title_separator))
|
76
53
|
end
|
77
54
|
|
78
|
-
def
|
79
|
-
return if attributes[:
|
55
|
+
def link_tag(attributes)
|
56
|
+
return if attributes[:href].blank?
|
80
57
|
|
81
|
-
tag.
|
58
|
+
tag.link(**attributes)
|
82
59
|
end
|
83
60
|
|
84
|
-
def
|
85
|
-
|
86
|
-
base_url = "#{url_options[:protocol]}://#{url_options[:host]}"
|
87
|
-
url = URI.join(base_url, @resource&.path).to_s
|
88
|
-
has_extension = URI(url).path.split("/").last&.include?(".")
|
61
|
+
def meta_tag(attributes)
|
62
|
+
return if attributes[:content].blank?
|
89
63
|
|
90
|
-
|
64
|
+
tag.meta(**attributes)
|
91
65
|
end
|
92
66
|
end
|
93
67
|
end
|
@@ -30,13 +30,17 @@ module Perron
|
|
30
30
|
|
31
31
|
def collection = Collection.new(collection_name)
|
32
32
|
|
33
|
+
def root
|
34
|
+
collection_name.pages? && collection.find_by_file_name("root", name.constantize)
|
35
|
+
end
|
36
|
+
|
33
37
|
def model_name
|
34
38
|
@model_name ||= ActiveModel::Name.new(self, nil, name.demodulize.to_s)
|
35
39
|
end
|
36
40
|
|
37
41
|
private
|
38
42
|
|
39
|
-
def collection_name = name.demodulize.underscore.pluralize
|
43
|
+
def collection_name = name.demodulize.underscore.pluralize.inquiry
|
40
44
|
end
|
41
45
|
end
|
42
46
|
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Perron
|
4
|
+
class Resource
|
5
|
+
class Metadata
|
6
|
+
def initialize(resource:, frontmatter:, collection:)
|
7
|
+
@resource = resource
|
8
|
+
@frontmatter = frontmatter&.deep_symbolize_keys || {}
|
9
|
+
@collection = collection
|
10
|
+
@config = Perron.configuration
|
11
|
+
end
|
12
|
+
|
13
|
+
def data
|
14
|
+
@data ||= ActiveSupport::OrderedOptions
|
15
|
+
.new
|
16
|
+
.merge(apply_fallbacks_and_defaults(to: merged_site_collection_resource_frontmatter))
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def merged_site_collection_resource_frontmatter = site_data.merge(collection_data).merge(@frontmatter)
|
22
|
+
|
23
|
+
def apply_fallbacks_and_defaults(to:)
|
24
|
+
to[:title] ||= @config.site_name || Rails.application.name.underscore.camelize
|
25
|
+
|
26
|
+
to[:canonical_url] ||= canonical_url
|
27
|
+
|
28
|
+
to[:og_image] ||= to[:image]
|
29
|
+
to[:twitter_image] ||= to[:og_image]
|
30
|
+
|
31
|
+
to[:og_title] ||= to[:title]
|
32
|
+
to[:twitter_title] ||= to[:title]
|
33
|
+
to[:og_description] ||= to[:description]
|
34
|
+
to[:twitter_description] ||= to[:description]
|
35
|
+
to[:og_type] ||= to[:type]
|
36
|
+
to[:og_logo] ||= to[:logo]
|
37
|
+
to[:og_author] ||= to[:author]
|
38
|
+
to[:og_locale] ||= to[:locale]
|
39
|
+
|
40
|
+
to[:og_site_name] = @config.site_name
|
41
|
+
to[:twitter_card] ||= "summary_large_image"
|
42
|
+
to[:og_url] = canonical_url
|
43
|
+
to[:article_published_time] = @resource.published_at
|
44
|
+
|
45
|
+
to.compact
|
46
|
+
end
|
47
|
+
|
48
|
+
def canonical_url
|
49
|
+
return @frontmatter[:canonical_url] if @frontmatter[:canonical_url]
|
50
|
+
return Rails.application.routes.url_helpers.root_url(**Perron.configuration.default_url_options) if @resource.slug == "/"
|
51
|
+
|
52
|
+
Rails.application.routes.url_helpers.polymorphic_url(
|
53
|
+
@resource,
|
54
|
+
**Perron.configuration.default_url_options
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
def site_data
|
59
|
+
@config.metadata.except(:title_separator, :title_suffix).deep_symbolize_keys || {}
|
60
|
+
end
|
61
|
+
|
62
|
+
def collection_data
|
63
|
+
@collection&.configuration&.metadata&.deep_symbolize_keys || {}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -7,10 +7,10 @@ module Perron
|
|
7
7
|
|
8
8
|
included do
|
9
9
|
def published?
|
10
|
-
return true if
|
10
|
+
return true if Perron.configuration.view_unpublished
|
11
11
|
|
12
|
-
return false if
|
13
|
-
return false if
|
12
|
+
return false if frontmatter.draft == true
|
13
|
+
return false if frontmatter.published == false
|
14
14
|
return false if publication_date&.after?(Time.current)
|
15
15
|
|
16
16
|
true
|
@@ -20,8 +20,8 @@ module Perron
|
|
20
20
|
|
21
21
|
def publication_date
|
22
22
|
@publication_date ||= begin
|
23
|
-
from_meta =
|
24
|
-
Time.zone.parse(
|
23
|
+
from_meta = frontmatter.published_at.present? ? begin
|
24
|
+
Time.zone.parse(frontmatter.published_at.to_s)
|
25
25
|
rescue
|
26
26
|
nil
|
27
27
|
end : nil
|
@@ -9,9 +9,9 @@ module Perron
|
|
9
9
|
parsed(content)
|
10
10
|
end
|
11
11
|
|
12
|
-
def
|
13
|
-
@
|
14
|
-
@
|
12
|
+
def frontmatter
|
13
|
+
@frontmatter_with_dot_access ||= ActiveSupport::OrderedOptions.new.tap do |options|
|
14
|
+
@frontmatter.each { |key, value| options[key] = value }
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
@@ -19,10 +19,10 @@ module Perron
|
|
19
19
|
|
20
20
|
def parsed(content)
|
21
21
|
if content =~ /\A---\s*(.*?)\s*---\s*(.*)/m
|
22
|
-
@
|
22
|
+
@frontmatter = YAML.safe_load($1, permitted_classes: [Date, Time]) || {}
|
23
23
|
@content = $2.strip
|
24
24
|
else
|
25
|
-
@
|
25
|
+
@frontmatter = {}
|
26
26
|
@content = content
|
27
27
|
end
|
28
28
|
end
|
@@ -7,13 +7,16 @@ module Perron
|
|
7
7
|
class Slug
|
8
8
|
using Perron::SuffixStripping
|
9
9
|
|
10
|
-
def initialize(resource)
|
10
|
+
def initialize(resource, frontmatter)
|
11
11
|
@resource = resource
|
12
|
-
@
|
12
|
+
@frontmatter = frontmatter
|
13
13
|
end
|
14
14
|
|
15
15
|
def create
|
16
|
-
|
16
|
+
return "/" if Perron.configuration.allowed_extensions.any? { @resource.filename == "root.#{it}" }
|
17
|
+
|
18
|
+
@frontmatter.slug.presence ||
|
19
|
+
@resource.filename.sub(/^[\d-]+-/, "").delete_suffixes(dot_prepended_allowed_extensions)
|
17
20
|
end
|
18
21
|
|
19
22
|
private
|
@@ -1,13 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "perron/
|
4
|
-
require "perron/
|
5
|
-
require "perron/
|
6
|
-
require "perron/
|
7
|
-
require "perron/
|
8
|
-
require "perron/
|
9
|
-
require "perron/
|
10
|
-
require "perron/
|
3
|
+
require "perron/resource/configuration"
|
4
|
+
require "perron/resource/core"
|
5
|
+
require "perron/resource/class_methods"
|
6
|
+
require "perron/resource/metadata"
|
7
|
+
require "perron/resource/publishable"
|
8
|
+
require "perron/resource/related"
|
9
|
+
require "perron/resource/renderer"
|
10
|
+
require "perron/resource/slug"
|
11
|
+
require "perron/resource/separator"
|
11
12
|
|
12
13
|
module Perron
|
13
14
|
class Resource
|
@@ -29,7 +30,7 @@ module Perron
|
|
29
30
|
|
30
31
|
def filename = File.basename(@file_path)
|
31
32
|
|
32
|
-
def slug = Perron::Resource::Slug.new(self).create
|
33
|
+
def slug = Perron::Resource::Slug.new(self, frontmatter).create
|
33
34
|
alias_method :path, :slug
|
34
35
|
alias_method :to_param, :slug
|
35
36
|
|
@@ -41,11 +42,30 @@ module Perron
|
|
41
42
|
Perron::Resource::Renderer.erb(page_content, {resource: self})
|
42
43
|
end
|
43
44
|
|
44
|
-
def metadata
|
45
|
+
def metadata
|
46
|
+
Perron::Resource::Metadata.new(
|
47
|
+
resource: self,
|
48
|
+
frontmatter: frontmatter,
|
49
|
+
collection: collection
|
50
|
+
).data
|
51
|
+
end
|
45
52
|
|
46
53
|
def raw_content = File.read(@file_path)
|
47
54
|
alias_method :raw, :raw_content
|
48
55
|
|
56
|
+
def to_partial_path
|
57
|
+
@to_partial_path ||= begin
|
58
|
+
element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(self.class.model_name))
|
59
|
+
collection = ActiveSupport::Inflector.tableize(self.class.model_name)
|
60
|
+
|
61
|
+
File.join("content", collection, element)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def root?
|
66
|
+
collection.name.inquiry.pages? && File.basename(filename) == "root"
|
67
|
+
end
|
68
|
+
|
49
69
|
def collection = Collection.new(self.class.model_name.collection)
|
50
70
|
|
51
71
|
def related_resources(limit: 5) = Perron::Site::Resource::Related.new(self).find(limit:)
|
@@ -53,6 +73,10 @@ module Perron
|
|
53
73
|
|
54
74
|
private
|
55
75
|
|
76
|
+
def frontmatter
|
77
|
+
@frontmatter ||= Perron::Resource::Separator.new(raw_content).frontmatter
|
78
|
+
end
|
79
|
+
|
56
80
|
def erb_processing?
|
57
81
|
@file_path.ends_with?(".erb") || metadata.erb == true
|
58
82
|
end
|
data/lib/perron/root.rb
CHANGED
data/lib/perron/site.rb
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "perron/site/builder"
|
4
|
-
require "perron/
|
5
|
-
require "perron/
|
6
|
-
require "perron/
|
7
|
-
require "perron/site/data/proxy"
|
4
|
+
require "perron/collection"
|
5
|
+
require "perron/data"
|
6
|
+
require "perron/data/proxy"
|
8
7
|
|
9
8
|
module Perron
|
10
9
|
module Site
|
data/lib/perron/version.rb
CHANGED
data/lib/perron.rb
CHANGED
data/perron.gemspec
CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
|
|
8
8
|
|
9
9
|
spec.summary = "Rails-based static site generator"
|
10
10
|
spec.description = "Perron is a Rails-based static site generator that follows Rails conventions. It allows you to create content collections with markdown or ERB, configure SEO metadata, and build production-ready static sites while leveraging your existing Rails knowledge with familiar patterns and minimal configuration."
|
11
|
-
spec.homepage = "https://railsdesigner.com/
|
11
|
+
spec.homepage = "https://perron.railsdesigner.com/"
|
12
12
|
spec.license = "MIT"
|
13
13
|
|
14
14
|
spec.metadata["homepage_uri"] = spec.homepage
|
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.
|
4
|
+
version: 0.11.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rails Designer Developers
|
@@ -96,17 +96,32 @@ files:
|
|
96
96
|
- lib/generators/perron/templates/README.md.tt
|
97
97
|
- lib/generators/perron/templates/initializer.rb.tt
|
98
98
|
- lib/perron.rb
|
99
|
+
- lib/perron/collection.rb
|
99
100
|
- lib/perron/configuration.rb
|
101
|
+
- lib/perron/data.rb
|
102
|
+
- lib/perron/data/proxy.rb
|
100
103
|
- lib/perron/engine.rb
|
101
104
|
- lib/perron/errors.rb
|
102
105
|
- lib/perron/feeds.rb
|
103
106
|
- lib/perron/html_processor.rb
|
104
107
|
- lib/perron/html_processor/base.rb
|
105
108
|
- lib/perron/html_processor/lazy_load_images.rb
|
109
|
+
- lib/perron/html_processor/syntax_highlight.rb
|
106
110
|
- lib/perron/html_processor/target_blank.rb
|
107
111
|
- lib/perron/markdown.rb
|
108
112
|
- lib/perron/metatags.rb
|
109
113
|
- lib/perron/refinements/delete_suffixes.rb
|
114
|
+
- lib/perron/resource.rb
|
115
|
+
- lib/perron/resource/class_methods.rb
|
116
|
+
- lib/perron/resource/configuration.rb
|
117
|
+
- lib/perron/resource/core.rb
|
118
|
+
- lib/perron/resource/metadata.rb
|
119
|
+
- lib/perron/resource/publishable.rb
|
120
|
+
- lib/perron/resource/related.rb
|
121
|
+
- lib/perron/resource/related/stop_words.rb
|
122
|
+
- lib/perron/resource/renderer.rb
|
123
|
+
- lib/perron/resource/separator.rb
|
124
|
+
- lib/perron/resource/slug.rb
|
110
125
|
- lib/perron/root.rb
|
111
126
|
- lib/perron/site.rb
|
112
127
|
- lib/perron/site/builder.rb
|
@@ -118,27 +133,14 @@ files:
|
|
118
133
|
- lib/perron/site/builder/paths.rb
|
119
134
|
- lib/perron/site/builder/public_files.rb
|
120
135
|
- lib/perron/site/builder/sitemap.rb
|
121
|
-
- lib/perron/site/collection.rb
|
122
|
-
- lib/perron/site/data.rb
|
123
|
-
- lib/perron/site/data/proxy.rb
|
124
|
-
- lib/perron/site/resource.rb
|
125
|
-
- lib/perron/site/resource/class_methods.rb
|
126
|
-
- lib/perron/site/resource/configuration.rb
|
127
|
-
- lib/perron/site/resource/core.rb
|
128
|
-
- lib/perron/site/resource/publishable.rb
|
129
|
-
- lib/perron/site/resource/related.rb
|
130
|
-
- lib/perron/site/resource/related/stop_words.rb
|
131
|
-
- lib/perron/site/resource/renderer.rb
|
132
|
-
- lib/perron/site/resource/separator.rb
|
133
|
-
- lib/perron/site/resource/slug.rb
|
134
136
|
- lib/perron/tasks/perron.rake
|
135
137
|
- lib/perron/version.rb
|
136
138
|
- perron.gemspec
|
137
|
-
homepage: https://railsdesigner.com/
|
139
|
+
homepage: https://perron.railsdesigner.com/
|
138
140
|
licenses:
|
139
141
|
- MIT
|
140
142
|
metadata:
|
141
|
-
homepage_uri: https://railsdesigner.com/
|
143
|
+
homepage_uri: https://perron.railsdesigner.com/
|
142
144
|
source_code_uri: https://github.com/Rails-Designer/perron/
|
143
145
|
rdoc_options: []
|
144
146
|
require_paths:
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|