jroes-jekyll 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
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