gabrielg-jekyll 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,24 @@
1
+ module Jekyll
2
+
3
+ module Filters
4
+ def date_to_string(date)
5
+ date.strftime("%d %b %Y")
6
+ end
7
+
8
+ def date_to_long_string(date)
9
+ date.strftime("%d %B %Y")
10
+ end
11
+
12
+ def date_to_xmlschema(date)
13
+ date.xmlschema
14
+ end
15
+
16
+ def xml_escape(input)
17
+ input.gsub("<", "&lt;").gsub(">", "&gt;")
18
+ end
19
+
20
+ def number_of_words(input)
21
+ input.split.length
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,48 @@
1
+ module Jekyll
2
+
3
+ class Layout
4
+ include Convertible
5
+
6
+ attr_accessor :ext
7
+ attr_accessor :data, :content
8
+
9
+ # Initialize a new Layout.
10
+ # +base+ is the String path to the <source>
11
+ # +name+ is the String filename of the post file
12
+ #
13
+ # Returns <Page>
14
+ def initialize(base, name)
15
+ @base = base
16
+ @name = name
17
+
18
+ self.data = {}
19
+
20
+ self.process(name)
21
+ self.read_yaml(base, name)
22
+ end
23
+
24
+ # Extract information from the layout filename
25
+ # +name+ is the String filename of the layout file
26
+ #
27
+ # Returns nothing
28
+ def process(name)
29
+ self.ext = File.extname(name)
30
+ end
31
+
32
+ # Add any necessary layouts to this post
33
+ # +layouts+ is a Hash of {"name" => "layout"}
34
+ # +site_payload+ is the site payload hash
35
+ #
36
+ # Returns nothing
37
+ def add_layout(layouts, site_payload)
38
+ payload = {"page" => self.data}.merge(site_payload)
39
+ self.content = Liquid::Template.parse(self.content).render(payload, [Jekyll::Filters])
40
+
41
+ layout = layouts[self.data["layout"]] || self.content
42
+ payload = {"content" => self.content, "page" => self.data}
43
+
44
+ self.content = Liquid::Template.parse(layout).render(payload, [Jekyll::Filters])
45
+ end
46
+ end
47
+
48
+ end
@@ -0,0 +1,64 @@
1
+ module Jekyll
2
+
3
+ class Page
4
+ include Convertible
5
+
6
+ attr_accessor :ext
7
+ attr_accessor :data, :content, :output
8
+
9
+ # Initialize a new Page.
10
+ # +base+ is the String path to the <source>
11
+ # +dir+ is the String path between <source> and the file
12
+ # +name+ is the String filename of the file
13
+ #
14
+ # Returns <Page>
15
+ def initialize(base, dir, name)
16
+ @base = base
17
+ @dir = dir
18
+ @name = name
19
+
20
+ self.data = {}
21
+
22
+ self.process(name)
23
+ self.read_yaml(File.join(base, dir), name)
24
+ #self.transform
25
+ end
26
+
27
+ # Extract information from the page filename
28
+ # +name+ is the String filename of the page file
29
+ #
30
+ # Returns nothing
31
+ def process(name)
32
+ self.ext = File.extname(name)
33
+ end
34
+
35
+ # Add any necessary layouts to this post
36
+ # +layouts+ is a Hash of {"name" => "layout"}
37
+ # +site_payload+ is the site payload hash
38
+ #
39
+ # Returns nothing
40
+ def add_layout(layouts, site_payload)
41
+ payload = {"page" => self.data}
42
+ do_layout(payload, layouts, site_payload)
43
+ end
44
+
45
+ # Write the generated page file to the destination directory.
46
+ # +dest+ is the String path to the destination dir
47
+ #
48
+ # Returns nothing
49
+ def write(dest)
50
+ FileUtils.mkdir_p(File.join(dest, @dir))
51
+
52
+ name = @name
53
+ if self.ext != ""
54
+ name = @name.split(".")[0..-2].join('.') + self.ext
55
+ end
56
+
57
+ path = File.join(dest, @dir, name)
58
+ File.open(path, 'w') do |f|
59
+ f.write(self.output)
60
+ end
61
+ end
62
+ end
63
+
64
+ end
@@ -0,0 +1,165 @@
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 :date, :slug, :ext, :categories
22
+ attr_accessor :data, :content, :output
23
+
24
+ # Initialize this Post instance.
25
+ # +base+ is the String path to the dir containing the post file
26
+ # +name+ is the String filename of the post file
27
+ # +categories+ is an Array of Strings for the categories for this post
28
+ #
29
+ # Returns <Post>
30
+ def initialize(base, name)
31
+ @base = base
32
+ @name = name
33
+ @categories = base.split('/').reject { |p| ['.', '_posts'].include? p }
34
+
35
+ self.process(name)
36
+ self.read_yaml(base, name)
37
+ #Removed to avoid munging of liquid tags, replaced in convertible.rb#48
38
+ #self.transform
39
+ end
40
+
41
+ # Spaceship is based on Post#date
42
+ #
43
+ # Returns -1, 0, 1
44
+ def <=>(other)
45
+ self.date <=> other.date
46
+ end
47
+
48
+ # Extract information from the post filename
49
+ # +name+ is the String filename of the post file
50
+ #
51
+ # Returns nothing
52
+ def process(name)
53
+ m, date, slug, ext = *name.match(MATCHER)
54
+ self.date = Time.parse(date)
55
+ self.slug = slug
56
+ self.ext = ext
57
+ end
58
+
59
+ # The generated directory into which the post will be placed
60
+ # upon generation. This is derived from the permalink or, if
61
+ # permalink is absent, set to the default date
62
+ # e.g. "/2008/11/05/"
63
+ #
64
+ # Returns <String>
65
+ def dir
66
+ path = (@categories && !@categories.empty?) ? '/' + @categories.join('/') : ''
67
+ if permalink
68
+ permalink.to_s.split("/")[0..-2].join("/")
69
+ else
70
+ "#{path}" + date.strftime("/%Y/%m/%d/")
71
+ end
72
+ end
73
+
74
+ # The full path and filename of the post.
75
+ # Defined in the YAML of the post body
76
+ # (Optional)
77
+ #
78
+ # Returns <String>
79
+ def permalink
80
+ self.data && self.data['permalink']
81
+ end
82
+
83
+ # The generated relative url of this post
84
+ # e.g. /2008/11/05/my-awesome-post.html
85
+ #
86
+ # Returns <String>
87
+ def url
88
+ self.dir + self.slug + ".html"
89
+ end
90
+
91
+ # The UID for this post (useful in feeds)
92
+ # e.g. /2008/11/05/my-awesome-post
93
+ #
94
+ # Returns <String>
95
+ def id
96
+ self.dir + self.slug
97
+ end
98
+
99
+ # Calculate related posts.
100
+ #
101
+ # Returns [<Post>]
102
+ def related_posts(posts)
103
+ return [] unless posts.size > 1
104
+
105
+ if Jekyll.lsi
106
+ self.class.lsi ||= begin
107
+ puts "Running the classifier... this could take a while."
108
+ lsi = Classifier::LSI.new
109
+ posts.each { |x| $stdout.print(".");$stdout.flush;lsi.add_item(x) }
110
+ puts ""
111
+ lsi
112
+ end
113
+
114
+ related = self.class.lsi.find_related(self.content, 11)
115
+ related - [self]
116
+ else
117
+ (posts - [self])[0..9]
118
+ end
119
+ end
120
+
121
+ # Add any necessary layouts to this post
122
+ # +layouts+ is a Hash of {"name" => "layout"}
123
+ # +site_payload+ is the site payload hash
124
+ #
125
+ # Returns nothing
126
+ def add_layout(layouts, site_payload)
127
+ # construct post payload
128
+ related = related_posts(site_payload["site"]["posts"])
129
+ payload = {"page" => self.to_liquid.merge(self.data)}
130
+ do_layout(payload, layouts, site_payload.merge({"site" => {"related_posts" => related}}))
131
+ end
132
+
133
+ # Write the generated post file to the destination directory.
134
+ # +dest+ is the String path to the destination dir
135
+ #
136
+ # Returns nothing
137
+ def write(dest)
138
+ FileUtils.mkdir_p(File.join(dest, dir))
139
+
140
+ path = File.join(dest, self.url)
141
+ File.open(path, 'w') do |f|
142
+ f.write(self.output)
143
+ end
144
+ end
145
+
146
+ # Returns nil, or a Markdown formatted summary if a summary is set in the YAML
147
+ # front matter
148
+ def summary
149
+ self.data['summary'] ? RedCloth.new(self.data['summary']).to_html : nil
150
+ end
151
+
152
+ # Convert this post into a Hash for use in Liquid templates.
153
+ #
154
+ # Returns <Hash>
155
+ def to_liquid
156
+ self.data.merge({ "title" => self.data["title"] || "",
157
+ "url" => self.url,
158
+ "date" => self.date,
159
+ "id" => self.id,
160
+ "summary" => self.summary,
161
+ "content" => self.content })
162
+ end
163
+ end
164
+
165
+ end
@@ -0,0 +1,134 @@
1
+ module Jekyll
2
+
3
+ class Site
4
+ attr_accessor :source, :dest
5
+ attr_accessor :layouts, :posts
6
+
7
+ # Initialize the site
8
+ # +source+ is String path to the source directory containing
9
+ # the proto-site
10
+ # +dest+ is the String path to the directory where the generated
11
+ # site should be written
12
+ #
13
+ # Returns <Site>
14
+ def initialize(source, dest)
15
+ self.source = source
16
+ self.dest = dest
17
+ self.layouts = {}
18
+ self.posts = []
19
+ end
20
+
21
+ # Do the actual work of processing the site and generating the
22
+ # real deal.
23
+ #
24
+ # Returns nothing
25
+ def process
26
+ self.read_layouts
27
+ self.transform_pages
28
+ self.write_posts
29
+ end
30
+
31
+ # Read all the files in <source>/_layouts into memory for
32
+ # later use.
33
+ #
34
+ # Returns nothing
35
+ def read_layouts
36
+ base = File.join(self.source, "_layouts")
37
+ entries = Dir.entries(base)
38
+ entries = entries.reject { |e| File.directory?(File.join(base, e)) }
39
+
40
+ entries.each do |f|
41
+ name = f.split(".")[0..-2].join(".")
42
+ self.layouts[name] = Layout.new(base, f)
43
+ end
44
+ rescue Errno::ENOENT => e
45
+ # ignore missing layout dir
46
+ end
47
+
48
+ # Read all the files in <base>/_posts and create a new Post
49
+ # object with each one.
50
+ #
51
+ # Returns nothing
52
+ def read_posts(base)
53
+ entries = Dir.entries(base)
54
+ entries = entries.reject { |e| File.directory?(e) }
55
+
56
+ entries.each do |f|
57
+ p = Post.new(base, f)
58
+ if Post.valid?(f)
59
+ p.do_layout({}, {}, {})
60
+ self.posts << p
61
+ end
62
+ end
63
+
64
+ self.posts.sort!
65
+ rescue Errno::ENOENT => e
66
+ # ignore missing layout dir
67
+ end
68
+
69
+ # Write each post to <dest>/<year>/<month>/<day>/<slug>
70
+ #
71
+ # Returns nothing
72
+ def write_posts
73
+ self.posts.each do |post|
74
+ post.add_layout(self.layouts, site_payload)
75
+ post.write(self.dest)
76
+ end
77
+ end
78
+
79
+ # Copy all regular files from <source> to <dest>/ ignoring
80
+ # any files/directories that are hidden (start with ".") or contain
81
+ # site content (start with "_") unless they are "_posts" directories
82
+ # The +dir+ String is a relative path used to call this method
83
+ # recursively as it descends through directories
84
+ #
85
+ # Returns nothing
86
+ def transform_pages(dir = '')
87
+ base = File.join(self.source, dir)
88
+ entries = Dir.entries(base)
89
+ entries = entries.reject { |e|
90
+ (e != '_posts') and ['.', '_'].include?(e[0..0])
91
+ }
92
+
93
+ entries.each do |f|
94
+ if f == '_posts'
95
+ read_posts(File.join(base, f))
96
+ elsif File.directory?(File.join(base, f))
97
+ next if self.dest.sub(/\/$/, '') == File.join(base, f)
98
+ transform_pages(File.join(dir, f))
99
+ else
100
+ first3 = File.open(File.join(self.source, dir, f)) { |fd| fd.read(3) }
101
+
102
+ # if the file appears to have a YAML header then process it as a page
103
+ if first3 == "---"
104
+ page = Page.new(self.source, dir, f)
105
+ page.add_layout(self.layouts, site_payload)
106
+ page.write(self.dest)
107
+ # otherwise copy the file without transforming it
108
+ else
109
+ FileUtils.mkdir_p(File.join(self.dest, dir))
110
+ FileUtils.cp(File.join(self.source, dir, f), File.join(self.dest, dir, f))
111
+ end
112
+ end
113
+ end
114
+ end
115
+
116
+ # The Hash payload containing site-wide data
117
+ #
118
+ # Returns {"site" => {"time" => <Time>, "posts" => [<Post>]}}
119
+ def site_payload
120
+ # Build the category hash map of category ( names => arrays of posts )
121
+ # then sort each array in reverse order
122
+ categories = Hash.new { |hash,key| hash[key] = Array.new }
123
+ self.posts.each { |p| p.categories.each { |c| categories[c] << p } }
124
+ categories.values.map { |cats| cats.sort! { |a,b| b <=> a} }
125
+
126
+ {"site" => {
127
+ "time" => Time.now,
128
+ "posts" => self.posts.sort { |a,b| b <=> a },
129
+ "categories" => categories
130
+ }}
131
+ end
132
+ end
133
+
134
+ end
@@ -0,0 +1,37 @@
1
+ module Jekyll
2
+
3
+ class HighlightBlock < Liquid::Block
4
+ include Liquid::StandardFilters
5
+
6
+ def initialize(tag_name, lang, tokens)
7
+ super
8
+ @lang = lang.strip
9
+ end
10
+
11
+ def render(context)
12
+ if Jekyll.pygments
13
+ render_pygments(context, super.to_s)
14
+ else
15
+ render_codehighlighter(context, super.to_s)
16
+ end
17
+ end
18
+
19
+ def render_pygments(context, code)
20
+ "<notextile>" + Albino.new(code, @lang).to_s + "</notextile>"
21
+ end
22
+
23
+ def render_codehighlighter(context, code)
24
+ #The div is required because RDiscount blows ass
25
+ <<-HTML
26
+ <div>
27
+ <pre>
28
+ <code class='#{@lang}'>#{h(code).strip}</code>
29
+ </pre>
30
+ </div>
31
+ HTML
32
+ end
33
+ end
34
+
35
+ end
36
+
37
+ Liquid::Template.register_tag('highlight', Jekyll::HighlightBlock)