realityforge-jekyll 0.7.1-java

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 (91) hide show
  1. data/History.txt +255 -0
  2. data/LICENSE +21 -0
  3. data/README.textile +41 -0
  4. data/Rakefile +159 -0
  5. data/bin/jekyll +178 -0
  6. data/cucumber.yml +1 -0
  7. data/features/create_sites.feature +94 -0
  8. data/features/embed_filters.feature +60 -0
  9. data/features/markdown.feature +30 -0
  10. data/features/pagination.feature +27 -0
  11. data/features/permalinks.feature +65 -0
  12. data/features/post_data.feature +153 -0
  13. data/features/site_configuration.feature +103 -0
  14. data/features/site_data.feature +82 -0
  15. data/features/step_definitions/jekyll_steps.rb +145 -0
  16. data/features/support/env.rb +16 -0
  17. data/jekyll.gemspec +135 -0
  18. data/lib/jekyll.rb +109 -0
  19. data/lib/jekyll/albino.rb +120 -0
  20. data/lib/jekyll/converter.rb +50 -0
  21. data/lib/jekyll/converters/identity.rb +22 -0
  22. data/lib/jekyll/converters/markdown.rb +80 -0
  23. data/lib/jekyll/converters/textile.rb +33 -0
  24. data/lib/jekyll/convertible.rb +82 -0
  25. data/lib/jekyll/core_ext.rb +52 -0
  26. data/lib/jekyll/errors.rb +6 -0
  27. data/lib/jekyll/filters.rb +47 -0
  28. data/lib/jekyll/generator.rb +7 -0
  29. data/lib/jekyll/generators/pagination.rb +87 -0
  30. data/lib/jekyll/layout.rb +36 -0
  31. data/lib/jekyll/migrators/csv.rb +26 -0
  32. data/lib/jekyll/migrators/mephisto.rb +79 -0
  33. data/lib/jekyll/migrators/mt.rb +59 -0
  34. data/lib/jekyll/migrators/textpattern.rb +50 -0
  35. data/lib/jekyll/migrators/typo.rb +49 -0
  36. data/lib/jekyll/migrators/wordpress.rb +55 -0
  37. data/lib/jekyll/page.rb +133 -0
  38. data/lib/jekyll/plugin.rb +76 -0
  39. data/lib/jekyll/post.rb +242 -0
  40. data/lib/jekyll/site.rb +235 -0
  41. data/lib/jekyll/static_file.rb +76 -0
  42. data/lib/jekyll/tags/highlight.rb +73 -0
  43. data/lib/jekyll/tags/include.rb +31 -0
  44. data/test/helper.rb +33 -0
  45. data/test/source/_includes/sig.markdown +3 -0
  46. data/test/source/_layouts/default.html +27 -0
  47. data/test/source/_layouts/simple.html +1 -0
  48. data/test/source/_posts/2008-02-02-not-published.textile +8 -0
  49. data/test/source/_posts/2008-02-02-published.textile +8 -0
  50. data/test/source/_posts/2008-10-18-foo-bar.textile +8 -0
  51. data/test/source/_posts/2008-11-21-complex.textile +8 -0
  52. data/test/source/_posts/2008-12-03-permalinked-post.textile +9 -0
  53. data/test/source/_posts/2008-12-13-include.markdown +8 -0
  54. data/test/source/_posts/2009-01-27-array-categories.textile +10 -0
  55. data/test/source/_posts/2009-01-27-categories.textile +7 -0
  56. data/test/source/_posts/2009-01-27-category.textile +7 -0
  57. data/test/source/_posts/2009-01-27-empty-categories.textile +7 -0
  58. data/test/source/_posts/2009-01-27-empty-category.textile +7 -0
  59. data/test/source/_posts/2009-03-12-hash-#1.markdown +6 -0
  60. data/test/source/_posts/2009-05-18-empty-tag.textile +6 -0
  61. data/test/source/_posts/2009-05-18-empty-tags.textile +6 -0
  62. data/test/source/_posts/2009-05-18-tag.textile +6 -0
  63. data/test/source/_posts/2009-05-18-tags.textile +9 -0
  64. data/test/source/_posts/2009-06-22-empty-yaml.textile +3 -0
  65. data/test/source/_posts/2009-06-22-no-yaml.textile +1 -0
  66. data/test/source/_posts/2010-01-08-triple-dash.markdown +5 -0
  67. data/test/source/_posts/2010-01-09-date-override.textile +7 -0
  68. data/test/source/_posts/2010-01-09-time-override.textile +7 -0
  69. data/test/source/_posts/2010-01-09-timezone-override.textile +7 -0
  70. data/test/source/_posts/2010-01-16-override-data.textile +4 -0
  71. data/test/source/about.html +6 -0
  72. data/test/source/category/_posts/2008-9-23-categories.textile +6 -0
  73. data/test/source/contacts.html +5 -0
  74. data/test/source/css/screen.css +76 -0
  75. data/test/source/foo/_posts/bar/2008-12-12-topical-post.textile +8 -0
  76. data/test/source/index.html +22 -0
  77. data/test/source/sitemap.xml +32 -0
  78. data/test/source/win/_posts/2009-05-24-yaml-linebreak.markdown +7 -0
  79. data/test/source/z_category/_posts/2008-9-23-categories.textile +6 -0
  80. data/test/suite.rb +9 -0
  81. data/test/test_configuration.rb +29 -0
  82. data/test/test_core_ext.rb +66 -0
  83. data/test/test_filters.rb +49 -0
  84. data/test/test_generated_site.rb +44 -0
  85. data/test/test_page.rb +98 -0
  86. data/test/test_pager.rb +113 -0
  87. data/test/test_post.rb +396 -0
  88. data/test/test_rdiscount.rb +18 -0
  89. data/test/test_site.rb +153 -0
  90. data/test/test_tags.rb +116 -0
  91. metadata +282 -0
@@ -0,0 +1,242 @@
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
22
+ attr_accessor :data, :content, :output, :ext
23
+ attr_accessor :date, :slug, :published, :tags, :categories
24
+
25
+ # Initialize this Post instance.
26
+ # +site+ is the Site
27
+ # +base+ is the String path to the dir containing the post file
28
+ # +name+ is the String filename of the post file
29
+ # +categories+ is an Array of Strings for the categories for this post
30
+ #
31
+ # Returns <Post>
32
+ def initialize(site, source, dir, name)
33
+ @site = site
34
+ @base = File.join(source, dir, '_posts')
35
+ @name = name
36
+
37
+ self.categories = dir.split('/').reject { |x| x.empty? }
38
+ self.process(name)
39
+ self.read_yaml(@base, name)
40
+
41
+ #If we've added a date and time to the yaml, use that instead of the filename date
42
+ #Means we'll sort correctly.
43
+ if self.data.has_key?('date')
44
+ # ensure Time via to_s and reparse
45
+ self.date = Time.parse(self.data["date"].to_s)
46
+ end
47
+
48
+ if self.data.has_key?('published') && self.data['published'] == false
49
+ self.published = false
50
+ else
51
+ self.published = true
52
+ end
53
+
54
+ self.tags = self.data.pluralized_array("tag", "tags")
55
+
56
+ if self.categories.empty?
57
+ self.categories = self.data.pluralized_array('category', 'categories')
58
+ end
59
+ end
60
+
61
+ # Spaceship is based on Post#date, slug
62
+ #
63
+ # Returns -1, 0, 1
64
+ def <=>(other)
65
+ cmp = self.date <=> other.date
66
+ if 0 == cmp
67
+ cmp = self.slug <=> other.slug
68
+ end
69
+ return cmp
70
+ end
71
+
72
+ # Extract information from the post filename
73
+ # +name+ is the String filename of the post file
74
+ #
75
+ # Returns nothing
76
+ def process(name)
77
+ m, cats, date, slug, ext = *name.match(MATCHER)
78
+ self.date = Time.parse(date)
79
+ self.slug = slug
80
+ self.ext = ext
81
+ end
82
+
83
+ # The generated directory into which the post will be placed
84
+ # upon generation. This is derived from the permalink or, if
85
+ # permalink is absent, set to the default date
86
+ # e.g. "/2008/11/05/" if the permalink style is :date, otherwise nothing
87
+ #
88
+ # Returns <String>
89
+ def dir
90
+ File.dirname(url)
91
+ end
92
+
93
+ # The full path and filename of the post.
94
+ # Defined in the YAML of the post body
95
+ # (Optional)
96
+ #
97
+ # Returns <String>
98
+ def permalink
99
+ self.data && self.data['permalink']
100
+ end
101
+
102
+ def template
103
+ case self.site.permalink_style
104
+ when :pretty
105
+ "/:categories/:year/:month/:day/:title/"
106
+ when :none
107
+ "/:categories/:title.html"
108
+ when :date
109
+ "/:categories/:year/:month/:day/:title.html"
110
+ else
111
+ self.site.permalink_style.to_s
112
+ end
113
+ end
114
+
115
+ # The generated relative url of this post
116
+ # e.g. /2008/11/05/my-awesome-post.html
117
+ #
118
+ # Returns <String>
119
+ def url
120
+ return permalink if permalink
121
+
122
+ @url ||= {
123
+ "year" => date.strftime("%Y"),
124
+ "month" => date.strftime("%m"),
125
+ "day" => date.strftime("%d"),
126
+ "title" => CGI.escape(slug),
127
+ "i_day" => date.strftime("%d").to_i.to_s,
128
+ "i_month" => date.strftime("%m").to_i.to_s,
129
+ "categories" => categories.join('/'),
130
+ "output_ext" => self.output_ext
131
+ }.inject(template) { |result, token|
132
+ result.gsub(/:#{Regexp.escape token.first}/, token.last)
133
+ }.gsub(/\/\//, "/")
134
+ end
135
+
136
+ # The UID for this post (useful in feeds)
137
+ # e.g. /2008/11/05/my-awesome-post
138
+ #
139
+ # Returns <String>
140
+ def id
141
+ File.join(self.dir, self.slug)
142
+ end
143
+
144
+ # Calculate related posts.
145
+ #
146
+ # Returns [<Post>]
147
+ def related_posts(posts)
148
+ return [] unless posts.size > 1
149
+ return [] if defined?(JRUBY_VERSION)
150
+
151
+ if self.site.lsi
152
+ self.class.lsi ||= begin
153
+ puts "Running the classifier... this could take a while."
154
+ lsi = Classifier::LSI.new
155
+ posts.each { |x| $stdout.print(".");$stdout.flush;lsi.add_item(x) }
156
+ puts ""
157
+ lsi
158
+ end
159
+
160
+ related = self.class.lsi.find_related(self.content, 11)
161
+ related - [self]
162
+ else
163
+ (posts - [self])[0..9]
164
+ end
165
+ end
166
+
167
+ # Add any necessary layouts to this post
168
+ # +layouts+ is a Hash of {"name" => "layout"}
169
+ # +site_payload+ is the site payload hash
170
+ #
171
+ # Returns nothing
172
+ def render(layouts, site_payload)
173
+ # construct payload
174
+ payload = {
175
+ "site" => { "related_posts" => related_posts(site_payload["site"]["posts"]) },
176
+ "page" => self.to_liquid
177
+ }.deep_merge(site_payload)
178
+
179
+ do_layout(payload, layouts)
180
+ end
181
+
182
+ # Write the generated post file to the destination directory.
183
+ # +dest+ is the String path to the destination dir
184
+ #
185
+ # Returns nothing
186
+ def write(dest)
187
+ FileUtils.mkdir_p(File.join(dest, dir))
188
+
189
+ # The url needs to be unescaped in order to preserve the correct filename
190
+ path = File.join(dest, CGI.unescape(self.url))
191
+
192
+ if template[/\.html$/].nil?
193
+ FileUtils.mkdir_p(path)
194
+ path = File.join(path, "index.html")
195
+ end
196
+
197
+ File.open(path, 'w') do |f|
198
+ f.write(self.output)
199
+ end
200
+ end
201
+
202
+ # Convert this post into a Hash for use in Liquid templates.
203
+ #
204
+ # Returns <Hash>
205
+ def to_liquid
206
+ self.data.deep_merge({
207
+ "title" => self.data["title"] || self.slug.split('-').select {|w| w.capitalize! || w }.join(' '),
208
+ "url" => self.url,
209
+ "date" => self.date,
210
+ "id" => self.id,
211
+ "categories" => self.categories,
212
+ "next" => self.next,
213
+ "previous" => self.previous,
214
+ "tags" => self.tags,
215
+ "content" => self.content })
216
+ end
217
+
218
+ def inspect
219
+ "<Post: #{self.id}>"
220
+ end
221
+
222
+ def next
223
+ pos = self.site.posts.index(self)
224
+
225
+ if pos && pos < self.site.posts.length-1
226
+ self.site.posts[pos+1]
227
+ else
228
+ nil
229
+ end
230
+ end
231
+
232
+ def previous
233
+ pos = self.site.posts.index(self)
234
+ if pos && pos > 0
235
+ self.site.posts[pos-1]
236
+ else
237
+ nil
238
+ end
239
+ end
240
+ end
241
+
242
+ end
@@ -0,0 +1,235 @@
1
+ module Jekyll
2
+
3
+ class Site
4
+ attr_accessor :config, :layouts, :posts, :pages, :static_files,
5
+ :categories, :exclude, :source, :dest, :lsi, :pygments,
6
+ :permalink_style, :tags, :time, :future, :safe, :plugins
7
+ attr_accessor :converters, :generators
8
+
9
+ # Initialize the site
10
+ # +config+ is a Hash containing site configurations details
11
+ #
12
+ # Returns <Site>
13
+ def initialize(config)
14
+ self.config = config.clone
15
+
16
+ self.safe = config['safe']
17
+ self.source = File.expand_path(config['source'])
18
+ self.dest = File.expand_path(config['destination'])
19
+ self.plugins = File.expand_path(config['plugins'])
20
+ self.lsi = config['lsi']
21
+ self.pygments = config['pygments']
22
+ self.permalink_style = config['permalink'].to_sym
23
+ self.exclude = config['exclude'] || []
24
+ self.future = config['future']
25
+
26
+ self.reset
27
+ self.setup
28
+ end
29
+
30
+ def reset
31
+ self.time = if self.config['time']
32
+ Time.parse(self.config['time'].to_s)
33
+ else
34
+ Time.now
35
+ end
36
+ self.layouts = {}
37
+ self.posts = []
38
+ self.pages = []
39
+ self.static_files = []
40
+ self.categories = Hash.new { |hash, key| hash[key] = [] }
41
+ self.tags = Hash.new { |hash, key| hash[key] = [] }
42
+ end
43
+
44
+ def setup
45
+ require 'classifier' if self.lsi
46
+
47
+ # If safe mode is off, load in any ruby files under the plugins
48
+ # directory.
49
+ unless self.safe
50
+ Dir[File.join(self.plugins, "**/*.rb")].each do |f|
51
+ require f
52
+ end
53
+ end
54
+
55
+ self.converters = Jekyll::Converter.subclasses.select do |c|
56
+ !self.safe || c.safe
57
+ end.map do |c|
58
+ c.new(self.config)
59
+ end
60
+
61
+ self.generators = Jekyll::Generator.subclasses.select do |c|
62
+ !self.safe || c.safe
63
+ end.map do |c|
64
+ c.new(self.config)
65
+ end
66
+ end
67
+
68
+ # Do the actual work of processing the site and generating the
69
+ # real deal. 5 phases; reset, read, generate, render, write. This allows
70
+ # rendering to have full site payload available.
71
+ #
72
+ # Returns nothing
73
+ def process
74
+ self.reset
75
+ self.read
76
+ self.generate
77
+ self.render
78
+ self.write
79
+ end
80
+
81
+ def read
82
+ self.read_layouts # existing implementation did this at top level only so preserved that
83
+ self.read_directories
84
+ end
85
+
86
+ # Read all the files in <source>/<dir>/_layouts and create a new Layout
87
+ # object with each one.
88
+ #
89
+ # Returns nothing
90
+ def read_layouts(dir = '')
91
+ base = File.join(self.source, dir, "_layouts")
92
+ return unless File.exists?(base)
93
+ entries = []
94
+ Dir.chdir(base) { entries = filter_entries(Dir['*.*']) }
95
+
96
+ entries.each do |f|
97
+ name = f.split(".")[0..-2].join(".")
98
+ self.layouts[name] = Layout.new(self, base, f)
99
+ end
100
+ end
101
+
102
+ # Read all the files in <source>/<dir>/_posts and create a new Post
103
+ # object with each one.
104
+ #
105
+ # Returns nothing
106
+ def read_posts(dir)
107
+ base = File.join(self.source, dir, '_posts')
108
+ return unless File.exists?(base)
109
+ entries = Dir.chdir(base) { filter_entries(Dir['**/*']) }
110
+
111
+ # first pass processes, but does not yet render post content
112
+ entries.each do |f|
113
+ if Post.valid?(f)
114
+ post = Post.new(self, self.source, dir, f)
115
+
116
+ if post.published && (self.future || post.date <= self.time)
117
+ self.posts << post
118
+ post.categories.each { |c| self.categories[c] << post }
119
+ post.tags.each { |c| self.tags[c] << post }
120
+ end
121
+ end
122
+ end
123
+
124
+ self.posts.sort!
125
+ end
126
+
127
+ def generate
128
+ self.generators.each do |generator|
129
+ generator.generate(self)
130
+ end
131
+ end
132
+
133
+ def render
134
+ self.posts.each do |post|
135
+ post.render(self.layouts, site_payload)
136
+ end
137
+
138
+ self.pages.each do |page|
139
+ page.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 static files, pages and posts
149
+ #
150
+ # Returns nothing
151
+ def write
152
+ self.posts.each do |post|
153
+ post.write(self.dest)
154
+ end
155
+ self.pages.each do |page|
156
+ page.write(self.dest)
157
+ end
158
+ self.static_files.each do |sf|
159
+ sf.write(self.dest)
160
+ end
161
+ end
162
+
163
+ # Reads the directories and finds posts, pages and static files that will
164
+ # become part of the valid site according to the rules in +filter_entries+.
165
+ # The +dir+ String is a relative path used to call this method
166
+ # recursively as it descends through directories
167
+ #
168
+ # Returns nothing
169
+ def read_directories(dir = '')
170
+ base = File.join(self.source, dir)
171
+ entries = filter_entries(Dir.entries(base))
172
+
173
+ self.read_posts(dir)
174
+
175
+ entries.each do |f|
176
+ f_abs = File.join(base, f)
177
+ f_rel = File.join(dir, f)
178
+ if File.directory?(f_abs)
179
+ next if self.dest.sub(/\/$/, '') == f_abs
180
+ read_directories(f_rel)
181
+ elsif !File.symlink?(f_abs)
182
+ first3 = File.open(f_abs) { |fd| fd.read(3) }
183
+ if first3 == "---"
184
+ # file appears to have a YAML header so process it as a page
185
+ pages << Page.new(self, self.source, dir, f)
186
+ else
187
+ # otherwise treat it as a static file
188
+ static_files << StaticFile.new(self, self.source, dir, f)
189
+ end
190
+ end
191
+ end
192
+ end
193
+
194
+ # Constructs a hash map of Posts indexed by the specified Post attribute
195
+ #
196
+ # Returns {post_attr => [<Post>]}
197
+ def post_attr_hash(post_attr)
198
+ # Build a hash map based on the specified post attribute ( post attr => array of posts )
199
+ # then sort each array in reverse order
200
+ hash = Hash.new { |hash, key| hash[key] = Array.new }
201
+ self.posts.each { |p| p.send(post_attr.to_sym).each { |t| hash[t] << p } }
202
+ hash.values.map { |sortme| sortme.sort! { |a, b| b <=> a} }
203
+ return hash
204
+ end
205
+
206
+ # The Hash payload containing site-wide data
207
+ #
208
+ # Returns {"site" => {"time" => <Time>,
209
+ # "posts" => [<Post>],
210
+ # "pages" => [<Page>],
211
+ # "categories" => [<Post>]}
212
+ def site_payload
213
+ {"site" => self.config.merge({
214
+ "time" => self.time,
215
+ "posts" => self.posts.sort { |a,b| b <=> a },
216
+ "pages" => self.pages,
217
+ "html_pages" => self.pages.reject { |page| !page.html? },
218
+ "categories" => post_attr_hash('categories'),
219
+ "tags" => post_attr_hash('tags')})}
220
+ end
221
+
222
+ # Filter out any files/directories that are hidden or backup files (start
223
+ # with "." or "#" or end with "~"), or contain site content (start with "_"),
224
+ # or are excluded in the site configuration, unless they are web server
225
+ # files such as '.htaccess'
226
+ def filter_entries(entries)
227
+ entries = entries.reject do |e|
228
+ unless ['.htaccess'].include?(e)
229
+ ['.', '_', '#'].include?(e[0..0]) || e[-1..-1] == '~' || self.exclude.include?(e)
230
+ end
231
+ end
232
+ end
233
+
234
+ end
235
+ end