dimples 4.3.2 → 5.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 +5 -5
- data/bin/dimples +11 -31
- data/lib/dimples.rb +4 -15
- data/lib/dimples/category.rb +3 -3
- data/lib/dimples/configuration.rb +32 -48
- data/lib/dimples/errors.rb +5 -7
- data/lib/dimples/frontable.rb +8 -13
- data/lib/dimples/page.rb +40 -34
- data/lib/dimples/pager.rb +80 -0
- data/lib/dimples/plugin.rb +40 -0
- data/lib/dimples/post.rb +14 -30
- data/lib/dimples/renderer.rb +48 -0
- data/lib/dimples/site.rb +180 -201
- data/lib/dimples/template.rb +10 -9
- data/lib/dimples/version.rb +1 -1
- metadata +41 -29
- data/lib/dimples/logger.rb +0 -33
- data/lib/dimples/pagination.rb +0 -112
- data/lib/dimples/renderable.rb +0 -50
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dimples
|
4
|
+
class Plugin
|
5
|
+
EVENTS = %i[
|
6
|
+
post_write
|
7
|
+
page_write
|
8
|
+
].freeze
|
9
|
+
|
10
|
+
def self.inherited(subclass)
|
11
|
+
(@subclasses ||= []) << subclass
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.plugins(site)
|
15
|
+
@plugins ||= @subclasses&.map { |subclass| subclass.new(site) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.process(site, event, item, &block)
|
19
|
+
plugins(site)&.each do |plugin|
|
20
|
+
plugin.process(event, item, &block) if plugin.supports_event?(event)
|
21
|
+
end
|
22
|
+
|
23
|
+
yield if block_given?
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(site)
|
27
|
+
@site = site
|
28
|
+
end
|
29
|
+
|
30
|
+
def process(event, item, &block); end
|
31
|
+
|
32
|
+
def supported_events
|
33
|
+
[]
|
34
|
+
end
|
35
|
+
|
36
|
+
def supports_event?(event)
|
37
|
+
supported_events.include?(event)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/dimples/post.rb
CHANGED
@@ -1,47 +1,31 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Dimples
|
4
|
-
# A class that models a single site post.
|
5
4
|
class Post < Page
|
6
|
-
|
7
|
-
attr_accessor :summary
|
8
|
-
attr_accessor :categories
|
9
|
-
attr_accessor :year
|
10
|
-
attr_accessor :month
|
11
|
-
attr_accessor :day
|
12
|
-
attr_accessor :previous_post
|
13
|
-
attr_accessor :next_post
|
14
|
-
attr_reader :date
|
15
|
-
|
16
|
-
FILENAME_DATE = /(\d{4})-(\d{2})-(\d{2})-(.+)/
|
5
|
+
POST_FILENAME = /(\d{4})-(\d{2})-(\d{2})-(.+)/
|
17
6
|
|
18
7
|
def initialize(site, path)
|
19
|
-
super
|
8
|
+
super
|
20
9
|
|
21
|
-
parts = File.basename(path, File.extname(path)).match(
|
10
|
+
parts = File.basename(path, File.extname(path)).match(POST_FILENAME)
|
22
11
|
|
23
|
-
@
|
24
|
-
@slug = parts[4]
|
25
|
-
@layout = @site.config[:layouts][:post]
|
12
|
+
@metadata[:layout] ||= @site.config.layouts.post
|
26
13
|
|
27
|
-
|
28
|
-
|
29
|
-
@
|
30
|
-
@date.strftime(@site.output_paths[:posts]),
|
31
|
-
@slug.to_s
|
32
|
-
)
|
14
|
+
@metadata[:date] = Date.new(parts[1].to_i, parts[2].to_i, parts[3].to_i)
|
15
|
+
@metadata[:slug] = parts[4]
|
16
|
+
@metadata[:categories] ||= []
|
33
17
|
end
|
34
18
|
|
35
|
-
def
|
36
|
-
@
|
19
|
+
def year
|
20
|
+
@year ||= @metadata[:date].strftime('%Y')
|
21
|
+
end
|
37
22
|
|
38
|
-
|
39
|
-
@month
|
40
|
-
@day = @date.strftime('%d')
|
23
|
+
def month
|
24
|
+
@month ||= @metadata[:date].strftime('%m')
|
41
25
|
end
|
42
26
|
|
43
|
-
def
|
44
|
-
|
27
|
+
def day
|
28
|
+
@day ||= @metadata[:date].strftime('%d')
|
45
29
|
end
|
46
30
|
end
|
47
31
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dimples
|
4
|
+
class Renderer
|
5
|
+
def initialize(site, source)
|
6
|
+
@site = site
|
7
|
+
@source = source
|
8
|
+
end
|
9
|
+
|
10
|
+
def render(context = {}, body = nil)
|
11
|
+
context[:site] ||= @site
|
12
|
+
context[:pagination] ||= nil
|
13
|
+
|
14
|
+
output = engine.render(scope, context) { body }.strip
|
15
|
+
@source.metadata[:rendered_contents] = output
|
16
|
+
|
17
|
+
template = @site.templates[@source.metadata[:layout]]
|
18
|
+
return output if template.nil?
|
19
|
+
|
20
|
+
template.render(context, output)
|
21
|
+
end
|
22
|
+
|
23
|
+
def engine
|
24
|
+
@engine ||= begin
|
25
|
+
callback = proc { @source.contents }
|
26
|
+
|
27
|
+
if @source.path
|
28
|
+
extension = File.extname(@source.path)
|
29
|
+
options = @site.config.rendering[extension.to_sym] || {}
|
30
|
+
|
31
|
+
Tilt.new(@source.path, options, &callback)
|
32
|
+
else
|
33
|
+
Tilt::StringTemplate.new(&callback)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def scope
|
39
|
+
@scope ||= Object.new.tap do |scope|
|
40
|
+
scope.instance_variable_set(:@site, @site)
|
41
|
+
|
42
|
+
scope.class.send(:define_method, :render) do |layout, locals = {}|
|
43
|
+
@site.templates[layout]&.render(locals)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/dimples/site.rb
CHANGED
@@ -1,283 +1,262 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Dimples
|
4
|
-
# A class that models a single site.
|
5
4
|
class Site
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
attr_accessor :pages
|
15
|
-
attr_accessor :posts
|
16
|
-
attr_accessor :latest_post
|
17
|
-
attr_accessor :errors
|
5
|
+
attr_reader :config
|
6
|
+
attr_reader :categories
|
7
|
+
attr_reader :errors
|
8
|
+
attr_reader :paths
|
9
|
+
attr_reader :posts
|
10
|
+
attr_reader :pages
|
11
|
+
attr_reader :templates
|
12
|
+
attr_reader :latest_post
|
18
13
|
|
19
14
|
def initialize(config = {})
|
20
|
-
@config =
|
21
|
-
|
22
|
-
@
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
@source_paths = { root: File.expand_path(@config[:source_path]) }
|
32
|
-
@output_paths = { site: File.expand_path(@config[:destination_path]) }
|
33
|
-
|
34
|
-
%w[pages posts public templates].each do |path|
|
35
|
-
@source_paths[path.to_sym] = File.join(@source_paths[:root], path)
|
15
|
+
@config = Hashie::Mash.new(Configuration.defaults).deep_merge(config)
|
16
|
+
|
17
|
+
@paths = {}.tap do |paths|
|
18
|
+
paths[:base] = Dir.pwd
|
19
|
+
paths[:output] = File.join(paths[:base], @config.paths.output)
|
20
|
+
paths[:sources] = {}.tap do |sources|
|
21
|
+
%w[pages posts public templates].each do |type|
|
22
|
+
sources[type.to_sym] = File.join(paths[:base], type)
|
23
|
+
end
|
24
|
+
end
|
36
25
|
end
|
37
26
|
|
38
|
-
|
39
|
-
@output_paths[path.to_sym] = File.join(
|
40
|
-
@output_paths[:site], @config[:paths][path.to_sym]
|
41
|
-
)
|
42
|
-
end
|
27
|
+
prepare
|
43
28
|
end
|
44
29
|
|
45
30
|
def generate
|
46
|
-
|
47
|
-
prepare_output_directory
|
48
|
-
|
49
|
-
publish_pages unless @pages.count.zero?
|
31
|
+
prepare
|
50
32
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
publish_categories if @config[:generation][:categories]
|
55
|
-
end
|
33
|
+
read_templates
|
34
|
+
read_posts
|
35
|
+
read_pages
|
56
36
|
|
37
|
+
create_output_directory
|
57
38
|
copy_assets
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
39
|
+
|
40
|
+
publish_posts
|
41
|
+
publish_pages
|
42
|
+
publish_archives
|
43
|
+
publish_categories if @config.generation.categories
|
44
|
+
rescue PublishingError, RenderingError, GenerationError => error
|
45
|
+
@errors << error
|
62
46
|
end
|
63
47
|
|
64
|
-
def
|
65
|
-
@
|
48
|
+
def inspect
|
49
|
+
"#<#{self.class} @paths=#{paths}>"
|
66
50
|
end
|
67
51
|
|
68
|
-
|
69
|
-
Dimples.logger.debug('Scanning files...') if @config[:verbose_logging]
|
52
|
+
private
|
70
53
|
|
71
|
-
|
72
|
-
|
73
|
-
scan_posts
|
74
|
-
end
|
54
|
+
def prepare
|
55
|
+
@archives = { year: {}, month: {}, day: {} }
|
75
56
|
|
76
|
-
|
77
|
-
|
78
|
-
template = Dimples::Template.new(self, path)
|
57
|
+
@categories = {}
|
58
|
+
@templates = {}
|
79
59
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
else
|
84
|
-
relative_path = parent_path.sub(@source_paths[:templates], '')[1..-1]
|
85
|
-
slug = relative_path.sub(File::SEPARATOR, '.') + ".#{template.slug}"
|
86
|
-
end
|
60
|
+
@pages = []
|
61
|
+
@posts = []
|
62
|
+
@errors = []
|
87
63
|
|
88
|
-
|
89
|
-
end
|
64
|
+
@latest_post = nil
|
90
65
|
end
|
91
66
|
|
92
|
-
def
|
93
|
-
|
94
|
-
@pages << scan_page(path)
|
95
|
-
end
|
96
|
-
end
|
67
|
+
def read_templates
|
68
|
+
@templates = {}
|
97
69
|
|
98
|
-
|
99
|
-
|
100
|
-
|
70
|
+
globbed_files(@paths[:sources][:templates]).each do |path|
|
71
|
+
basename = File.basename(path, File.extname(path))
|
72
|
+
dir_name = File.dirname(path)
|
101
73
|
|
102
|
-
|
103
|
-
|
74
|
+
unless dir_name == @paths[:sources][:templates]
|
75
|
+
basename = dir_name.split(File::SEPARATOR)[-1] + '.' + basename
|
76
|
+
end
|
104
77
|
|
105
|
-
|
106
|
-
@posts << scan_post(path)
|
78
|
+
@templates[basename] = Template.new(self, path)
|
107
79
|
end
|
80
|
+
end
|
108
81
|
|
109
|
-
|
110
|
-
|
111
|
-
|
82
|
+
def read_posts
|
83
|
+
@posts = globbed_files(@paths[:sources][:posts]).sort.map do |path|
|
84
|
+
Post.new(self, path).tap do |post|
|
85
|
+
add_archive_post(post)
|
86
|
+
categorise_post(post)
|
112
87
|
end
|
88
|
+
end.reverse
|
113
89
|
|
114
|
-
|
115
|
-
|
116
|
-
|
90
|
+
@latest_post = @posts[0]
|
91
|
+
end
|
92
|
+
|
93
|
+
def read_pages
|
94
|
+
@pages = globbed_files(@paths[:sources][:pages]).sort.map do |path|
|
95
|
+
Page.new(self, path)
|
117
96
|
end
|
97
|
+
end
|
118
98
|
|
119
|
-
|
99
|
+
def add_archive_post(post)
|
100
|
+
archive_year(post.year) << post
|
101
|
+
archive_month(post.year, post.month) << post
|
102
|
+
archive_day(post.year, post.month, post.day) << post
|
120
103
|
end
|
121
104
|
|
122
|
-
def
|
123
|
-
|
124
|
-
|
125
|
-
@categories[slug] ||= Dimples::Category.new(self, slug)
|
126
|
-
@categories[slug].posts << post
|
127
|
-
end
|
105
|
+
def categorise_post(post)
|
106
|
+
post.categories.each do |slug|
|
107
|
+
slug_sym = slug.to_sym
|
128
108
|
|
129
|
-
|
130
|
-
|
131
|
-
archive_day(post.year, post.month, post.day) << post
|
109
|
+
@categories[slug_sym] ||= Category.new(self, slug)
|
110
|
+
@categories[slug_sym].posts << post
|
132
111
|
end
|
133
112
|
end
|
134
113
|
|
135
|
-
def
|
136
|
-
if Dir.exist?(@
|
137
|
-
|
138
|
-
|
114
|
+
def create_output_directory
|
115
|
+
FileUtils.remove_dir(@paths[:output]) if Dir.exist?(@paths[:output])
|
116
|
+
Dir.mkdir(@paths[:output])
|
117
|
+
rescue StandardError => e
|
118
|
+
message = "Couldn't prepare the output directory (#{e.message})"
|
119
|
+
raise GenerationError, message
|
120
|
+
end
|
139
121
|
|
140
|
-
|
122
|
+
def copy_assets
|
123
|
+
return unless Dir.exist?(@paths[:sources][:public])
|
124
|
+
FileUtils.cp_r(File.join(@paths[:sources][:public], '.'), @paths[:output])
|
141
125
|
rescue StandardError => e
|
142
|
-
|
143
|
-
raise Errors::GenerationError, error_message
|
126
|
+
raise GenerationError, "Failed to copy site assets (#{e.message})"
|
144
127
|
end
|
145
128
|
|
146
129
|
def publish_posts
|
147
|
-
|
148
|
-
|
130
|
+
@posts.each do |post|
|
131
|
+
Plugin.process(self, :post_write, post) do
|
132
|
+
path = File.join(
|
133
|
+
@paths[:output],
|
134
|
+
post.date.strftime(@config.paths.posts),
|
135
|
+
post.slug
|
136
|
+
)
|
137
|
+
|
138
|
+
post.write(path)
|
139
|
+
end
|
149
140
|
end
|
150
141
|
|
151
|
-
@posts.
|
152
|
-
|
153
|
-
paginate(
|
154
|
-
self,
|
155
|
-
@posts,
|
156
|
-
@output_paths[:archives],
|
157
|
-
@config[:layouts][:posts]
|
158
|
-
)
|
159
|
-
|
160
|
-
publish_posts_feeds if @config[:generation][:feeds]
|
142
|
+
publish_feeds(@posts, @paths[:output]) if @config.generation.main_feed
|
161
143
|
end
|
162
144
|
|
163
145
|
def publish_pages
|
164
|
-
|
165
|
-
|
146
|
+
@pages.each do |page|
|
147
|
+
Plugin.process(self, :page_write, page) do
|
148
|
+
path = if page.path
|
149
|
+
File.dirname(page.path).sub(
|
150
|
+
@paths[:sources][:pages],
|
151
|
+
@paths[:output]
|
152
|
+
)
|
153
|
+
else
|
154
|
+
@paths[:output]
|
155
|
+
end
|
156
|
+
|
157
|
+
page.write(path)
|
158
|
+
end
|
166
159
|
end
|
167
|
-
|
168
|
-
@pages.each(&:write)
|
169
160
|
end
|
170
161
|
|
171
|
-
def
|
172
|
-
if @config
|
173
|
-
|
162
|
+
def publish_archives
|
163
|
+
if @config.generation.archives
|
164
|
+
paginate_posts(
|
165
|
+
@posts,
|
166
|
+
File.join(@paths[:output], @config.paths.archives),
|
167
|
+
@config.layouts.archive
|
168
|
+
)
|
174
169
|
end
|
175
170
|
|
176
|
-
|
177
|
-
|
171
|
+
%w[year month day].each do |date_type|
|
172
|
+
next unless @config.generation["#{date_type}_archives"]
|
173
|
+
publish_date_archive(date_type.to_sym)
|
174
|
+
end
|
175
|
+
end
|
178
176
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
177
|
+
def publish_date_archive(date_type)
|
178
|
+
@archives[date_type].each do |date, posts|
|
179
|
+
date_parts = date.split('-')
|
180
|
+
path = File.join(@paths[:output], @config.paths.archives, date_parts)
|
183
181
|
|
184
|
-
|
185
|
-
|
186
|
-
category.posts,
|
182
|
+
paginate_posts(
|
183
|
+
posts.reverse,
|
187
184
|
path,
|
188
|
-
@config
|
189
|
-
|
185
|
+
@config.layouts.date_archive,
|
186
|
+
page: {
|
187
|
+
title: posts[0].date.strftime(@config.date_formats[date_type]),
|
188
|
+
archive_date: posts[0].date,
|
189
|
+
archive_type: date_type
|
190
|
+
}
|
190
191
|
)
|
191
192
|
end
|
192
|
-
|
193
|
-
publish_category_feeds if @config[:generation][:category_feeds]
|
194
193
|
end
|
195
194
|
|
196
|
-
def
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
dates = { year: year }
|
205
|
-
dates[:month] = month if month
|
206
|
-
dates[:day] = day if day
|
207
|
-
|
208
|
-
path = File.join(@output_paths[:archives], dates.values)
|
195
|
+
def publish_categories
|
196
|
+
@categories.each_value do |category|
|
197
|
+
path = File.join(
|
198
|
+
@paths[:output],
|
199
|
+
@config.paths.categories,
|
200
|
+
category.slug
|
201
|
+
)
|
209
202
|
|
210
|
-
|
211
|
-
|
203
|
+
category_posts = category.posts.reverse
|
204
|
+
context = { page: { title: category.name, category: category } }
|
212
205
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
206
|
+
paginate_posts(
|
207
|
+
category_posts,
|
208
|
+
path,
|
209
|
+
@config.layouts.category,
|
210
|
+
context
|
211
|
+
)
|
217
212
|
|
218
|
-
|
219
|
-
end
|
213
|
+
publish_feeds(category_posts, path, context)
|
220
214
|
end
|
221
215
|
end
|
222
216
|
|
223
|
-
def
|
224
|
-
|
225
|
-
|
217
|
+
def paginate_posts(posts, path, layout, context = {})
|
218
|
+
pager = Pager.new(
|
219
|
+
path.sub(@paths[:output], '') + '/',
|
220
|
+
posts,
|
221
|
+
@config.pagination
|
222
|
+
)
|
226
223
|
|
227
|
-
|
224
|
+
pager.each do |index|
|
225
|
+
page = Page.new(self)
|
226
|
+
page.layout = layout
|
228
227
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
228
|
+
page_prefix = @config.pagination.page_prefix
|
229
|
+
page_path = if index == 1
|
230
|
+
path
|
231
|
+
else
|
232
|
+
File.join(path, "#{page_prefix}#{index}")
|
233
|
+
end
|
233
234
|
|
234
|
-
|
235
|
+
page.write(
|
236
|
+
page_path,
|
237
|
+
context.merge(pagination: pager.to_context)
|
238
|
+
)
|
235
239
|
end
|
236
240
|
end
|
237
241
|
|
238
|
-
def
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
+
def publish_feeds(posts, path, context = {})
|
243
|
+
@config.feed_formats.each do |feed_format|
|
244
|
+
feed_layout = "feeds.#{feed_format}"
|
245
|
+
next unless @templates.key?(feed_layout)
|
242
246
|
|
243
|
-
|
244
|
-
@categories.each_value do |category|
|
245
|
-
path = File.join(@output_paths[:categories], category.slug)
|
246
|
-
posts = category.posts[0..@config[:pagination][:per_page] - 1]
|
247
|
+
page = Page.new(self)
|
247
248
|
|
248
|
-
|
249
|
-
|
250
|
-
|
249
|
+
page.layout = feed_layout
|
250
|
+
page.feed_posts = posts.slice(0, @config.pagination.per_page)
|
251
|
+
page.filename = 'feed'
|
252
|
+
page.extension = feed_format
|
251
253
|
|
252
|
-
|
253
|
-
@feed_templates ||= @templates.keys.select { |k| k =~ /^feeds\./ }
|
254
|
-
end
|
255
|
-
|
256
|
-
def copy_assets
|
257
|
-
if Dir.exist?(@source_paths[:public])
|
258
|
-
Dimples.logger.debug('Copying assets...') if @config[:verbose_logging]
|
259
|
-
|
260
|
-
path = File.join(@source_paths[:public], '.')
|
261
|
-
FileUtils.cp_r(path, @output_paths[:site])
|
254
|
+
page.write(path, context)
|
262
255
|
end
|
263
|
-
rescue StandardError => e
|
264
|
-
raise Errors::GenerationError, "Site assets failed to copy (#{e.message})"
|
265
256
|
end
|
266
257
|
|
267
|
-
def
|
268
|
-
|
269
|
-
"@source_paths=#{@source_paths} " \
|
270
|
-
"@output_paths=#{@output_paths}>"
|
271
|
-
end
|
272
|
-
|
273
|
-
private
|
274
|
-
|
275
|
-
def post_class
|
276
|
-
@post_class ||= if @config[:class_overrides][:post]
|
277
|
-
Object.const_get(config[:class_overrides][:post])
|
278
|
-
else
|
279
|
-
Dimples::Post
|
280
|
-
end
|
258
|
+
def globbed_files(path)
|
259
|
+
Dir.glob(File.join(path, '**', '*.*'))
|
281
260
|
end
|
282
261
|
|
283
262
|
def archive_year(year)
|