jroes-jekyll 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. data/History.txt +127 -0
  2. data/README.textile +41 -0
  3. data/Rakefile +91 -0
  4. data/VERSION.yml +4 -0
  5. data/bin/jekyll +150 -0
  6. data/lib/jekyll/albino.rb +122 -0
  7. data/lib/jekyll/converters/csv.rb +26 -0
  8. data/lib/jekyll/converters/mephisto.rb +79 -0
  9. data/lib/jekyll/converters/mt.rb +59 -0
  10. data/lib/jekyll/converters/textpattern.rb +50 -0
  11. data/lib/jekyll/converters/typo.rb +49 -0
  12. data/lib/jekyll/converters/wordpress.rb +54 -0
  13. data/lib/jekyll/convertible.rb +82 -0
  14. data/lib/jekyll/core_ext.rb +22 -0
  15. data/lib/jekyll/filters.rb +47 -0
  16. data/lib/jekyll/layout.rb +36 -0
  17. data/lib/jekyll/page.rb +70 -0
  18. data/lib/jekyll/pager.rb +45 -0
  19. data/lib/jekyll/post.rb +247 -0
  20. data/lib/jekyll/site.rb +266 -0
  21. data/lib/jekyll/tags/highlight.rb +56 -0
  22. data/lib/jekyll/tags/include.rb +31 -0
  23. data/lib/jekyll.rb +84 -0
  24. data/test/helper.rb +27 -0
  25. data/test/source/_includes/sig.markdown +3 -0
  26. data/test/source/_layouts/default.html +27 -0
  27. data/test/source/_layouts/simple.html +1 -0
  28. data/test/source/_posts/2008-02-02-not-published.textile +8 -0
  29. data/test/source/_posts/2008-02-02-published.textile +8 -0
  30. data/test/source/_posts/2008-10-18-foo-bar.textile +8 -0
  31. data/test/source/_posts/2008-11-21-complex.textile +8 -0
  32. data/test/source/_posts/2008-12-03-permalinked-post.textile +9 -0
  33. data/test/source/_posts/2008-12-13-include.markdown +8 -0
  34. data/test/source/_posts/2009-01-27-array-categories.textile +10 -0
  35. data/test/source/_posts/2009-01-27-categories.textile +7 -0
  36. data/test/source/_posts/2009-01-27-category.textile +7 -0
  37. data/test/source/_posts/2009-03-12-hash-#1.markdown +6 -0
  38. data/test/source/category/_posts/2008-9-23-categories.textile +6 -0
  39. data/test/source/css/screen.css +76 -0
  40. data/test/source/foo/_posts/bar/2008-12-12-topical-post.textile +8 -0
  41. data/test/source/index.html +22 -0
  42. data/test/source/z_category/_posts/2008-9-23-categories.textile +6 -0
  43. data/test/suite.rb +9 -0
  44. data/test/test_filters.rb +49 -0
  45. data/test/test_post.rb +279 -0
  46. data/test/test_site.rb +69 -0
  47. data/test/test_tags.rb +116 -0
  48. metadata +163 -0
@@ -0,0 +1,247 @@
1
+ module Jekyll
2
+
3
+ class Post
4
+ include Comparable
5
+ include Convertible
6
+
7
+ class << self
8
+ attr_accessor :lsi
9
+ end
10
+
11
+ MATCHER = /^(.+\/)*(\d+-\d+-\d+)-(.*)(\.[^.]+)$/
12
+
13
+ # Post name validator. Post filenames must be like:
14
+ # 2008-11-05-my-awesome-post.textile
15
+ #
16
+ # Returns <Bool>
17
+ def self.valid?(name)
18
+ name =~ MATCHER
19
+ end
20
+
21
+ attr_accessor :site, :date, :slug, :ext, :published, :data, :content, :output, :tags
22
+ attr_writer :categories
23
+
24
+ def categories
25
+ @categories ||= []
26
+ end
27
+
28
+ # Initialize this Post instance.
29
+ # +site+ is the Site
30
+ # +base+ is the String path to the dir containing the post file
31
+ # +name+ is the String filename of the post file
32
+ # +categories+ is an Array of Strings for the categories for this post
33
+ #
34
+ # Returns <Post>
35
+ def initialize(site, source, dir, name)
36
+ @site = site
37
+ @base = File.join(source, dir, '_posts')
38
+ @name = name
39
+
40
+ self.categories = dir.split('/').reject { |x| x.empty? }
41
+ self.process(name)
42
+ self.read_yaml(@base, name)
43
+
44
+ if self.data.has_key?('published') && self.data['published'] == false
45
+ self.published = false
46
+ else
47
+ self.published = true
48
+ end
49
+
50
+ if self.data.has_key?("tag")
51
+ self.tags = [self.data["tag"]]
52
+ elsif self.data.has_key?("tags")
53
+ self.tags = self.data['tags']
54
+ else
55
+ self.tags = []
56
+ end
57
+
58
+ if self.categories.empty?
59
+ if self.data.has_key?('category')
60
+ self.categories << self.data['category']
61
+ elsif self.data.has_key?('categories')
62
+ # Look for categories in the YAML-header, either specified as
63
+ # an array or a string.
64
+ if self.data['categories'].kind_of? String
65
+ self.categories = self.data['categories'].split
66
+ else
67
+ self.categories = self.data['categories']
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ # Spaceship is based on Post#date
74
+ #
75
+ # Returns -1, 0, 1
76
+ def <=>(other)
77
+ self.date <=> other.date
78
+ end
79
+
80
+ # Extract information from the post filename
81
+ # +name+ is the String filename of the post file
82
+ #
83
+ # Returns nothing
84
+ def process(name)
85
+ m, cats, date, slug, ext = *name.match(MATCHER)
86
+ self.date = Time.parse(date)
87
+ self.slug = slug
88
+ self.ext = ext
89
+ end
90
+
91
+ # The generated directory into which the post will be placed
92
+ # upon generation. This is derived from the permalink or, if
93
+ # permalink is absent, set to the default date
94
+ # e.g. "/2008/11/05/" if the permalink style is :date, otherwise nothing
95
+ #
96
+ # Returns <String>
97
+ def dir
98
+ File.dirname(url)
99
+ end
100
+
101
+ # The full path and filename of the post.
102
+ # Defined in the YAML of the post body
103
+ # (Optional)
104
+ #
105
+ # Returns <String>
106
+ def permalink
107
+ self.data && self.data['permalink']
108
+ end
109
+
110
+ def template
111
+ case self.site.permalink_style
112
+ when :pretty
113
+ "/:categories/:year/:month/:day/:title"
114
+ when :none
115
+ "/:categories/:title.html"
116
+ when :date
117
+ "/:categories/:year/:month/:day/:title.html"
118
+ else
119
+ self.site.permalink_style.to_s
120
+ end
121
+ end
122
+
123
+ # The generated relative url of this post
124
+ # e.g. /2008/11/05/my-awesome-post.html
125
+ #
126
+ # Returns <String>
127
+ def url
128
+ return permalink if permalink
129
+
130
+ @url ||= {
131
+ "year" => date.strftime("%Y"),
132
+ "month" => date.strftime("%m"),
133
+ "day" => date.strftime("%d"),
134
+ "title" => CGI.escape(slug),
135
+ "categories" => categories.sort.join('/')
136
+ }.inject(template) { |result, token|
137
+ result.gsub(/:#{token.first}/, token.last)
138
+ }.gsub(/\/\//, "/")
139
+ end
140
+
141
+ # The UID for this post (useful in feeds)
142
+ # e.g. /2008/11/05/my-awesome-post
143
+ #
144
+ # Returns <String>
145
+ def id
146
+ File.join(self.dir, self.slug)
147
+ end
148
+
149
+ # Calculate related posts.
150
+ #
151
+ # Returns [<Post>]
152
+ def related_posts(posts)
153
+ return [] unless posts.size > 1
154
+
155
+ if self.site.lsi
156
+ self.class.lsi ||= begin
157
+ puts "Running the classifier... this could take a while."
158
+ lsi = Classifier::LSI.new
159
+ posts.each { |x| $stdout.print(".");$stdout.flush;lsi.add_item(x) }
160
+ puts ""
161
+ lsi
162
+ end
163
+
164
+ related = self.class.lsi.find_related(self.content, 11)
165
+ related - [self]
166
+ else
167
+ (posts - [self])[0..9]
168
+ end
169
+ end
170
+
171
+ # Add any necessary layouts to this post
172
+ # +layouts+ is a Hash of {"name" => "layout"}
173
+ # +site_payload+ is the site payload hash
174
+ #
175
+ # Returns nothing
176
+ def render(layouts, site_payload)
177
+ # construct payload
178
+ payload =
179
+ {
180
+ "site" => { "related_posts" => related_posts(site_payload["site"]["posts"]) },
181
+ "page" => self.to_liquid
182
+ }
183
+ payload = payload.deep_merge(site_payload)
184
+
185
+ do_layout(payload, layouts)
186
+ end
187
+
188
+ # Write the generated post file to the destination directory.
189
+ # +dest+ is the String path to the destination dir
190
+ #
191
+ # Returns nothing
192
+ def write(dest)
193
+ FileUtils.mkdir_p(File.join(dest, dir))
194
+
195
+ # The url needs to be unescaped in order to preserve the correct filename
196
+ path = File.join(dest, CGI.unescape(self.url))
197
+
198
+ if template[/\.html$/].nil?
199
+ FileUtils.mkdir_p(path)
200
+ path = File.join(path, "index.html")
201
+ end
202
+
203
+ File.open(path, 'w') do |f|
204
+ f.write(self.output)
205
+ end
206
+ end
207
+
208
+ # Convert this post into a Hash for use in Liquid templates.
209
+ #
210
+ # Returns <Hash>
211
+ def to_liquid
212
+ { "title" => self.data["title"] || self.slug.split('-').select {|w| w.capitalize! || w }.join(' '),
213
+ "url" => self.url,
214
+ "date" => self.date,
215
+ "id" => self.id,
216
+ "categories" => self.categories,
217
+ "next" => self.next,
218
+ "previous" => self.previous,
219
+ "tags" => self.tags,
220
+ "content" => self.content }.deep_merge(self.data)
221
+ end
222
+
223
+ def inspect
224
+ "<Post: #{self.id}>"
225
+ end
226
+
227
+ def next
228
+ pos = self.site.posts.index(self)
229
+
230
+ if pos && pos < self.site.posts.length-1
231
+ self.site.posts[pos+1]
232
+ else
233
+ nil
234
+ end
235
+ end
236
+
237
+ def previous
238
+ pos = self.site.posts.index(self)
239
+ if pos && pos > 0
240
+ self.site.posts[pos-1]
241
+ else
242
+ nil
243
+ end
244
+ end
245
+ end
246
+
247
+ end
@@ -0,0 +1,266 @@
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
+ end
81
+ end
82
+
83
+ def textile(content)
84
+ RedCloth.new(content).to_html
85
+ end
86
+
87
+ # Do the actual work of processing the site and generating the
88
+ # real deal.
89
+ #
90
+ # Returns nothing
91
+ def process
92
+ self.reset
93
+ self.read_layouts
94
+ self.transform_pages
95
+ self.write_posts
96
+ end
97
+
98
+ # Read all the files in <source>/_layouts into memory for later use.
99
+ #
100
+ # Returns nothing
101
+ def read_layouts
102
+ base = File.join(self.source, "_layouts")
103
+ entries = []
104
+ Dir.chdir(base) { entries = filter_entries(Dir['*.*']) }
105
+
106
+ entries.each do |f|
107
+ name = f.split(".")[0..-2].join(".")
108
+ self.layouts[name] = Layout.new(self, base, f)
109
+ end
110
+ rescue Errno::ENOENT => e
111
+ # ignore missing layout dir
112
+ end
113
+
114
+ # Read all the files in <base>/_posts and create a new Post object with each one.
115
+ #
116
+ # Returns nothing
117
+ def read_posts(dir)
118
+ base = File.join(self.source, dir, '_posts')
119
+ entries = []
120
+ Dir.chdir(base) { entries = filter_entries(Dir['**/*']) }
121
+
122
+ # first pass processes, but does not yet render post content
123
+ entries.each do |f|
124
+ if Post.valid?(f)
125
+ post = Post.new(self, self.source, dir, f)
126
+
127
+ if post.published
128
+ self.posts << post
129
+ post.categories.each { |c| self.categories[c] << post }
130
+ post.tags.each { |c| self.tags[c] << post }
131
+ end
132
+ end
133
+ end
134
+
135
+ self.posts.sort!
136
+
137
+ # second pass renders each post now that full site payload is available
138
+ self.posts.each do |post|
139
+ post.render(self.layouts, site_payload)
140
+ end
141
+
142
+ self.categories.values.map { |ps| ps.sort! { |a, b| b <=> a} }
143
+ self.tags.values.map { |ps| ps.sort! { |a, b| b <=> a} }
144
+ rescue Errno::ENOENT => e
145
+ # ignore missing layout dir
146
+ end
147
+
148
+ # Write each post to <dest>/<year>/<month>/<day>/<slug>
149
+ #
150
+ # Returns nothing
151
+ def write_posts
152
+ self.posts.each do |post|
153
+ post.write(self.dest)
154
+ end
155
+ end
156
+
157
+ # Copy all regular files from <source> to <dest>/ ignoring
158
+ # any files/directories that are hidden or backup files (start
159
+ # with "." or "#" or end with "~") or contain site content (start with "_")
160
+ # unless they are "_posts" directories or web server files such as
161
+ # '.htaccess'
162
+ # The +dir+ String is a relative path used to call this method
163
+ # recursively as it descends through directories
164
+ #
165
+ # Returns nothing
166
+ def transform_pages(dir = '')
167
+ base = File.join(self.source, dir)
168
+ entries = filter_entries(Dir.entries(base))
169
+ directories = entries.select { |e| File.directory?(File.join(base, e)) }
170
+ files = entries.reject { |e| File.directory?(File.join(base, e)) }
171
+
172
+ # we need to make sure to process _posts *first* otherwise they
173
+ # might not be available yet to other templates as {{ site.posts }}
174
+ if directories.include?('_posts')
175
+ directories.delete('_posts')
176
+ read_posts(dir)
177
+ end
178
+
179
+ [directories, files].each do |entries|
180
+ entries.each do |f|
181
+ if File.directory?(File.join(base, f))
182
+ next if self.dest.sub(/\/$/, '') == File.join(base, f)
183
+ transform_pages(File.join(dir, f))
184
+ elsif Pager.pagination_enabled?(self.config, f)
185
+ paginate_posts(f, dir)
186
+ else
187
+ first3 = File.open(File.join(self.source, dir, f)) { |fd| fd.read(3) }
188
+
189
+ if first3 == "---"
190
+ # file appears to have a YAML header so process it as a page
191
+ page = Page.new(self, self.source, dir, f)
192
+ page.render(self.layouts, site_payload)
193
+ page.write(self.dest)
194
+ else
195
+ # otherwise copy the file without transforming it
196
+ FileUtils.mkdir_p(File.join(self.dest, dir))
197
+ FileUtils.cp(File.join(self.source, dir, f), File.join(self.dest, dir, f))
198
+ end
199
+ end
200
+ end
201
+ end
202
+ end
203
+
204
+ # Constructs a hash map of Posts indexed by the specified Post attribute
205
+ #
206
+ # Returns {post_attr => [<Post>]}
207
+ def post_attr_hash(post_attr)
208
+ # Build a hash map based on the specified post attribute ( post attr => array of posts )
209
+ # then sort each array in reverse order
210
+ hash = Hash.new { |hash, key| hash[key] = Array.new }
211
+ self.posts.each { |p| p.send(post_attr.to_sym).each { |t| hash[t] << p } }
212
+ hash.values.map { |sortme| sortme.sort! { |a, b| b <=> a} }
213
+ return hash
214
+ end
215
+
216
+ # The Hash payload containing site-wide data
217
+ #
218
+ # Returns {"site" => {"time" => <Time>,
219
+ # "posts" => [<Post>],
220
+ # "categories" => [<Post>]}
221
+ def site_payload
222
+ {"site" => {
223
+ "time" => Time.now,
224
+ "posts" => self.posts.sort { |a,b| b <=> a },
225
+ "categories" => post_attr_hash('categories'),
226
+ "tags" => post_attr_hash('tags')}}
227
+ end
228
+
229
+ # Filter out any files/directories that are hidden or backup files (start
230
+ # with "." or "#" or end with "~") or contain site content (start with "_")
231
+ # unless they are "_posts" directories or web server files such as
232
+ # '.htaccess'
233
+ def filter_entries(entries)
234
+ entries = entries.reject do |e|
235
+ unless ['_posts', '.htaccess'].include?(e)
236
+ ['.', '_', '#'].include?(e[0..0]) || e[-1..-1] == '~' || self.exclude.include?(e)
237
+ end
238
+ end
239
+ end
240
+
241
+ # Paginates the blog's posts. Renders the index.html file into paginated directories, ie: page2, page3...
242
+ # and adds more wite-wide data
243
+ #
244
+ # {"paginator" => { "page" => <Number>,
245
+ # "per_page" => <Number>,
246
+ # "posts" => [<Post>],
247
+ # "total_posts" => <Number>,
248
+ # "total_pages" => <Number>,
249
+ # "previous_page" => <Number>,
250
+ # "next_page" => <Number> }}
251
+ def paginate_posts(file, dir)
252
+ all_posts = self.posts.sort { |a,b| b <=> a }
253
+ page = Page.new(self, self.source, dir, file)
254
+
255
+ pages = Pager.calculate_pages(all_posts, self.config['paginate'].to_i)
256
+
257
+ (1..pages).each do |num_page|
258
+ pager = Pager.new(self.config, num_page, all_posts, pages)
259
+
260
+ page.render(self.layouts, site_payload.merge({'paginator' => pager.to_hash}))
261
+ suffix = "page#{num_page}" if num_page > 1
262
+ page.write(self.dest, suffix)
263
+ end
264
+ end
265
+ end
266
+ 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)
data/lib/jekyll.rb ADDED
@@ -0,0 +1,84 @@
1
+ $:.unshift File.dirname(__FILE__) # For use/testing when no gem is installed
2
+
3
+ # rubygems
4
+ require 'rubygems'
5
+
6
+ # core
7
+ require 'fileutils'
8
+ require 'time'
9
+ require 'yaml'
10
+
11
+ # stdlib
12
+
13
+ # 3rd party
14
+ require 'liquid'
15
+ require 'redcloth'
16
+
17
+ # internal requires
18
+ require 'jekyll/core_ext'
19
+ require 'jekyll/pager'
20
+ require 'jekyll/site'
21
+ require 'jekyll/convertible'
22
+ require 'jekyll/layout'
23
+ require 'jekyll/page'
24
+ require 'jekyll/post'
25
+ require 'jekyll/filters'
26
+ require 'jekyll/tags/highlight'
27
+ require 'jekyll/tags/include'
28
+ require 'jekyll/albino'
29
+
30
+ module Jekyll
31
+ # Default options. Overriden by values in _config.yml or command-line opts.
32
+ # (Strings rather symbols used for compatability with YAML)
33
+ DEFAULTS = {
34
+ 'auto' => false,
35
+ 'server' => false,
36
+ 'server_port' => 4000,
37
+
38
+ 'source' => '.',
39
+ 'destination' => File.join('.', '_site'),
40
+
41
+ 'lsi' => false,
42
+ 'pygments' => false,
43
+ 'markdown' => 'maruku',
44
+ 'permalink' => 'date',
45
+
46
+ 'maruku' => {
47
+ 'use_tex' => false,
48
+ 'use_divs' => false,
49
+ 'png_engine' => 'blahtex',
50
+ 'png_dir' => 'images/latex',
51
+ 'png_url' => '/images/latex'
52
+ }
53
+ }
54
+
55
+ # Generate a Jekyll configuration Hash by merging the default options
56
+ # with anything in _config.yml, and adding the given options on top
57
+ # +override+ is a Hash of config directives
58
+ #
59
+ # Returns Hash
60
+ def self.configuration(override)
61
+ # _config.yml may override default source location, but until
62
+ # then, we need to know where to look for _config.yml
63
+ source = override['source'] || Jekyll::DEFAULTS['source']
64
+
65
+ # Get configuration from <source>/_config.yml
66
+ config = {}
67
+ config_file = File.join(source, '_config.yml')
68
+ begin
69
+ config = YAML.load_file(config_file)
70
+ puts "Configuration from #{config_file}"
71
+ rescue => err
72
+ puts "WARNING: Could not read configuration. Using defaults (and options)."
73
+ puts "\t" + err
74
+ end
75
+
76
+ # Merge DEFAULTS < _config.yml < override
77
+ Jekyll::DEFAULTS.deep_merge(config).deep_merge(override)
78
+ end
79
+
80
+ def self.version
81
+ yml = YAML.load(File.read(File.join(File.dirname(__FILE__), *%w[.. VERSION.yml])))
82
+ "#{yml[:major]}.#{yml[:minor]}.#{yml[:patch]}"
83
+ end
84
+ end