jekyllplusadmin 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.
Files changed (95) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +80 -0
  3. data/LICENSE +21 -0
  4. data/README.markdown +60 -0
  5. data/bin/jekyll +51 -0
  6. data/lib/jekyll.rb +180 -0
  7. data/lib/jekyll/cleaner.rb +105 -0
  8. data/lib/jekyll/collection.rb +205 -0
  9. data/lib/jekyll/command.rb +65 -0
  10. data/lib/jekyll/commands/build.rb +77 -0
  11. data/lib/jekyll/commands/clean.rb +42 -0
  12. data/lib/jekyll/commands/doctor.rb +114 -0
  13. data/lib/jekyll/commands/help.rb +31 -0
  14. data/lib/jekyll/commands/new.rb +82 -0
  15. data/lib/jekyll/commands/serve.rb +235 -0
  16. data/lib/jekyll/commands/serve/servlet.rb +61 -0
  17. data/lib/jekyll/configuration.rb +323 -0
  18. data/lib/jekyll/converter.rb +48 -0
  19. data/lib/jekyll/converters/identity.rb +21 -0
  20. data/lib/jekyll/converters/markdown.rb +92 -0
  21. data/lib/jekyll/converters/markdown/kramdown_parser.rb +117 -0
  22. data/lib/jekyll/converters/markdown/rdiscount_parser.rb +33 -0
  23. data/lib/jekyll/converters/markdown/redcarpet_parser.rb +102 -0
  24. data/lib/jekyll/converters/smartypants.rb +34 -0
  25. data/lib/jekyll/convertible.rb +297 -0
  26. data/lib/jekyll/deprecator.rb +46 -0
  27. data/lib/jekyll/document.rb +444 -0
  28. data/lib/jekyll/drops/collection_drop.rb +22 -0
  29. data/lib/jekyll/drops/document_drop.rb +27 -0
  30. data/lib/jekyll/drops/drop.rb +176 -0
  31. data/lib/jekyll/drops/jekyll_drop.rb +21 -0
  32. data/lib/jekyll/drops/site_drop.rb +38 -0
  33. data/lib/jekyll/drops/unified_payload_drop.rb +25 -0
  34. data/lib/jekyll/drops/url_drop.rb +83 -0
  35. data/lib/jekyll/entry_filter.rb +72 -0
  36. data/lib/jekyll/errors.rb +10 -0
  37. data/lib/jekyll/excerpt.rb +127 -0
  38. data/lib/jekyll/external.rb +59 -0
  39. data/lib/jekyll/filters.rb +367 -0
  40. data/lib/jekyll/frontmatter_defaults.rb +188 -0
  41. data/lib/jekyll/generator.rb +3 -0
  42. data/lib/jekyll/hooks.rb +101 -0
  43. data/lib/jekyll/layout.rb +49 -0
  44. data/lib/jekyll/liquid_extensions.rb +22 -0
  45. data/lib/jekyll/liquid_renderer.rb +39 -0
  46. data/lib/jekyll/liquid_renderer/file.rb +50 -0
  47. data/lib/jekyll/liquid_renderer/table.rb +94 -0
  48. data/lib/jekyll/log_adapter.rb +115 -0
  49. data/lib/jekyll/mime.types +800 -0
  50. data/lib/jekyll/page.rb +180 -0
  51. data/lib/jekyll/plugin.rb +96 -0
  52. data/lib/jekyll/plugin_manager.rb +95 -0
  53. data/lib/jekyll/publisher.rb +21 -0
  54. data/lib/jekyll/reader.rb +126 -0
  55. data/lib/jekyll/readers/collection_reader.rb +20 -0
  56. data/lib/jekyll/readers/data_reader.rb +69 -0
  57. data/lib/jekyll/readers/layout_reader.rb +53 -0
  58. data/lib/jekyll/readers/page_reader.rb +21 -0
  59. data/lib/jekyll/readers/post_reader.rb +62 -0
  60. data/lib/jekyll/readers/static_file_reader.rb +21 -0
  61. data/lib/jekyll/regenerator.rb +175 -0
  62. data/lib/jekyll/related_posts.rb +56 -0
  63. data/lib/jekyll/renderer.rb +191 -0
  64. data/lib/jekyll/site.rb +391 -0
  65. data/lib/jekyll/static_file.rb +141 -0
  66. data/lib/jekyll/stevenson.rb +58 -0
  67. data/lib/jekyll/tags/highlight.rb +122 -0
  68. data/lib/jekyll/tags/include.rb +190 -0
  69. data/lib/jekyll/tags/post_url.rb +88 -0
  70. data/lib/jekyll/url.rb +136 -0
  71. data/lib/jekyll/utils.rb +287 -0
  72. data/lib/jekyll/utils/ansi.rb +59 -0
  73. data/lib/jekyll/utils/platforms.rb +30 -0
  74. data/lib/jekyll/version.rb +3 -0
  75. data/lib/site_template/.gitignore +3 -0
  76. data/lib/site_template/_config.yml +21 -0
  77. data/lib/site_template/_includes/footer.html +38 -0
  78. data/lib/site_template/_includes/head.html +12 -0
  79. data/lib/site_template/_includes/header.html +27 -0
  80. data/lib/site_template/_includes/icon-github.html +1 -0
  81. data/lib/site_template/_includes/icon-github.svg +1 -0
  82. data/lib/site_template/_includes/icon-twitter.html +1 -0
  83. data/lib/site_template/_includes/icon-twitter.svg +1 -0
  84. data/lib/site_template/_layouts/default.html +20 -0
  85. data/lib/site_template/_layouts/page.html +14 -0
  86. data/lib/site_template/_layouts/post.html +15 -0
  87. data/lib/site_template/_posts/0000-00-00-welcome-to-jekyll.markdown.erb +25 -0
  88. data/lib/site_template/_sass/_base.scss +206 -0
  89. data/lib/site_template/_sass/_layout.scss +242 -0
  90. data/lib/site_template/_sass/_syntax-highlighting.scss +71 -0
  91. data/lib/site_template/about.md +15 -0
  92. data/lib/site_template/css/main.scss +53 -0
  93. data/lib/site_template/feed.xml +30 -0
  94. data/lib/site_template/index.html +23 -0
  95. metadata +252 -0
@@ -0,0 +1,297 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'set'
4
+
5
+ # Convertible provides methods for converting a pagelike item
6
+ # from a certain type of markup into actual content
7
+ #
8
+ # Requires
9
+ # self.site -> Jekyll::Site
10
+ # self.content
11
+ # self.content=
12
+ # self.data=
13
+ # self.ext=
14
+ # self.output=
15
+ # self.name
16
+ # self.path
17
+ # self.type -> :page, :post or :draft
18
+
19
+ module Jekyll
20
+ module Convertible
21
+ # Returns the contents as a String.
22
+ def to_s
23
+ content || ''
24
+ end
25
+
26
+ # Whether the file is published or not, as indicated in YAML front-matter
27
+ def published?
28
+ !(data.key?('published') && data['published'] == false)
29
+ end
30
+
31
+ # Read the YAML frontmatter.
32
+ #
33
+ # base - The String path to the dir containing the file.
34
+ # name - The String filename of the file.
35
+ # opts - optional parameter to File.read, default at site configs
36
+ #
37
+ # Returns nothing.
38
+ def read_yaml(base, name, opts = {})
39
+ filename = File.join(base, name)
40
+
41
+ begin
42
+ self.content = File.read(site.in_source_dir(base, name),
43
+ Utils.merged_file_read_opts(site, opts))
44
+ if content =~ /\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)/m
45
+ self.content = $POSTMATCH
46
+ self.data = SafeYAML.load(Regexp.last_match(1))
47
+ end
48
+ rescue SyntaxError => e
49
+ Jekyll.logger.warn "YAML Exception reading #{filename}: #{e.message}"
50
+ rescue Exception => e
51
+ Jekyll.logger.warn "Error reading file #{filename}: #{e.message}"
52
+ end
53
+
54
+ self.data ||= {}
55
+
56
+ validate_data! filename
57
+ validate_permalink! filename
58
+
59
+ self.data
60
+ end
61
+
62
+ def validate_data!(filename)
63
+ unless self.data.is_a?(Hash)
64
+ raise Errors::InvalidYAMLFrontMatterError, "Invalid YAML front matter in #{filename}"
65
+ end
66
+ end
67
+
68
+ def validate_permalink!(filename)
69
+ if self.data['permalink'] && self.data['permalink'].size == 0
70
+ raise Errors::InvalidPermalinkError, "Invalid permalink in #{filename}"
71
+ end
72
+ end
73
+
74
+ # Transform the contents based on the content type.
75
+ #
76
+ # Returns the transformed contents.
77
+ def transform
78
+ converters.reduce(content) do |output, converter|
79
+ begin
80
+ converter.convert output
81
+ rescue => e
82
+ Jekyll.logger.error "Conversion error:", "#{converter.class} encountered an error while converting '#{path}':"
83
+ Jekyll.logger.error("", e.to_s)
84
+ raise e
85
+ end
86
+ end
87
+ end
88
+
89
+ # Determine the extension depending on content_type.
90
+ #
91
+ # Returns the String extension for the output file.
92
+ # e.g. ".html" for an HTML output file.
93
+ def output_ext
94
+ Jekyll::Renderer.new(site, self).output_ext
95
+ end
96
+
97
+ # Determine which converter to use based on this convertible's
98
+ # extension.
99
+ #
100
+ # Returns the Converter instance.
101
+ def converters
102
+ @converters ||= site.converters.select { |c| c.matches(ext) }.sort
103
+ end
104
+
105
+ # Render Liquid in the content
106
+ #
107
+ # content - the raw Liquid content to render
108
+ # payload - the payload for Liquid
109
+ # info - the info for Liquid
110
+ #
111
+ # Returns the converted content
112
+ def render_liquid(content, payload, info, path)
113
+ site.liquid_renderer.file(path).parse(content).render!(payload, info)
114
+ rescue Tags::IncludeTagError => e
115
+ Jekyll.logger.error "Liquid Exception:", "#{e.message} in #{e.path}, included in #{path || self.path}"
116
+ raise e
117
+ rescue Exception => e
118
+ Jekyll.logger.error "Liquid Exception:", "#{e.message} in #{path || self.path}"
119
+ raise e
120
+ end
121
+
122
+ # Convert this Convertible's data to a Hash suitable for use by Liquid.
123
+ #
124
+ # Returns the Hash representation of this Convertible.
125
+ def to_liquid(attrs = nil)
126
+ further_data = Hash[(attrs || self.class::ATTRIBUTES_FOR_LIQUID).map do |attribute|
127
+ [attribute, send(attribute)]
128
+ end]
129
+
130
+ defaults = site.frontmatter_defaults.all(relative_path, type)
131
+ Utils.deep_merge_hashes defaults, Utils.deep_merge_hashes(data, further_data)
132
+ end
133
+
134
+ # The type of a document,
135
+ # i.e., its classname downcase'd and to_sym'd.
136
+ #
137
+ # Returns the type of self.
138
+ def type
139
+ if is_a?(Page)
140
+ :pages
141
+ end
142
+ end
143
+
144
+ # returns the owner symbol for hook triggering
145
+ def hook_owner
146
+ if is_a?(Page)
147
+ :pages
148
+ end
149
+ end
150
+
151
+ # Determine whether the document is an asset file.
152
+ # Asset files include CoffeeScript files and Sass/SCSS files.
153
+ #
154
+ # Returns true if the extname belongs to the set of extensions
155
+ # that asset files use.
156
+ def asset_file?
157
+ sass_file? || coffeescript_file?
158
+ end
159
+
160
+ # Determine whether the document is a Sass file.
161
+ #
162
+ # Returns true if extname == .sass or .scss, false otherwise.
163
+ def sass_file?
164
+ %w(.sass .scss).include?(ext)
165
+ end
166
+
167
+ # Determine whether the document is a CoffeeScript file.
168
+ #
169
+ # Returns true if extname == .coffee, false otherwise.
170
+ def coffeescript_file?
171
+ '.coffee'.eql?(ext)
172
+ end
173
+
174
+ # Determine whether the file should be rendered with Liquid.
175
+ #
176
+ # Always returns true.
177
+ def render_with_liquid?
178
+ true
179
+ end
180
+
181
+ # Determine whether the file should be placed into layouts.
182
+ #
183
+ # Returns false if the document is an asset file.
184
+ def place_in_layout?
185
+ !asset_file?
186
+ end
187
+
188
+ # Checks if the layout specified in the document actually exists
189
+ #
190
+ # layout - the layout to check
191
+ #
192
+ # Returns true if the layout is invalid, false if otherwise
193
+ def invalid_layout?(layout)
194
+ !data["layout"].nil? && layout.nil? && !(self.is_a? Jekyll::Excerpt)
195
+ end
196
+
197
+ # Recursively render layouts
198
+ #
199
+ # layouts - a list of the layouts
200
+ # payload - the payload for Liquid
201
+ # info - the info for Liquid
202
+ #
203
+ # Returns nothing
204
+ def render_all_layouts(layouts, payload, info)
205
+ # recursively render layouts
206
+ layout = layouts[data["layout"]]
207
+
208
+ Jekyll.logger.warn("Build Warning:", "Layout '#{data["layout"]}' requested in #{path} does not exist.") if invalid_layout? layout
209
+
210
+ used = Set.new([layout])
211
+
212
+ while layout
213
+ Jekyll.logger.debug "Rendering Layout:", path
214
+ payload["content"] = output
215
+ payload["layout"] = Utils.deep_merge_hashes(payload["layout"] || {}, layout.data)
216
+
217
+ self.output = render_liquid(layout.content,
218
+ payload,
219
+ info,
220
+ File.join(site.config['layouts_dir'], layout.name))
221
+
222
+ # Add layout to dependency tree
223
+ site.regenerator.add_dependency(
224
+ site.in_source_dir(path),
225
+ site.in_source_dir(layout.path)
226
+ )
227
+
228
+ if layout = layouts[layout.data["layout"]]
229
+ if used.include?(layout)
230
+ layout = nil # avoid recursive chain
231
+ else
232
+ used << layout
233
+ end
234
+ end
235
+ end
236
+ end
237
+
238
+ # Add any necessary layouts to this convertible document.
239
+ #
240
+ # payload - The site payload Drop or Hash.
241
+ # layouts - A Hash of {"name" => "layout"}.
242
+ #
243
+ # Returns nothing.
244
+ def do_layout(payload, layouts)
245
+ Jekyll.logger.debug "Rendering:", self.relative_path
246
+
247
+ Jekyll.logger.debug "Pre-Render Hooks:", self.relative_path
248
+ Jekyll::Hooks.trigger hook_owner, :pre_render, self, payload
249
+ info = { :filters => [Jekyll::Filters], :registers => { :site => site, :page => payload["page"] } }
250
+
251
+ # render and transform content (this becomes the final content of the object)
252
+ payload["highlighter_prefix"] = converters.first.highlighter_prefix
253
+ payload["highlighter_suffix"] = converters.first.highlighter_suffix
254
+
255
+ if render_with_liquid?
256
+ Jekyll.logger.debug "Rendering Liquid:", self.relative_path
257
+ self.content = render_liquid(content, payload, info, path)
258
+ end
259
+ Jekyll.logger.debug "Rendering Markup:", self.relative_path
260
+ self.content = transform
261
+
262
+ # output keeps track of what will finally be written
263
+ self.output = content
264
+
265
+ render_all_layouts(layouts, payload, info) if place_in_layout?
266
+ Jekyll.logger.debug "Post-Render Hooks:", self.relative_path
267
+ Jekyll::Hooks.trigger hook_owner, :post_render, self
268
+ end
269
+
270
+ # Write the generated page file to the destination directory.
271
+ #
272
+ # dest - The String path to the destination dir.
273
+ #
274
+ # Returns nothing.
275
+ def write(dest)
276
+ path = destination(dest)
277
+ FileUtils.mkdir_p(File.dirname(path))
278
+ File.open(path, 'wb') do |f|
279
+ f.write(output)
280
+ end
281
+ Jekyll::Hooks.trigger hook_owner, :post_write, self
282
+ end
283
+
284
+ # Accessor for data properties by Liquid.
285
+ #
286
+ # property - The String name of the property to retrieve.
287
+ #
288
+ # Returns the String value or nil if the property isn't included.
289
+ def [](property)
290
+ if self.class::ATTRIBUTES_FOR_LIQUID.include?(property)
291
+ send(property)
292
+ else
293
+ data[property]
294
+ end
295
+ end
296
+ end
297
+ end
@@ -0,0 +1,46 @@
1
+ module Jekyll
2
+ module Deprecator
3
+ extend self
4
+
5
+ def process(args)
6
+ arg_is_present? args, "--server", "The --server command has been replaced by the \
7
+ 'serve' subcommand."
8
+ arg_is_present? args, "--serve", "The --server command has been replaced by the \
9
+ 'serve' subcommand."
10
+ arg_is_present? args, "--no-server", "To build Jekyll without launching a server, \
11
+ use the 'build' subcommand."
12
+ arg_is_present? args, "--auto", "The switch '--auto' has been replaced with '--watch'."
13
+ arg_is_present? args, "--no-auto", "To disable auto-replication, simply leave off \
14
+ the '--watch' switch."
15
+ arg_is_present? args, "--pygments", "The 'pygments'settings has been removed in \
16
+ favour of 'highlighter'."
17
+ arg_is_present? args, "--paginate", "The 'paginate' setting can only be set in your \
18
+ config files."
19
+ arg_is_present? args, "--url", "The 'url' setting can only be set in your config files."
20
+ no_subcommand(args)
21
+ end
22
+
23
+ def no_subcommand(args)
24
+ if args.size > 0 && args.first =~ /^--/ && !%w(--help --version).include?(args.first)
25
+ deprecation_message "Jekyll now uses subcommands instead of just switches. Run `jekyll --help` to find out more."
26
+ abort
27
+ end
28
+ end
29
+
30
+ def arg_is_present?(args, deprecated_argument, message)
31
+ if args.include?(deprecated_argument)
32
+ deprecation_message(message)
33
+ end
34
+ end
35
+
36
+ def deprecation_message(message)
37
+ Jekyll.logger.error "Deprecation:", message
38
+ end
39
+
40
+ def defaults_deprecate_type(old, current)
41
+ Jekyll.logger.warn "Defaults:", "The '#{old}' type has become '#{current}'."
42
+ Jekyll.logger.warn "Defaults:", "Please update your front-matter defaults to use 'type: #{current}'."
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,444 @@
1
+ # encoding: UTF-8
2
+
3
+ module Jekyll
4
+ class Document
5
+ include Comparable
6
+
7
+ attr_reader :path, :site, :extname, :collection
8
+ attr_accessor :content, :output
9
+
10
+ YAML_FRONT_MATTER_REGEXP = /\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)/m
11
+ DATELESS_FILENAME_MATCHER = /^(.+\/)*(.*)(\.[^.]+)$/
12
+ DATE_FILENAME_MATCHER = /^(.+\/)*(\d+-\d+-\d+)-(.*)(\.[^.]+)$/
13
+
14
+ # Create a new Document.
15
+ #
16
+ # path - the path to the file
17
+ # relations - a hash with keys :site and :collection, the values of which
18
+ # are the Jekyll::Site and Jekyll::Collection to which this
19
+ # Document belong.
20
+ #
21
+ # Returns nothing.
22
+ def initialize(path, relations = {})
23
+ @site = relations[:site]
24
+ @path = path
25
+ @extname = File.extname(path)
26
+ @collection = relations[:collection]
27
+ @has_yaml_header = nil
28
+
29
+ if draft?
30
+ categories_from_path("_drafts")
31
+ else
32
+ categories_from_path(collection.relative_directory)
33
+ end
34
+
35
+ data.default_proc = proc do |_, key|
36
+ site.frontmatter_defaults.find(relative_path, collection.label, key)
37
+ end
38
+
39
+ trigger_hooks(:post_init)
40
+ end
41
+
42
+ # Fetch the Document's data.
43
+ #
44
+ # Returns a Hash containing the data. An empty hash is returned if
45
+ # no data was read.
46
+ def data
47
+ @data ||= {}
48
+ end
49
+
50
+ # Merge some data in with this document's data.
51
+ #
52
+ # Returns the merged data.
53
+ def merge_data!(other, source: "YAML front matter")
54
+ if other.key?('categories') && !other['categories'].nil?
55
+ if other['categories'].is_a?(String)
56
+ other['categories'] = other['categories'].split(" ").map(&:strip)
57
+ end
58
+ other['categories'] = (data['categories'] || []) | other['categories']
59
+ end
60
+ Utils.deep_merge_hashes!(data, other)
61
+ if data.key?('date') && !data['date'].is_a?(Time)
62
+ data['date'] = Utils.parse_date(
63
+ data['date'].to_s,
64
+ "Document '#{relative_path}' does not have a valid date in the #{source}."
65
+ )
66
+ end
67
+ data
68
+ end
69
+
70
+ def date
71
+ data['date'] ||= site.time
72
+ end
73
+
74
+ # Returns whether the document is a draft. This is only the case if
75
+ # the document is in the 'posts' collection but in a different
76
+ # directory than '_posts'.
77
+ #
78
+ # Returns whether the document is a draft.
79
+ def draft?
80
+ data['draft'] ||= relative_path.index(collection.relative_directory).nil? && collection.label == "posts"
81
+ end
82
+
83
+ # The path to the document, relative to the site source.
84
+ #
85
+ # Returns a String path which represents the relative path
86
+ # from the site source to this document
87
+ def relative_path
88
+ @relative_path ||= Pathname.new(path).relative_path_from(Pathname.new(site.source)).to_s
89
+ end
90
+
91
+ # The output extension of the document.
92
+ #
93
+ # Returns the output extension
94
+ def output_ext
95
+ Jekyll::Renderer.new(site, self).output_ext
96
+ end
97
+
98
+ # The base filename of the document, without the file extname.
99
+ #
100
+ # Returns the basename without the file extname.
101
+ def basename_without_ext
102
+ @basename_without_ext ||= File.basename(path, '.*')
103
+ end
104
+
105
+ # The base filename of the document.
106
+ #
107
+ # Returns the base filename of the document.
108
+ def basename
109
+ @basename ||= File.basename(path)
110
+ end
111
+
112
+ # Produces a "cleaned" relative path.
113
+ # The "cleaned" relative path is the relative path without the extname
114
+ # and with the collection's directory removed as well.
115
+ # This method is useful when building the URL of the document.
116
+ #
117
+ # Examples:
118
+ # When relative_path is "_methods/site/generate.md":
119
+ # cleaned_relative_path
120
+ # # => "/site/generate"
121
+ #
122
+ # Returns the cleaned relative path of the document.
123
+ def cleaned_relative_path
124
+ @cleaned_relative_path ||=
125
+ relative_path[0..-extname.length - 1].sub(collection.relative_directory, "")
126
+ end
127
+
128
+ # Determine whether the document is a YAML file.
129
+ #
130
+ # Returns true if the extname is either .yml or .yaml, false otherwise.
131
+ def yaml_file?
132
+ %w(.yaml .yml).include?(extname)
133
+ end
134
+
135
+ # Determine whether the document is an asset file.
136
+ # Asset files include CoffeeScript files and Sass/SCSS files.
137
+ #
138
+ # Returns true if the extname belongs to the set of extensions
139
+ # that asset files use.
140
+ def asset_file?
141
+ sass_file? || coffeescript_file?
142
+ end
143
+
144
+ # Determine whether the document is a Sass file.
145
+ #
146
+ # Returns true if extname == .sass or .scss, false otherwise.
147
+ def sass_file?
148
+ %w(.sass .scss).include?(extname)
149
+ end
150
+
151
+ # Determine whether the document is a CoffeeScript file.
152
+ #
153
+ # Returns true if extname == .coffee, false otherwise.
154
+ def coffeescript_file?
155
+ '.coffee'.eql?(extname)
156
+ end
157
+
158
+ # Determine whether the file should be rendered with Liquid.
159
+ #
160
+ # Returns false if the document is either an asset file or a yaml file,
161
+ # true otherwise.
162
+ def render_with_liquid?
163
+ !(coffeescript_file? || yaml_file?)
164
+ end
165
+
166
+ # Determine whether the file should be placed into layouts.
167
+ #
168
+ # Returns false if the document is either an asset file or a yaml file,
169
+ # true otherwise.
170
+ def place_in_layout?
171
+ !(asset_file? || yaml_file?)
172
+ end
173
+
174
+ # The URL template where the document would be accessible.
175
+ #
176
+ # Returns the URL template for the document.
177
+ def url_template
178
+ collection.url_template
179
+ end
180
+
181
+ # Construct a Hash of key-value pairs which contain a mapping between
182
+ # a key in the URL template and the corresponding value for this document.
183
+ #
184
+ # Returns the Hash of key-value pairs for replacement in the URL.
185
+ def url_placeholders
186
+ @url_placeholders ||= Drops::UrlDrop.new(self)
187
+ end
188
+
189
+ # The permalink for this Document.
190
+ # Permalink is set via the data Hash.
191
+ #
192
+ # Returns the permalink or nil if no permalink was set in the data.
193
+ def permalink
194
+ data && data.is_a?(Hash) && data['permalink']
195
+ end
196
+
197
+ # The computed URL for the document. See `Jekyll::URL#to_s` for more details.
198
+ #
199
+ # Returns the computed URL for the document.
200
+ def url
201
+ @url = URL.new({
202
+ :template => url_template,
203
+ :placeholders => url_placeholders,
204
+ :permalink => permalink
205
+ }).to_s
206
+ end
207
+
208
+ def [](key)
209
+ data[key]
210
+ end
211
+
212
+ # The full path to the output file.
213
+ #
214
+ # base_directory - the base path of the output directory
215
+ #
216
+ # Returns the full path to the output file of this document.
217
+ def destination(base_directory)
218
+ dest = site.in_dest_dir(base_directory)
219
+ path = site.in_dest_dir(dest, URL.unescape_path(url))
220
+ path = File.join(path, "index.html") if url.end_with?("/")
221
+ path << output_ext unless path.end_with? output_ext
222
+ path
223
+ end
224
+
225
+ # Write the generated Document file to the destination directory.
226
+ #
227
+ # dest - The String path to the destination dir.
228
+ #
229
+ # Returns nothing.
230
+ def write(dest)
231
+ path = destination(dest)
232
+ FileUtils.mkdir_p(File.dirname(path))
233
+ File.open(path, 'wb') do |f|
234
+ f.write(output)
235
+ end
236
+
237
+ trigger_hooks(:post_write)
238
+ end
239
+
240
+ # Whether the file is published or not, as indicated in YAML front-matter
241
+ #
242
+ # Returns true if the 'published' key is specified in the YAML front-matter and not `false`.
243
+ def published?
244
+ !(data.key?('published') && data['published'] == false)
245
+ end
246
+
247
+ # Read in the file and assign the content and data based on the file contents.
248
+ # Merge the frontmatter of the file with the frontmatter default
249
+ # values
250
+ #
251
+ # Returns nothing.
252
+ def read(opts = {})
253
+ Jekyll.logger.debug "Reading:", relative_path
254
+
255
+ if yaml_file?
256
+ @data = SafeYAML.load_file(path)
257
+ else
258
+ begin
259
+ defaults = @site.frontmatter_defaults.all(url, collection.label.to_sym)
260
+ merge_data!(defaults, source: "front matter defaults") unless defaults.empty?
261
+
262
+ self.content = File.read(path, Utils.merged_file_read_opts(site, opts))
263
+ if content =~ YAML_FRONT_MATTER_REGEXP
264
+ self.content = $POSTMATCH
265
+ data_file = SafeYAML.load(Regexp.last_match(1))
266
+ merge_data!(data_file, source: "YAML front matter") if data_file
267
+ end
268
+
269
+ post_read
270
+ rescue SyntaxError => e
271
+ Jekyll.logger.error "Error:", "YAML Exception reading #{path}: #{e.message}"
272
+ rescue Exception => e
273
+ if e.is_a? Jekyll::Errors::FatalException
274
+ raise e
275
+ end
276
+ Jekyll.logger.error "Error:", "could not read file #{path}: #{e.message}"
277
+ end
278
+ end
279
+ end
280
+
281
+ def post_read
282
+ if relative_path =~ DATE_FILENAME_MATCHER
283
+ date, slug, ext = $2, $3, $4
284
+ if !data['date'] || data['date'].to_i == site.time.to_i
285
+ merge_data!({"date" => date}, source: "filename")
286
+ end
287
+ elsif relative_path =~ DATELESS_FILENAME_MATCHER
288
+ slug, ext = $2, $3
289
+ end
290
+
291
+ # Try to ensure the user gets a title.
292
+ data["title"] ||= Utils.titleize_slug(slug)
293
+ # Only overwrite slug & ext if they aren't specified.
294
+ data['slug'] ||= slug
295
+ data['ext'] ||= ext
296
+
297
+ populate_categories
298
+ populate_tags
299
+ generate_excerpt
300
+ end
301
+
302
+ # Add superdirectories of the special_dir to categories.
303
+ # In the case of es/_posts, 'es' is added as a category.
304
+ # In the case of _posts/es, 'es' is NOT added as a category.
305
+ #
306
+ # Returns nothing.
307
+ def categories_from_path(special_dir)
308
+ superdirs = relative_path.sub(/#{special_dir}(.*)/, '').split(File::SEPARATOR).reject do |c|
309
+ c.empty? || c.eql?(special_dir) || c.eql?(basename)
310
+ end
311
+ merge_data!({ 'categories' => superdirs }, source: "file path")
312
+ end
313
+
314
+ def populate_categories
315
+ merge_data!({
316
+ 'categories' => (
317
+ Array(data['categories']) + Utils.pluralized_array_from_hash(data, 'category', 'categories')
318
+ ).map(&:to_s).flatten.uniq
319
+ })
320
+ end
321
+
322
+ def populate_tags
323
+ merge_data!({
324
+ "tags" => Utils.pluralized_array_from_hash(data, "tag", "tags").flatten
325
+ })
326
+ end
327
+
328
+ # Create a Liquid-understandable version of this Document.
329
+ #
330
+ # Returns a Hash representing this Document's data.
331
+ def to_liquid
332
+ @to_liquid ||= Drops::DocumentDrop.new(self)
333
+ end
334
+
335
+ # The inspect string for this document.
336
+ # Includes the relative path and the collection label.
337
+ #
338
+ # Returns the inspect string for this document.
339
+ def inspect
340
+ "#<Jekyll::Document #{relative_path} collection=#{collection.label}>"
341
+ end
342
+
343
+ # The string representation for this document.
344
+ #
345
+ # Returns the content of the document
346
+ def to_s
347
+ output || content || 'NO CONTENT'
348
+ end
349
+
350
+ # Compare this document against another document.
351
+ # Comparison is a comparison between the 2 paths of the documents.
352
+ #
353
+ # Returns -1, 0, +1 or nil depending on whether this doc's path is less than,
354
+ # equal or greater than the other doc's path. See String#<=> for more details.
355
+ def <=>(other)
356
+ return nil unless other.respond_to?(:data)
357
+ cmp = data['date'] <=> other.data['date']
358
+ cmp = path <=> other.path if cmp.nil? || cmp == 0
359
+ cmp
360
+ end
361
+
362
+ # Determine whether this document should be written.
363
+ # Based on the Collection to which it belongs.
364
+ #
365
+ # True if the document has a collection and if that collection's #write?
366
+ # method returns true, otherwise false.
367
+ def write?
368
+ collection && collection.write?
369
+ end
370
+
371
+ # The Document excerpt_separator, from the YAML Front-Matter or site
372
+ # default excerpt_separator value
373
+ #
374
+ # Returns the document excerpt_separator
375
+ def excerpt_separator
376
+ (data['excerpt_separator'] || site.config['excerpt_separator']).to_s
377
+ end
378
+
379
+ # Whether to generate an excerpt
380
+ #
381
+ # Returns true if the excerpt separator is configured.
382
+ def generate_excerpt?
383
+ !excerpt_separator.empty?
384
+ end
385
+
386
+ def next_doc
387
+ pos = collection.docs.index { |post| post.equal?(self) }
388
+ if pos && pos < collection.docs.length - 1
389
+ collection.docs[pos + 1]
390
+ else
391
+ nil
392
+ end
393
+ end
394
+
395
+ def previous_doc
396
+ pos = collection.docs.index { |post| post.equal?(self) }
397
+ if pos && pos > 0
398
+ collection.docs[pos - 1]
399
+ else
400
+ nil
401
+ end
402
+ end
403
+
404
+ def trigger_hooks(hook_name, *args)
405
+ Jekyll::Hooks.trigger collection.label.to_sym, hook_name, self, *args if collection
406
+ Jekyll::Hooks.trigger :documents, hook_name, self, *args
407
+ end
408
+
409
+ def id
410
+ @id ||= File.join(File.dirname(url), (data['slug'] || basename_without_ext).to_s)
411
+ end
412
+
413
+ # Calculate related posts.
414
+ #
415
+ # Returns an Array of related Posts.
416
+ def related_posts
417
+ Jekyll::RelatedPosts.new(self).build
418
+ end
419
+
420
+ # Override of normal respond_to? to match method_missing's logic for
421
+ # looking in @data.
422
+ def respond_to?(method, include_private = false)
423
+ data.key?(method.to_s) || super
424
+ end
425
+
426
+ # Override of method_missing to check in @data for the key.
427
+ def method_missing(method, *args, &blck)
428
+ if data.key?(method.to_s)
429
+ Jekyll.logger.warn "Deprecation:", "Document##{method} is now a key in the #data hash."
430
+ Jekyll.logger.warn "", "Called by #{caller.first}."
431
+ data[method.to_s]
432
+ else
433
+ super
434
+ end
435
+ end
436
+
437
+ private # :nodoc:
438
+ def generate_excerpt
439
+ if generate_excerpt?
440
+ data["excerpt"] ||= Jekyll::Excerpt.new(self)
441
+ end
442
+ end
443
+ end
444
+ end