cloudcannon-jekyll 2.3.4 → 3.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.
@@ -1,281 +1,53 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "jekyll"
4
- require "fileutils"
5
- require_relative "reader"
3
+ require 'jekyll'
4
+ require 'json'
5
+ require 'fileutils'
6
+ require_relative 'logger'
7
+ require_relative 'config'
8
+ require_relative 'generators/info'
6
9
 
7
10
  module CloudCannonJekyll
8
- # Generates JSON files containing build config and build output details
11
+ # Generates JSON file with build information
9
12
  class Generator < Jekyll::Generator
10
- # Override the Jekyll::Plugin spaceship to push our plugin to the very end
11
13
  priority :lowest
14
+
15
+ # Override the Jekyll::Plugin spaceship to run at the end
12
16
  def self.<=>(*)
13
17
  1
14
18
  end
15
19
 
16
- # rubocop:disable Metrics/MethodLength
17
20
  def generate(site)
18
- log "⭐️ Starting #{"cloudcannon-jekyll".blue}"
19
-
21
+ Logger.info "⭐️ Starting #{'cloudcannon-jekyll'.blue}"
20
22
  @site = site
21
- @reader = Reader.new(@site)
22
-
23
- migrate_legacy_config
24
- pages = generate_pages
25
- collections_config = generate_collections_config(pages)
26
- drafts = add_blogging_config(collections_config)
27
- add_collection_paths(collections_config)
28
-
29
- collections = generate_collections(collections_config, pages, drafts)
30
- remove_empty_collection_config(collections_config, collections)
31
-
32
- add_data_config(collections_config)
33
- data = generate_data
34
-
35
- generate_file("info", @site.site_payload.merge({
36
- "pwd" => Dir.pwd,
37
- "version" => "0.0.2",
38
- "gem_version" => CloudCannonJekyll::VERSION,
39
- "config" => @site.config,
40
- "collections_config" => collections_config,
41
- "collections" => collections,
42
- "data" => data,
43
- }))
44
- end
45
- # rubocop:enable Metrics/MethodLength
46
-
47
- # rubocop:disable Metrics/CyclomaticComplexity
48
- def generate_collections_config(pages)
49
- collections = @site.config["collections"]&.dup || {}
50
- collections_config = @site.config.dig("cloudcannon", "collections")&.dup || {}
51
-
52
- collections.each_key do |key|
53
- # Workaround for empty collection configurations
54
- defaults = collections[key] || { "output" => false }
55
- collections_config[key] = (collections_config[key] || {}).merge(defaults)
56
- end
57
-
58
- unless pages.empty? || collections.key?("pages")
59
- pages_defaults = {
60
- "output" => true,
61
- "filter" => "strict",
62
- "path" => "",
63
- }
64
-
65
- collections_config["pages"] = pages_defaults.merge(collections_config["pages"] || {})
66
- end
67
-
68
- collections_config
69
- end
70
-
71
- # rubocop:disable Metrics/PerceivedComplexity
72
- def generate_collections(collections_config, pages, drafts)
73
- split_posts = group_by_category_folder(all_posts, "posts")
74
- split_drafts = group_by_category_folder(drafts, "drafts")
75
-
76
- collections = {}
77
- collections_config.each_key do |key|
78
- collections[key] = if key == "posts" || key.end_with?("/posts")
79
- split_posts[key]
80
- elsif key == "drafts" || key.end_with?("/drafts")
81
- split_drafts[key]
82
- else
83
- @site.collections[key]&.docs
84
- end
85
-
86
- collections[key] ||= []
87
- end
88
-
89
- has_collection_pages = collections.key?("pages") && !collections["pages"].empty?
90
- collections["pages"] = pages unless pages.empty? || has_collection_pages
91
- collections
92
- end
93
- # rubocop:enable Metrics/PerceivedComplexity
94
- # rubocop:enable Metrics/CyclomaticComplexity
95
-
96
- def generate_data
97
- cc_data = @site.config.dig("cloudcannon", "data")
98
- data = if cc_data == true
99
- @site.data&.dup
100
- elsif cc_data&.is_a?(Hash)
101
- @site.data&.select { |key, _| cc_data.key?(key) }
102
- end
103
-
104
- data ||= {}
105
- data["categories"] ||= @site.categories.keys
106
- data["tags"] ||= @site.tags.keys
107
-
108
- data.each_key do |key|
109
- log "💾 Processed #{key.bold} data set"
110
- end
111
-
112
- data
113
- end
114
-
115
- def generate_pages
116
- html_pages = @site.pages.select do |page|
117
- page.html? || page.url.end_with?("/")
118
- end
119
-
120
- static_pages = @site.static_files.select do |static_page|
121
- JsonifyFilter::STATIC_EXTENSIONS.include?(static_page.extname)
122
- end
123
-
124
- html_pages + static_pages
125
- end
126
-
127
- def collections_dir
128
- return "" if Jekyll::VERSION.start_with? "2."
129
-
130
- @site.config["collections_dir"] || ""
131
- end
132
-
133
- def data_dir
134
- @site.config["data_dir"] || "_data"
135
- end
136
-
137
- def all_posts
138
- posts = @site.posts || @site.collections["posts"]
139
- posts.class.method_defined?(:docs) ? posts.docs : posts
140
- end
141
-
142
- def group_by_category_folder(collection, key)
143
- split_path = "/_#{key}/"
144
- collection.group_by do |doc|
145
- parts = doc.relative_path.split(split_path)
146
- if parts.length > 1
147
- "#{parts.first}/#{key}".sub(%r!^\/+!, "")
148
- else
149
- key
150
- end
151
- end
152
- end
153
-
154
- def add_category_folder_config(collections_config, posts_config = {})
155
- seen = {}
156
-
157
- all_posts.map do |post|
158
- parts = post.relative_path.split("/_posts/")
159
- path = parts.first
160
-
161
- # Ignore unless it's an unseen category folder post
162
- next if parts.length < 2 || path.empty? || seen[path]
163
-
164
- # Could check this to ensure raw files exist since posts can be generated without files
165
- # next if @reader.read_posts(parts[0]).empty?
166
-
167
- seen[path] = true
168
- folder = path.sub(%r!^\/+!, "")
169
- collections_path = "#{collections_dir}/#{folder}".gsub(%r!\/+!, "/").sub(%r!^\/+!, "")
170
-
171
- collections_config["#{folder}/posts"] = posts_config.merge({
172
- "path" => "#{collections_path}/_posts",
173
- })
174
-
175
- # Adding the category draft config like this isn't ideal, since you could have drafts
176
- # without posts, but it's a decent trade off vs looking for _drafts folders
177
- collections_config["#{folder}/drafts"] = posts_config.merge({
178
- "path" => "#{collections_path}/_drafts",
179
- })
180
-
181
- path
182
- end
183
- end
184
-
185
- def remove_empty_collection_config(collections_config, collections)
186
- cc_collections = @site.config.dig("cloudcannon", "collections") || {}
187
-
188
- collections_config.each_key do |key|
189
- if collections[key].empty? && !cc_collections.key?(key)
190
- log "📂 #{"Ignored".yellow} #{key.bold} collection"
191
- collections_config.delete(key)
192
- else
193
- log "📁 Processed #{key.bold} collection with #{collections[key]&.length || 0} files"
194
- end
195
- end
196
- end
197
-
198
- def migrate_legacy_config
199
- add_legacy_explore_groups
200
- end
201
-
202
- # Support for the deprecated _explore configuration
203
- def add_legacy_explore_groups
204
- unless @site.config.key?("_collection_groups")
205
- @site.config["_collection_groups"] = @site.config.dig("_explore", "groups")&.dup
206
- end
207
- end
208
-
209
- # Add data to collections config if raw data files exist
210
- def add_data_config(collections_config)
211
- data_files = @reader.read_data(data_dir)
212
- collections_config["data"] = { "path" => data_dir } if data_files&.keys&.any?
213
- end
214
-
215
- # Add posts/drafts to collections config
216
- def add_blogging_config(collections_config)
217
- collections_config["posts"] = { "output" => true } if Jekyll::VERSION.start_with? "2."
218
- drafts = @reader.read_drafts(collections_dir)
219
-
220
- if drafts.any? && !collections_config.key?("drafts")
221
- collections_config["drafts"] = { "output" => !!@site.show_drafts }
222
- end
223
-
224
- folders = add_category_folder_config(collections_config, collections_config["posts"])
225
- folders.compact.each do |folder|
226
- drafts += @reader.read_drafts(folder)
227
- end
228
-
229
- drafts
230
- end
231
-
232
- # Add path to each collection config
233
- def add_collection_paths(collections_config)
234
- collections_config.each do |key, collection|
235
- collection["path"] ||= File.join(collections_dir, "_#{key}").sub(%r!^\/+!, "")
236
- end
23
+ config = Config.new(site)
24
+ generate_file('info', Info.new.generate_info(site, config.read))
237
25
  end
238
26
 
239
27
  def generate_file(filename, data)
240
28
  dest = destination_path(filename)
241
29
  FileUtils.mkdir_p(File.dirname(dest))
242
- File.open(dest, "w") { |file| file.write(file_content(filename, data)) }
30
+ File.open(dest, 'w') { |file| file.write(file_content(data)) }
243
31
  @site.keep_files ||= []
244
32
  @site.keep_files << path(filename)
245
- log "🏁 Generated #{path(filename).bold} #{"successfully".green}"
246
- end
247
-
248
- def log(str)
249
- Jekyll.logger.info("CloudCannon:", str)
250
- end
251
-
252
- def version_path_suffix
253
- return "-2.x" if Jekyll::VERSION.start_with? "2."
254
- return "-3.0-4.x" if Jekyll::VERSION.match? %r!3\.[0-4]\.!
255
-
256
- ""
33
+ Logger.info "🏁 Generated #{path(filename).bold} #{'successfully'.green}"
257
34
  end
258
35
 
259
- def path(filename, suffix = "")
260
- "_cloudcannon/#{filename}#{suffix}.json"
36
+ def path(filename)
37
+ "_cloudcannon/#{filename}.json"
261
38
  end
262
39
 
263
40
  def source_path(filename)
264
- File.expand_path(path(filename, version_path_suffix), File.dirname(__FILE__))
41
+ file_path = path(filename)
42
+ File.expand_path(file_path, File.dirname(__FILE__))
265
43
  end
266
44
 
267
45
  def destination_path(filename)
268
46
  Jekyll.sanitized_path(@site.dest, path(filename))
269
47
  end
270
48
 
271
- def file_content(filename, data)
272
- page = PageWithoutAFile.new(@site, File.dirname(__FILE__), "", path(filename))
273
- page.content = File.read(source_path(filename))
274
- page.data["layout"] = nil
275
- page.data["sitemap"] = false
276
- page.data["permalink"] = "/#{path(filename)}"
277
- page.render({}, data)
278
- page.output
49
+ def file_content(data)
50
+ JSON.pretty_generate(data)
279
51
  end
280
52
  end
281
53
  end
@@ -0,0 +1,267 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require_relative '../readers/reader'
5
+ require_relative '../logger'
6
+ require_relative 'paths'
7
+
8
+ module CloudCannonJekyll
9
+ STATIC_EXTENSIONS = ['.html', '.htm'].freeze
10
+ IS_JEKYLL_2_X_X = Jekyll::VERSION.start_with?('2.').freeze
11
+ IS_JEKYLL_3_04_X = Jekyll::VERSION.match?(/3\.[0-4]\./).freeze
12
+
13
+ # Helper functions for generating collection configuration and summaries
14
+ class Collections
15
+ def initialize(site, config)
16
+ @site = site
17
+ @config = config
18
+ @reader = Reader.new(site)
19
+ @collections_dir = Paths.collections_dir(site)
20
+ @data_dir = Paths.data_dir(site)
21
+ @split_posts = group_by_category_folder(all_posts, 'posts')
22
+ @split_drafts = group_by_category_folder(all_drafts, 'drafts')
23
+ end
24
+
25
+ def generate_collections_config
26
+ collections = @site.config['collections'] || {}
27
+ collections_config = @config['collections_config']&.dup || {}
28
+
29
+ return collections_config if @config['collections_config_override']
30
+
31
+ if collections.is_a?(Array)
32
+ collections = collections.each_with_object({}) { |key, memo| memo[key] = {} }
33
+ end
34
+
35
+ defaults = {
36
+ 'data' => {
37
+ 'path' => @data_dir,
38
+ 'output' => false
39
+ },
40
+ 'posts' => {
41
+ 'output' => true
42
+ },
43
+ 'drafts' => {
44
+ 'output' => !!@site.show_drafts
45
+ }
46
+ }
47
+
48
+ unless collections.key?('pages') && !collections['pages'].empty?
49
+ defaults['pages'] = {
50
+ 'path' => '',
51
+ 'output' => true,
52
+ 'filter' => 'strict'
53
+ }
54
+ end
55
+
56
+ collection_keys = (defaults.keys + collections.keys).uniq
57
+
58
+ collection_keys.each do |key|
59
+ processed = (defaults[key] || {})
60
+ .merge(collections[key] || {})
61
+ .merge(collections_config[key] || {})
62
+
63
+ processed['output'] ||= false
64
+ processed['auto_discovered'] = !collections_config.key?(key)
65
+ processed['path'] ||= File.join(@collections_dir, "_#{key}")
66
+ processed['path'] = processed['path'].sub(%r{^/+}, '')
67
+
68
+ Config.rename_legacy_collection_config_keys(processed)
69
+
70
+ collections_config[key] = processed
71
+ end
72
+
73
+ @split_posts.each_key do |key|
74
+ posts_path = @split_posts[key]&.first&.relative_path&.sub(%r{(^|/)_posts.*}, '\1_posts')
75
+ next unless posts_path
76
+
77
+ defaults = {
78
+ 'auto_discovered' => !collections_config.key?(key),
79
+ 'path' => File.join(@collections_dir, posts_path).sub(%r{^/+}, ''),
80
+ 'output' => true
81
+ }
82
+
83
+ collections_config[key] = defaults.merge(collections_config[key] || {})
84
+ end
85
+
86
+ @split_drafts.each_key do |key|
87
+ drafts_path = @split_drafts[key]&.first&.relative_path&.sub(%r{(^|/)_drafts.*}, '\1_drafts')
88
+ next unless drafts_path
89
+
90
+ defaults = {
91
+ 'auto_discovered' => !collections_config.key?(key),
92
+ 'path' => File.join(@collections_dir, drafts_path).sub(%r{^/+}, ''),
93
+ 'output' => !!@site.show_drafts
94
+ }
95
+
96
+ collections_config[key] = defaults.merge(collections_config[key] || {})
97
+ end
98
+
99
+ collections_config
100
+ end
101
+
102
+ def drafts_paths
103
+ paths = @split_posts.keys.map do |key|
104
+ File.join('/', @collections_dir, key.sub(/posts$/, '_drafts'))
105
+ end
106
+
107
+ paths.empty? ? [File.join('/', @collections_dir, '_drafts')] : paths
108
+ end
109
+
110
+ def generate_collections(collections_config)
111
+ collections = {}
112
+
113
+ collections_config.each_key do |key|
114
+ next if key == 'data'
115
+
116
+ collections[key] = if key == 'posts' || key.end_with?('/posts')
117
+ @split_posts[key]
118
+ elsif key == 'drafts' || key.end_with?('/drafts')
119
+ @split_drafts[key]
120
+ else
121
+ @site.collections[key]&.docs
122
+ end
123
+
124
+ collections[key] ||= []
125
+ collections[key] = collections[key].map do |doc|
126
+ document_to_json(doc, key)
127
+ end
128
+ end
129
+
130
+ if collections.key?('pages') && collections['pages'].empty?
131
+ collections['pages'] = all_pages.map do |doc|
132
+ document_to_json(doc, 'pages')
133
+ end
134
+ end
135
+
136
+ collections
137
+ end
138
+
139
+ def remove_empty_collection_config(collections_config, collections)
140
+ collections_config.each do |key, collection_config|
141
+ should_delete = if key == 'data'
142
+ !data_files?
143
+ else
144
+ collections[key].empty? && collection_config['auto_discovered']
145
+ end
146
+
147
+ if should_delete
148
+ Logger.info "📂 #{'Ignored'.yellow} #{key.bold} collection"
149
+ collections_config.delete(key)
150
+ else
151
+ count = collections[key]&.length || 0
152
+ Logger.info "📁 Processed #{key.bold} collection with #{count} files"
153
+ end
154
+ end
155
+ end
156
+
157
+ def document_type(doc)
158
+ if IS_JEKYLL_2_X_X && (doc.instance_of?(Jekyll::Post) || doc.instance_of?(Jekyll::Draft))
159
+ :posts
160
+ elsif doc.respond_to? :type
161
+ doc.type
162
+ elsif doc.respond_to?(:collection)
163
+ doc.collection.label.to_sym
164
+ elsif doc.instance_of?(Jekyll::Page)
165
+ :pages
166
+ end
167
+ end
168
+
169
+ def legacy_document_data(doc)
170
+ data = doc.data.merge(
171
+ {
172
+ categories: doc.categories,
173
+ tags: doc.tags,
174
+ date: doc.date
175
+ }
176
+ )
177
+
178
+ data['slug'] = doc.slug if doc.respond_to?(:slug)
179
+ data
180
+ end
181
+
182
+ def legacy_doc?(doc)
183
+ (IS_JEKYLL_3_04_X && doc.instance_of?(Jekyll::Document) && doc.collection.label == 'posts') ||
184
+ (IS_JEKYLL_2_X_X && (doc.instance_of?(Jekyll::Draft) || doc.instance_of?(Jekyll::Post)))
185
+ end
186
+
187
+ def document_data(doc)
188
+ data = if legacy_doc?(doc)
189
+ legacy_document_data(doc)
190
+ elsif doc.respond_to?(:data)
191
+ doc.data
192
+ else
193
+ {}
194
+ end
195
+
196
+ defaults = @site.frontmatter_defaults.all(doc.relative_path, document_type(doc))
197
+ defaults.merge(data)
198
+ end
199
+
200
+ def document_url(doc)
201
+ doc.respond_to?(:url) ? doc.url : doc.relative_path
202
+ end
203
+
204
+ def document_path(doc)
205
+ path = if doc.respond_to?(:collection) && doc.collection
206
+ File.join(@collections_dir, doc.relative_path)
207
+ else
208
+ doc.relative_path
209
+ end
210
+
211
+ path.sub(%r{^/+}, '')
212
+ end
213
+
214
+ def document_to_json(doc, collection)
215
+ base = document_data(doc).merge(
216
+ {
217
+ 'path' => document_path(doc),
218
+ 'url' => document_url(doc),
219
+ 'collection' => collection
220
+ }
221
+ )
222
+
223
+ base['id'] = doc.id if doc.respond_to? :id
224
+ base
225
+ end
226
+
227
+ def all_posts
228
+ posts = @site.posts || @site.collections['posts']
229
+ posts.class.method_defined?(:docs) ? posts.docs : posts
230
+ end
231
+
232
+ def all_drafts
233
+ drafts_paths.reduce([]) do |drafts, drafts_path|
234
+ base_path = drafts_path.gsub(%r{(^|/)_drafts}, '')
235
+ drafts + @reader.read_drafts(base_path)
236
+ end
237
+ end
238
+
239
+ def all_pages
240
+ html_pages = @site.pages.select do |page|
241
+ page.html? || page.url.end_with?('/')
242
+ end
243
+
244
+ static_pages = @site.static_files.select do |static_page|
245
+ STATIC_EXTENSIONS.include?(static_page.extname)
246
+ end
247
+
248
+ html_pages + static_pages
249
+ end
250
+
251
+ def data_files?
252
+ @reader.read_data(@data_dir)&.keys&.any?
253
+ end
254
+
255
+ def group_by_category_folder(collection, key)
256
+ split_path = "/_#{key}/"
257
+ collection.group_by do |doc|
258
+ parts = doc.relative_path.split(split_path)
259
+ if parts.length > 1
260
+ File.join(parts.first, key).sub(%r{^/+}, '')
261
+ else
262
+ key
263
+ end
264
+ end
265
+ end
266
+ end
267
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../logger'
4
+ require_relative 'paths'
5
+
6
+ module CloudCannonJekyll
7
+ # Generator functions for site data
8
+ class Data
9
+ def initialize(site, config)
10
+ @site = site
11
+ @config = config
12
+ end
13
+
14
+ def generate_data
15
+ data_config = @config['data_config']
16
+ data = case data_config
17
+ when true
18
+ @site.data&.dup
19
+ when Hash
20
+ @site.data&.select { |key, _| data_config.key?(key) && data_config[key] }
21
+ end
22
+
23
+ data ||= {}
24
+ data['categories'] ||= @site.categories.keys
25
+ data['tags'] ||= @site.tags.keys
26
+
27
+ data.each_key do |key|
28
+ Logger.info "💾 Processed #{key.bold} data set"
29
+ end
30
+
31
+ data
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jekyll'
4
+ require 'fileutils'
5
+ require_relative '../logger'
6
+ require_relative 'collections'
7
+ require_relative 'data'
8
+ require_relative 'paths'
9
+
10
+ module CloudCannonJekyll
11
+ # Generates a summary of a Jekyll site
12
+ class Info
13
+ def generate_info(site, config)
14
+ @site = site
15
+ @site_config = site.config
16
+ @config = config
17
+ @data_dir = Paths.data_dir(site)
18
+ @collections_dir = Paths.collections_dir(site)
19
+
20
+ collections_generator = Collections.new(site, config)
21
+ collections_config = collections_generator.generate_collections_config
22
+ collections = collections_generator.generate_collections(collections_config)
23
+ collections_generator.remove_empty_collection_config(collections_config, collections)
24
+
25
+ {
26
+ time: site.time.iso8601,
27
+ version: '0.0.3',
28
+ cloudcannon: generate_cloudcannon,
29
+ generator: generate_generator,
30
+ paths: generate_paths,
31
+ collections_config: collections_config,
32
+ collection_groups: @config['collection_groups'],
33
+ collections: collections,
34
+ data: generate_data,
35
+ source: @config['source'] || '',
36
+ timezone: @config['timezone'],
37
+ base_url: @config['base_url'] || '',
38
+ editor: @config['editor'],
39
+ source_editor: @config['source_editor'],
40
+ _inputs: @config['_inputs'],
41
+ _editables: @config['_editables'],
42
+ _select_data: @config['_select_data'],
43
+ _structures: @config['_structures'],
44
+
45
+ # Deprecated
46
+ _array_structures: @config['_array_structures'],
47
+ _comments: @config['_comments'],
48
+ _enabled_editors: @config['_enabled_editors'],
49
+ _instance_values: @config['_instance_values'],
50
+ _options: @config['_options'],
51
+
52
+ # Jekyll-only
53
+ defaults: @site_config['defaults']
54
+ }.compact
55
+ end
56
+
57
+ def generate_data
58
+ data_generator = Data.new(@site, @config)
59
+ data_generator.generate_data
60
+ end
61
+
62
+ def generate_cloudcannon
63
+ {
64
+ name: 'cloudcannon-jekyll',
65
+ version: CloudCannonJekyll::VERSION
66
+ }
67
+ end
68
+
69
+ def generate_paths
70
+ {
71
+ static: '',
72
+ uploads: @config.dig('paths', 'uploads') || 'uploads',
73
+ data: @data_dir,
74
+ collections: @collections_dir,
75
+ layouts: @site_config['layouts_dir'] || '_layouts'
76
+ }
77
+ end
78
+
79
+ def generate_generator
80
+ {
81
+ name: 'jekyll',
82
+ version: Jekyll::VERSION,
83
+ environment: Jekyll.env,
84
+ metadata: {
85
+ markdown: @site_config['markdown'],
86
+ kramdown: @site_config['kramdown'],
87
+ commonmark: @site_config['commonmark']
88
+ }
89
+ }
90
+ end
91
+ end
92
+ end