perron 0.9.1 → 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 +129 -36
- data/app/helpers/meta_tags_helper.rb +1 -1
- data/app/helpers/perron/erb_helper.rb +9 -0
- data/lib/generators/content/content_generator.rb +2 -0
- data/lib/generators/content/templates/root.erb.tt +0 -1
- data/lib/generators/perron/install_generator.rb +13 -0
- data/lib/generators/perron/templates/initializer.rb.tt +9 -6
- data/lib/perron/{site/collection.rb → collection.rb} +7 -1
- data/lib/perron/configuration.rb +10 -3
- data/lib/perron/data/proxy.rb +47 -0
- data/lib/perron/{site/data.rb → data.rb} +36 -16
- 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/resource/renderer.rb +18 -0
- data/lib/perron/{site/resource → resource}/separator.rb +5 -5
- data/lib/perron/resource/slug.rb +27 -0
- data/lib/perron/resource.rb +90 -0
- data/lib/perron/root.rb +1 -1
- data/lib/perron/site/builder/assets.rb +2 -2
- data/lib/perron/site/builder/page.rb +1 -1
- data/lib/perron/site/builder/public_files.rb +1 -1
- data/lib/perron/site/builder.rb +2 -4
- data/lib/perron/site.rb +3 -14
- data/lib/perron/version.rb +1 -1
- data/lib/perron.rb +1 -0
- data/perron.gemspec +1 -1
- metadata +19 -15
- data/lib/perron/site/data/proxy.rb +0 -17
- data/lib/perron/site/resource/slug.rb +0 -20
- data/lib/perron/site/resource.rb +0 -68
- /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
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
|
|
@@ -31,7 +31,7 @@ rails generate perron:install
|
|
31
31
|
This creates an initializer:
|
32
32
|
```ruby
|
33
33
|
Perron.configure do |config|
|
34
|
-
config.site_name = "
|
34
|
+
config.site_name = "Helptail"
|
35
35
|
end
|
36
36
|
```
|
37
37
|
|
@@ -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.
|
@@ -131,7 +155,61 @@ end
|
|
131
155
|
```
|
132
156
|
|
133
157
|
|
134
|
-
|
158
|
+
### Embed Ruby
|
159
|
+
|
160
|
+
Perron provides flexible options for embedding dynamic Ruby code in your content using ERB.
|
161
|
+
|
162
|
+
|
163
|
+
#### 1. File extension
|
164
|
+
|
165
|
+
Any content file with a `.erb` extension (e.g., `about.erb`) will automatically have its content processed as ERB.
|
166
|
+
|
167
|
+
|
168
|
+
#### 2. Frontmatter
|
169
|
+
|
170
|
+
You can enable ERB processing on a per-file basis, even for standard `.md` files, by adding `erb: true` to the file's frontmatter.
|
171
|
+
```markdown
|
172
|
+
---
|
173
|
+
title: Dynamic Page
|
174
|
+
erb: true
|
175
|
+
---
|
176
|
+
|
177
|
+
This entire page will be processed by ERB.
|
178
|
+
The current time is: <%= Time.current.to_fs(:long_ordinal) %>.
|
179
|
+
```
|
180
|
+
|
181
|
+
|
182
|
+
#### 3. `erbify` helper
|
183
|
+
|
184
|
+
For the most granular control, the `erbify` helper allows to process specific sections of a file as ERB.
|
185
|
+
This is ideal for generating dynamic content like lists or tables from your resource's metadata, without needing to enable ERB for the entire file. The `erbify` helper can be used with a string or, more commonly, a block.
|
186
|
+
|
187
|
+
**Example:** Generating a list from frontmatter data in a standard `.md` file.
|
188
|
+
```markdown
|
189
|
+
---
|
190
|
+
title: Features
|
191
|
+
features:
|
192
|
+
- Rails based
|
193
|
+
- SEO friendly
|
194
|
+
- Markdown first
|
195
|
+
- ERB support
|
196
|
+
---
|
197
|
+
|
198
|
+
Check out our amazing features:
|
199
|
+
|
200
|
+
<%= erbify do %>
|
201
|
+
<ul>
|
202
|
+
<% @resource.metadata.features.each do |feature| %>
|
203
|
+
<li>
|
204
|
+
<%= feature %>
|
205
|
+
</li>
|
206
|
+
<% end %>
|
207
|
+
</ul>
|
208
|
+
<% end %>
|
209
|
+
```
|
210
|
+
|
211
|
+
|
212
|
+
## Data files
|
135
213
|
|
136
214
|
Perron can consume structured data from YML, JSON, or CSV files, making them available within your templates.
|
137
215
|
This is useful for populating features, team members, or any other repeated data structure.
|
@@ -146,12 +224,12 @@ To use a data file, instantiate `Perron::Site.data` with the basename of the fil
|
|
146
224
|
<% end %>
|
147
225
|
```
|
148
226
|
|
149
|
-
### File
|
227
|
+
### File location and formats
|
150
228
|
|
151
229
|
By default, Perron looks up `app/content/data/` for files with a `.yml`, `.json`, or `.csv` extension.
|
152
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")`.
|
153
231
|
|
154
|
-
### Accessing
|
232
|
+
### Accessing data
|
155
233
|
|
156
234
|
The wrapper object provides flexible, read-only access to each record's attributes. Both dot notation and hash-like key access are supported.
|
157
235
|
```ruby
|
@@ -159,6 +237,22 @@ feature.name
|
|
159
237
|
feature[:name]
|
160
238
|
```
|
161
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
|
+
|
162
256
|
|
163
257
|
## Feeds
|
164
258
|
|
@@ -234,7 +328,7 @@ Or exclude certain tags:
|
|
234
328
|
|
235
329
|
Values are determined with the following precedence, from highest to lowest:
|
236
330
|
|
237
|
-
#### 1. Controller
|
331
|
+
#### 1. Controller action
|
238
332
|
|
239
333
|
Define a `@metadata` instance variable in your controller:
|
240
334
|
```ruby
|
@@ -249,10 +343,9 @@ class Content::PostsController < ApplicationController
|
|
249
343
|
end
|
250
344
|
```
|
251
345
|
|
252
|
-
#### 2. Page
|
346
|
+
#### 2. Page frontmatter
|
253
347
|
|
254
348
|
Add values to the YAML frontmatter in content files:
|
255
|
-
|
256
349
|
```yaml
|
257
350
|
---
|
258
351
|
title: My Awesome Post
|
@@ -266,36 +359,36 @@ Your content here…
|
|
266
359
|
|
267
360
|
#### 3. Collection configuration
|
268
361
|
|
269
|
-
Set
|
362
|
+
Set collection defaults in the resource model:
|
270
363
|
```ruby
|
271
364
|
class Content::Post < Perron::Resource
|
272
365
|
# …
|
273
366
|
|
274
|
-
config.metadata.description = "
|
275
|
-
config.metadata.author = "
|
367
|
+
config.metadata.description = "Put your routine tasks on autopilot"
|
368
|
+
config.metadata.author = "Helptail team"
|
276
369
|
end
|
277
370
|
```
|
278
371
|
|
279
|
-
#### 4. Default
|
372
|
+
#### 4. Default values
|
280
373
|
|
281
374
|
Set site-wide defaults in the initializer:
|
282
375
|
```ruby
|
283
376
|
Perron.configure do |config|
|
284
377
|
# …
|
285
378
|
|
286
|
-
config.metadata.description = "
|
287
|
-
config.metadata.author = "
|
379
|
+
config.metadata.description = "Put your routine tasks on autopilot"
|
380
|
+
config.metadata.author = "Helptail team"
|
288
381
|
end
|
289
382
|
```
|
290
383
|
|
291
384
|
|
292
|
-
## Related
|
385
|
+
## Related resources
|
293
386
|
|
294
387
|
The `related_resources` method allows to find and display a list of similar resources
|
295
|
-
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.
|
296
389
|
|
297
390
|
|
298
|
-
### Basic
|
391
|
+
### Basic usage
|
299
392
|
|
300
393
|
To get a list of the 5 most similar resources, call the method on any resource instance.
|
301
394
|
```ruby
|
@@ -307,9 +400,9 @@ To get a list of the 5 most similar resources, call the method on any resource i
|
|
307
400
|
```
|
308
401
|
|
309
402
|
|
310
|
-
## XML
|
403
|
+
## XML sitemap
|
311
404
|
|
312
|
-
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.
|
313
406
|
|
314
407
|
Enable it with the following line in the Perron configuration:
|
315
408
|
```ruby
|
@@ -344,7 +437,7 @@ sitemap_change_frequency: :daily
|
|
344
437
|
```
|
345
438
|
|
346
439
|
|
347
|
-
## Building
|
440
|
+
## Building your static site
|
348
441
|
|
349
442
|
When in `standalone` mode and you're ready to generate your static site, run:
|
350
443
|
```bash
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Perron
|
2
4
|
class InstallGenerator < Rails::Generators::Base
|
3
5
|
source_root File.expand_path("templates", __dir__)
|
@@ -12,5 +14,16 @@ module Perron
|
|
12
14
|
|
13
15
|
template "README.md.tt", File.join(data_directory, "README.md")
|
14
16
|
end
|
17
|
+
|
18
|
+
def add_markdown_gems
|
19
|
+
append_to_file "Gemfile", <<~RUBY
|
20
|
+
|
21
|
+
# Perron supports Markdown rendering using one of the following gems.
|
22
|
+
# Uncomment your preferred choice and run `bundle install`
|
23
|
+
# gem "commonmarker"
|
24
|
+
# gem "kramdown"
|
25
|
+
# gem "redcarpet"
|
26
|
+
RUBY
|
27
|
+
end
|
15
28
|
end
|
16
29
|
end
|
@@ -1,20 +1,23 @@
|
|
1
1
|
Perron.configure do |config|
|
2
2
|
# config.output = "output"
|
3
3
|
|
4
|
-
# config.site_name = "
|
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
|
-
# config.default_url_options = {host: "
|
12
|
+
# config.default_url_options = {host: "helptail.com", protocol: "https", trailing_slash: true}
|
13
|
+
|
14
|
+
# The options hash is passed directly to the chosen library
|
15
|
+
# config.markdown_options = {}
|
13
16
|
|
14
17
|
# Set default meta values
|
15
18
|
# Examples:
|
16
|
-
# - `config.metadata.description = "
|
17
|
-
# - `config.metadata.author = "
|
19
|
+
# - `config.metadata.description = "Put your routine tasks on autopilot"`
|
20
|
+
# - `config.metadata.author = "Helptail Team"`
|
18
21
|
|
19
22
|
# Set meta title suffix
|
20
23
|
# config.metadata.title_suffix = nil
|
@@ -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
@@ -13,24 +13,31 @@ module Perron
|
|
13
13
|
def initialize
|
14
14
|
@config = ActiveSupport::OrderedOptions.new
|
15
15
|
|
16
|
+
@config.site_name = nil
|
17
|
+
@config.site_description = nil
|
18
|
+
|
19
|
+
@config.site_email = nil
|
20
|
+
|
16
21
|
@config.output = "output"
|
17
22
|
|
18
23
|
@config.mode = :standalone
|
19
24
|
@config.include_root = false
|
20
25
|
|
21
|
-
@config.
|
22
|
-
@config.site_description = nil
|
26
|
+
@config.allowed_extensions = %w[erb md]
|
23
27
|
|
24
|
-
@config.allowed_extensions = [".erb", ".md"]
|
25
28
|
@config.exclude_from_public = %w[assets storage]
|
26
29
|
@config.excluded_assets = %w[action_cable actioncable actiontext activestorage rails-ujs trix turbo]
|
27
30
|
|
31
|
+
@config.view_unpublished = Rails.env.development?
|
32
|
+
|
28
33
|
@config.default_url_options = {
|
29
34
|
host: ENV.fetch("PERRON_HOST", "localhost:3000"),
|
30
35
|
protocol: ENV.fetch("PERRON_PROTOCOL", "http"),
|
31
36
|
trailing_slash: ENV.fetch("PERRON_TRAILING_SLASH", "true") == "true"
|
32
37
|
}
|
33
38
|
|
39
|
+
@config.markdown_options = {}
|
40
|
+
|
34
41
|
@config.sitemap = ActiveSupport::OrderedOptions.new
|
35
42
|
@config.sitemap.enabled = false
|
36
43
|
@config.sitemap.priority = 0.5
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Perron
|
4
|
+
class Data
|
5
|
+
class Proxy
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
def initialize(parts = [])
|
9
|
+
@parts = parts
|
10
|
+
@data = data_for_proxy
|
11
|
+
end
|
12
|
+
|
13
|
+
def each(&block) = @data.each(&block)
|
14
|
+
|
15
|
+
def inspect = @data.inspect
|
16
|
+
|
17
|
+
def respond_to_missing?(name, include_private = false)
|
18
|
+
identifier = File.join(*@parts, name.to_s)
|
19
|
+
|
20
|
+
Perron::Data.directory?(identifier) || Perron::Data.path_for(identifier) || super
|
21
|
+
end
|
22
|
+
|
23
|
+
def method_missing(name, *arguments, &block)
|
24
|
+
raise ArgumentError, "Data access does not accept arguments" if arguments.any? || block
|
25
|
+
|
26
|
+
new_parts = @parts + [name.to_s]
|
27
|
+
identifier = File.join(*new_parts)
|
28
|
+
|
29
|
+
return Proxy.new(new_parts) if Perron::Data.directory?(identifier)
|
30
|
+
return Perron::Data.new(identifier) if Perron::Data.path_for(identifier)
|
31
|
+
|
32
|
+
super
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def data_for_proxy
|
38
|
+
return [] if @parts.empty?
|
39
|
+
|
40
|
+
identifier = File.join(*@parts)
|
41
|
+
data_path = Perron::Data.path_for(identifier) || Perron::Data.path_for(File.join(identifier, "index"))
|
42
|
+
|
43
|
+
data_path ? Perron::Data.new(data_path) : []
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -1,19 +1,37 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "erb"
|
4
|
-
require "singleton"
|
5
|
-
|
6
3
|
require "csv"
|
7
4
|
|
8
5
|
module Perron
|
9
6
|
class Data < SimpleDelegator
|
10
7
|
def initialize(identifier)
|
11
|
-
@
|
8
|
+
@identifier = identifier
|
9
|
+
@file_path = self.class.path_for!(identifier)
|
12
10
|
@records = records
|
13
11
|
|
14
12
|
super(records)
|
15
13
|
end
|
16
14
|
|
15
|
+
class << self
|
16
|
+
def path_for(identifier)
|
17
|
+
path = Pathname.new(identifier)
|
18
|
+
|
19
|
+
return path.to_s if path.file? && path.absolute?
|
20
|
+
|
21
|
+
base_path = Rails.root.join("app", "content", "data")
|
22
|
+
|
23
|
+
SUPPORTED_EXTENSIONS.lazy.map { base_path.join("#{identifier}#{it}") }.find(&:exist?)&.to_s
|
24
|
+
end
|
25
|
+
|
26
|
+
def path_for!(identifier)
|
27
|
+
path_for(identifier).tap do |path|
|
28
|
+
raise Errors::FileNotFoundError, "No data file found for `#{identifier}`" unless path
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def directory?(identifier) = Dir.exist?(Rails.root.join("app", "content", "data", identifier))
|
33
|
+
end
|
34
|
+
|
17
35
|
private
|
18
36
|
|
19
37
|
PARSER_METHODS = {
|
@@ -22,24 +40,15 @@ module Perron
|
|
22
40
|
}.freeze
|
23
41
|
SUPPORTED_EXTENSIONS = PARSER_METHODS.keys
|
24
42
|
|
25
|
-
def path_for(identifier)
|
26
|
-
path = Pathname.new(identifier)
|
27
|
-
|
28
|
-
return path.to_s if path.file? && path.absolute?
|
29
|
-
|
30
|
-
path = SUPPORTED_EXTENSIONS.lazy.map { Rails.root.join("app", "content", "data").join("#{identifier}#{it}") }.find(&:exist?)
|
31
|
-
path&.to_s or raise Errors::FileNotFoundError, "No data file found for '#{identifier}'"
|
32
|
-
end
|
33
|
-
|
34
43
|
def records
|
35
44
|
content = rendered_from(@file_path)
|
36
45
|
data = parsed_from(content, @file_path)
|
37
46
|
|
38
47
|
unless data.is_a?(Array)
|
39
|
-
raise Errors::DataParseError, "Data in
|
48
|
+
raise Errors::DataParseError, "Data in `#{@file_path}` must be an array of objects."
|
40
49
|
end
|
41
50
|
|
42
|
-
data.map { Item.new(it) }
|
51
|
+
data.map { Item.new(it, identifier: @identifier) }
|
43
52
|
end
|
44
53
|
|
45
54
|
def rendered_from(path)
|
@@ -89,12 +98,23 @@ module Perron
|
|
89
98
|
private_constant :HelperContext
|
90
99
|
|
91
100
|
class Item
|
92
|
-
def initialize(attributes)
|
101
|
+
def initialize(attributes, identifier:)
|
93
102
|
@attributes = attributes.transform_keys(&:to_sym)
|
103
|
+
@identifier = identifier
|
94
104
|
end
|
95
105
|
|
96
106
|
def [](key) = @attributes[key.to_sym]
|
97
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
|
+
|
98
118
|
def method_missing(method_name, *arguments, &block)
|
99
119
|
return super if !@attributes.key?(method_name) || arguments.any? || block
|
100
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
|