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.
- checksums.yaml +7 -0
- data/.rubocop.yml +80 -0
- data/LICENSE +21 -0
- data/README.markdown +60 -0
- data/bin/jekyll +51 -0
- data/lib/jekyll.rb +180 -0
- data/lib/jekyll/cleaner.rb +105 -0
- data/lib/jekyll/collection.rb +205 -0
- data/lib/jekyll/command.rb +65 -0
- data/lib/jekyll/commands/build.rb +77 -0
- data/lib/jekyll/commands/clean.rb +42 -0
- data/lib/jekyll/commands/doctor.rb +114 -0
- data/lib/jekyll/commands/help.rb +31 -0
- data/lib/jekyll/commands/new.rb +82 -0
- data/lib/jekyll/commands/serve.rb +235 -0
- data/lib/jekyll/commands/serve/servlet.rb +61 -0
- data/lib/jekyll/configuration.rb +323 -0
- data/lib/jekyll/converter.rb +48 -0
- data/lib/jekyll/converters/identity.rb +21 -0
- data/lib/jekyll/converters/markdown.rb +92 -0
- data/lib/jekyll/converters/markdown/kramdown_parser.rb +117 -0
- data/lib/jekyll/converters/markdown/rdiscount_parser.rb +33 -0
- data/lib/jekyll/converters/markdown/redcarpet_parser.rb +102 -0
- data/lib/jekyll/converters/smartypants.rb +34 -0
- data/lib/jekyll/convertible.rb +297 -0
- data/lib/jekyll/deprecator.rb +46 -0
- data/lib/jekyll/document.rb +444 -0
- data/lib/jekyll/drops/collection_drop.rb +22 -0
- data/lib/jekyll/drops/document_drop.rb +27 -0
- data/lib/jekyll/drops/drop.rb +176 -0
- data/lib/jekyll/drops/jekyll_drop.rb +21 -0
- data/lib/jekyll/drops/site_drop.rb +38 -0
- data/lib/jekyll/drops/unified_payload_drop.rb +25 -0
- data/lib/jekyll/drops/url_drop.rb +83 -0
- data/lib/jekyll/entry_filter.rb +72 -0
- data/lib/jekyll/errors.rb +10 -0
- data/lib/jekyll/excerpt.rb +127 -0
- data/lib/jekyll/external.rb +59 -0
- data/lib/jekyll/filters.rb +367 -0
- data/lib/jekyll/frontmatter_defaults.rb +188 -0
- data/lib/jekyll/generator.rb +3 -0
- data/lib/jekyll/hooks.rb +101 -0
- data/lib/jekyll/layout.rb +49 -0
- data/lib/jekyll/liquid_extensions.rb +22 -0
- data/lib/jekyll/liquid_renderer.rb +39 -0
- data/lib/jekyll/liquid_renderer/file.rb +50 -0
- data/lib/jekyll/liquid_renderer/table.rb +94 -0
- data/lib/jekyll/log_adapter.rb +115 -0
- data/lib/jekyll/mime.types +800 -0
- data/lib/jekyll/page.rb +180 -0
- data/lib/jekyll/plugin.rb +96 -0
- data/lib/jekyll/plugin_manager.rb +95 -0
- data/lib/jekyll/publisher.rb +21 -0
- data/lib/jekyll/reader.rb +126 -0
- data/lib/jekyll/readers/collection_reader.rb +20 -0
- data/lib/jekyll/readers/data_reader.rb +69 -0
- data/lib/jekyll/readers/layout_reader.rb +53 -0
- data/lib/jekyll/readers/page_reader.rb +21 -0
- data/lib/jekyll/readers/post_reader.rb +62 -0
- data/lib/jekyll/readers/static_file_reader.rb +21 -0
- data/lib/jekyll/regenerator.rb +175 -0
- data/lib/jekyll/related_posts.rb +56 -0
- data/lib/jekyll/renderer.rb +191 -0
- data/lib/jekyll/site.rb +391 -0
- data/lib/jekyll/static_file.rb +141 -0
- data/lib/jekyll/stevenson.rb +58 -0
- data/lib/jekyll/tags/highlight.rb +122 -0
- data/lib/jekyll/tags/include.rb +190 -0
- data/lib/jekyll/tags/post_url.rb +88 -0
- data/lib/jekyll/url.rb +136 -0
- data/lib/jekyll/utils.rb +287 -0
- data/lib/jekyll/utils/ansi.rb +59 -0
- data/lib/jekyll/utils/platforms.rb +30 -0
- data/lib/jekyll/version.rb +3 -0
- data/lib/site_template/.gitignore +3 -0
- data/lib/site_template/_config.yml +21 -0
- data/lib/site_template/_includes/footer.html +38 -0
- data/lib/site_template/_includes/head.html +12 -0
- data/lib/site_template/_includes/header.html +27 -0
- data/lib/site_template/_includes/icon-github.html +1 -0
- data/lib/site_template/_includes/icon-github.svg +1 -0
- data/lib/site_template/_includes/icon-twitter.html +1 -0
- data/lib/site_template/_includes/icon-twitter.svg +1 -0
- data/lib/site_template/_layouts/default.html +20 -0
- data/lib/site_template/_layouts/page.html +14 -0
- data/lib/site_template/_layouts/post.html +15 -0
- data/lib/site_template/_posts/0000-00-00-welcome-to-jekyll.markdown.erb +25 -0
- data/lib/site_template/_sass/_base.scss +206 -0
- data/lib/site_template/_sass/_layout.scss +242 -0
- data/lib/site_template/_sass/_syntax-highlighting.scss +71 -0
- data/lib/site_template/about.md +15 -0
- data/lib/site_template/css/main.scss +53 -0
- data/lib/site_template/feed.xml +30 -0
- data/lib/site_template/index.html +23 -0
- 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
|