ngage 0.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.
Files changed (109) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +22 -0
  3. data/exe/ngage +55 -0
  4. data/lib/ngage.rb +3 -0
  5. data/lib/ngage/jekyll.rb +204 -0
  6. data/lib/ngage/jekyll/cleaner.rb +111 -0
  7. data/lib/ngage/jekyll/collection.rb +235 -0
  8. data/lib/ngage/jekyll/command.rb +103 -0
  9. data/lib/ngage/jekyll/commands/build.rb +93 -0
  10. data/lib/ngage/jekyll/commands/clean.rb +45 -0
  11. data/lib/ngage/jekyll/commands/doctor.rb +173 -0
  12. data/lib/ngage/jekyll/commands/help.rb +34 -0
  13. data/lib/ngage/jekyll/commands/new.rb +157 -0
  14. data/lib/ngage/jekyll/commands/new_theme.rb +42 -0
  15. data/lib/ngage/jekyll/commands/serve.rb +354 -0
  16. data/lib/ngage/jekyll/commands/serve/live_reload_reactor.rb +122 -0
  17. data/lib/ngage/jekyll/commands/serve/livereload_assets/livereload.js +1183 -0
  18. data/lib/ngage/jekyll/commands/serve/servlet.rb +203 -0
  19. data/lib/ngage/jekyll/commands/serve/websockets.rb +81 -0
  20. data/lib/ngage/jekyll/configuration.rb +391 -0
  21. data/lib/ngage/jekyll/converter.rb +54 -0
  22. data/lib/ngage/jekyll/converters/identity.rb +41 -0
  23. data/lib/ngage/jekyll/converters/markdown.rb +116 -0
  24. data/lib/ngage/jekyll/converters/markdown/kramdown_parser.rb +122 -0
  25. data/lib/ngage/jekyll/converters/smartypants.rb +70 -0
  26. data/lib/ngage/jekyll/convertible.rb +253 -0
  27. data/lib/ngage/jekyll/deprecator.rb +50 -0
  28. data/lib/ngage/jekyll/document.rb +503 -0
  29. data/lib/ngage/jekyll/drops/collection_drop.rb +20 -0
  30. data/lib/ngage/jekyll/drops/document_drop.rb +69 -0
  31. data/lib/ngage/jekyll/drops/drop.rb +209 -0
  32. data/lib/ngage/jekyll/drops/excerpt_drop.rb +15 -0
  33. data/lib/ngage/jekyll/drops/jekyll_drop.rb +32 -0
  34. data/lib/ngage/jekyll/drops/site_drop.rb +56 -0
  35. data/lib/ngage/jekyll/drops/static_file_drop.rb +14 -0
  36. data/lib/ngage/jekyll/drops/unified_payload_drop.rb +26 -0
  37. data/lib/ngage/jekyll/drops/url_drop.rb +89 -0
  38. data/lib/ngage/jekyll/entry_filter.rb +127 -0
  39. data/lib/ngage/jekyll/errors.rb +20 -0
  40. data/lib/ngage/jekyll/excerpt.rb +180 -0
  41. data/lib/ngage/jekyll/external.rb +76 -0
  42. data/lib/ngage/jekyll/filters.rb +390 -0
  43. data/lib/ngage/jekyll/filters/date_filters.rb +110 -0
  44. data/lib/ngage/jekyll/filters/grouping_filters.rb +64 -0
  45. data/lib/ngage/jekyll/filters/url_filters.rb +68 -0
  46. data/lib/ngage/jekyll/frontmatter_defaults.rb +233 -0
  47. data/lib/ngage/jekyll/generator.rb +5 -0
  48. data/lib/ngage/jekyll/hooks.rb +106 -0
  49. data/lib/ngage/jekyll/layout.rb +62 -0
  50. data/lib/ngage/jekyll/liquid_extensions.rb +22 -0
  51. data/lib/ngage/jekyll/liquid_renderer.rb +63 -0
  52. data/lib/ngage/jekyll/liquid_renderer/file.rb +56 -0
  53. data/lib/ngage/jekyll/liquid_renderer/table.rb +98 -0
  54. data/lib/ngage/jekyll/log_adapter.rb +151 -0
  55. data/lib/ngage/jekyll/mime.types +825 -0
  56. data/lib/ngage/jekyll/page.rb +185 -0
  57. data/lib/ngage/jekyll/page_without_a_file.rb +14 -0
  58. data/lib/ngage/jekyll/plugin.rb +92 -0
  59. data/lib/ngage/jekyll/plugin_manager.rb +115 -0
  60. data/lib/ngage/jekyll/publisher.rb +23 -0
  61. data/lib/ngage/jekyll/reader.rb +154 -0
  62. data/lib/ngage/jekyll/readers/collection_reader.rb +22 -0
  63. data/lib/ngage/jekyll/readers/data_reader.rb +75 -0
  64. data/lib/ngage/jekyll/readers/layout_reader.rb +70 -0
  65. data/lib/ngage/jekyll/readers/page_reader.rb +25 -0
  66. data/lib/ngage/jekyll/readers/post_reader.rb +72 -0
  67. data/lib/ngage/jekyll/readers/static_file_reader.rb +25 -0
  68. data/lib/ngage/jekyll/readers/theme_assets_reader.rb +51 -0
  69. data/lib/ngage/jekyll/regenerator.rb +195 -0
  70. data/lib/ngage/jekyll/related_posts.rb +52 -0
  71. data/lib/ngage/jekyll/renderer.rb +266 -0
  72. data/lib/ngage/jekyll/site.rb +476 -0
  73. data/lib/ngage/jekyll/static_file.rb +169 -0
  74. data/lib/ngage/jekyll/stevenson.rb +60 -0
  75. data/lib/ngage/jekyll/tags/highlight.rb +108 -0
  76. data/lib/ngage/jekyll/tags/include.rb +226 -0
  77. data/lib/ngage/jekyll/tags/link.rb +40 -0
  78. data/lib/ngage/jekyll/tags/post_url.rb +104 -0
  79. data/lib/ngage/jekyll/theme.rb +73 -0
  80. data/lib/ngage/jekyll/theme_builder.rb +121 -0
  81. data/lib/ngage/jekyll/url.rb +160 -0
  82. data/lib/ngage/jekyll/utils.rb +370 -0
  83. data/lib/ngage/jekyll/utils/ansi.rb +57 -0
  84. data/lib/ngage/jekyll/utils/exec.rb +26 -0
  85. data/lib/ngage/jekyll/utils/internet.rb +37 -0
  86. data/lib/ngage/jekyll/utils/platforms.rb +82 -0
  87. data/lib/ngage/jekyll/utils/thread_event.rb +31 -0
  88. data/lib/ngage/jekyll/utils/win_tz.rb +75 -0
  89. data/lib/ngage/site_template/.gitignore +5 -0
  90. data/lib/ngage/site_template/404.html +25 -0
  91. data/lib/ngage/site_template/_config.yml +47 -0
  92. data/lib/ngage/site_template/_posts/0000-00-00-welcome-to-jekyll.markdown.erb +29 -0
  93. data/lib/ngage/site_template/about.markdown +18 -0
  94. data/lib/ngage/site_template/index.markdown +6 -0
  95. data/lib/ngage/theme_template/CODE_OF_CONDUCT.md.erb +74 -0
  96. data/lib/ngage/theme_template/Gemfile +4 -0
  97. data/lib/ngage/theme_template/LICENSE.txt.erb +21 -0
  98. data/lib/ngage/theme_template/README.md.erb +52 -0
  99. data/lib/ngage/theme_template/_layouts/default.html +1 -0
  100. data/lib/ngage/theme_template/_layouts/page.html +5 -0
  101. data/lib/ngage/theme_template/_layouts/post.html +5 -0
  102. data/lib/ngage/theme_template/example/_config.yml.erb +1 -0
  103. data/lib/ngage/theme_template/example/_post.md +12 -0
  104. data/lib/ngage/theme_template/example/index.html +14 -0
  105. data/lib/ngage/theme_template/example/style.scss +7 -0
  106. data/lib/ngage/theme_template/gitignore.erb +6 -0
  107. data/lib/ngage/theme_template/theme.gemspec.erb +19 -0
  108. data/lib/ngage/version.rb +5 -0
  109. metadata +328 -0
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module Deprecator
5
+ extend self
6
+
7
+ def process(args)
8
+ arg_is_present? args, "--server", "The --server command has been replaced by the \
9
+ 'serve' subcommand."
10
+ arg_is_present? args, "--serve", "The --serve command has been replaced by the \
11
+ 'serve' subcommand."
12
+ arg_is_present? args, "--no-server", "To build Jekyll without launching a server, \
13
+ use the 'build' subcommand."
14
+ arg_is_present? args, "--auto", "The switch '--auto' has been replaced with \
15
+ '--watch'."
16
+ arg_is_present? args, "--no-auto", "To disable auto-replication, simply leave off \
17
+ the '--watch' switch."
18
+ arg_is_present? args, "--pygments", "The 'pygments'settings has been removed in \
19
+ favour of 'highlighter'."
20
+ arg_is_present? args, "--paginate", "The 'paginate' setting can only be set in \
21
+ your config files."
22
+ arg_is_present? args, "--url", "The 'url' setting can only be set in your \
23
+ config files."
24
+ no_subcommand(args)
25
+ end
26
+
27
+ def no_subcommand(args)
28
+ unless args.empty? ||
29
+ args.first !~ %r(!/^--/!) || %w(--help --version).include?(args.first)
30
+ deprecation_message "Jekyll now uses subcommands instead of just switches. \
31
+ Run `jekyll help` to find out more."
32
+ abort
33
+ end
34
+ end
35
+
36
+ def arg_is_present?(args, deprecated_argument, message)
37
+ deprecation_message(message) if args.include?(deprecated_argument)
38
+ end
39
+
40
+ def deprecation_message(message)
41
+ Jekyll.logger.warn "Deprecation:", message
42
+ end
43
+
44
+ def defaults_deprecate_type(old, current)
45
+ Jekyll.logger.warn "Defaults:", "The '#{old}' type has become '#{current}'."
46
+ Jekyll.logger.warn "Defaults:", "Please update your front-matter defaults to use \
47
+ 'type: #{current}'."
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,503 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ class Document
5
+ include Comparable
6
+ extend Forwardable
7
+
8
+ attr_reader :path, :site, :extname, :collection
9
+ attr_accessor :content, :output
10
+
11
+ def_delegator :self, :read_post_data, :post_read
12
+
13
+ YAML_FRONT_MATTER_REGEXP = %r!\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)!m
14
+ DATELESS_FILENAME_MATCHER = %r!^(?:.+/)*(.*)(\.[^.]+)$!
15
+ DATE_FILENAME_MATCHER = %r!^(?:.+/)*(\d{2,4}-\d{1,2}-\d{1,2})-(.*)(\.[^.]+)$!
16
+
17
+ # Create a new Document.
18
+ #
19
+ # path - the path to the file
20
+ # relations - a hash with keys :site and :collection, the values of which
21
+ # are the Jekyll::Site and Jekyll::Collection to which this
22
+ # Document belong.
23
+ #
24
+ # Returns nothing.
25
+ def initialize(path, relations = {})
26
+ @site = relations[:site]
27
+ @path = path
28
+ @extname = File.extname(path)
29
+ @collection = relations[:collection]
30
+ @has_yaml_header = nil
31
+
32
+ if draft?
33
+ categories_from_path("_drafts")
34
+ else
35
+ categories_from_path(collection.relative_directory)
36
+ end
37
+
38
+ data.default_proc = proc do |_, key|
39
+ site.frontmatter_defaults.find(relative_path, collection.label, key)
40
+ end
41
+
42
+ trigger_hooks(:post_init)
43
+ end
44
+
45
+ # Fetch the Document's data.
46
+ #
47
+ # Returns a Hash containing the data. An empty hash is returned if
48
+ # no data was read.
49
+ def data
50
+ @data ||= {}
51
+ end
52
+
53
+ # Merge some data in with this document's data.
54
+ #
55
+ # Returns the merged data.
56
+ def merge_data!(other, source: "YAML front matter")
57
+ merge_categories!(other)
58
+ Utils.deep_merge_hashes!(data, other)
59
+ merge_date!(source)
60
+ data
61
+ end
62
+
63
+ # Returns the document date. If metadata is not present then calculates it
64
+ # based on Jekyll::Site#time or the document file modification time.
65
+ #
66
+ # Return document date string.
67
+ def date
68
+ data["date"] ||= (draft? ? source_file_mtime : site.time)
69
+ end
70
+
71
+ # Return document file modification time in the form of a Time object.
72
+ #
73
+ # Return document file modification Time object.
74
+ def source_file_mtime
75
+ File.mtime(path)
76
+ end
77
+
78
+ # Returns whether the document is a draft. This is only the case if
79
+ # the document is in the 'posts' collection but in a different
80
+ # directory than '_posts'.
81
+ #
82
+ # Returns whether the document is a draft.
83
+ def draft?
84
+ data["draft"] ||= relative_path.index(collection.relative_directory).nil? &&
85
+ collection.label == "posts"
86
+ end
87
+
88
+ # The path to the document, relative to the collections_dir.
89
+ #
90
+ # Returns a String path which represents the relative path from the collections_dir
91
+ # to this document.
92
+ def relative_path
93
+ @relative_path ||= path.sub("#{site.collections_path}/", "")
94
+ end
95
+
96
+ # The output extension of the document.
97
+ #
98
+ # Returns the output extension
99
+ def output_ext
100
+ @output_ext ||= Jekyll::Renderer.new(site, self).output_ext
101
+ end
102
+
103
+ # The base filename of the document, without the file extname.
104
+ #
105
+ # Returns the basename without the file extname.
106
+ def basename_without_ext
107
+ @basename_without_ext ||= File.basename(path, ".*")
108
+ end
109
+
110
+ # The base filename of the document.
111
+ #
112
+ # Returns the base filename of the document.
113
+ def basename
114
+ @basename ||= File.basename(path)
115
+ end
116
+
117
+ # Produces a "cleaned" relative path.
118
+ # The "cleaned" relative path is the relative path without the extname
119
+ # and with the collection's directory removed as well.
120
+ # This method is useful when building the URL of the document.
121
+ #
122
+ # Examples:
123
+ # When relative_path is "_methods/site/generate.md":
124
+ # cleaned_relative_path
125
+ # # => "/site/generate"
126
+ #
127
+ # Returns the cleaned relative path of the document.
128
+ def cleaned_relative_path
129
+ @cleaned_relative_path ||=
130
+ relative_path[0..-extname.length - 1].sub(collection.relative_directory, "")
131
+ end
132
+
133
+ # Determine whether the document is a YAML file.
134
+ #
135
+ # Returns true if the extname is either .yml or .yaml, false otherwise.
136
+ def yaml_file?
137
+ %w(.yaml .yml).include?(extname)
138
+ end
139
+
140
+ # Determine whether the document is an asset file.
141
+ # Asset files include CoffeeScript files and Sass/SCSS files.
142
+ #
143
+ # Returns true if the extname belongs to the set of extensions
144
+ # that asset files use.
145
+ def asset_file?
146
+ sass_file? || coffeescript_file?
147
+ end
148
+
149
+ # Determine whether the document is a Sass file.
150
+ #
151
+ # Returns true if extname == .sass or .scss, false otherwise.
152
+ def sass_file?
153
+ %w(.sass .scss).include?(extname)
154
+ end
155
+
156
+ # Determine whether the document is a CoffeeScript file.
157
+ #
158
+ # Returns true if extname == .coffee, false otherwise.
159
+ def coffeescript_file?
160
+ extname == ".coffee"
161
+ end
162
+
163
+ # Determine whether the file should be rendered with Liquid.
164
+ #
165
+ # Returns false if the document is either an asset file or a yaml file,
166
+ # or if the document doesn't contain any Liquid Tags or Variables,
167
+ # true otherwise.
168
+ def render_with_liquid?
169
+ return false if data["render_with_liquid"] == false
170
+
171
+ !(coffeescript_file? || yaml_file? || !Utils.has_liquid_construct?(content))
172
+ end
173
+
174
+ # Determine whether the file should be rendered with a layout.
175
+ #
176
+ # Returns true if the Front Matter specifies that `layout` is set to `none`.
177
+ def no_layout?
178
+ data["layout"] == "none"
179
+ end
180
+
181
+ # Determine whether the file should be placed into layouts.
182
+ #
183
+ # Returns false if the document is set to `layouts: none`, or is either an
184
+ # asset file or a yaml file. Returns true otherwise.
185
+ def place_in_layout?
186
+ !(asset_file? || yaml_file? || no_layout?)
187
+ end
188
+
189
+ # The URL template where the document would be accessible.
190
+ #
191
+ # Returns the URL template for the document.
192
+ def url_template
193
+ collection.url_template
194
+ end
195
+
196
+ # Construct a Hash of key-value pairs which contain a mapping between
197
+ # a key in the URL template and the corresponding value for this document.
198
+ #
199
+ # Returns the Hash of key-value pairs for replacement in the URL.
200
+ def url_placeholders
201
+ @url_placeholders ||= Drops::UrlDrop.new(self)
202
+ end
203
+
204
+ # The permalink for this Document.
205
+ # Permalink is set via the data Hash.
206
+ #
207
+ # Returns the permalink or nil if no permalink was set in the data.
208
+ def permalink
209
+ data && data.is_a?(Hash) && data["permalink"]
210
+ end
211
+
212
+ # The computed URL for the document. See `Jekyll::URL#to_s` for more details.
213
+ #
214
+ # Returns the computed URL for the document.
215
+ def url
216
+ @url ||= URL.new(
217
+ :template => url_template,
218
+ :placeholders => url_placeholders,
219
+ :permalink => permalink
220
+ ).to_s
221
+ end
222
+
223
+ def [](key)
224
+ data[key]
225
+ end
226
+
227
+ # The full path to the output file.
228
+ #
229
+ # base_directory - the base path of the output directory
230
+ #
231
+ # Returns the full path to the output file of this document.
232
+ def destination(base_directory)
233
+ dest = site.in_dest_dir(base_directory)
234
+ path = site.in_dest_dir(dest, URL.unescape_path(url))
235
+ if url.end_with? "/"
236
+ path = File.join(path, "index.html")
237
+ else
238
+ path << output_ext unless path.end_with? output_ext
239
+ end
240
+ path
241
+ end
242
+
243
+ # Write the generated Document file to the destination directory.
244
+ #
245
+ # dest - The String path to the destination dir.
246
+ #
247
+ # Returns nothing.
248
+ def write(dest)
249
+ path = destination(dest)
250
+ FileUtils.mkdir_p(File.dirname(path))
251
+ Jekyll.logger.debug "Writing:", path
252
+ File.write(path, output, :mode => "wb")
253
+
254
+ trigger_hooks(:post_write)
255
+ end
256
+
257
+ # Whether the file is published or not, as indicated in YAML front-matter
258
+ #
259
+ # Returns 'false' if the 'published' key is specified in the
260
+ # YAML front-matter and is 'false'. Otherwise returns 'true'.
261
+ def published?
262
+ !(data.key?("published") && data["published"] == false)
263
+ end
264
+
265
+ # Read in the file and assign the content and data based on the file contents.
266
+ # Merge the frontmatter of the file with the frontmatter default
267
+ # values
268
+ #
269
+ # Returns nothing.
270
+ def read(opts = {})
271
+ Jekyll.logger.debug "Reading:", relative_path
272
+
273
+ if yaml_file?
274
+ @data = SafeYAML.load_file(path)
275
+ else
276
+ begin
277
+ merge_defaults
278
+ read_content(opts)
279
+ read_post_data
280
+ rescue StandardError => e
281
+ handle_read_error(e)
282
+ end
283
+ end
284
+ end
285
+
286
+ # Create a Liquid-understandable version of this Document.
287
+ #
288
+ # Returns a Hash representing this Document's data.
289
+ def to_liquid
290
+ @to_liquid ||= Drops::DocumentDrop.new(self)
291
+ end
292
+
293
+ # The inspect string for this document.
294
+ # Includes the relative path and the collection label.
295
+ #
296
+ # Returns the inspect string for this document.
297
+ def inspect
298
+ "#<Jekyll::Document #{relative_path} collection=#{collection.label}>"
299
+ end
300
+
301
+ # The string representation for this document.
302
+ #
303
+ # Returns the content of the document
304
+ def to_s
305
+ output || content || "NO CONTENT"
306
+ end
307
+
308
+ # Compare this document against another document.
309
+ # Comparison is a comparison between the 2 paths of the documents.
310
+ #
311
+ # Returns -1, 0, +1 or nil depending on whether this doc's path is less than,
312
+ # equal or greater than the other doc's path. See String#<=> for more details.
313
+ def <=>(other)
314
+ return nil unless other.respond_to?(:data)
315
+
316
+ cmp = data["date"] <=> other.data["date"]
317
+ cmp = path <=> other.path if cmp.nil? || cmp.zero?
318
+ cmp
319
+ end
320
+
321
+ # Determine whether this document should be written.
322
+ # Based on the Collection to which it belongs.
323
+ #
324
+ # True if the document has a collection and if that collection's #write?
325
+ # method returns true, and if the site's Publisher will publish the document.
326
+ # False otherwise.
327
+ def write?
328
+ collection&.write? && site.publisher.publish?(self)
329
+ end
330
+
331
+ # The Document excerpt_separator, from the YAML Front-Matter or site
332
+ # default excerpt_separator value
333
+ #
334
+ # Returns the document excerpt_separator
335
+ def excerpt_separator
336
+ (data["excerpt_separator"] || site.config["excerpt_separator"]).to_s
337
+ end
338
+
339
+ # Whether to generate an excerpt
340
+ #
341
+ # Returns true if the excerpt separator is configured.
342
+ def generate_excerpt?
343
+ !excerpt_separator.empty?
344
+ end
345
+
346
+ def next_doc
347
+ pos = collection.docs.index { |post| post.equal?(self) }
348
+ collection.docs[pos + 1] if pos && pos < collection.docs.length - 1
349
+ end
350
+
351
+ def previous_doc
352
+ pos = collection.docs.index { |post| post.equal?(self) }
353
+ collection.docs[pos - 1] if pos && pos.positive?
354
+ end
355
+
356
+ def trigger_hooks(hook_name, *args)
357
+ Jekyll::Hooks.trigger collection.label.to_sym, hook_name, self, *args if collection
358
+ Jekyll::Hooks.trigger :documents, hook_name, self, *args
359
+ end
360
+
361
+ def id
362
+ @id ||= File.join(File.dirname(url), (data["slug"] || basename_without_ext).to_s)
363
+ end
364
+
365
+ # Calculate related posts.
366
+ #
367
+ # Returns an Array of related Posts.
368
+ def related_posts
369
+ @related_posts ||= Jekyll::RelatedPosts.new(self).build
370
+ end
371
+
372
+ # Override of normal respond_to? to match method_missing's logic for
373
+ # looking in @data.
374
+ def respond_to?(method, include_private = false)
375
+ data.key?(method.to_s) || super
376
+ end
377
+
378
+ # Override of method_missing to check in @data for the key.
379
+ def method_missing(method, *args, &blck)
380
+ if data.key?(method.to_s)
381
+ Jekyll::Deprecator.deprecation_message "Document##{method} is now a key "\
382
+ "in the #data hash."
383
+ Jekyll::Deprecator.deprecation_message "Called by #{caller(0..0)}."
384
+ data[method.to_s]
385
+ else
386
+ super
387
+ end
388
+ end
389
+
390
+ def respond_to_missing?(method, *)
391
+ data.key?(method.to_s) || super
392
+ end
393
+
394
+ # Add superdirectories of the special_dir to categories.
395
+ # In the case of es/_posts, 'es' is added as a category.
396
+ # In the case of _posts/es, 'es' is NOT added as a category.
397
+ #
398
+ # Returns nothing.
399
+ def categories_from_path(special_dir)
400
+ superdirs = relative_path.sub(%r!#{special_dir}(.*)!, "")
401
+ .split(File::SEPARATOR)
402
+ .reject do |c|
403
+ c.empty? || c == special_dir || c == basename
404
+ end
405
+ merge_data!({ "categories" => superdirs }, :source => "file path")
406
+ end
407
+
408
+ def populate_categories
409
+ merge_data!(
410
+ "categories" => (
411
+ Array(data["categories"]) + Utils.pluralized_array_from_hash(
412
+ data, "category", "categories"
413
+ )
414
+ ).map(&:to_s).flatten.uniq
415
+ )
416
+ end
417
+
418
+ def populate_tags
419
+ merge_data!(
420
+ "tags" => Utils.pluralized_array_from_hash(data, "tag", "tags").flatten
421
+ )
422
+ end
423
+
424
+ private
425
+
426
+ def merge_categories!(other)
427
+ if other.key?("categories") && !other["categories"].nil?
428
+ other["categories"] = other["categories"].split if other["categories"].is_a?(String)
429
+ other["categories"] = (data["categories"] || []) | other["categories"]
430
+ end
431
+ end
432
+
433
+ def merge_date!(source)
434
+ if data.key?("date")
435
+ data["date"] = Utils.parse_date(
436
+ data["date"].to_s,
437
+ "Document '#{relative_path}' does not have a valid date in the #{source}."
438
+ )
439
+ end
440
+ end
441
+
442
+ def merge_defaults
443
+ defaults = @site.frontmatter_defaults.all(
444
+ relative_path,
445
+ collection.label.to_sym
446
+ )
447
+ merge_data!(defaults, :source => "front matter defaults") unless defaults.empty?
448
+ end
449
+
450
+ def read_content(opts)
451
+ self.content = File.read(path, Utils.merged_file_read_opts(site, opts))
452
+ if content =~ YAML_FRONT_MATTER_REGEXP
453
+ self.content = $POSTMATCH
454
+ data_file = SafeYAML.load(Regexp.last_match(1))
455
+ merge_data!(data_file, :source => "YAML front matter") if data_file
456
+ end
457
+ end
458
+
459
+ def read_post_data
460
+ populate_title
461
+ populate_categories
462
+ populate_tags
463
+ generate_excerpt
464
+ end
465
+
466
+ def handle_read_error(error)
467
+ if error.is_a? Psych::SyntaxError
468
+ Jekyll.logger.error "Error:", "YAML Exception reading #{path}: #{error.message}"
469
+ else
470
+ Jekyll.logger.error "Error:", "could not read file #{path}: #{error.message}"
471
+ end
472
+
473
+ if site.config["strict_front_matter"] || error.is_a?(Jekyll::Errors::FatalException)
474
+ raise error
475
+ end
476
+ end
477
+
478
+ def populate_title
479
+ if relative_path =~ DATE_FILENAME_MATCHER
480
+ date, slug, ext = Regexp.last_match.captures
481
+ modify_date(date)
482
+ elsif relative_path =~ DATELESS_FILENAME_MATCHER
483
+ slug, ext = Regexp.last_match.captures
484
+ end
485
+
486
+ # Try to ensure the user gets a title.
487
+ data["title"] ||= Utils.titleize_slug(slug)
488
+ # Only overwrite slug & ext if they aren't specified.
489
+ data["slug"] ||= slug
490
+ data["ext"] ||= ext
491
+ end
492
+
493
+ def modify_date(date)
494
+ if !data["date"] || data["date"].to_i == site.time.to_i
495
+ merge_data!({ "date" => date }, :source => "filename")
496
+ end
497
+ end
498
+
499
+ def generate_excerpt
500
+ data["excerpt"] ||= Jekyll::Excerpt.new(self) if generate_excerpt?
501
+ end
502
+ end
503
+ end