perron 0.10.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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -0
  3. data/Gemfile.lock +3 -1
  4. data/README.md +108 -46
  5. data/app/helpers/meta_tags_helper.rb +1 -1
  6. data/lib/generators/content/content_generator.rb +25 -0
  7. data/lib/generators/content/templates/root.erb.tt +0 -1
  8. data/lib/generators/perron/install_generator.rb +6 -1
  9. data/lib/generators/perron/templates/README.md.tt +18 -1
  10. data/lib/generators/perron/templates/initializer.rb.tt +5 -2
  11. data/lib/perron/{site/collection.rb → collection.rb} +8 -2
  12. data/lib/perron/configuration.rb +4 -0
  13. data/lib/perron/{site/data.rb → data.rb} +16 -2
  14. data/lib/perron/feeds.rb +1 -0
  15. data/lib/perron/html_processor/syntax_highlight.rb +30 -0
  16. data/lib/perron/html_processor.rb +12 -8
  17. data/lib/perron/markdown.rb +6 -5
  18. data/lib/perron/metatags.rb +33 -59
  19. data/lib/perron/refinements/delete_suffixes.rb +11 -7
  20. data/lib/perron/{site/resource → resource}/class_methods.rb +5 -1
  21. data/lib/perron/resource/metadata.rb +67 -0
  22. data/lib/perron/{site/resource → resource}/publishable.rb +5 -5
  23. data/lib/perron/{site/resource → resource}/related.rb +2 -1
  24. data/lib/perron/{site/resource → resource}/separator.rb +5 -5
  25. data/lib/perron/resource/slug.rb +27 -0
  26. data/lib/perron/{site/resource.rb → resource.rb} +38 -14
  27. data/lib/perron/root.rb +1 -1
  28. data/lib/perron/site/builder/assets.rb +2 -1
  29. data/lib/perron/site/builder/feeds/json.rb +6 -4
  30. data/lib/perron/site/builder/feeds/rss.rb +6 -4
  31. data/lib/perron/site/builder/feeds.rb +2 -0
  32. data/lib/perron/site/builder/page.rb +5 -3
  33. data/lib/perron/site/builder/sitemap.rb +1 -0
  34. data/lib/perron/site/builder.rb +1 -1
  35. data/lib/perron/site.rb +3 -4
  36. data/lib/perron/version.rb +1 -1
  37. data/lib/perron.rb +1 -0
  38. data/perron.gemspec +1 -1
  39. metadata +18 -16
  40. data/lib/perron/site/resource/slug.rb +0 -24
  41. /data/lib/perron/{site/data → data}/proxy.rb +0 -0
  42. /data/lib/perron/{site/resource → resource}/configuration.rb +0 -0
  43. /data/lib/perron/{site/resource → resource}/core.rb +0 -0
  44. /data/lib/perron/{site/resource → resource}/related/stop_words.rb +0 -0
  45. /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: 2aa4c7af63f95aef3bc25c9ef97a4da02d75486ec3ee975f69a980f66cb34557
4
- data.tar.gz: f1c62fff6d0ba6c121da85f463dc32fd5656c884d059d0f2955c6a95a4c0bf3a
3
+ metadata.gz: 70a9d142afefaac54496efbd93312cae38e14525b7f6310be576f74e7f9dfb4d
4
+ data.tar.gz: fd010ef141b72700120c926a104b8f33f72971d94e58ae01c8c793424d4c5319
5
5
  SHA512:
6
- metadata.gz: 175ecbdc5dc5468e28e0abe13926b578fb6669d078b594da1dea91ab8cce68c3a7f63f12d3be4f0de757d270c5d3a95ce96db925ea9c3cbe238e1dd72069aaa1
7
- data.tar.gz: b65f95000da601c8ea2f6dcea2e1cf9edb907d366ff783b3ba37e4b2c2d0ff0222cf98821f7537690895087c39ad0c902397c6325e309e27019914101072b6df
6
+ metadata.gz: 9ab5c1d522c8645f3470451e74c7a5a3475f1a683d6dc953e88fd8fb123e4477558ae99839f2db4742741663c2957eec9d94696d05d7936aa1df14880a5d59a4
7
+ data.tar.gz: fb40411ad56556967b82396d1bb540766550e73131b9358f870f441c75bd1538c9ef071b3fcb4a6c371f389ee7a1a031c101c4e24cb76b9875b4841756a68dc7
data/Gemfile CHANGED
@@ -14,4 +14,5 @@ group :development, :test do
14
14
  gem "rake", "~> 13.3.0"
15
15
  gem "minitest", "~> 5.25", ">= 5.25.5"
16
16
  gem "debug", "~> 1.11.0"
17
+ gem "rouge", "~> 4.6.0"
17
18
  end
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- perron (0.10.0)
4
+ perron (0.12.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 Started
16
+ ## Getting started
17
17
 
18
18
  ### Installation
19
19
 
@@ -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 you to build either a full static site or integrate pages into a dynamic Rails application.
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
  | :--- | :--- | :--- |
@@ -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
- ## Creating Content
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}`. Content is backed by a class, located in `app/models/content/` that inherits from `Perron::Resource`.
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
- ### Collections
57
+ ### Create a new collection
58
58
 
59
59
  ```bash
60
60
  bin/rails generate content Post
@@ -66,32 +66,82 @@ 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
- * Adds route: `resources :posts, module: :content, only: %w[index show]`
70
69
 
70
+ And adds a route: `resources :posts, module: :content, only: %w[index show]`
71
71
 
72
- ### Setting a Root Page
73
72
 
74
- To set a root page, include `Perron::Root` in your `Content::PagesController` and add a `app/content/pages/root.[md,erb]` file (make sure to set `slug: "/"` in its frontmatter).
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
+ ```
75
95
 
76
- This is automatically added when you create a `Page` collection.
77
96
 
97
+ ### Setting a root page
78
98
 
79
- ## Markdown Support
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.
102
+
103
+
104
+ ## Markdown support
80
105
 
81
106
  Perron supports markdown with the `markdownify` helper.
82
107
 
83
- There are no markdown gems bundled by default, so you'll need to add one of these:
108
+ There are no markdown gems bundled by default, so you'll need to add one of these to your `Gemfile`:
84
109
 
85
- - CommonMarker
86
- - Kramdown
87
- - Redcarpet
110
+ - `commonmarker`
111
+ - `kramdown`
112
+ - `redcarpet`
88
113
 
89
114
  ```bash
90
115
  bundle add {commonmarker,kramdown,redcarpet}
91
116
  ```
92
117
 
118
+ ### Configuration
93
119
 
94
- ## HTML Transformations
120
+ To pass options to the parser, set `markdown_options` in `config/initializers/perron.rb`. The options hash is passed directly to the chosen library.
121
+
122
+ **Commonmarker**
123
+ ```ruby
124
+ # Options are passed as keyword arguments.
125
+ Perron.configuration.markdown_options = { options: [:HARDBREAKS], extensions: [:table] }
126
+ ```
127
+
128
+ **Kramdown**
129
+ ```ruby
130
+ # Options are passed as a standard hash.
131
+ Perron.configuration.markdown_options = { input: "GFM", smart_quotes: "apos,quot" }
132
+ ```
133
+
134
+ **Redcarpet**
135
+ ```ruby
136
+ # Options are nested under :renderer_options and :markdown_options.
137
+ Perron.configuration.markdown_options = {
138
+ renderer_options: { hard_wrap: true },
139
+ markdown_options: { tables: true, autolink: true }
140
+ }
141
+ ```
142
+
143
+
144
+ ## HTML transformations
95
145
 
96
146
  Perron can post-process the HTML generated from your Markdown content.
97
147
 
@@ -100,19 +150,20 @@ Perron can post-process the HTML generated from your Markdown content.
100
150
 
101
151
  Apply transformations by passing an array of processor names or classes to the `markdownify` helper via the `process` option.
102
152
  ```erb
103
- <%= markdownify @resource.content, process: %w[target_blank lazy_load_images] %>
153
+ <%= markdownify @resource.content, process: %w[lazy_load_images syntax_highlight target_blank] %>
104
154
  ```
105
155
 
106
156
 
107
- ### Available Processors
157
+ ### Available processors
108
158
 
109
159
  The following processors are built-in and can be activated by passing their string name:
110
160
 
111
161
  - `target_blank`: Adds `target="_blank"` to all external links;
112
162
  - `lazy_load_images`: Adds `loading="lazy"` to all `<img>` tags.
163
+ - `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
164
 
114
165
 
115
- ### Creating Your Own
166
+ ### Creating your own processors
116
167
 
117
168
  You can create your own processor by defining a class that inherits from `Perron::HtmlProcessor::Base` and implements a `process` method.
118
169
  Then, pass the class constant directly in the `process` array.
@@ -184,19 +235,8 @@ Check out our amazing features:
184
235
  <% end %>
185
236
  ```
186
237
 
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
238
 
199
- ## Data Files
239
+ ## Data files
200
240
 
201
241
  Perron can consume structured data from YML, JSON, or CSV files, making them available within your templates.
202
242
  This is useful for populating features, team members, or any other repeated data structure.
@@ -211,12 +251,12 @@ To use a data file, instantiate `Perron::Site.data` with the basename of the fil
211
251
  <% end %>
212
252
  ```
213
253
 
214
- ### File Location and Formats
254
+ ### File location and formats
215
255
 
216
256
  By default, Perron looks up `app/content/data/` for files with a `.yml`, `.json`, or `.csv` extension.
217
257
  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
258
 
219
- ### Accessing Data
259
+ ### Accessing data
220
260
 
221
261
  The wrapper object provides flexible, read-only access to each record's attributes. Both dot notation and hash-like key access are supported.
222
262
  ```ruby
@@ -224,6 +264,22 @@ feature.name
224
264
  feature[:name]
225
265
  ```
226
266
 
267
+ ### Rendering
268
+
269
+ 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.
270
+ ```erb
271
+ <%= render Perron::Site.data.features %>
272
+ ```
273
+
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.
275
+ ```erb
276
+ <!-- app/views/content/features/_feature.html.erb -->
277
+ <div class="feature">
278
+ <h4><%= feature.name %></h4>
279
+ <p><%= feature.description %></p>
280
+ </div>
281
+ ```
282
+
227
283
 
228
284
  ## Feeds
229
285
 
@@ -260,9 +316,14 @@ Feeds are configured within the `Resource` class corresponding to a collection:
260
316
  class Content::Post < Perron::Resource
261
317
  configure do |config|
262
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
263
321
  # config.feeds.rss.path = "path-to-feed.xml"
264
322
  # config.feeds.rss.max_items = 25
323
+ #
265
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
266
327
  # config.feeds.json.max_items = 15
267
328
  # config.feeds.json.path = "path-to-feed.json"
268
329
  end
@@ -299,7 +360,7 @@ Or exclude certain tags:
299
360
 
300
361
  Values are determined with the following precedence, from highest to lowest:
301
362
 
302
- #### 1. Controller Action
363
+ #### 1. Controller action
303
364
 
304
365
  Define a `@metadata` instance variable in your controller:
305
366
  ```ruby
@@ -314,10 +375,9 @@ class Content::PostsController < ApplicationController
314
375
  end
315
376
  ```
316
377
 
317
- #### 2. Page Frontmatter
378
+ #### 2. Page frontmatter
318
379
 
319
380
  Add values to the YAML frontmatter in content files:
320
-
321
381
  ```yaml
322
382
  ---
323
383
  title: My Awesome Post
@@ -331,17 +391,19 @@ Your content here…
331
391
 
332
392
  #### 3. Collection configuration
333
393
 
334
- Set site-wide defaults in the initializer:
394
+ Set collection defaults in the resource model:
335
395
  ```ruby
336
396
  class Content::Post < Perron::Resource
337
- #
397
+ Perron.configure do |config|
398
+ # …
338
399
 
339
- config.metadata.description = "Put your routine tasks on autopilot"
340
- config.metadata.author = "Helptail team"
400
+ config.metadata.description = "Put your routine tasks on autopilot"
401
+ config.metadata.author = "Helptail team"
402
+ end
341
403
  end
342
404
  ```
343
405
 
344
- #### 4. Default Values
406
+ #### 4. Default values
345
407
 
346
408
  Set site-wide defaults in the initializer:
347
409
  ```ruby
@@ -354,13 +416,13 @@ end
354
416
  ```
355
417
 
356
418
 
357
- ## Related Resources
419
+ ## Related resources
358
420
 
359
421
  The `related_resources` method allows to find and display a list of similar resources
360
- from the same collection. Similarity is calculated using the **TF-IDF** algorithm on the content of each resource.
422
+ 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
423
 
362
424
 
363
- ### Basic Usage
425
+ ### Basic usage
364
426
 
365
427
  To get a list of the 5 most similar resources, call the method on any resource instance.
366
428
  ```ruby
@@ -372,9 +434,9 @@ To get a list of the 5 most similar resources, call the method on any resource i
372
434
  ```
373
435
 
374
436
 
375
- ## XML Sitemap
437
+ ## XML sitemap
376
438
 
377
- A sitemap is an 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.
439
+ 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
440
 
379
441
  Enable it with the following line in the Perron configuration:
380
442
  ```ruby
@@ -409,7 +471,7 @@ sitemap_change_frequency: :daily
409
471
  ```
410
472
 
411
473
 
412
- ## Building Your Static Site
474
+ ## Building your static site
413
475
 
414
476
  When in `standalone` mode and you're ready to generate your static site, run:
415
477
  ```bash
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MetaTagsHelper
4
- def meta_tags(options = {}) = Perron::Metatags.new(resource).render(options)
4
+ def meta_tags(options = {}) = Perron::Metatags.new(resource.metadata).render(options)
5
5
 
6
6
  private
7
7
 
@@ -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
@@ -1,5 +1,4 @@
1
1
  ---
2
- slug: "/"
3
2
  ---
4
3
 
5
4
  Find me in `app/content/pages/root.erb`
@@ -18,11 +18,16 @@ module Perron
18
18
  def add_markdown_gems
19
19
  append_to_file "Gemfile", <<~RUBY
20
20
 
21
- # Perron can use one of the following gems. Uncomment your preferred choice and run `bundle install`
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"
25
26
  RUBY
26
27
  end
28
+
29
+ def gitignore_output_folder
30
+ append_to_file ".gitignore", "/#{Perron.configuration.output}/\n"
31
+ end
27
32
  end
28
33
  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
- <%% Perron::Data.new("features").each do |feature| %>
20
+ <%% Perron::Data.new("features").each do |feature| %>
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
+ ```
@@ -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,14 +5,14 @@ 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)
12
12
  end
13
13
 
14
14
  def configuration(resource_class = "Content::#{name.classify}".safe_constantize)
15
- resource_class.configuration
15
+ resource_class&.configuration
16
16
  end
17
17
 
18
18
  def all(resource_class = "Content::#{name.classify}".safe_constantize)
@@ -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
@@ -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)
@@ -93,16 +94,29 @@ module Perron
93
94
  end
94
95
 
95
96
  def get_binding = binding
97
+
98
+ def default_url_options = Perron.configuration.default_url_options || {}
96
99
  end
97
100
  private_constant :HelperContext
98
101
 
99
102
  class Item
100
- def initialize(attributes)
103
+ def initialize(attributes, identifier:)
101
104
  @attributes = attributes.transform_keys(&:to_sym)
105
+ @identifier = identifier
102
106
  end
103
107
 
104
108
  def [](key) = @attributes[key.to_sym]
105
109
 
110
+ def to_partial_path
111
+ @to_partial_path ||= begin
112
+ identifier = @identifier.to_s
113
+ collection = File.extname(identifier).present? ? File.basename(identifier, ".*") : identifier
114
+ element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.singularize(File.basename(collection)))
115
+
116
+ File.join("content", collection, element)
117
+ end
118
+ end
119
+
106
120
  def method_missing(method_name, *arguments, &block)
107
121
  return super if !@attributes.key?(method_name) || arguments.any? || block
108
122
 
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)
@@ -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
- document = Nokogiri::HTML::DocumentFragment.parse(@html)
15
-
16
- @processors.each { it.new(document).process }
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
- "Could not find processor `#{name}`. It is not a Perron-included processor and the constant `#{name.camelize}` could not be found."
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