bunto 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/LICENSE +21 -0
- data/README.markdown +59 -0
- data/bin/bunto +51 -0
- data/lib/bunto.rb +179 -0
- data/lib/bunto/cleaner.rb +105 -0
- data/lib/bunto/collection.rb +205 -0
- data/lib/bunto/command.rb +65 -0
- data/lib/bunto/commands/build.rb +77 -0
- data/lib/bunto/commands/clean.rb +42 -0
- data/lib/bunto/commands/doctor.rb +114 -0
- data/lib/bunto/commands/help.rb +31 -0
- data/lib/bunto/commands/new.rb +82 -0
- data/lib/bunto/commands/serve.rb +204 -0
- data/lib/bunto/commands/serve/servlet.rb +61 -0
- data/lib/bunto/configuration.rb +323 -0
- data/lib/bunto/converter.rb +48 -0
- data/lib/bunto/converters/identity.rb +21 -0
- data/lib/bunto/converters/markdown.rb +92 -0
- data/lib/bunto/converters/markdown/kramdown_parser.rb +117 -0
- data/lib/bunto/converters/markdown/rdiscount_parser.rb +33 -0
- data/lib/bunto/converters/markdown/redcarpet_parser.rb +102 -0
- data/lib/bunto/converters/smartypants.rb +34 -0
- data/lib/bunto/convertible.rb +297 -0
- data/lib/bunto/deprecator.rb +46 -0
- data/lib/bunto/document.rb +444 -0
- data/lib/bunto/drops/bunto_drop.rb +21 -0
- data/lib/bunto/drops/collection_drop.rb +22 -0
- data/lib/bunto/drops/document_drop.rb +27 -0
- data/lib/bunto/drops/drop.rb +176 -0
- data/lib/bunto/drops/site_drop.rb +38 -0
- data/lib/bunto/drops/unified_payload_drop.rb +25 -0
- data/lib/bunto/drops/url_drop.rb +83 -0
- data/lib/bunto/entry_filter.rb +72 -0
- data/lib/bunto/errors.rb +10 -0
- data/lib/bunto/excerpt.rb +127 -0
- data/lib/bunto/external.rb +59 -0
- data/lib/bunto/filters.rb +367 -0
- data/lib/bunto/frontmatter_defaults.rb +188 -0
- data/lib/bunto/generator.rb +3 -0
- data/lib/bunto/hooks.rb +101 -0
- data/lib/bunto/layout.rb +49 -0
- data/lib/bunto/liquid_extensions.rb +22 -0
- data/lib/bunto/liquid_renderer.rb +39 -0
- data/lib/bunto/liquid_renderer/file.rb +50 -0
- data/lib/bunto/liquid_renderer/table.rb +94 -0
- data/lib/bunto/log_adapter.rb +115 -0
- data/lib/bunto/mime.types +800 -0
- data/lib/bunto/page.rb +180 -0
- data/lib/bunto/plugin.rb +96 -0
- data/lib/bunto/plugin_manager.rb +95 -0
- data/lib/bunto/post.rb +329 -0
- data/lib/bunto/publisher.rb +21 -0
- data/lib/bunto/reader.rb +126 -0
- data/lib/bunto/readers/collection_reader.rb +20 -0
- data/lib/bunto/readers/data_reader.rb +69 -0
- data/lib/bunto/readers/layout_reader.rb +53 -0
- data/lib/bunto/readers/page_reader.rb +21 -0
- data/lib/bunto/readers/post_reader.rb +62 -0
- data/lib/bunto/readers/static_file_reader.rb +21 -0
- data/lib/bunto/regenerator.rb +175 -0
- data/lib/bunto/related_posts.rb +56 -0
- data/lib/bunto/renderer.rb +191 -0
- data/lib/bunto/site.rb +391 -0
- data/lib/bunto/static_file.rb +141 -0
- data/lib/bunto/stevenson.rb +58 -0
- data/lib/bunto/tags/highlight.rb +122 -0
- data/lib/bunto/tags/include.rb +190 -0
- data/lib/bunto/tags/post_url.rb +88 -0
- data/lib/bunto/url.rb +136 -0
- data/lib/bunto/utils.rb +287 -0
- data/lib/bunto/utils/ansi.rb +59 -0
- data/lib/bunto/utils/platforms.rb +30 -0
- data/lib/bunto/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-bunto.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
data/lib/bunto/page.rb
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
module Bunto
|
2
|
+
class Page
|
3
|
+
include Convertible
|
4
|
+
|
5
|
+
attr_writer :dir
|
6
|
+
attr_accessor :site, :pager
|
7
|
+
attr_accessor :name, :ext, :basename
|
8
|
+
attr_accessor :data, :content, :output
|
9
|
+
|
10
|
+
alias_method :extname, :ext
|
11
|
+
|
12
|
+
FORWARD_SLASH = '/'.freeze
|
13
|
+
|
14
|
+
# Attributes for Liquid templates
|
15
|
+
ATTRIBUTES_FOR_LIQUID = %w(
|
16
|
+
content
|
17
|
+
dir
|
18
|
+
name
|
19
|
+
path
|
20
|
+
url
|
21
|
+
)
|
22
|
+
|
23
|
+
# A set of extensions that are considered HTML or HTML-like so we
|
24
|
+
# should not alter them, this includes .xhtml through XHTM5.
|
25
|
+
|
26
|
+
HTML_EXTENSIONS = %W(
|
27
|
+
.html
|
28
|
+
.xhtml
|
29
|
+
.htm
|
30
|
+
)
|
31
|
+
|
32
|
+
# Initialize a new Page.
|
33
|
+
#
|
34
|
+
# site - The Site object.
|
35
|
+
# base - The String path to the source.
|
36
|
+
# dir - The String path between the source and the file.
|
37
|
+
# name - The String filename of the file.
|
38
|
+
def initialize(site, base, dir, name)
|
39
|
+
@site = site
|
40
|
+
@base = base
|
41
|
+
@dir = dir
|
42
|
+
@name = name
|
43
|
+
|
44
|
+
process(name)
|
45
|
+
read_yaml(File.join(base, dir), name)
|
46
|
+
|
47
|
+
data.default_proc = proc do |_, key|
|
48
|
+
site.frontmatter_defaults.find(File.join(dir, name), type, key)
|
49
|
+
end
|
50
|
+
|
51
|
+
Bunto::Hooks.trigger :pages, :post_init, self
|
52
|
+
end
|
53
|
+
|
54
|
+
# The generated directory into which the page will be placed
|
55
|
+
# upon generation. This is derived from the permalink or, if
|
56
|
+
# permalink is absent, we be '/'
|
57
|
+
#
|
58
|
+
# Returns the String destination directory.
|
59
|
+
def dir
|
60
|
+
if url.end_with?(FORWARD_SLASH)
|
61
|
+
url
|
62
|
+
else
|
63
|
+
url_dir = File.dirname(url)
|
64
|
+
url_dir.end_with?(FORWARD_SLASH) ? url_dir : "#{url_dir}/"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# The full path and filename of the post. Defined in the YAML of the post
|
69
|
+
# body.
|
70
|
+
#
|
71
|
+
# Returns the String permalink or nil if none has been set.
|
72
|
+
def permalink
|
73
|
+
data.nil? ? nil : data['permalink']
|
74
|
+
end
|
75
|
+
|
76
|
+
# The template of the permalink.
|
77
|
+
#
|
78
|
+
# Returns the template String.
|
79
|
+
def template
|
80
|
+
if !html?
|
81
|
+
"/:path/:basename:output_ext"
|
82
|
+
elsif index?
|
83
|
+
"/:path/"
|
84
|
+
else
|
85
|
+
Utils.add_permalink_suffix("/:path/:basename", site.permalink_style)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# The generated relative url of this page. e.g. /about.html.
|
90
|
+
#
|
91
|
+
# Returns the String url.
|
92
|
+
def url
|
93
|
+
@url ||= URL.new({
|
94
|
+
:template => template,
|
95
|
+
:placeholders => url_placeholders,
|
96
|
+
:permalink => permalink
|
97
|
+
}).to_s
|
98
|
+
end
|
99
|
+
|
100
|
+
# Returns a hash of URL placeholder names (as symbols) mapping to the
|
101
|
+
# desired placeholder replacements. For details see "url.rb"
|
102
|
+
def url_placeholders
|
103
|
+
{
|
104
|
+
:path => @dir,
|
105
|
+
:basename => basename,
|
106
|
+
:output_ext => output_ext
|
107
|
+
}
|
108
|
+
end
|
109
|
+
|
110
|
+
# Extract information from the page filename.
|
111
|
+
#
|
112
|
+
# name - The String filename of the page file.
|
113
|
+
#
|
114
|
+
# Returns nothing.
|
115
|
+
def process(name)
|
116
|
+
self.ext = File.extname(name)
|
117
|
+
self.basename = name[0..-ext.length - 1]
|
118
|
+
end
|
119
|
+
|
120
|
+
# Add any necessary layouts to this post
|
121
|
+
#
|
122
|
+
# layouts - The Hash of {"name" => "layout"}.
|
123
|
+
# site_payload - The site payload Hash.
|
124
|
+
#
|
125
|
+
# Returns nothing.
|
126
|
+
def render(layouts, site_payload)
|
127
|
+
site_payload["page"] = to_liquid
|
128
|
+
site_payload["paginator"] = pager.to_liquid
|
129
|
+
|
130
|
+
do_layout(site_payload, layouts)
|
131
|
+
end
|
132
|
+
|
133
|
+
# The path to the source file
|
134
|
+
#
|
135
|
+
# Returns the path to the source file
|
136
|
+
def path
|
137
|
+
data.fetch('path') { relative_path.sub(/\A\//, '') }
|
138
|
+
end
|
139
|
+
|
140
|
+
# The path to the page source file, relative to the site source
|
141
|
+
def relative_path
|
142
|
+
File.join(*[@dir, @name].map(&:to_s).reject(&:empty?))
|
143
|
+
end
|
144
|
+
|
145
|
+
# Obtain destination path.
|
146
|
+
#
|
147
|
+
# dest - The String path to the destination dir.
|
148
|
+
#
|
149
|
+
# Returns the destination file path String.
|
150
|
+
def destination(dest)
|
151
|
+
path = site.in_dest_dir(dest, URL.unescape_path(url))
|
152
|
+
path = File.join(path, "index") if url.end_with?("/")
|
153
|
+
path << output_ext unless path.end_with? output_ext
|
154
|
+
path
|
155
|
+
end
|
156
|
+
|
157
|
+
# Returns the object as a debug String.
|
158
|
+
def inspect
|
159
|
+
"#<Bunto:Page @name=#{name.inspect}>"
|
160
|
+
end
|
161
|
+
|
162
|
+
# Returns the Boolean of whether this Page is HTML or not.
|
163
|
+
def html?
|
164
|
+
HTML_EXTENSIONS.include?(output_ext)
|
165
|
+
end
|
166
|
+
|
167
|
+
# Returns the Boolean of whether this Page is an index file or not.
|
168
|
+
def index?
|
169
|
+
basename == 'index'
|
170
|
+
end
|
171
|
+
|
172
|
+
def trigger_hooks(hook_name, *args)
|
173
|
+
Bunto::Hooks.trigger :pages, hook_name, self, *args
|
174
|
+
end
|
175
|
+
|
176
|
+
def write?
|
177
|
+
true
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
data/lib/bunto/plugin.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
module Bunto
|
2
|
+
class Plugin
|
3
|
+
PRIORITIES = {
|
4
|
+
:low => -10,
|
5
|
+
:highest => 100,
|
6
|
+
:lowest => -100,
|
7
|
+
:normal => 0,
|
8
|
+
:high => 10
|
9
|
+
}
|
10
|
+
|
11
|
+
#
|
12
|
+
|
13
|
+
def self.inherited(const)
|
14
|
+
return catch_inheritance(const) do |const_|
|
15
|
+
catch_inheritance(const_)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
#
|
20
|
+
|
21
|
+
def self.catch_inheritance(const)
|
22
|
+
const.define_singleton_method :inherited do |const_|
|
23
|
+
(@children ||= Set.new).add const_
|
24
|
+
if block_given?
|
25
|
+
yield const_
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
|
32
|
+
def self.descendants
|
33
|
+
@children ||= Set.new
|
34
|
+
out = @children.map(&:descendants)
|
35
|
+
out << self unless superclass == Plugin
|
36
|
+
Set.new(out).flatten
|
37
|
+
end
|
38
|
+
|
39
|
+
# Get or set the priority of this plugin. When called without an
|
40
|
+
# argument it returns the priority. When an argument is given, it will
|
41
|
+
# set the priority.
|
42
|
+
#
|
43
|
+
# priority - The Symbol priority (default: nil). Valid options are:
|
44
|
+
# :lowest, :low, :normal, :high, :highest
|
45
|
+
#
|
46
|
+
# Returns the Symbol priority.
|
47
|
+
def self.priority(priority = nil)
|
48
|
+
@priority ||= nil
|
49
|
+
if priority && PRIORITIES.key?(priority)
|
50
|
+
@priority = priority
|
51
|
+
end
|
52
|
+
@priority || :normal
|
53
|
+
end
|
54
|
+
|
55
|
+
# Get or set the safety of this plugin. When called without an argument
|
56
|
+
# it returns the safety. When an argument is given, it will set the
|
57
|
+
# safety.
|
58
|
+
#
|
59
|
+
# safe - The Boolean safety (default: nil).
|
60
|
+
#
|
61
|
+
# Returns the safety Boolean.
|
62
|
+
def self.safe(safe = nil)
|
63
|
+
if safe
|
64
|
+
@safe = safe
|
65
|
+
end
|
66
|
+
@safe || false
|
67
|
+
end
|
68
|
+
|
69
|
+
# Spaceship is priority [higher -> lower]
|
70
|
+
#
|
71
|
+
# other - The class to be compared.
|
72
|
+
#
|
73
|
+
# Returns -1, 0, 1.
|
74
|
+
def self.<=>(other)
|
75
|
+
PRIORITIES[other.priority] <=> PRIORITIES[self.priority]
|
76
|
+
end
|
77
|
+
|
78
|
+
# Spaceship is priority [higher -> lower]
|
79
|
+
#
|
80
|
+
# other - The class to be compared.
|
81
|
+
#
|
82
|
+
# Returns -1, 0, 1.
|
83
|
+
def <=>(other)
|
84
|
+
self.class <=> other.class
|
85
|
+
end
|
86
|
+
|
87
|
+
# Initialize a new plugin. This should be overridden by the subclass.
|
88
|
+
#
|
89
|
+
# config - The Hash of configuration options.
|
90
|
+
#
|
91
|
+
# Returns a new instance.
|
92
|
+
def initialize(config = {})
|
93
|
+
# no-op for default
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Bunto
|
2
|
+
class PluginManager
|
3
|
+
attr_reader :site
|
4
|
+
|
5
|
+
# Create an instance of this class.
|
6
|
+
#
|
7
|
+
# site - the instance of Bunto::Site we're concerned with
|
8
|
+
#
|
9
|
+
# Returns nothing
|
10
|
+
def initialize(site)
|
11
|
+
@site = site
|
12
|
+
end
|
13
|
+
|
14
|
+
# Require all the plugins which are allowed.
|
15
|
+
#
|
16
|
+
# Returns nothing
|
17
|
+
def conscientious_require
|
18
|
+
require_plugin_files
|
19
|
+
require_gems
|
20
|
+
deprecation_checks
|
21
|
+
end
|
22
|
+
|
23
|
+
# Require each of the gem plugins specified.
|
24
|
+
#
|
25
|
+
# Returns nothing.
|
26
|
+
def require_gems
|
27
|
+
Bunto::External.require_with_graceful_fail(site.gems.select { |gem| plugin_allowed?(gem) })
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.require_from_bundler
|
31
|
+
if !ENV["BUNTO_NO_BUNDLER_REQUIRE"] && File.file?("Gemfile")
|
32
|
+
require "bundler"
|
33
|
+
Bundler.setup # puts all groups on the load path
|
34
|
+
required_gems = Bundler.require(:bunto_plugins) # requires the gems in this group only
|
35
|
+
Bunto.logger.debug("PluginManager:", "Required #{required_gems.map(&:name).join(', ')}")
|
36
|
+
ENV["BUNTO_NO_BUNDLER_REQUIRE"] = "true"
|
37
|
+
true
|
38
|
+
else
|
39
|
+
false
|
40
|
+
end
|
41
|
+
rescue LoadError, Bundler::GemfileNotFound
|
42
|
+
false
|
43
|
+
end
|
44
|
+
|
45
|
+
# Check whether a gem plugin is allowed to be used during this build.
|
46
|
+
#
|
47
|
+
# gem_name - the name of the gem
|
48
|
+
#
|
49
|
+
# Returns true if the gem name is in the whitelist or if the site is not
|
50
|
+
# in safe mode.
|
51
|
+
def plugin_allowed?(gem_name)
|
52
|
+
!site.safe || whitelist.include?(gem_name)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Build an array of allowed plugin gem names.
|
56
|
+
#
|
57
|
+
# Returns an array of strings, each string being the name of a gem name
|
58
|
+
# that is allowed to be used.
|
59
|
+
def whitelist
|
60
|
+
@whitelist ||= Array[site.config['whitelist']].flatten
|
61
|
+
end
|
62
|
+
|
63
|
+
# Require all .rb files if safe mode is off
|
64
|
+
#
|
65
|
+
# Returns nothing.
|
66
|
+
def require_plugin_files
|
67
|
+
unless site.safe
|
68
|
+
plugins_path.each do |plugin_search_path|
|
69
|
+
plugin_files = Utils.safe_glob(plugin_search_path, File.join("**", "*.rb"))
|
70
|
+
Bunto::External.require_with_graceful_fail(plugin_files)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Public: Setup the plugin search path
|
76
|
+
#
|
77
|
+
# Returns an Array of plugin search paths
|
78
|
+
def plugins_path
|
79
|
+
if site.config['plugins_dir'] == Bunto::Configuration::DEFAULTS['plugins_dir']
|
80
|
+
[site.in_source_dir(site.config['plugins_dir'])]
|
81
|
+
else
|
82
|
+
Array(site.config['plugins_dir']).map { |d| File.expand_path(d) }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def deprecation_checks
|
87
|
+
pagination_included = (site.config['gems'] || []).include?('bunto-paginate') || defined?(Bunto::Paginate)
|
88
|
+
if site.config['paginate'] && !pagination_included
|
89
|
+
Bunto::Deprecator.deprecation_message "You appear to have pagination " \
|
90
|
+
"turned on, but you haven't included the `bunto-paginate` gem. " \
|
91
|
+
"Ensure you have `gems: [bunto-paginate]` in your configuration file."
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
data/lib/bunto/post.rb
ADDED
@@ -0,0 +1,329 @@
|
|
1
|
+
module Bunto
|
2
|
+
class Post
|
3
|
+
include Comparable
|
4
|
+
include Convertible
|
5
|
+
|
6
|
+
# Valid post name regex.
|
7
|
+
MATCHER = /^(.+\/)*(\d+-\d+-\d+)-(.*)(\.[^.]+)$/
|
8
|
+
|
9
|
+
EXCERPT_ATTRIBUTES_FOR_LIQUID = %w[
|
10
|
+
title
|
11
|
+
url
|
12
|
+
dir
|
13
|
+
date
|
14
|
+
id
|
15
|
+
categories
|
16
|
+
next
|
17
|
+
previous
|
18
|
+
tags
|
19
|
+
path
|
20
|
+
]
|
21
|
+
|
22
|
+
# Attributes for Liquid templates
|
23
|
+
ATTRIBUTES_FOR_LIQUID = EXCERPT_ATTRIBUTES_FOR_LIQUID + %w[
|
24
|
+
content
|
25
|
+
excerpt
|
26
|
+
excerpt_separator
|
27
|
+
draft?
|
28
|
+
]
|
29
|
+
|
30
|
+
# Post name validator. Post filenames must be like:
|
31
|
+
# 2008-11-05-my-awesome-post.textile
|
32
|
+
#
|
33
|
+
# Returns true if valid, false if not.
|
34
|
+
def self.valid?(name)
|
35
|
+
name =~ MATCHER
|
36
|
+
end
|
37
|
+
|
38
|
+
attr_accessor :site
|
39
|
+
attr_accessor :data, :extracted_excerpt, :content, :output, :ext
|
40
|
+
attr_accessor :date, :slug, :tags, :categories
|
41
|
+
|
42
|
+
attr_reader :name
|
43
|
+
|
44
|
+
# Initialize this Post instance.
|
45
|
+
#
|
46
|
+
# site - The Site.
|
47
|
+
# base - The String path to the dir containing the post file.
|
48
|
+
# name - The String filename of the post file.
|
49
|
+
#
|
50
|
+
# Returns the new Post.
|
51
|
+
def initialize(site, source, dir, name)
|
52
|
+
@site = site
|
53
|
+
@dir = dir
|
54
|
+
@base = containing_dir(dir)
|
55
|
+
@name = name
|
56
|
+
|
57
|
+
self.categories = dir.split('/').reject { |x| x.empty? }
|
58
|
+
process(name)
|
59
|
+
read_yaml(@base, name)
|
60
|
+
|
61
|
+
data.default_proc = proc do |hash, key|
|
62
|
+
site.frontmatter_defaults.find(relative_path, type, key)
|
63
|
+
end
|
64
|
+
|
65
|
+
if data.key?('date')
|
66
|
+
self.date = Utils.parse_date(data["date"].to_s, "Post '#{relative_path}' does not have a valid date in the YAML front matter.")
|
67
|
+
end
|
68
|
+
|
69
|
+
populate_categories
|
70
|
+
populate_tags
|
71
|
+
end
|
72
|
+
|
73
|
+
def published?
|
74
|
+
if data.key?('published') && data['published'] == false
|
75
|
+
false
|
76
|
+
else
|
77
|
+
true
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def populate_categories
|
82
|
+
categories_from_data = Utils.pluralized_array_from_hash(data, 'category', 'categories')
|
83
|
+
self.categories = (
|
84
|
+
Array(categories) + categories_from_data
|
85
|
+
).map { |c| c.to_s }.flatten.uniq
|
86
|
+
end
|
87
|
+
|
88
|
+
def populate_tags
|
89
|
+
self.tags = Utils.pluralized_array_from_hash(data, "tag", "tags").flatten
|
90
|
+
end
|
91
|
+
|
92
|
+
# Get the full path to the directory containing the post files
|
93
|
+
def containing_dir(dir)
|
94
|
+
site.in_source_dir(dir, '_posts')
|
95
|
+
end
|
96
|
+
|
97
|
+
# Read the YAML frontmatter.
|
98
|
+
#
|
99
|
+
# base - The String path to the dir containing the file.
|
100
|
+
# name - The String filename of the file.
|
101
|
+
#
|
102
|
+
# Returns nothing.
|
103
|
+
def read_yaml(base, name)
|
104
|
+
super(base, name)
|
105
|
+
self.extracted_excerpt = extract_excerpt
|
106
|
+
end
|
107
|
+
|
108
|
+
# The post excerpt. This is either a custom excerpt
|
109
|
+
# set in YAML front matter or the result of extract_excerpt.
|
110
|
+
#
|
111
|
+
# Returns excerpt string.
|
112
|
+
def excerpt
|
113
|
+
data.fetch('excerpt') { extracted_excerpt.to_s }
|
114
|
+
end
|
115
|
+
|
116
|
+
# Public: the Post title, from the YAML Front-Matter or from the slug
|
117
|
+
#
|
118
|
+
# Returns the post title
|
119
|
+
def title
|
120
|
+
data.fetch('title') { titleized_slug }
|
121
|
+
end
|
122
|
+
|
123
|
+
# Public: the Post excerpt_separator, from the YAML Front-Matter or site default
|
124
|
+
# excerpt_separator value
|
125
|
+
#
|
126
|
+
# Returns the post excerpt_separator
|
127
|
+
def excerpt_separator
|
128
|
+
(data['excerpt_separator'] || site.config['excerpt_separator']).to_s
|
129
|
+
end
|
130
|
+
|
131
|
+
# Turns the post slug into a suitable title
|
132
|
+
def titleized_slug
|
133
|
+
slug.split('-').select {|w| w.capitalize! || w }.join(' ')
|
134
|
+
end
|
135
|
+
|
136
|
+
# Public: the path to the post relative to the site source,
|
137
|
+
# from the YAML Front-Matter or from a combination of
|
138
|
+
# the directory it's in, "_posts", and the name of the
|
139
|
+
# post file
|
140
|
+
#
|
141
|
+
# Returns the path to the file relative to the site source
|
142
|
+
def path
|
143
|
+
data.fetch('path') { relative_path.sub(/\A\//, '') }
|
144
|
+
end
|
145
|
+
|
146
|
+
# The path to the post source file, relative to the site source
|
147
|
+
def relative_path
|
148
|
+
File.join(*[@dir, "_posts", @name].map(&:to_s).reject(&:empty?))
|
149
|
+
end
|
150
|
+
|
151
|
+
# Compares Post objects. First compares the Post date. If the dates are
|
152
|
+
# equal, it compares the Post slugs.
|
153
|
+
#
|
154
|
+
# other - The other Post we are comparing to.
|
155
|
+
#
|
156
|
+
# Returns -1, 0, 1
|
157
|
+
def <=>(other)
|
158
|
+
cmp = self.date <=> other.date
|
159
|
+
if 0 == cmp
|
160
|
+
cmp = self.slug <=> other.slug
|
161
|
+
end
|
162
|
+
return cmp
|
163
|
+
end
|
164
|
+
|
165
|
+
# Extract information from the post filename.
|
166
|
+
#
|
167
|
+
# name - The String filename of the post file.
|
168
|
+
#
|
169
|
+
# Returns nothing.
|
170
|
+
def process(name)
|
171
|
+
m, cats, date, slug, ext = *name.match(MATCHER)
|
172
|
+
self.date = Utils.parse_date(date, "Post '#{relative_path}' does not have a valid date in the filename.")
|
173
|
+
self.slug = slug
|
174
|
+
self.ext = ext
|
175
|
+
end
|
176
|
+
|
177
|
+
# The generated directory into which the post will be placed
|
178
|
+
# upon generation. This is derived from the permalink or, if
|
179
|
+
# permalink is absent, set to the default date
|
180
|
+
# e.g. "/2008/11/05/" if the permalink style is :date, otherwise nothing.
|
181
|
+
#
|
182
|
+
# Returns the String directory.
|
183
|
+
def dir
|
184
|
+
File.dirname(url)
|
185
|
+
end
|
186
|
+
|
187
|
+
# The full path and filename of the post. Defined in the YAML of the post
|
188
|
+
# body (optional).
|
189
|
+
#
|
190
|
+
# Returns the String permalink.
|
191
|
+
def permalink
|
192
|
+
data && data['permalink']
|
193
|
+
end
|
194
|
+
|
195
|
+
def template
|
196
|
+
case site.permalink_style
|
197
|
+
when :pretty
|
198
|
+
"/:categories/:year/:month/:day/:title/"
|
199
|
+
when :none
|
200
|
+
"/:categories/:title.html"
|
201
|
+
when :date
|
202
|
+
"/:categories/:year/:month/:day/:title.html"
|
203
|
+
when :ordinal
|
204
|
+
"/:categories/:year/:y_day/:title.html"
|
205
|
+
else
|
206
|
+
site.permalink_style.to_s
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# The generated relative url of this post.
|
211
|
+
#
|
212
|
+
# Returns the String url.
|
213
|
+
def url
|
214
|
+
@url ||= URL.new({
|
215
|
+
:template => template,
|
216
|
+
:placeholders => url_placeholders,
|
217
|
+
:permalink => permalink
|
218
|
+
}).to_s
|
219
|
+
end
|
220
|
+
|
221
|
+
# Returns a hash of URL placeholder names (as symbols) mapping to the
|
222
|
+
# desired placeholder replacements. For details see "url.rb"
|
223
|
+
def url_placeholders
|
224
|
+
{
|
225
|
+
:year => date.strftime("%Y"),
|
226
|
+
:month => date.strftime("%m"),
|
227
|
+
:day => date.strftime("%d"),
|
228
|
+
:title => slug,
|
229
|
+
:i_day => date.strftime("%-d"),
|
230
|
+
:i_month => date.strftime("%-m"),
|
231
|
+
:categories => (categories || []).map { |c| c.to_s.downcase }.uniq.join('/'),
|
232
|
+
:short_month => date.strftime("%b"),
|
233
|
+
:short_year => date.strftime("%y"),
|
234
|
+
:y_day => date.strftime("%j"),
|
235
|
+
:output_ext => output_ext
|
236
|
+
}
|
237
|
+
end
|
238
|
+
|
239
|
+
# The UID for this post (useful in feeds).
|
240
|
+
# e.g. /2008/11/05/my-awesome-post
|
241
|
+
#
|
242
|
+
# Returns the String UID.
|
243
|
+
def id
|
244
|
+
File.join(dir, slug)
|
245
|
+
end
|
246
|
+
|
247
|
+
# Calculate related posts.
|
248
|
+
#
|
249
|
+
# Returns an Array of related Posts.
|
250
|
+
def related_posts(posts)
|
251
|
+
Bunto::RelatedPosts.new(self).build
|
252
|
+
end
|
253
|
+
|
254
|
+
# Add any necessary layouts to this post.
|
255
|
+
#
|
256
|
+
# layouts - A Hash of {"name" => "layout"}.
|
257
|
+
# site_payload - The site payload hash.
|
258
|
+
#
|
259
|
+
# Returns nothing.
|
260
|
+
def render(layouts, site_payload)
|
261
|
+
# construct payload
|
262
|
+
payload = Utils.deep_merge_hashes({
|
263
|
+
"site" => { "related_posts" => related_posts(site_payload["site"]["posts"]) },
|
264
|
+
"page" => to_liquid(self.class::EXCERPT_ATTRIBUTES_FOR_LIQUID)
|
265
|
+
}, site_payload)
|
266
|
+
|
267
|
+
if generate_excerpt?
|
268
|
+
extracted_excerpt.do_layout(payload, {})
|
269
|
+
end
|
270
|
+
|
271
|
+
do_layout(payload.merge({"page" => to_liquid}), layouts)
|
272
|
+
end
|
273
|
+
|
274
|
+
# Obtain destination path.
|
275
|
+
#
|
276
|
+
# dest - The String path to the destination dir.
|
277
|
+
#
|
278
|
+
# Returns destination file path String.
|
279
|
+
def destination(dest)
|
280
|
+
# The url needs to be unescaped in order to preserve the correct filename
|
281
|
+
path = site.in_dest_dir(dest, URL.unescape_path(url))
|
282
|
+
path = File.join(path, "index.html") if self.url.end_with?("/")
|
283
|
+
path << output_ext unless path.end_with?(output_ext)
|
284
|
+
path
|
285
|
+
end
|
286
|
+
|
287
|
+
# Returns the shorthand String identifier of this Post.
|
288
|
+
def inspect
|
289
|
+
"<Post: #{id}>"
|
290
|
+
end
|
291
|
+
|
292
|
+
def next
|
293
|
+
pos = site.posts.index {|post| post.equal?(self) }
|
294
|
+
if pos && pos < site.posts.length - 1
|
295
|
+
site.posts[pos + 1]
|
296
|
+
else
|
297
|
+
nil
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
def previous
|
302
|
+
pos = site.posts.index {|post| post.equal?(self) }
|
303
|
+
if pos && pos > 0
|
304
|
+
site.posts[pos - 1]
|
305
|
+
else
|
306
|
+
nil
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
# Returns if this Post is a Draft
|
311
|
+
def draft?
|
312
|
+
is_a?(Bunto::Draft)
|
313
|
+
end
|
314
|
+
|
315
|
+
protected
|
316
|
+
|
317
|
+
def extract_excerpt
|
318
|
+
if generate_excerpt?
|
319
|
+
Bunto::Excerpt.new(self)
|
320
|
+
else
|
321
|
+
""
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
def generate_excerpt?
|
326
|
+
!excerpt_separator.empty?
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|