perron 0.18.0 → 1.1.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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +11 -15
  3. data/app/controllers/perron/concierge_controller.rb +16 -0
  4. data/app/helpers/perron/markdown_helper.rb +3 -2
  5. data/app/helpers/perron/meta_tags_helper.rb +3 -9
  6. data/app/helpers/perron/paginate_helper.rb +40 -0
  7. data/app/views/perron/concierge/show.html.erb +271 -0
  8. data/lib/generators/rails/content/USAGE +21 -4
  9. data/lib/generators/rails/content/content_generator.rb +16 -12
  10. data/lib/generators/rails/content/templates/controller.rb.tt +6 -0
  11. data/lib/generators/rails/content/templates/model.rb.tt +1 -1
  12. data/lib/perron/assets/icon.png +0 -0
  13. data/lib/perron/assets/icon.svg +1 -0
  14. data/lib/perron/collection.rb +2 -1
  15. data/lib/perron/configuration.rb +27 -2
  16. data/lib/perron/data_source/class_methods.rb +8 -0
  17. data/lib/perron/data_source.rb +20 -33
  18. data/lib/perron/development_feed_server.rb +69 -0
  19. data/lib/perron/engine.rb +32 -1
  20. data/lib/perron/errors.rb +2 -0
  21. data/lib/perron/feeds.rb +4 -3
  22. data/lib/perron/html_processor/absolute_urls.rb +27 -0
  23. data/lib/perron/html_processor/base.rb +2 -2
  24. data/lib/perron/html_processor.rb +7 -11
  25. data/lib/{generators/perron/templates → perron/install}/README.md.tt +7 -9
  26. data/lib/perron/install/deploy.yml +15 -0
  27. data/lib/perron/install.rb +26 -0
  28. data/lib/perron/markdown.rb +2 -2
  29. data/lib/perron/output_server.rb +9 -0
  30. data/lib/perron/paginate.rb +58 -0
  31. data/lib/perron/relation.rb +24 -6
  32. data/lib/perron/resource/adjacency.rb +70 -0
  33. data/lib/perron/resource/associations.rb +1 -1
  34. data/lib/perron/resource/class_methods.rb +6 -0
  35. data/lib/perron/resource/configuration.rb +12 -4
  36. data/lib/perron/resource/metadata.rb +19 -4
  37. data/lib/perron/resource/publishable.rb +2 -0
  38. data/lib/perron/resource/related.rb +32 -31
  39. data/lib/perron/resource/sourceable.rb +98 -16
  40. data/lib/perron/resource.rb +8 -0
  41. data/lib/perron/site/builder/assets.rb +1 -1
  42. data/lib/perron/site/builder/feeds/atom.erb +44 -0
  43. data/lib/perron/site/builder/feeds/atom.rb +41 -0
  44. data/lib/perron/site/builder/feeds/json.erb +19 -0
  45. data/lib/perron/site/builder/feeds/json.rb +7 -33
  46. data/lib/perron/site/builder/feeds/rss.erb +28 -0
  47. data/lib/perron/site/builder/feeds/rss.rb +6 -28
  48. data/lib/perron/site/builder/feeds/template.rb +63 -0
  49. data/lib/perron/site/builder/feeds.rb +8 -3
  50. data/lib/perron/site/builder/page.rb +19 -4
  51. data/lib/perron/site/builder/paths.rb +75 -13
  52. data/lib/perron/site/builder/route_resources.rb +79 -0
  53. data/lib/perron/site/builder/sitemap.rb +71 -20
  54. data/lib/perron/site/builder.rb +25 -1
  55. data/lib/perron/site/validate.rb +19 -7
  56. data/lib/perron/site.rb +7 -0
  57. data/lib/perron/tasks/build.rake +6 -7
  58. data/lib/perron/tasks/deploy.rake +58 -0
  59. data/lib/perron/tasks/install.rake +12 -0
  60. data/lib/perron/version.rb +1 -1
  61. data/lib/perron.rb +1 -0
  62. data/perron.gemspec +1 -1
  63. metadata +25 -8
  64. data/lib/generators/perron/install_generator.rb +0 -32
  65. data/lib/perron/html_processor/syntax_highlight.rb +0 -32
  66. /data/lib/{generators/perron/templates → perron/install}/initializer.rb.tt +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4b7d197a1f26d5fae6c488fe7be6aef6c3818e408d97b5d789f46111db3207be
4
- data.tar.gz: 0cb809db9260c8e5ded0626f9d790dfb003b937892c1bbce022bcbdac3e5053d
3
+ metadata.gz: bdd54bac7e739c0199d3381c1f20aaf15dd5c4dfddec9f57635c0dcf41b66505
4
+ data.tar.gz: c04dc9b074c2a53803086ee6f9a5a7780a26f930661a0884513185323197ba03
5
5
  SHA512:
6
- metadata.gz: cc876ae9e8fb6daf1c6496c381efc05eba46fe5bbff9174502480488c34a4dbf5c94d9a360752942a53c395d7df6cb3b9e8342936e8e795b3ab3ccc20f6b3cdd
7
- data.tar.gz: 99681d91d5d5ea2ffb8488ba4c93c27b2115aebde8132d6f0c8376a6b3e7819c62aef196b424e244af04d8ada7a12080c181cd0f44cf9d52c79125263c3f849b
6
+ metadata.gz: 0fdac048810289509dc253a93839fd2df30358a90e463dac6acc3e0cad1171640698e973522d5321b53ca807c56f9a806aeb4bdc972f56773ee0b2693871009b
7
+ data.tar.gz: 8864e0a699f0cf04cc67c696dee53df33e70cd7abe9d57a34b75312613d332c09d0e675fc46a02a52870f30c0a556bb00c98fd7150c8769af5bd1a1ddb79429c
data/Gemfile.lock CHANGED
@@ -1,10 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- perron (0.18.0)
4
+ perron (1.1.0)
5
5
  csv
6
6
  json
7
- mata (~> 0.8.0)
7
+ mata (~> 0.10.0)
8
8
  psych
9
9
  rails (>= 7.2.0)
10
10
 
@@ -136,11 +136,10 @@ GEM
136
136
  net-pop
137
137
  net-smtp
138
138
  marcel (1.0.4)
139
- mata (0.8.0)
139
+ mata (0.10.0)
140
140
  listen (~> 3.0)
141
141
  rack (>= 3.0)
142
142
  mini_mime (1.1.5)
143
- mini_portile2 (2.8.9)
144
143
  minitest (5.27.0)
145
144
  net-imap (0.5.9)
146
145
  date
@@ -152,24 +151,21 @@ GEM
152
151
  net-smtp (0.5.1)
153
152
  net-protocol
154
153
  nio4r (2.7.4)
155
- nokogiri (1.18.8)
156
- mini_portile2 (~> 2.8.2)
154
+ nokogiri (1.19.3-aarch64-linux-gnu)
157
155
  racc (~> 1.4)
158
- nokogiri (1.18.8-aarch64-linux-gnu)
156
+ nokogiri (1.19.3-aarch64-linux-musl)
159
157
  racc (~> 1.4)
160
- nokogiri (1.18.8-aarch64-linux-musl)
158
+ nokogiri (1.19.3-arm-linux-gnu)
161
159
  racc (~> 1.4)
162
- nokogiri (1.18.8-arm-linux-gnu)
160
+ nokogiri (1.19.3-arm-linux-musl)
163
161
  racc (~> 1.4)
164
- nokogiri (1.18.8-arm-linux-musl)
162
+ nokogiri (1.19.3-arm64-darwin)
165
163
  racc (~> 1.4)
166
- nokogiri (1.18.8-arm64-darwin)
164
+ nokogiri (1.19.3-x86_64-darwin)
167
165
  racc (~> 1.4)
168
- nokogiri (1.18.8-x86_64-darwin)
166
+ nokogiri (1.19.3-x86_64-linux-gnu)
169
167
  racc (~> 1.4)
170
- nokogiri (1.18.8-x86_64-linux-gnu)
171
- racc (~> 1.4)
172
- nokogiri (1.18.8-x86_64-linux-musl)
168
+ nokogiri (1.19.3-x86_64-linux-musl)
173
169
  racc (~> 1.4)
174
170
  parallel (1.27.0)
175
171
  parser (3.3.8.0)
@@ -0,0 +1,16 @@
1
+ module Perron
2
+ class ConciergeController < ActionController::Base
3
+ def show
4
+ render :show
5
+ end
6
+
7
+ def run_command
8
+ command = params[:command]
9
+
10
+ return redirect_back fallback_location: root_path unless command.start_with?("bin/rails generate content")
11
+
12
+ system(command)
13
+ redirect_back fallback_location: root_path
14
+ end
15
+ end
16
+ end
@@ -4,10 +4,11 @@ require "perron/markdown"
4
4
 
5
5
  module Perron
6
6
  module MarkdownHelper
7
- def markdownify(content = nil, process: [], &block)
7
+ def markdownify(content = nil, process: nil, resource: nil, &block)
8
8
  text = block_given? ? capture(&block).strip_heredoc : content
9
+ processors = (process.nil? || process.empty?) ? Perron.configuration.default_processors : process
9
10
 
10
- Perron::Markdown.render(text, processors: process)
11
+ Perron::Markdown.render(text, processors: processors, resource: resource || @resource)
11
12
  end
12
13
  end
13
14
  end
@@ -2,16 +2,10 @@
2
2
 
3
3
  module Perron
4
4
  module MetaTagsHelper
5
- def meta_tags(options = {}) = Perron::Metatags.new(resource.metadata).render(options)
5
+ def meta_tags(options = {})
6
+ metadata = (@metadata || {}).merge(@resource&.metadata || {})
6
7
 
7
- private
8
-
9
- Resource = Data.define(:path, :collection, :metadata, :published_at)
10
-
11
- def resource
12
- return Resource.new(request.path, nil, @metadata, nil) if @metadata.present?
13
-
14
- @resource || Resource.new(request.path, nil, {}, nil)
8
+ Perron::Metatags.new(metadata).render(options)
15
9
  end
16
10
  end
17
11
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Perron
4
+ module PaginateHelper
5
+ def paginate(scope, page: nil, **options)
6
+ page ||= (params[:page] || 1).to_i
7
+ resource_class = scope.model_class
8
+
9
+ config = resource_class.configuration.pagination
10
+ per_page = options[:per_page] || config.per_page
11
+ page_path_template = options[:path_template] || config.path_template
12
+
13
+ route = find_index_route(resource_class)
14
+ base_path = route_path(route)
15
+
16
+ use_query_params = Rails.env.development? || Rails.env.local?
17
+ paginate = Paginate.new(scope, page: page, per_page: per_page, base_path: base_path, page_path_template: page_path_template, use_query_params: use_query_params)
18
+
19
+ [paginate, paginate.items]
20
+ end
21
+
22
+ private
23
+
24
+ def find_index_route(resource_class)
25
+ controller_name = resource_class.name.demodulize.sub("Controller", "").underscore.pluralize
26
+
27
+ Rails.application.routes.routes.find do |r|
28
+ r.defaults[:controller] == "content/#{controller_name}" &&
29
+ r.defaults[:action] == "index"
30
+ end
31
+ end
32
+
33
+ def route_path(route)
34
+ return "/#{route.name}/" unless route
35
+
36
+ path_spec = route.path.spec.to_s
37
+ path_spec.sub(/\(.*?\)/, "").gsub(/:[^\/]+/, "").sub(/\/$/, "") + "/"
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,271 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <title>Welcome to Perron</title>
5
+ <meta charset="utf-8">
6
+
7
+ <link rel="icon" href="/icon.png" type="image/png">
8
+ <link rel="apple-touch-icon" href="/icon.png" sizes="512x512">
9
+
10
+ <style>
11
+ * { margin: 0; padding: 0; box-sizing: border-box; }
12
+ ul { list-style: none; }
13
+
14
+ a {
15
+ color: #1f2937;
16
+
17
+ &:hover: { text-decoration: none; }
18
+ }
19
+
20
+ body {
21
+ padding: 2rem 1rem;
22
+ min-height: 100vh;
23
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
24
+ line-height: 1.6;
25
+ color: #1f2937;
26
+ background: linear-gradient(135deg, #f9fafb 0%, #f3f4f6 100%);
27
+ }
28
+
29
+ .hero {
30
+ max-width: 72rem;
31
+ margin: 0 auto 4rem;
32
+ padding: 3rem 0;
33
+
34
+ h1 {
35
+ font-size: clamp(2rem, 5vw, 3.5rem);
36
+ font-weight: 900;
37
+ text-align: center;
38
+ color: #111827;
39
+ letter-spacing: -.025em;
40
+ }
41
+
42
+ ul {
43
+ display: grid;
44
+ row-gap: 1rem;
45
+ margin: 1rem auto 0;
46
+ max-width: 48rem;
47
+ padding: 1rem;
48
+ background: white;
49
+ border-radius: .5rem;
50
+ border: 1px solid #e5e7eb;
51
+
52
+ li {
53
+ border-radius: .5rem;
54
+
55
+ p { font-weight: 600; }
56
+
57
+ div {
58
+ display: grid;
59
+ grid-template-columns: 1fr min-content;
60
+ margin-top: .25rem;
61
+ padding: .75rem 1rem;
62
+ background-color: rgb(248 250 252);
63
+ border-radius: .5rem;
64
+ }
65
+
66
+ code {
67
+ font-size: .875rem;
68
+ font-family: "SF Mono", Monaco, "Cascadia Code", monospace;
69
+ font-weight: 500;
70
+ color: rgb(51 65 85);
71
+ overflow: auto;
72
+ white-space: nowrap;
73
+ user-select: all;
74
+ }
75
+
76
+ svg {
77
+ width: 1.125rem;
78
+ aspect-ratio: 1/1;
79
+ color: rgb(100 116 139);
80
+ }
81
+
82
+ input[type=submit] {
83
+ border: 0;
84
+ padding: .25rem 1rem;
85
+ font-size: .875rem;
86
+ font-weight: 300;
87
+ color: white;
88
+ background-color: rgb(249 115 22);
89
+ border-radius: .25rem;
90
+ transition: background-color 300ms ease;
91
+ cursor: pointer;
92
+
93
+ &:hover {
94
+ background-color: rgb(251 146 60);
95
+ }
96
+ }
97
+ }
98
+ }
99
+ }
100
+
101
+ .info {
102
+ display: grid;
103
+ gap: 2rem;
104
+ max-width: 6xl;
105
+ margin: 0 auto;
106
+ overflow-x: clip;
107
+
108
+ @media (min-width: 768px) {
109
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
110
+ }
111
+
112
+ li {
113
+ &:nth-child(odd) {
114
+ @media (min-width: 640px) {
115
+ transform: rotate(-1deg);
116
+ }
117
+ }
118
+
119
+ &:nth-child(even) {
120
+ @media (min-width: 640px) {
121
+ transform: rotate(1deg);
122
+ }
123
+ }
124
+ }
125
+
126
+ a {
127
+ display: block;
128
+ padding: 2rem 1.5rem;
129
+ text-decoration: none;
130
+ background: white;
131
+ border: 1px solid #e5e7eb;
132
+ border-radius: 1rem;
133
+ transition: all 300ms ease;
134
+
135
+ &:hover {
136
+ transform: translateY(-4px);
137
+ box-shadow: 0 20px 25px -5px rgb(0 0 0 / .1);
138
+ }
139
+
140
+ h2 {
141
+ font-size: 1.25rem;
142
+ font-weight: 700;
143
+ color: #111827;
144
+ }
145
+
146
+ p {
147
+ margin-top: .25rem;
148
+ color: #4b5563;
149
+ line-height: 1.7;
150
+
151
+ a {
152
+ color: #f97316;
153
+ text-decoration: none;
154
+ font-weight: 500;
155
+ transition: color 200ms ease;
156
+
157
+ &:hover {
158
+ color: #ea580c;
159
+ text-decoration: underline;
160
+ }
161
+ }
162
+ }
163
+ }
164
+ }
165
+
166
+ .versions {
167
+ display: flex;
168
+ align-items: center;
169
+ justify-content: center;
170
+ column-gap: 1.5rem;
171
+ margin-top: 2rem;
172
+ font-family: "SF Mono", Monaco, "Cascadia Code", monospace;
173
+ text-align: center;
174
+ color: #6b7280;
175
+ font-size: .75rem;
176
+ }
177
+ </style>
178
+ </head>
179
+
180
+ <body>
181
+ <section class="hero">
182
+ <h1>Welcome to Perron</h1>
183
+
184
+ <ul>
185
+ <li>
186
+ <p>
187
+ Create a collection
188
+ </p>
189
+
190
+ <div>
191
+ <code>bin/rails generate content Post</code>
192
+
193
+ <%= form_with url: perron_run_command_path do |form| %>
194
+ <%= form.hidden_field :command, value: "bin/rails generate content Post" %>
195
+
196
+ <% if defined?(Content::Post) %>
197
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm45.66,85.66-56,56a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35a8,8,0,0,1,11.32,11.32Z"/></svg>
198
+ <% else %>
199
+ <%= form.submit "Run" %>
200
+ <% end %>
201
+ <% end %>
202
+ </div>
203
+ </li>
204
+
205
+ <li>
206
+ <p>
207
+ Create your first content
208
+ </p>
209
+
210
+ <div>
211
+ <code>bin/rails generate content Post --new "My first post"</code>
212
+
213
+ <%= form_with url: perron_run_command_path do |form| %>
214
+ <%= form.hidden_field :command, value: 'bin/rails generate content Post --new' %>
215
+
216
+ <% if File.file?("app/content/posts/untitled.md") %>
217
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm45.66,85.66-56,56a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35a8,8,0,0,1,11.32,11.32Z"/></svg>
218
+ <% else %>
219
+ <%= form.submit "Run" %>
220
+ <% end %>
221
+ <% end %>
222
+ </div>
223
+ </li>
224
+
225
+ <li>
226
+ <p>
227
+ Run the server, and visit <code><a href="/posts/">http://localhost:3000/posts/</a></code>
228
+ </p>
229
+
230
+ <div>
231
+ <code>bin/dev</code>
232
+ </div>
233
+ </li>
234
+ </ul>
235
+ </section>
236
+
237
+ <ul class="info">
238
+ <li>
239
+ <a href="https://perron.railsdesigner.com/docs/">
240
+ <h2>Documentation</h2>
241
+
242
+ <p>Visit the docs for more</p>
243
+ </a>
244
+ </li>
245
+
246
+ <li>
247
+ <a href="https://perron.railsdesigner.com/library/">
248
+ <h2>Library</h2>
249
+
250
+ <p>Check out the library for snippets, components and templates</p>
251
+ </a>
252
+ </li>
253
+
254
+ <li>
255
+ <a href="https://github.com/Rails-Designer/perron">
256
+ <h2>GitHub</h2>
257
+
258
+ <p>Check out Perron's source</p>
259
+ </a>
260
+ </li>
261
+ </ul>
262
+
263
+ <div class="versions">
264
+ <span>Ruby <%= RUBY_VERSION %></span>
265
+
266
+ <span>Rails <%= Rails.version %></span>
267
+
268
+ <span>Perron <%= Perron::VERSION %></span>
269
+ </div>
270
+ </body>
271
+ </html>
@@ -12,6 +12,7 @@ Options:
12
12
  --force-plural Use plural form for model name and class
13
13
  --[no-]include-root Include root action and route
14
14
  (default: true for pages, false otherwise)
15
+ --inline Render show action inline instead of using view template
15
16
 
16
17
  Examples:
17
18
  Generate basic content scaffold:
@@ -29,6 +30,17 @@ Examples:
29
30
  Adds route:
30
31
  resources :posts, module: :content, only: %w[index show]
31
32
 
33
+ Generate inline show action:
34
+ rails generate content Post --inline
35
+
36
+ Creates controller with inline rendering:
37
+ def show
38
+ @resource = Content::Post.find!(params[:id])
39
+ render @resource.inline
40
+ end
41
+
42
+ Skips creating app/views/content/posts/show.html.erb
43
+
32
44
  Generate pages scaffold with root:
33
45
  rails generate content Page
34
46
 
@@ -48,17 +60,22 @@ Examples:
48
60
  Creates data source files in app/content/data/ and adds
49
61
  .sources and .template_source class methods to the model.
50
62
 
51
- Adds .sources and .template_source class methods to model.
52
-
53
63
  Create new content file from template:
54
64
  rails generate content Post --new
55
65
  rails generate content Post --new "My First Post"
56
66
 
57
67
  Creates a new content file in app/content/posts/ using:
58
- 1. YYYY-MM-DD-template.*.tt (if exists, with date prefix)
59
- 2. template.*.tt (if exists, without date prefix)
68
+ 1. Template file with strftime patterns (e.g., %Y-%m-%d-title.md.tt)
69
+ 2. Template file without strftime patterns (e.g., template.md.tt)
60
70
  3. Empty file with frontmatter dashes (if no template)
61
71
 
72
+ Template filename examples:
73
+ %Y-%m-%d-title.md.tt → 2026-03-10-my-post.md
74
+ %s-title.md.tt → 1741737600-my-post.md
75
+ %d-title.md.tt → 10-my-post.md
76
+ title.md.tt → my-post.md
77
+ %Y-%m.md.tt → 2026-03.md
78
+
62
79
  Template files support ERB:
63
80
  ---
64
81
  title: <%= @title %>
@@ -13,6 +13,7 @@ module Rails
13
13
  desc: "Create a new content file from template instead of generating scaffold"
14
14
  class_option :data, type: :array, default: [], banner: "source1(.ext) source2(.ext)",
15
15
  desc: "Specify data sources with optional extensions (defaults to .yml)"
16
+ class_option :inline, type: :boolean, default: false, desc: "Render show action inline instead of using a view template"
16
17
 
17
18
  argument :actions, type: :array, default: %w[index show], banner: "actions", desc: "Specify which actions to generate (index/show)"
18
19
 
@@ -53,6 +54,8 @@ module Rails
53
54
  empty_directory view_directory
54
55
 
55
56
  actions.each do |action|
57
+ next if action == "show" && options[:inline]
58
+
56
59
  template "#{action}.html.erb.tt", File.join(view_directory, "#{action}.html.erb")
57
60
  end
58
61
  end
@@ -84,15 +87,12 @@ module Rails
84
87
  return if @content_mode
85
88
  return unless should_include_root?
86
89
 
87
- inject_into_class "app/controllers/content/#{plural_file_name}_controller.rb", "Content::#{plural_class_name}Controller" do
88
- <<~RUBY
89
- def root
90
- @resource = Content::#{class_name}.root
90
+ controller_file = "app/controllers/content/#{plural_file_name}_controller.rb"
91
+ return unless File.exist?(File.join(destination_root, controller_file))
91
92
 
92
- render :show
93
- end
94
- RUBY
95
- end
93
+ root_action = " def root\n @resource = Content::#{class_name}.root\n\n render :show\n end\n\n"
94
+
95
+ inject_into_file controller_file, root_action, after: "class Content::#{plural_class_name}Controller < ApplicationController\n"
96
96
  end
97
97
 
98
98
  def create_root_content_file
@@ -129,16 +129,20 @@ module Rails
129
129
  def pages_controller? = plural_file_name == "pages"
130
130
 
131
131
  def template_file
132
- @template_file ||= Dir.glob(File.join(content_directory, "{YYYY-MM-DD-,}template.*.tt")).first
132
+ @template_file ||= Dir.glob(File.join(content_directory, "*.tt")).first
133
133
  end
134
134
 
135
135
  def filename_from_template
136
136
  @filename_from_template ||= begin
137
137
  return "untitled.md" unless template_file
138
138
 
139
- File.basename(template_file, ".tt").tap do |name|
140
- name.gsub!("YYYY-MM-DD", Time.current.strftime("%Y-%m-%d"))
141
- name.sub!("template", @content_title ? @content_title.parameterize : "untitled")
139
+ name = File.basename(template_file, ".tt")
140
+ name = Time.current.strftime(name)
141
+
142
+ if name.include?("title")
143
+ name.sub("title", @content_title ? @content_title.parameterize : "untitled")
144
+ else
145
+ name
142
146
  end
143
147
  end
144
148
  end
@@ -3,11 +3,17 @@ class Content::<%= plural_class_name %>Controller < ApplicationController
3
3
  def index
4
4
  @resources = Content::<%= class_name %>.all
5
5
  end
6
+ <%- if actions.include?("show") -%>
6
7
 
8
+ <%- end -%>
7
9
  <%- end -%>
8
10
  <%- if actions.include?("show") -%>
9
11
  def show
10
12
  @resource = Content::<%= class_name %>.find!(params[:id])
13
+ <%- if options[:inline] -%>
14
+
15
+ render @resource.inline
16
+ <%- end -%>
11
17
  end
12
18
  <%- end -%>
13
19
  end
@@ -2,7 +2,7 @@ class Content::<%= class_name %> < Perron::Resource
2
2
  <% if data_sources? -%>
3
3
  sources <%= data_sources.map { ":#{it}" }.join(", ") %>
4
4
 
5
- def self.source_template(sources)
5
+ def self.source_template(source)
6
6
  <<~MARKDOWN
7
7
  ---
8
8
  ---
Binary file
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="400" height="400" fill="none" viewBox="0 0 400 400"><path fill="#f97316" d="M177.101 221.582a8.59 8.59 0 0 1 8.587-8.587h68.524q23.93 0 40.424-16.458 16.728-16.697 16.728-46.99 0-22.66-17.425-40.549-17.424-18.129-39.727-18.128H126.636c-8.836 0-16 7.163-16 16v244.766a8.364 8.364 0 1 1-16.727 0V89.696c0-8.837 7.164-16 16-16h144.303q29.272 0 51.576 23.137 22.303 23.136 22.303 52.714 0 36.495-21.606 58.678-21.606 21.944-52.273 21.944h-68.524a8.587 8.587 0 0 1-8.587-8.587m20 34.348a8.59 8.59 0 0 1 8.587-8.587h48.524q24.162 0 44.606-11.926 20.678-12.165 33.222-34.825 12.778-22.66 12.778-51.045 0-36.733-27.414-64.88-27.182-28.145-63.192-28.145H93.182c-8.837 0-16 7.163-16 16v299.114a8.364 8.364 0 1 1-16.727 0V55.348c0-8.837 7.163-16 16-16h177.757q28.111 0 52.97 15.266t39.495 40.788q14.868 25.522 14.868 54.145 0 32.44-14.868 59.155-14.87 26.477-39.495 41.265-24.394 14.55-52.97 14.55h-48.524a8.59 8.59 0 0 1-8.587-8.587m20 34.348a8.587 8.587 0 0 1 8.587-8.587h28.524q24.86 0 47.626-10.018 22.768-10.257 39.495-27.431 16.96-17.412 26.95-41.981 9.99-24.806 9.99-52.714 0-33.393-16.96-62.732-16.959-29.578-45.768-46.99-28.575-17.65-61.333-17.651H59.727c-8.836 0-16 7.163-16 16v353.462a8.364 8.364 0 0 1-16.727 0V21c0-8.837 7.163-16 16-16h211.212q37.172 0 69.697 20.036 32.526 19.799 51.808 53.192Q395 111.62 395 149.547q0 33.871-11.384 62.494-11.383 28.385-30.667 47.228-19.283 18.844-44.838 29.339-25.555 10.257-53.899 10.257h-28.524a8.59 8.59 0 0 1-8.587-8.587m-73.01 41.358a8.364 8.364 0 0 1-16.727 0V124.043c0-8.836 7.163-16 16-16h110.848q16.495 0 28.344 12.404 12.08 12.404 12.08 29.1 0 46.274-40.424 46.274h-88.524a8.587 8.587 0 1 1 0-17.174h88.524q11.152 0 17.424-6.44t6.273-22.66q0-10.495-6.97-17.412-6.737-6.918-16.727-6.918h-94.121c-8.837 0-16 7.164-16 16z" style="mix-blend-mode:multiply"/></svg>
@@ -16,7 +16,7 @@ module Perron
16
16
  end
17
17
 
18
18
  def all(resource_class = "Content::#{name.classify}".safe_constantize)
19
- Perron::Relation.new(load_resources(resource_class).select(&:published?))
19
+ Perron::Relation.new(load_resources(resource_class).select(&:published?), resource_class)
20
20
  end
21
21
  alias_method :resources, :all
22
22
 
@@ -52,6 +52,7 @@ module Perron
52
52
 
53
53
  Dir.glob("#{@collection_path}/**/*.*")
54
54
  .select { allowed_extensions.include?(File.extname(it)) }
55
+ .reject { File.basename(it, ".*").downcase == "readme" }
55
56
  .map { resource_class.new(it) }
56
57
  end
57
58
  end
@@ -15,6 +15,8 @@ module Perron
15
15
 
16
16
  @config.output = "output"
17
17
 
18
+ @config.output_server_strict = true
19
+
18
20
  @config.mode = :standalone
19
21
 
20
22
  @config.live_reload = false
@@ -35,8 +37,12 @@ module Perron
35
37
 
36
38
  @config.markdown_options = {}
37
39
 
40
+ @config.default_processors = []
41
+
38
42
  @config.search_scope = []
39
43
 
44
+ @config.cache_data_sources = false
45
+
40
46
  @config.sitemap = ActiveSupport::OrderedOptions.new
41
47
  @config.sitemap.enabled = false
42
48
  @config.sitemap.priority = 0.5
@@ -47,6 +53,9 @@ module Perron
47
53
 
48
54
  @config.metadata = ActiveSupport::OrderedOptions.new
49
55
  @config.metadata.title_separator = " — "
56
+
57
+ @config.before_build = nil
58
+ @config.after_build = nil
50
59
  end
51
60
 
52
61
  def input = Rails.root.join("app", "content")
@@ -61,6 +70,22 @@ module Perron
61
70
  @additional_routes || (mode.integrated? ? [] : %w[root_path])
62
71
  end
63
72
 
73
+ def deploy
74
+ @deploy ||= ActiveSupport::OrderedOptions.new.tap do |config|
75
+ def config.method_missing(method_name, *args, &block)
76
+ if method_name.to_s.end_with?("=")
77
+ super
78
+ else
79
+ self[method_name] ||= ActiveSupport::OrderedOptions.new
80
+ end
81
+ end
82
+
83
+ def config.respond_to_missing?(method_name, include_private = false)
84
+ !method_name.to_s.end_with?("=") || super
85
+ end
86
+ end
87
+ end
88
+
64
89
  attr_writer :additional_routes
65
90
 
66
91
  def url
@@ -78,8 +103,8 @@ module Perron
78
103
  end
79
104
  end
80
105
 
81
- def respond_to_missing?(method_name)
82
- @config.respond_to?(method_name) || super
106
+ def respond_to_missing?(method_name, ...)
107
+ @config.respond_to?(method_name, ...) || super
83
108
  end
84
109
  end
85
110
  end
@@ -17,6 +17,14 @@ module Perron
17
17
  all.find { it[:id] == id || it["id"] == id }
18
18
  end
19
19
 
20
+ def find!(id)
21
+ data_source = all.find { it[:id] == id || it["id"] == id }
22
+
23
+ return data_source if data_source
24
+
25
+ raise Errors::DataSourceNotFoundError, "Row not found with id: #{id}"
26
+ end
27
+
20
28
  def count = all.size
21
29
 
22
30
  def first = all.first