bunto 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,88 @@
|
|
1
|
+
module Bunto
|
2
|
+
module Tags
|
3
|
+
class PostComparer
|
4
|
+
MATCHER = /^(.+\/)*(\d+-\d+-\d+)-(.*)$/
|
5
|
+
|
6
|
+
attr_reader :path, :date, :slug, :name
|
7
|
+
|
8
|
+
def initialize(name)
|
9
|
+
@name = name
|
10
|
+
all, @path, @date, @slug = *name.sub(/^\//, "").match(MATCHER)
|
11
|
+
raise ArgumentError.new("'#{name}' does not contain valid date and/or title.") unless all
|
12
|
+
|
13
|
+
@name_regex = /^#{path}#{date}-#{slug}\.[^.]+/
|
14
|
+
end
|
15
|
+
|
16
|
+
def ==(other)
|
17
|
+
other.basename.match(@name_regex)
|
18
|
+
end
|
19
|
+
|
20
|
+
def deprecated_equality(other)
|
21
|
+
date = Utils.parse_date(name, "'#{name}' does not contain valid date and/or title.")
|
22
|
+
slug == post_slug(other) &&
|
23
|
+
date.year == other.date.year &&
|
24
|
+
date.month == other.date.month &&
|
25
|
+
date.day == other.date.day
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
# Construct the directory-aware post slug for a Bunto::Post
|
30
|
+
#
|
31
|
+
# other - the Bunto::Post
|
32
|
+
#
|
33
|
+
# Returns the post slug with the subdirectory (relative to _posts)
|
34
|
+
def post_slug(other)
|
35
|
+
path = other.basename.split("/")[0...-1].join("/")
|
36
|
+
if path.nil? || path == ""
|
37
|
+
other.data['slug']
|
38
|
+
else
|
39
|
+
path + '/' + other.data['slug']
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class PostUrl < Liquid::Tag
|
45
|
+
def initialize(tag_name, post, tokens)
|
46
|
+
super
|
47
|
+
@orig_post = post.strip
|
48
|
+
begin
|
49
|
+
@post = PostComparer.new(@orig_post)
|
50
|
+
rescue
|
51
|
+
raise ArgumentError.new <<-eos
|
52
|
+
Could not parse name of post "#{@orig_post}" in tag 'post_url'.
|
53
|
+
|
54
|
+
Make sure the post exists and the name is correct.
|
55
|
+
eos
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def render(context)
|
60
|
+
site = context.registers[:site]
|
61
|
+
|
62
|
+
site.posts.docs.each do |p|
|
63
|
+
return p.url if @post == p
|
64
|
+
end
|
65
|
+
|
66
|
+
# New matching method did not match, fall back to old method
|
67
|
+
# with deprecation warning if this matches
|
68
|
+
|
69
|
+
site.posts.docs.each do |p|
|
70
|
+
next unless @post.deprecated_equality p
|
71
|
+
Bunto::Deprecator.deprecation_message "A call to '{{ post_url #{@post.name} }}' did not match " \
|
72
|
+
"a post using the new matching method of checking name " \
|
73
|
+
"(path-date-slug) equality. Please make sure that you " \
|
74
|
+
"change this tag to match the post's name exactly."
|
75
|
+
return p.url
|
76
|
+
end
|
77
|
+
|
78
|
+
raise ArgumentError.new <<-eos
|
79
|
+
Could not find post "#{@orig_post}" in tag 'post_url'.
|
80
|
+
|
81
|
+
Make sure the post exists and the name is correct.
|
82
|
+
eos
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
Liquid::Template.register_tag('post_url', Bunto::Tags::PostUrl)
|
data/lib/bunto/url.rb
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
# Public: Methods that generate a URL for a resource such as a Post or a Page.
|
4
|
+
#
|
5
|
+
# Examples
|
6
|
+
#
|
7
|
+
# URL.new({
|
8
|
+
# :template => /:categories/:title.html",
|
9
|
+
# :placeholders => {:categories => "ruby", :title => "something"}
|
10
|
+
# }).to_s
|
11
|
+
#
|
12
|
+
module Bunto
|
13
|
+
class URL
|
14
|
+
# options - One of :permalink or :template must be supplied.
|
15
|
+
# :template - The String used as template for URL generation,
|
16
|
+
# for example "/:path/:basename:output_ext", where
|
17
|
+
# a placeholder is prefixed with a colon.
|
18
|
+
# :placeholders - A hash containing the placeholders which will be
|
19
|
+
# replaced when used inside the template. E.g.
|
20
|
+
# { "year" => Time.now.strftime("%Y") } would replace
|
21
|
+
# the placeholder ":year" with the current year.
|
22
|
+
# :permalink - If supplied, no URL will be generated from the
|
23
|
+
# template. Instead, the given permalink will be
|
24
|
+
# used as URL.
|
25
|
+
def initialize(options)
|
26
|
+
@template = options[:template]
|
27
|
+
@placeholders = options[:placeholders] || {}
|
28
|
+
@permalink = options[:permalink]
|
29
|
+
|
30
|
+
if (@template || @permalink).nil?
|
31
|
+
raise ArgumentError, "One of :template or :permalink must be supplied."
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# The generated relative URL of the resource
|
36
|
+
#
|
37
|
+
# Returns the String URL
|
38
|
+
def to_s
|
39
|
+
sanitize_url(generated_permalink || generated_url)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Generates a URL from the permalink
|
43
|
+
#
|
44
|
+
# Returns the _unsanitized String URL
|
45
|
+
def generated_permalink
|
46
|
+
(@generated_permalink ||= generate_url(@permalink)) if @permalink
|
47
|
+
end
|
48
|
+
|
49
|
+
# Generates a URL from the template
|
50
|
+
#
|
51
|
+
# Returns the unsanitized String URL
|
52
|
+
def generated_url
|
53
|
+
@generated_url ||= generate_url(@template)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Internal: Generate the URL by replacing all placeholders with their
|
57
|
+
# respective values in the given template
|
58
|
+
#
|
59
|
+
# Returns the unsanitized String URL
|
60
|
+
def generate_url(template)
|
61
|
+
if @placeholders.is_a? Drops::UrlDrop
|
62
|
+
generate_url_from_drop(template)
|
63
|
+
else
|
64
|
+
generate_url_from_hash(template)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def generate_url_from_hash(template)
|
69
|
+
@placeholders.inject(template) do |result, token|
|
70
|
+
break result if result.index(':').nil?
|
71
|
+
if token.last.nil?
|
72
|
+
# Remove leading '/' to avoid generating urls with `//`
|
73
|
+
result.gsub(/\/:#{token.first}/, '')
|
74
|
+
else
|
75
|
+
result.gsub(/:#{token.first}/, self.class.escape_path(token.last))
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def generate_url_from_drop(template)
|
81
|
+
template.gsub(/:([a-z_]+)/.freeze) do |match|
|
82
|
+
replacement = @placeholders.public_send(match.sub(':'.freeze, ''.freeze))
|
83
|
+
if replacement.nil?
|
84
|
+
''.freeze
|
85
|
+
else
|
86
|
+
self.class.escape_path(replacement)
|
87
|
+
end
|
88
|
+
end.gsub(/\/\//.freeze, '/'.freeze)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns a sanitized String URL, stripping "../../" and multiples of "/",
|
92
|
+
# as well as the beginning "/" so we can enforce and ensure it.
|
93
|
+
|
94
|
+
def sanitize_url(str)
|
95
|
+
"/" + str.gsub(/\/{2,}/, "/").gsub(/\.+\/|\A\/+/, "")
|
96
|
+
end
|
97
|
+
|
98
|
+
# Escapes a path to be a valid URL path segment
|
99
|
+
#
|
100
|
+
# path - The path to be escaped.
|
101
|
+
#
|
102
|
+
# Examples:
|
103
|
+
#
|
104
|
+
# URL.escape_path("/a b")
|
105
|
+
# # => "/a%20b"
|
106
|
+
#
|
107
|
+
# Returns the escaped path.
|
108
|
+
def self.escape_path(path)
|
109
|
+
# Because URI.escape doesn't escape '?', '[' and ']' by default,
|
110
|
+
# specify unsafe string (except unreserved, sub-delims, ":", "@" and "/").
|
111
|
+
#
|
112
|
+
# URI path segment is defined in RFC 3986 as follows:
|
113
|
+
# segment = *pchar
|
114
|
+
# pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
|
115
|
+
# unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
116
|
+
# pct-encoded = "%" HEXDIG HEXDIG
|
117
|
+
# sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
118
|
+
# / "*" / "+" / "," / ";" / "="
|
119
|
+
URI.escape(path, /[^a-zA-Z\d\-._~!$&'()*+,;=:@\/]/).encode('utf-8')
|
120
|
+
end
|
121
|
+
|
122
|
+
# Unescapes a URL path segment
|
123
|
+
#
|
124
|
+
# path - The path to be unescaped.
|
125
|
+
#
|
126
|
+
# Examples:
|
127
|
+
#
|
128
|
+
# URL.unescape_path("/a%20b")
|
129
|
+
# # => "/a b"
|
130
|
+
#
|
131
|
+
# Returns the unescaped path.
|
132
|
+
def self.unescape_path(path)
|
133
|
+
URI.unescape(path.encode('utf-8'))
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
data/lib/bunto/utils.rb
ADDED
@@ -0,0 +1,287 @@
|
|
1
|
+
module Bunto
|
2
|
+
module Utils
|
3
|
+
extend self
|
4
|
+
autoload :Platforms, 'bunto/utils/platforms'
|
5
|
+
autoload :Ansi, "bunto/utils/ansi"
|
6
|
+
|
7
|
+
# Constants for use in #slugify
|
8
|
+
SLUGIFY_MODES = %w(raw default pretty)
|
9
|
+
SLUGIFY_RAW_REGEXP = Regexp.new('\\s+').freeze
|
10
|
+
SLUGIFY_DEFAULT_REGEXP = Regexp.new('[^[:alnum:]]+').freeze
|
11
|
+
SLUGIFY_PRETTY_REGEXP = Regexp.new("[^[:alnum:]._~!$&'()+,;=@]+").freeze
|
12
|
+
|
13
|
+
# Takes an indented string and removes the preceding spaces on each line
|
14
|
+
|
15
|
+
def strip_heredoc(str)
|
16
|
+
str.gsub(/^[ \t]{#{(str.scan(/^[ \t]*(?=\S)/).min || "").size}}/, "")
|
17
|
+
end
|
18
|
+
|
19
|
+
# Takes a slug and turns it into a simple title.
|
20
|
+
|
21
|
+
def titleize_slug(slug)
|
22
|
+
slug.split("-").map! do |val|
|
23
|
+
val.capitalize!
|
24
|
+
end.join(" ")
|
25
|
+
end
|
26
|
+
|
27
|
+
# Non-destructive version of deep_merge_hashes! See that method.
|
28
|
+
#
|
29
|
+
# Returns the merged hashes.
|
30
|
+
def deep_merge_hashes(master_hash, other_hash)
|
31
|
+
deep_merge_hashes!(master_hash.dup, other_hash)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Merges a master hash with another hash, recursively.
|
35
|
+
#
|
36
|
+
# master_hash - the "parent" hash whose values will be overridden
|
37
|
+
# other_hash - the other hash whose values will be persisted after the merge
|
38
|
+
#
|
39
|
+
# This code was lovingly stolen from some random gem:
|
40
|
+
# http://gemjack.com/gems/tartan-0.1.1/classes/Hash.html
|
41
|
+
#
|
42
|
+
# Thanks to whoever made it.
|
43
|
+
def deep_merge_hashes!(target, overwrite)
|
44
|
+
target.merge!(overwrite) do |key, old_val, new_val|
|
45
|
+
if new_val.nil?
|
46
|
+
old_val
|
47
|
+
else
|
48
|
+
mergable?(old_val) && mergable?(new_val) ? deep_merge_hashes(old_val, new_val) : new_val
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
if target.respond_to?(:default_proc) && overwrite.respond_to?(:default_proc) && target.default_proc.nil?
|
53
|
+
target.default_proc = overwrite.default_proc
|
54
|
+
end
|
55
|
+
|
56
|
+
target
|
57
|
+
end
|
58
|
+
|
59
|
+
def mergable?(value)
|
60
|
+
value.is_a?(Hash) || value.is_a?(Drops::Drop)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Read array from the supplied hash favouring the singular key
|
64
|
+
# and then the plural key, and handling any nil entries.
|
65
|
+
#
|
66
|
+
# hash - the hash to read from
|
67
|
+
# singular_key - the singular key
|
68
|
+
# plural_key - the plural key
|
69
|
+
#
|
70
|
+
# Returns an array
|
71
|
+
def pluralized_array_from_hash(hash, singular_key, plural_key)
|
72
|
+
[].tap do |array|
|
73
|
+
array << (value_from_singular_key(hash, singular_key) || value_from_plural_key(hash, plural_key))
|
74
|
+
end.flatten.compact
|
75
|
+
end
|
76
|
+
|
77
|
+
def value_from_singular_key(hash, key)
|
78
|
+
hash[key] if hash.key?(key) || (hash.default_proc && hash[key])
|
79
|
+
end
|
80
|
+
|
81
|
+
def value_from_plural_key(hash, key)
|
82
|
+
if hash.key?(key) || (hash.default_proc && hash[key])
|
83
|
+
val = hash[key]
|
84
|
+
case val
|
85
|
+
when String
|
86
|
+
val.split
|
87
|
+
when Array
|
88
|
+
val.compact
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def transform_keys(hash)
|
94
|
+
result = {}
|
95
|
+
hash.each_key do |key|
|
96
|
+
result[yield(key)] = hash[key]
|
97
|
+
end
|
98
|
+
result
|
99
|
+
end
|
100
|
+
|
101
|
+
# Apply #to_sym to all keys in the hash
|
102
|
+
#
|
103
|
+
# hash - the hash to which to apply this transformation
|
104
|
+
#
|
105
|
+
# Returns a new hash with symbolized keys
|
106
|
+
def symbolize_hash_keys(hash)
|
107
|
+
transform_keys(hash) { |key| key.to_sym rescue key }
|
108
|
+
end
|
109
|
+
|
110
|
+
# Apply #to_s to all keys in the Hash
|
111
|
+
#
|
112
|
+
# hash - the hash to which to apply this transformation
|
113
|
+
#
|
114
|
+
# Returns a new hash with stringified keys
|
115
|
+
def stringify_hash_keys(hash)
|
116
|
+
transform_keys(hash) { |key| key.to_s rescue key }
|
117
|
+
end
|
118
|
+
|
119
|
+
# Parse a date/time and throw an error if invalid
|
120
|
+
#
|
121
|
+
# input - the date/time to parse
|
122
|
+
# msg - (optional) the error message to show the user
|
123
|
+
#
|
124
|
+
# Returns the parsed date if successful, throws a FatalException
|
125
|
+
# if not
|
126
|
+
def parse_date(input, msg = "Input could not be parsed.")
|
127
|
+
Time.parse(input).localtime
|
128
|
+
rescue ArgumentError
|
129
|
+
raise Errors::FatalException.new("Invalid date '#{input}': " + msg)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Determines whether a given file has
|
133
|
+
#
|
134
|
+
# Returns true if the YAML front matter is present.
|
135
|
+
def has_yaml_header?(file)
|
136
|
+
!!(File.open(file, 'rb') { |f| f.readline } =~ /\A---\s*\r?\n/)
|
137
|
+
rescue EOFError
|
138
|
+
false
|
139
|
+
end
|
140
|
+
|
141
|
+
# Slugify a filename or title.
|
142
|
+
#
|
143
|
+
# string - the filename or title to slugify
|
144
|
+
# mode - how string is slugified
|
145
|
+
# cased - whether to replace all uppercase letters with their
|
146
|
+
# lowercase counterparts
|
147
|
+
#
|
148
|
+
# When mode is "none", return the given string.
|
149
|
+
#
|
150
|
+
# When mode is "raw", return the given string,
|
151
|
+
# with every sequence of spaces characters replaced with a hyphen.
|
152
|
+
#
|
153
|
+
# When mode is "default" or nil, non-alphabetic characters are
|
154
|
+
# replaced with a hyphen too.
|
155
|
+
#
|
156
|
+
# When mode is "pretty", some non-alphabetic characters (._~!$&'()+,;=@)
|
157
|
+
# are not replaced with hyphen.
|
158
|
+
#
|
159
|
+
# If cased is true, all uppercase letters in the result string are
|
160
|
+
# replaced with their lowercase counterparts.
|
161
|
+
#
|
162
|
+
# Examples:
|
163
|
+
# slugify("The _config.yml file")
|
164
|
+
# # => "the-config-yml-file"
|
165
|
+
#
|
166
|
+
# slugify("The _config.yml file", "pretty")
|
167
|
+
# # => "the-_config.yml-file"
|
168
|
+
#
|
169
|
+
# slugify("The _config.yml file", "pretty", true)
|
170
|
+
# # => "The-_config.yml file"
|
171
|
+
#
|
172
|
+
# Returns the slugified string.
|
173
|
+
def slugify(string, mode: nil, cased: false)
|
174
|
+
mode ||= 'default'
|
175
|
+
return nil if string.nil?
|
176
|
+
|
177
|
+
unless SLUGIFY_MODES.include?(mode)
|
178
|
+
return cased ? string : string.downcase
|
179
|
+
end
|
180
|
+
|
181
|
+
# Replace each character sequence with a hyphen
|
182
|
+
re =
|
183
|
+
case mode
|
184
|
+
when 'raw'
|
185
|
+
SLUGIFY_RAW_REGEXP
|
186
|
+
when 'default'
|
187
|
+
SLUGIFY_DEFAULT_REGEXP
|
188
|
+
when 'pretty'
|
189
|
+
# "._~!$&'()+,;=@" is human readable (not URI-escaped) in URL
|
190
|
+
# and is allowed in both extN and NTFS.
|
191
|
+
SLUGIFY_PRETTY_REGEXP
|
192
|
+
end
|
193
|
+
|
194
|
+
# Strip according to the mode
|
195
|
+
slug = string.gsub(re, '-')
|
196
|
+
|
197
|
+
# Remove leading/trailing hyphen
|
198
|
+
slug.gsub!(/^\-|\-$/i, '')
|
199
|
+
|
200
|
+
slug.downcase! unless cased
|
201
|
+
slug
|
202
|
+
end
|
203
|
+
|
204
|
+
# Add an appropriate suffix to template so that it matches the specified
|
205
|
+
# permalink style.
|
206
|
+
#
|
207
|
+
# template - permalink template without trailing slash or file extension
|
208
|
+
# permalink_style - permalink style, either built-in or custom
|
209
|
+
#
|
210
|
+
# The returned permalink template will use the same ending style as
|
211
|
+
# specified in permalink_style. For example, if permalink_style contains a
|
212
|
+
# trailing slash (or is :pretty, which indirectly has a trailing slash),
|
213
|
+
# then so will the returned template. If permalink_style has a trailing
|
214
|
+
# ":output_ext" (or is :none, :date, or :ordinal) then so will the returned
|
215
|
+
# template. Otherwise, template will be returned without modification.
|
216
|
+
#
|
217
|
+
# Examples:
|
218
|
+
# add_permalink_suffix("/:basename", :pretty)
|
219
|
+
# # => "/:basename/"
|
220
|
+
#
|
221
|
+
# add_permalink_suffix("/:basename", :date)
|
222
|
+
# # => "/:basename:output_ext"
|
223
|
+
#
|
224
|
+
# add_permalink_suffix("/:basename", "/:year/:month/:title/")
|
225
|
+
# # => "/:basename/"
|
226
|
+
#
|
227
|
+
# add_permalink_suffix("/:basename", "/:year/:month/:title")
|
228
|
+
# # => "/:basename"
|
229
|
+
#
|
230
|
+
# Returns the updated permalink template
|
231
|
+
def add_permalink_suffix(template, permalink_style)
|
232
|
+
case permalink_style
|
233
|
+
when :pretty
|
234
|
+
template << "/"
|
235
|
+
when :date, :ordinal, :none
|
236
|
+
template << ":output_ext"
|
237
|
+
else
|
238
|
+
template << "/" if permalink_style.to_s.end_with?("/")
|
239
|
+
template << ":output_ext" if permalink_style.to_s.end_with?(":output_ext")
|
240
|
+
end
|
241
|
+
template
|
242
|
+
end
|
243
|
+
|
244
|
+
# Work the same way as Dir.glob but seperating the input into two parts
|
245
|
+
# ('dir' + '/' + 'pattern') to make sure the first part('dir') does not act
|
246
|
+
# as a pattern.
|
247
|
+
#
|
248
|
+
# For example, Dir.glob("path[/*") always returns an empty array,
|
249
|
+
# because the method fails to find the closing pattern to '[' which is ']'
|
250
|
+
#
|
251
|
+
# Examples:
|
252
|
+
# safe_glob("path[", "*")
|
253
|
+
# # => ["path[/file1", "path[/file2"]
|
254
|
+
#
|
255
|
+
# safe_glob("path", "*", File::FNM_DOTMATCH)
|
256
|
+
# # => ["path/.", "path/..", "path/file1"]
|
257
|
+
#
|
258
|
+
# safe_glob("path", ["**", "*"])
|
259
|
+
# # => ["path[/file1", "path[/folder/file2"]
|
260
|
+
#
|
261
|
+
# dir - the dir where glob will be executed under
|
262
|
+
# (the dir will be included to each result)
|
263
|
+
# patterns - the patterns (or the pattern) which will be applied under the dir
|
264
|
+
# flags - the flags which will be applied to the pattern
|
265
|
+
#
|
266
|
+
# Returns matched pathes
|
267
|
+
def safe_glob(dir, patterns, flags = 0)
|
268
|
+
return [] unless Dir.exist?(dir)
|
269
|
+
pattern = File.join(Array patterns)
|
270
|
+
return [dir] if pattern.empty?
|
271
|
+
Dir.chdir(dir) do
|
272
|
+
Dir.glob(pattern, flags).map { |f| File.join(dir, f) }
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
# Returns merged option hash for File.read of self.site (if exists)
|
277
|
+
# and a given param
|
278
|
+
def merged_file_read_opts(site, opts)
|
279
|
+
merged = (site ? site.file_read_opts : {}).merge(opts)
|
280
|
+
if merged["encoding"] && !merged["encoding"].start_with?("bom|")
|
281
|
+
merged["encoding"].insert(0, "bom|")
|
282
|
+
end
|
283
|
+
merged
|
284
|
+
end
|
285
|
+
|
286
|
+
end
|
287
|
+
end
|