perron 0.18.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/app/controllers/perron/concierge_controller.rb +13 -0
- data/app/helpers/perron/markdown_helper.rb +2 -2
- data/app/views/perron/concierge/show.html.erb +271 -0
- data/lib/generators/rails/content/USAGE +21 -4
- data/lib/generators/rails/content/content_generator.rb +16 -12
- data/lib/generators/rails/content/templates/controller.rb.tt +6 -0
- data/lib/generators/rails/content/templates/model.rb.tt +1 -1
- data/lib/perron/assets/icon.png +0 -0
- data/lib/perron/assets/icon.svg +1 -0
- data/lib/perron/configuration.rb +4 -0
- data/lib/perron/data_source/class_methods.rb +8 -0
- data/lib/perron/data_source.rb +14 -29
- data/lib/perron/development_feed_server.rb +69 -0
- data/lib/perron/engine.rb +29 -1
- data/lib/perron/errors.rb +2 -0
- data/lib/perron/feeds.rb +4 -3
- data/lib/perron/html_processor/absolute_urls.rb +27 -0
- data/lib/perron/html_processor/base.rb +2 -2
- data/lib/perron/html_processor.rb +7 -11
- data/lib/{generators/perron/templates → perron/install}/README.md.tt +7 -9
- data/lib/perron/install.rb +23 -0
- data/lib/perron/markdown.rb +2 -2
- data/lib/perron/output_server.rb +9 -0
- data/lib/perron/resource/adjacency.rb +70 -0
- data/lib/perron/resource/associations.rb +1 -1
- data/lib/perron/resource/configuration.rb +9 -4
- data/lib/perron/resource/metadata.rb +10 -1
- data/lib/perron/resource/publishable.rb +2 -0
- data/lib/perron/resource/related.rb +32 -31
- data/lib/perron/resource/sourceable.rb +39 -9
- data/lib/perron/resource.rb +2 -0
- data/lib/perron/site/builder/assets.rb +1 -1
- data/lib/perron/site/builder/feeds/atom.erb +44 -0
- data/lib/perron/site/builder/feeds/atom.rb +41 -0
- data/lib/perron/site/builder/feeds/json.erb +19 -0
- data/lib/perron/site/builder/feeds/json.rb +7 -33
- data/lib/perron/site/builder/feeds/rss.erb +28 -0
- data/lib/perron/site/builder/feeds/rss.rb +6 -28
- data/lib/perron/site/builder/feeds/template.rb +63 -0
- data/lib/perron/site/builder/feeds.rb +8 -3
- data/lib/perron/site/builder/paths.rb +58 -14
- data/lib/perron/site/builder/route_resources.rb +79 -0
- data/lib/perron/site/builder/sitemap.rb +71 -20
- data/lib/perron/site/builder.rb +1 -1
- data/lib/perron/site/validate.rb +1 -2
- data/lib/perron/site.rb +7 -0
- data/lib/perron/tasks/build.rake +6 -7
- data/lib/perron/tasks/install.rake +12 -0
- data/lib/perron/version.rb +1 -1
- metadata +18 -5
- data/lib/generators/perron/install_generator.rb +0 -32
- data/lib/perron/html_processor/syntax_highlight.rb +0 -32
- /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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 72473e733aa6ec4313dc71e66b2a949dea6497b427e304cb4656be0d0a5fbc86
|
|
4
|
+
data.tar.gz: 37a57b14891cf3fef0dfb1a4c98b414852fdd1bd3f3e377c674e15673f6d2547
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 478b181f494c2c0ac54e996c0d9f289c7694bedbcd203fd435c9222ecdd07ad6c5a3f5e8baeb497ee2f77499e3580f0f915439d2feba1edf8ab42385285a7e96
|
|
7
|
+
data.tar.gz: 19750396f542ac909322aa5cd3ed247dc10a03509ab521bdb75dd0ae705c502e80b3431737d5c34eb42bcacda17c0ef0c35889cb65561339646f6eb7a2ee1e58
|
data/Gemfile.lock
CHANGED
|
@@ -4,10 +4,10 @@ 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: [], resource: nil, &block)
|
|
8
8
|
text = block_given? ? capture(&block).strip_heredoc : content
|
|
9
9
|
|
|
10
|
-
Perron::Markdown.render(text, processors: process)
|
|
10
|
+
Perron::Markdown.render(text, processors: process, resource: resource || @resource)
|
|
11
11
|
end
|
|
12
12
|
end
|
|
13
13
|
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 "My first post"' %>
|
|
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.
|
|
59
|
-
2.
|
|
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
|
-
|
|
88
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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, "
|
|
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")
|
|
140
|
-
|
|
141
|
-
|
|
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
|
|
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>
|
data/lib/perron/configuration.rb
CHANGED
|
@@ -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
|
|
@@ -37,6 +39,8 @@ module Perron
|
|
|
37
39
|
|
|
38
40
|
@config.search_scope = []
|
|
39
41
|
|
|
42
|
+
@config.cache_data_sources = false
|
|
43
|
+
|
|
40
44
|
@config.sitemap = ActiveSupport::OrderedOptions.new
|
|
41
45
|
@config.sitemap.enabled = false
|
|
42
46
|
@config.sitemap.priority = 0.5
|
|
@@ -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
|
data/lib/perron/data_source.rb
CHANGED
|
@@ -20,6 +20,20 @@ module Perron
|
|
|
20
20
|
super(records)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
+
def self.all
|
|
24
|
+
identifier = name.to_s.split("::").drop(2).map { it.underscore }.join("/")
|
|
25
|
+
identifier = name.demodulize.underscore if identifier.empty?
|
|
26
|
+
|
|
27
|
+
return cached(identifier) if Perron.configuration.cache_data_sources
|
|
28
|
+
|
|
29
|
+
new(identifier)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.cached(identifier)
|
|
33
|
+
@_data_sources ||= {}
|
|
34
|
+
@_data_sources[identifier] ||= new(identifier)
|
|
35
|
+
end
|
|
36
|
+
|
|
23
37
|
def each(&block) = @records.each(&block)
|
|
24
38
|
|
|
25
39
|
def count = @records.count
|
|
@@ -59,16 +73,6 @@ module Perron
|
|
|
59
73
|
Item.new(item, identifier: @identifier)
|
|
60
74
|
end
|
|
61
75
|
end
|
|
62
|
-
# def records
|
|
63
|
-
# content = rendered_from(@file_path)
|
|
64
|
-
# data = parsed_from(content, @file_path)
|
|
65
|
-
|
|
66
|
-
# unless data.is_a?(Array)
|
|
67
|
-
# raise Errors::DataParseError, "Data in `#{@file_path}` must be an array of objects."
|
|
68
|
-
# end
|
|
69
|
-
|
|
70
|
-
# data.map { Item.new(it, identifier: @identifier) }
|
|
71
|
-
# end
|
|
72
76
|
|
|
73
77
|
def rendered_from(path)
|
|
74
78
|
raw_content = File.read(path)
|
|
@@ -86,16 +90,6 @@ module Perron
|
|
|
86
90
|
|
|
87
91
|
send(parser_method, content, path)
|
|
88
92
|
end
|
|
89
|
-
# def parsed_from(content, path)
|
|
90
|
-
# extension = File.extname(path)
|
|
91
|
-
# parser_method = PARSER_METHODS.fetch(extension) do
|
|
92
|
-
# raise Errors::UnsupportedDataFormatError, "Unsupported data format: #{extension}"
|
|
93
|
-
# end
|
|
94
|
-
|
|
95
|
-
# send(parser_method, content)
|
|
96
|
-
# rescue Psych::SyntaxError, JSON::ParserError, CSV::MalformedCSVError => error
|
|
97
|
-
# raise Errors::DataParseError, "Failed to parse data format in `#{path}`: (#{error.class}) #{error.message}"
|
|
98
|
-
# end
|
|
99
93
|
|
|
100
94
|
def render_erb(content) = ERB.new(content).result(HelperContext.instance.get_binding)
|
|
101
95
|
|
|
@@ -107,9 +101,6 @@ module Perron
|
|
|
107
101
|
|
|
108
102
|
raise Errors::DataParseError, "Invalid YAML syntax in `#{path}`#{line_info}#{column_info}: #{error.problem}"
|
|
109
103
|
end
|
|
110
|
-
# def parse_yaml(content)
|
|
111
|
-
# YAML.safe_load(content, permitted_classes: [Symbol, Time], aliases: true)
|
|
112
|
-
# end
|
|
113
104
|
|
|
114
105
|
def parse_json(content, path)
|
|
115
106
|
JSON.parse(content, symbolize_names: true)
|
|
@@ -119,9 +110,6 @@ module Perron
|
|
|
119
110
|
|
|
120
111
|
raise Errors::DataParseError, "Invalid JSON syntax in `#{path}`#{line_info}: #{error.message}"
|
|
121
112
|
end
|
|
122
|
-
# def parse_json(content)
|
|
123
|
-
# JSON.parse(content, symbolize_names: true)
|
|
124
|
-
# end
|
|
125
113
|
|
|
126
114
|
def parse_csv(content, path)
|
|
127
115
|
expected_headers = nil
|
|
@@ -148,8 +136,5 @@ module Perron
|
|
|
148
136
|
|
|
149
137
|
raise Errors::DataParseError, "Malformed CSV in `#{path}`#{line_info}: #{error.message}"
|
|
150
138
|
end
|
|
151
|
-
# def parse_csv(content)
|
|
152
|
-
# CSV.new(content, headers: true, header_converters: :symbol).to_a.map(&:to_h)
|
|
153
|
-
# end
|
|
154
139
|
end
|
|
155
140
|
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Perron
|
|
4
|
+
class DevelopmentFeedServer
|
|
5
|
+
def initialize(app)
|
|
6
|
+
@app = app
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def call(environment)
|
|
10
|
+
request = Rack::Request.new(environment)
|
|
11
|
+
|
|
12
|
+
if build_only_path?(request.path_info)
|
|
13
|
+
render_message(request.path_info)
|
|
14
|
+
else
|
|
15
|
+
@app.call(environment)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def build_only_path?(path)
|
|
22
|
+
sitemap?(path) || feed?(path)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def render_message(path)
|
|
26
|
+
content_type = path.end_with?(".json") ? "application/json" : "application/xml"
|
|
27
|
+
|
|
28
|
+
[
|
|
29
|
+
200,
|
|
30
|
+
|
|
31
|
+
{
|
|
32
|
+
"Content-Type" => "#{content_type}; charset=utf-8",
|
|
33
|
+
"Content-Length" => message(path).bytesize.to_s
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
[message(path)]
|
|
37
|
+
]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def sitemap?(path)
|
|
41
|
+
path.match?(/\/sitemap\.xml$/)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def feed?(path)
|
|
45
|
+
feed_paths.any? { path.end_with?("/#{it}") || path == "/#{it}" }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def message(path)
|
|
49
|
+
if path.end_with?(".json")
|
|
50
|
+
"{ \"message\": \"This feed is generated during build\" }"
|
|
51
|
+
elsif sitemap?(path)
|
|
52
|
+
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n <!-- Sitemap is generated during build -->\n</urlset>"
|
|
53
|
+
else
|
|
54
|
+
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<feed xmlns=\"http://www.w3.org/2005/Atom\">\n <!-- Feed is generated during build -->\n</feed>"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def feed_paths
|
|
59
|
+
@feed_paths ||= Perron::Site.collections.flat_map do |collection|
|
|
60
|
+
config = collection.configuration
|
|
61
|
+
next [] unless config && config[:feeds]
|
|
62
|
+
|
|
63
|
+
config[:feeds].values.filter_map do |feed_config|
|
|
64
|
+
feed_config[:path] if feed_config[:enabled]
|
|
65
|
+
end
|
|
66
|
+
end.compact
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|