nirvdrum-jekyll 0.5.2
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.
- data/.gitignore +7 -0
- data/History.txt +143 -0
- data/README.textile +42 -0
- data/Rakefile +92 -0
- data/VERSION.yml +4 -0
- data/bin/jekyll +150 -0
- data/features/create_sites.feature +60 -0
- data/features/embed_filters.feature +60 -0
- data/features/pagination.feature +40 -0
- data/features/permalinks.feature +63 -0
- data/features/post_data.feature +153 -0
- data/features/site_configuration.feature +63 -0
- data/features/site_data.feature +82 -0
- data/features/step_definitions/jekyll_steps.rb +136 -0
- data/features/support/env.rb +16 -0
- data/jekyll.gemspec +134 -0
- data/lib/jekyll.rb +86 -0
- data/lib/jekyll/albino.rb +122 -0
- data/lib/jekyll/converters/csv.rb +26 -0
- data/lib/jekyll/converters/mephisto.rb +79 -0
- data/lib/jekyll/converters/mt.rb +59 -0
- data/lib/jekyll/converters/textpattern.rb +50 -0
- data/lib/jekyll/converters/typo.rb +49 -0
- data/lib/jekyll/converters/wordpress.rb +54 -0
- data/lib/jekyll/convertible.rb +89 -0
- data/lib/jekyll/core_ext.rb +30 -0
- data/lib/jekyll/filters.rb +47 -0
- data/lib/jekyll/layout.rb +36 -0
- data/lib/jekyll/page.rb +112 -0
- data/lib/jekyll/pager.rb +45 -0
- data/lib/jekyll/post.rb +251 -0
- data/lib/jekyll/site.rb +295 -0
- data/lib/jekyll/stylesheet.rb +88 -0
- data/lib/jekyll/tags/highlight.rb +56 -0
- data/lib/jekyll/tags/include.rb +31 -0
- data/test/helper.rb +27 -0
- data/test/source/_includes/sig.markdown +3 -0
- data/test/source/_layouts/default.html +27 -0
- data/test/source/_layouts/simple.html +1 -0
- data/test/source/_posts/2008-02-02-not-published.textile +8 -0
- data/test/source/_posts/2008-02-02-published.textile +8 -0
- data/test/source/_posts/2008-10-18-foo-bar.textile +8 -0
- data/test/source/_posts/2008-11-21-complex.textile +8 -0
- data/test/source/_posts/2008-12-03-permalinked-post.textile +9 -0
- data/test/source/_posts/2008-12-13-include.markdown +8 -0
- data/test/source/_posts/2009-01-27-array-categories.textile +10 -0
- data/test/source/_posts/2009-01-27-categories.textile +7 -0
- data/test/source/_posts/2009-01-27-category.textile +7 -0
- data/test/source/_posts/2009-03-12-hash-#1.markdown +6 -0
- data/test/source/_posts/2009-05-18-tag.textile +6 -0
- data/test/source/_posts/2009-05-18-tags.textile +9 -0
- data/test/source/_posts/2009-06-22-empty-yaml.textile +3 -0
- data/test/source/_posts/2009-06-22-no-yaml.textile +1 -0
- data/test/source/_stylesheets/nested/override.less +1 -0
- data/test/source/_stylesheets/simple.less +3 -0
- data/test/source/about.html +6 -0
- data/test/source/category/_posts/2008-9-23-categories.textile +6 -0
- data/test/source/contacts.html +5 -0
- data/test/source/css/screen.css +76 -0
- data/test/source/foo/_posts/bar/2008-12-12-topical-post.textile +8 -0
- data/test/source/index.html +22 -0
- data/test/source/win/_posts/2009-05-24-yaml-linebreak.markdown +7 -0
- data/test/source/z_category/_posts/2008-9-23-categories.textile +6 -0
- data/test/suite.rb +9 -0
- data/test/test_configuration.rb +29 -0
- data/test/test_filters.rb +49 -0
- data/test/test_generated_site.rb +53 -0
- data/test/test_page.rb +87 -0
- data/test/test_pager.rb +47 -0
- data/test/test_post.rb +302 -0
- data/test/test_site.rb +85 -0
- data/test/test_stylesheet.rb +67 -0
- data/test/test_tags.rb +116 -0
- metadata +196 -0
data/lib/jekyll/site.rb
ADDED
@@ -0,0 +1,295 @@
|
|
1
|
+
module Jekyll
|
2
|
+
|
3
|
+
class Site
|
4
|
+
attr_accessor :config, :layouts, :posts, :categories, :exclude,
|
5
|
+
:source, :dest, :lsi, :pygments, :permalink_style, :tags
|
6
|
+
|
7
|
+
# Initialize the site
|
8
|
+
# +config+ is a Hash containing site configurations details
|
9
|
+
#
|
10
|
+
# Returns <Site>
|
11
|
+
def initialize(config)
|
12
|
+
self.config = config.clone
|
13
|
+
|
14
|
+
self.source = config['source']
|
15
|
+
self.dest = config['destination']
|
16
|
+
self.lsi = config['lsi']
|
17
|
+
self.pygments = config['pygments']
|
18
|
+
self.permalink_style = config['permalink'].to_sym
|
19
|
+
self.exclude = config['exclude'] || []
|
20
|
+
|
21
|
+
self.reset
|
22
|
+
self.setup
|
23
|
+
end
|
24
|
+
|
25
|
+
def reset
|
26
|
+
self.layouts = {}
|
27
|
+
self.posts = []
|
28
|
+
self.categories = Hash.new { |hash, key| hash[key] = [] }
|
29
|
+
self.tags = Hash.new { |hash, key| hash[key] = [] }
|
30
|
+
end
|
31
|
+
|
32
|
+
def setup
|
33
|
+
# Check to see if LSI is enabled.
|
34
|
+
require 'classifier' if self.lsi
|
35
|
+
|
36
|
+
# Set the Markdown interpreter (and Maruku self.config, if necessary)
|
37
|
+
case self.config['markdown']
|
38
|
+
when 'rdiscount'
|
39
|
+
begin
|
40
|
+
require 'rdiscount'
|
41
|
+
|
42
|
+
def markdown(content)
|
43
|
+
RDiscount.new(content).to_html
|
44
|
+
end
|
45
|
+
|
46
|
+
rescue LoadError
|
47
|
+
puts 'You must have the rdiscount gem installed first'
|
48
|
+
end
|
49
|
+
when 'maruku'
|
50
|
+
begin
|
51
|
+
require 'maruku'
|
52
|
+
|
53
|
+
def markdown(content)
|
54
|
+
Maruku.new(content).to_html
|
55
|
+
end
|
56
|
+
|
57
|
+
if self.config['maruku']['use_divs']
|
58
|
+
require 'maruku/ext/div'
|
59
|
+
puts 'Maruku: Using extended syntax for div elements.'
|
60
|
+
end
|
61
|
+
|
62
|
+
if self.config['maruku']['use_tex']
|
63
|
+
require 'maruku/ext/math'
|
64
|
+
puts "Maruku: Using LaTeX extension. Images in `#{self.config['maruku']['png_dir']}`."
|
65
|
+
|
66
|
+
# Switch off MathML output
|
67
|
+
MaRuKu::Globals[:html_math_output_mathml] = false
|
68
|
+
MaRuKu::Globals[:html_math_engine] = 'none'
|
69
|
+
|
70
|
+
# Turn on math to PNG support with blahtex
|
71
|
+
# Resulting PNGs stored in `images/latex`
|
72
|
+
MaRuKu::Globals[:html_math_output_png] = true
|
73
|
+
MaRuKu::Globals[:html_png_engine] = self.config['maruku']['png_engine']
|
74
|
+
MaRuKu::Globals[:html_png_dir] = self.config['maruku']['png_dir']
|
75
|
+
MaRuKu::Globals[:html_png_url] = self.config['maruku']['png_url']
|
76
|
+
end
|
77
|
+
rescue LoadError
|
78
|
+
puts "The maruku gem is required for markdown support!"
|
79
|
+
end
|
80
|
+
else
|
81
|
+
raise "Invalid Markdown processor: '#{self.config['markdown']}' -- did you mean 'maruku' or 'rdiscount'?"
|
82
|
+
end
|
83
|
+
|
84
|
+
begin
|
85
|
+
require 'less'
|
86
|
+
|
87
|
+
def less(content)
|
88
|
+
Less::Engine.new(content).to_css
|
89
|
+
end
|
90
|
+
rescue LoadError
|
91
|
+
# Less is not required for stylesheet handling.
|
92
|
+
def less(content)
|
93
|
+
raise "The less gem is required for stylesheet support!"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def textile(content)
|
99
|
+
RedCloth.new(content).to_html
|
100
|
+
end
|
101
|
+
|
102
|
+
# Do the actual work of processing the site and generating the
|
103
|
+
# real deal.
|
104
|
+
#
|
105
|
+
# Returns nothing
|
106
|
+
def process
|
107
|
+
self.reset
|
108
|
+
self.read_layouts
|
109
|
+
self.transform_stylesheets
|
110
|
+
self.transform_pages
|
111
|
+
self.write_posts
|
112
|
+
end
|
113
|
+
|
114
|
+
# Read all the files in <source>/_layouts into memory for later use.
|
115
|
+
#
|
116
|
+
# Returns nothing
|
117
|
+
def read_layouts
|
118
|
+
base = File.join(self.source, "_layouts")
|
119
|
+
entries = []
|
120
|
+
Dir.chdir(base) { entries = filter_entries(Dir['*.*']) }
|
121
|
+
|
122
|
+
entries.each do |f|
|
123
|
+
name = f.split(".")[0..-2].join(".")
|
124
|
+
self.layouts[name] = Layout.new(self, base, f)
|
125
|
+
end
|
126
|
+
rescue Errno::ENOENT => e
|
127
|
+
# ignore missing layout dir
|
128
|
+
end
|
129
|
+
|
130
|
+
# Read all the files in <base>/_posts and create a new Post object with each one.
|
131
|
+
#
|
132
|
+
# Returns nothing
|
133
|
+
def read_posts(dir)
|
134
|
+
base = File.join(self.source, dir, '_posts')
|
135
|
+
entries = []
|
136
|
+
Dir.chdir(base) { entries = filter_entries(Dir['**/*']) }
|
137
|
+
|
138
|
+
# first pass processes, but does not yet render post content
|
139
|
+
entries.each do |f|
|
140
|
+
if Post.valid?(f)
|
141
|
+
post = Post.new(self, self.source, dir, f)
|
142
|
+
|
143
|
+
if post.published
|
144
|
+
self.posts << post
|
145
|
+
post.categories.each { |c| self.categories[c] << post }
|
146
|
+
post.tags.each { |c| self.tags[c] << post }
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
self.posts.sort!
|
152
|
+
|
153
|
+
# second pass renders each post now that full site payload is available
|
154
|
+
self.posts.each do |post|
|
155
|
+
post.render(self.layouts, site_payload)
|
156
|
+
end
|
157
|
+
|
158
|
+
self.categories.values.map { |ps| ps.sort! { |a, b| b <=> a} }
|
159
|
+
self.tags.values.map { |ps| ps.sort! { |a, b| b <=> a} }
|
160
|
+
rescue Errno::ENOENT => e
|
161
|
+
# ignore missing layout dir
|
162
|
+
end
|
163
|
+
|
164
|
+
# Write each post to <dest>/<year>/<month>/<day>/<slug>
|
165
|
+
#
|
166
|
+
# Returns nothing
|
167
|
+
def write_posts
|
168
|
+
self.posts.each do |post|
|
169
|
+
post.write(self.dest)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# Copy all regular files from <source> to <dest>/ ignoring
|
174
|
+
# any files/directories that are hidden or backup files (start
|
175
|
+
# with "." or "#" or end with "~") or contain site content (start with "_")
|
176
|
+
# unless they are "_posts" directories or web server files such as
|
177
|
+
# '.htaccess'
|
178
|
+
# The +dir+ String is a relative path used to call this method
|
179
|
+
# recursively as it descends through directories
|
180
|
+
#
|
181
|
+
# Returns nothing
|
182
|
+
def transform_pages(dir = '')
|
183
|
+
base = File.join(self.source, dir)
|
184
|
+
entries = filter_entries(Dir.entries(base))
|
185
|
+
directories = entries.select { |e| File.directory?(File.join(base, e)) }
|
186
|
+
files = entries.reject { |e| File.directory?(File.join(base, e)) }
|
187
|
+
|
188
|
+
# we need to make sure to process _posts *first* otherwise they
|
189
|
+
# might not be available yet to other templates as {{ site.posts }}
|
190
|
+
if directories.include?('_posts')
|
191
|
+
directories.delete('_posts')
|
192
|
+
read_posts(dir)
|
193
|
+
end
|
194
|
+
|
195
|
+
[directories, files].each do |entries|
|
196
|
+
entries.each do |f|
|
197
|
+
if File.directory?(File.join(base, f))
|
198
|
+
next if self.dest.sub(/\/$/, '') == File.join(base, f)
|
199
|
+
transform_pages(File.join(dir, f))
|
200
|
+
elsif Pager.pagination_enabled?(self.config, f)
|
201
|
+
paginate_posts(f, dir)
|
202
|
+
else
|
203
|
+
first3 = File.open(File.join(self.source, dir, f)) { |fd| fd.read(3) }
|
204
|
+
|
205
|
+
if first3 == "---"
|
206
|
+
# file appears to have a YAML header so process it as a page
|
207
|
+
page = Page.new(self, self.source, dir, f)
|
208
|
+
page.render(self.layouts, site_payload)
|
209
|
+
page.write(self.dest)
|
210
|
+
else
|
211
|
+
# otherwise copy the file without transforming it
|
212
|
+
FileUtils.mkdir_p(File.join(self.dest, dir))
|
213
|
+
FileUtils.cp(File.join(self.source, dir, f), File.join(self.dest, dir, f))
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def transform_stylesheets
|
221
|
+
dir = "_stylesheets"
|
222
|
+
base = File.join(self.source, dir)
|
223
|
+
entries = []
|
224
|
+
Dir.chdir(base) { entries = filter_entries(Dir['**/*.*']) }
|
225
|
+
|
226
|
+
entries.each do |f|
|
227
|
+
stylesheet = Stylesheet.new(self, self.source, dir, f)
|
228
|
+
stylesheet.render()
|
229
|
+
stylesheet.write(self.dest)
|
230
|
+
end
|
231
|
+
rescue Errno::ENOENT => e
|
232
|
+
# ignore missing stylesheet dir
|
233
|
+
end
|
234
|
+
|
235
|
+
# Constructs a hash map of Posts indexed by the specified Post attribute
|
236
|
+
#
|
237
|
+
# Returns {post_attr => [<Post>]}
|
238
|
+
def post_attr_hash(post_attr)
|
239
|
+
# Build a hash map based on the specified post attribute ( post attr => array of posts )
|
240
|
+
# then sort each array in reverse order
|
241
|
+
hash = Hash.new { |hash, key| hash[key] = Array.new }
|
242
|
+
self.posts.each { |p| p.send(post_attr.to_sym).each { |t| hash[t] << p } }
|
243
|
+
hash.values.map { |sortme| sortme.sort! { |a, b| b <=> a} }
|
244
|
+
return hash
|
245
|
+
end
|
246
|
+
|
247
|
+
# The Hash payload containing site-wide data
|
248
|
+
#
|
249
|
+
# Returns {"site" => {"time" => <Time>,
|
250
|
+
# "posts" => [<Post>],
|
251
|
+
# "categories" => [<Post>]}
|
252
|
+
def site_payload
|
253
|
+
{"site" => self.config.merge({
|
254
|
+
"time" => Time.now,
|
255
|
+
"posts" => self.posts.sort { |a,b| b <=> a },
|
256
|
+
"categories" => post_attr_hash('categories'),
|
257
|
+
"tags" => post_attr_hash('tags')})}
|
258
|
+
end
|
259
|
+
|
260
|
+
# Filter out any files/directories that are hidden or backup files (start
|
261
|
+
# with "." or "#" or end with "~") or contain site content (start with "_")
|
262
|
+
# unless they are "_posts" directories or web server files such as
|
263
|
+
# '.htaccess'
|
264
|
+
def filter_entries(entries)
|
265
|
+
entries = entries.reject do |e|
|
266
|
+
unless ['_posts', '.htaccess'].include?(e)
|
267
|
+
['.', '_', '#'].include?(e[0..0]) || e[-1..-1] == '~' || self.exclude.include?(e)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
# Paginates the blog's posts. Renders the index.html file into paginated directories, ie: page2, page3...
|
273
|
+
# and adds more wite-wide data
|
274
|
+
#
|
275
|
+
# {"paginator" => { "page" => <Number>,
|
276
|
+
# "per_page" => <Number>,
|
277
|
+
# "posts" => [<Post>],
|
278
|
+
# "total_posts" => <Number>,
|
279
|
+
# "total_pages" => <Number>,
|
280
|
+
# "previous_page" => <Number>,
|
281
|
+
# "next_page" => <Number> }}
|
282
|
+
def paginate_posts(file, dir)
|
283
|
+
all_posts = self.posts.sort { |a,b| b <=> a }
|
284
|
+
pages = Pager.calculate_pages(all_posts, self.config['paginate'].to_i)
|
285
|
+
pages += 1
|
286
|
+
(1..pages).each do |num_page|
|
287
|
+
pager = Pager.new(self.config, num_page, all_posts, pages)
|
288
|
+
page = Page.new(self, self.source, dir, file)
|
289
|
+
page.render(self.layouts, site_payload.merge({'paginator' => pager.to_hash}))
|
290
|
+
suffix = "page#{num_page}" if num_page > 1
|
291
|
+
page.write(self.dest, suffix)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Jekyll
|
2
|
+
|
3
|
+
class Stylesheet
|
4
|
+
include Convertible
|
5
|
+
|
6
|
+
attr_accessor :site
|
7
|
+
attr_accessor :name, :ext, :basename
|
8
|
+
attr_accessor :data, :content, :output
|
9
|
+
|
10
|
+
# Initialize a new Page.
|
11
|
+
# +site+ is the Site
|
12
|
+
# +base+ is the String path to the <source>
|
13
|
+
# +dir+ is the String path between <source> and the file
|
14
|
+
# +name+ is the String filename of the file
|
15
|
+
#
|
16
|
+
# Returns <Page>
|
17
|
+
def initialize(site, base, dir, name)
|
18
|
+
@site = site
|
19
|
+
@base = base
|
20
|
+
@dir = dir
|
21
|
+
@name = name
|
22
|
+
|
23
|
+
self.process(name)
|
24
|
+
self.content = File.read(File.join(File.join(base, dir), name))
|
25
|
+
end
|
26
|
+
|
27
|
+
# The generated directory into which the page will be placed
|
28
|
+
# upon generation. This is derived from the permalink or, if
|
29
|
+
# permalink is absent, set to '/'
|
30
|
+
#
|
31
|
+
# Returns <String>
|
32
|
+
def dir
|
33
|
+
url[-1, 1] == '/' ? url : File.dirname(url)
|
34
|
+
end
|
35
|
+
|
36
|
+
# The generated relative url of this stylesheet
|
37
|
+
# e.g. /css/site.css
|
38
|
+
#
|
39
|
+
# Returns <String>
|
40
|
+
def url
|
41
|
+
@url ||= "/css/#{basename}.css"
|
42
|
+
end
|
43
|
+
|
44
|
+
# Extract information from the page filename
|
45
|
+
# +name+ is the String filename of the page file
|
46
|
+
#
|
47
|
+
# Returns nothing
|
48
|
+
def process(name)
|
49
|
+
self.ext = File.extname(name)
|
50
|
+
self.basename = name.split('.')[0..-2].first
|
51
|
+
end
|
52
|
+
|
53
|
+
# Add any necessary layouts to this post
|
54
|
+
# +layouts+ is a Hash of {"name" => "layout"}
|
55
|
+
# +site_payload+ is the site payload hash
|
56
|
+
#
|
57
|
+
# Returns nothing
|
58
|
+
def render()
|
59
|
+
self.transform
|
60
|
+
|
61
|
+
# output keeps track of what will finally be written
|
62
|
+
self.output = self.content
|
63
|
+
end
|
64
|
+
|
65
|
+
# Write the generated page file to the destination directory.
|
66
|
+
# +dest_prefix+ is the String path to the destination dir
|
67
|
+
# +dest_suffix+ is a suffix path to the destination dir
|
68
|
+
#
|
69
|
+
# Returns nothing
|
70
|
+
def write(dest_prefix, dest_suffix = nil)
|
71
|
+
dest = dest_suffix ? File.join(dest_prefix, dest_suffix) : dest_prefix
|
72
|
+
FileUtils.mkdir_p(dest)
|
73
|
+
|
74
|
+
# The url needs to be unescaped in order to preserve the correct filename
|
75
|
+
path = File.join(dest, CGI.unescape(self.url))
|
76
|
+
dirname = File.dirname(path)
|
77
|
+
unless File.exists?(dirname)
|
78
|
+
FileUtils.mkdir_p(dirname)
|
79
|
+
end
|
80
|
+
|
81
|
+
File.open(path, 'w') do |f|
|
82
|
+
f.write(self.output)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Jekyll
|
2
|
+
|
3
|
+
class HighlightBlock < Liquid::Block
|
4
|
+
include Liquid::StandardFilters
|
5
|
+
|
6
|
+
# we need a language, but the linenos argument is optional.
|
7
|
+
SYNTAX = /(\w+)\s?(:?linenos)?\s?/
|
8
|
+
|
9
|
+
def initialize(tag_name, markup, tokens)
|
10
|
+
super
|
11
|
+
if markup =~ SYNTAX
|
12
|
+
@lang = $1
|
13
|
+
if defined? $2
|
14
|
+
# additional options to pass to Albino.
|
15
|
+
@options = { 'O' => 'linenos=inline' }
|
16
|
+
else
|
17
|
+
@options = {}
|
18
|
+
end
|
19
|
+
else
|
20
|
+
raise SyntaxError.new("Syntax Error in 'highlight' - Valid syntax: highlight <lang> [linenos]")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def render(context)
|
25
|
+
if context.registers[:site].pygments
|
26
|
+
render_pygments(context, super.to_s)
|
27
|
+
else
|
28
|
+
render_codehighlighter(context, super.to_s)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def render_pygments(context, code)
|
33
|
+
if context["content_type"] == "markdown"
|
34
|
+
return "\n" + Albino.new(code, @lang).to_s(@options) + "\n"
|
35
|
+
elsif context["content_type"] == "textile"
|
36
|
+
return "<notextile>" + Albino.new(code, @lang).to_s(@options) + "</notextile>"
|
37
|
+
else
|
38
|
+
return Albino.new(code, @lang).to_s(@options)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def render_codehighlighter(context, code)
|
43
|
+
#The div is required because RDiscount blows ass
|
44
|
+
<<-HTML
|
45
|
+
<div>
|
46
|
+
<pre>
|
47
|
+
<code class='#{@lang}'>#{h(code).strip}</code>
|
48
|
+
</pre>
|
49
|
+
</div>
|
50
|
+
HTML
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
Liquid::Template.register_tag('highlight', Jekyll::HighlightBlock)
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Jekyll
|
2
|
+
|
3
|
+
class IncludeTag < Liquid::Tag
|
4
|
+
def initialize(tag_name, file, tokens)
|
5
|
+
super
|
6
|
+
@file = file.strip
|
7
|
+
end
|
8
|
+
|
9
|
+
def render(context)
|
10
|
+
if @file !~ /^[a-zA-Z0-9_\/\.-]+$/ || @file =~ /\.\// || @file =~ /\/\./
|
11
|
+
return "Include file '#{@file}' contains invalid characters or sequences"
|
12
|
+
end
|
13
|
+
|
14
|
+
Dir.chdir(File.join(context.registers[:site].source, '_includes')) do
|
15
|
+
choices = Dir['**/*'].reject { |x| File.symlink?(x) }
|
16
|
+
if choices.include?(@file)
|
17
|
+
source = File.read(@file)
|
18
|
+
partial = Liquid::Template.parse(source)
|
19
|
+
context.stack do
|
20
|
+
partial.render(context)
|
21
|
+
end
|
22
|
+
else
|
23
|
+
"Included file '#{@file}' not found in _includes directory"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
Liquid::Template.register_tag('include', Jekyll::IncludeTag)
|