henshin 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/.gitignore +5 -1
  2. data/README.markdown +9 -46
  3. data/Rakefile +2 -1
  4. data/VERSION +1 -1
  5. data/bin/henshin +11 -12
  6. data/henshin.gemspec +20 -27
  7. data/lib/henshin.rb +21 -98
  8. data/lib/henshin/archive.rb +87 -115
  9. data/{bin → lib/henshin/exec}/files.rb +0 -0
  10. data/lib/henshin/ext.rb +35 -58
  11. data/lib/henshin/gen.rb +83 -68
  12. data/lib/henshin/labels.rb +144 -0
  13. data/lib/henshin/plugin.rb +70 -33
  14. data/lib/henshin/plugins/highlight.rb +19 -26
  15. data/lib/henshin/plugins/liquid.rb +50 -52
  16. data/lib/henshin/plugins/maruku.rb +14 -16
  17. data/lib/henshin/plugins/sass.rb +19 -23
  18. data/lib/henshin/plugins/textile.rb +14 -16
  19. data/lib/henshin/post.rb +67 -120
  20. data/lib/henshin/site.rb +154 -131
  21. data/lib/henshin/static.rb +10 -8
  22. data/test/helper.rb +20 -8
  23. data/test/site/css/{includes/reset.sass → _reset.sass} +0 -0
  24. data/test/site/css/screen.sass +1 -1
  25. data/test/site/index.html +2 -3
  26. data/test/site/layouts/archive_date.html +5 -4
  27. data/test/site/layouts/archive_month.html +10 -5
  28. data/test/site/layouts/archive_year.html +13 -6
  29. data/test/site/layouts/category_page.html +7 -7
  30. data/test/site/layouts/main.html +3 -4
  31. data/test/site/layouts/post.html +1 -1
  32. data/test/site/layouts/tag_index.html +3 -2
  33. data/test/site/layouts/tag_page.html +6 -6
  34. data/test/site/options.yaml +2 -2
  35. data/test/site/plugins/test.rb +1 -3
  36. data/test/site/posts/Testing-Stuff.markdown +1 -1
  37. data/test/site/posts/Textile-Test.textile +1 -1
  38. data/test/site/posts/lorem-ipsum.markdown +1 -1
  39. data/test/site/posts/same-date.markdown +1 -1
  40. data/test/{test_henshin.rb → suite.rb} +0 -1
  41. data/test/test_gen.rb +98 -0
  42. data/test/test_options.rb +34 -19
  43. data/test/test_post.rb +67 -0
  44. data/test/test_site.rb +159 -46
  45. data/test/test_static.rb +13 -0
  46. metadata +53 -32
  47. data/lib/henshin/categories.rb +0 -29
  48. data/lib/henshin/tags.rb +0 -28
  49. data/test/test_archives.rb +0 -27
  50. data/test/test_categories.rb +0 -0
  51. data/test/test_gens.rb +0 -54
  52. data/test/test_layouts.rb +0 -14
  53. data/test/test_posts.rb +0 -75
  54. data/test/test_statics.rb +0 -0
  55. data/test/test_tags.rb +0 -0
  56. data/test/text_exts.rb +0 -0
@@ -1,31 +1,120 @@
1
1
  module Henshin
2
2
 
3
3
  class Site
4
-
5
- attr_accessor :posts, :gens, :statics, :archive, :tags, :categories
6
- attr_accessor :layouts, :config
7
4
 
8
- def initialize( config )
5
+ # @return [Hash] the configuration made up of the override, options.yaml and Henshin::Defaults
6
+ attr_accessor :config
7
+
8
+ # @return [Pathname] the path to the site to generate from where the command is executed
9
+ attr_accessor :root
10
+
11
+ # @return [Pathname] the path to where the finished site should be written
12
+ attr_accessor :target
13
+
14
+ # @return [String] a path which should be prepended to all urls
15
+ attr_accessor :base
16
+
17
+ # @return [Array]
18
+ attr_accessor :gens, :posts, :statics
19
+
20
+ # @return [Hash{String => String}]
21
+ attr_accessor :layouts
22
+
23
+ # @return [Tags]
24
+ attr_accessor :tags
25
+
26
+ # @return [Categories]
27
+ attr_accessor :categories
28
+
29
+ # @return [Archive]
30
+ attr_accessor :archive
31
+
32
+ # @return [Hash{String => Plugin}]
33
+ attr_accessor :plugins
34
+
35
+ # A new instance of site
36
+ #
37
+ # @param [Hash{String => Object}] override data to override loaded options
38
+ # @return [self]
39
+ def initialize(override={})
9
40
  self.reset
10
- @config = config
41
+ self.configure(override)
42
+ self.load_plugins
43
+ self
11
44
  end
12
45
 
13
- # Resets everything
14
- def reset
15
- @posts = []
16
- @gens = []
46
+ # Resets all instance variables, except +config+ and +plugins+ as these shouldn't
47
+ # need to be reloaded each time.
48
+ #
49
+ # @return [self]
50
+ def reset
51
+ @gens = []
52
+ @posts = []
17
53
  @statics = []
54
+ @layouts = {}
18
55
 
19
- @archive = Archive.new( self )
20
- @tags = Hash.new { |h, k| h[k] = Tag.new(k) }
21
- @categories = Hash.new { |h, k| h[k] = Category.new(k) }
56
+ @archive = Archive.new(self)
57
+ @tags = Labels.new('tag', self)
58
+ @categories = Labels.new('category', self)
59
+ self
60
+ end
61
+
62
+ # Creates the configuration hash by merging defaults, supplied options and options
63
+ # read from the 'options.yaml' file. Then sets root, target, base and appends
64
+ # special directories to @config['exclude']
65
+ #
66
+ # @param [Hash] override to override other set options
67
+ def configure(override)
68
+ @config = {}
69
+ config_file = File.join((override['root'] || Defaults['root']), '/options.yaml')
22
70
 
23
- @layouts = {}
71
+ # change target to represent given root, only if root given
72
+ if override['root'] && !override['target']
73
+ override['target'] = File.join(override['root'], Defaults['target'])
74
+ end
75
+
76
+ begin
77
+ config = YAML.load_file(config_file)
78
+ @config = Defaults.merge(config).merge(override)
79
+ rescue => e
80
+ $stderr.puts "\nCould not read configuration, falling back to defaults..."
81
+ $stderr.puts "-> #{e.to_s}"
82
+ @config = Defaults.merge(override)
83
+ end
84
+ @root = @config['root'].to_p
85
+ @target = @config['target'].to_p
86
+
87
+ @base = @config['base'] || "/"
88
+ @base = '/' + @base unless @base[0] == '/' # need to make sure it starts with slash
89
+
90
+ @config['exclude'] << '/_site' << '/plugins'
24
91
  end
25
92
 
93
+ # Requires each plugin in @config['plugins'], then loads and sorts them into
94
+ # +plugins+ by type
95
+ def load_plugins
96
+ @plugins = {:generators => {}, :layoutors => []}
97
+
98
+ @config['plugins'].each do |plugin|
99
+ begin
100
+ require File.join('henshin', 'plugins', plugin)
101
+ rescue LoadError
102
+ require File.join(@root, 'plugins', plugin)
103
+ end
104
+ end
105
+
106
+ Henshin::Generator.subclasses.each do |plugin|
107
+ plugin = plugin.new(self)
108
+ plugin.extensions[:input].each do |ext|
109
+ @plugins[:generators][ext] = plugin
110
+ end
111
+ end
112
+
113
+ @plugins[:layoutors] = Henshin::Layoutor.subclasses.map {|l| l.new(self)}.sort
114
+ end
26
115
 
27
116
  ##
28
- # Read, process, render and write everything
117
+ # Read, process, render and write
29
118
  def build
30
119
  self.reset
31
120
  self.read
@@ -37,45 +126,48 @@ module Henshin
37
126
 
38
127
  ##
39
128
  # Reads all necessary files and puts them into the necessary arrays
129
+ #
130
+ # @return [self]
40
131
  def read
41
132
  self.read_layouts
42
133
  self.read_posts
43
134
  self.read_gens
44
135
  self.read_statics
136
+ self
45
137
  end
46
138
 
47
- # Adds all items in 'layouts' to the layouts array
139
+ # Adds all items in '/layouts' to the layouts array
48
140
  def read_layouts
49
- path = File.join(@config[:root], 'layouts')
141
+ path = File.join(@root, 'layouts')
50
142
  Dir.glob(path + '/*.*').each do |layout|
51
143
  layout =~ /([a-zA-Z0-9 _-]+)\.([a-zA-Z0-9-]+)/
52
144
  @layouts[$1] = File.open(layout, 'r') {|f| f.read}
53
145
  end
54
146
  end
55
147
 
56
- # Adds all items in 'posts' to the posts array
148
+ # Adds all items in '/posts' to the posts array
57
149
  def read_posts
58
- path = File.join(@config[:root], 'posts')
150
+ path = File.join(@root, 'posts')
59
151
  Dir.glob(path + '/**/*.*').each do |post|
60
- @posts << Post.new(post, self)
152
+ @posts << Post.new(post.to_p, self).read
61
153
  end
62
154
  end
63
155
 
64
156
  # Adds all files that need to be run through a plugin in an array
65
157
  def read_gens
66
- files = Dir.glob( File.join(@config[:root], '**', '*.*') )
158
+ files = Dir.glob( File.join(@root, '**', '*.*') )
67
159
  gens = files.select {|i| gen?(i) }
68
- gens.each do |g|
69
- @gens << Gen.new(g, self)
160
+ gens.each do |gen|
161
+ @gens << Gen.new(gen.to_p, self).read
70
162
  end
71
163
  end
72
164
 
73
165
  # Adds all static files to an array
74
166
  def read_statics
75
- files = Dir.glob( File.join(@config[:root], '**', '*.*') )
167
+ files = Dir.glob( File.join(@root, '**', '*.*') )
76
168
  static = files.select {|i| static?(i) }
77
- static.each do |s|
78
- @statics << Static.new(s, self)
169
+ static.each do |static|
170
+ @statics << Static.new(static.to_p, self)
79
171
  end
80
172
  end
81
173
 
@@ -83,153 +175,84 @@ module Henshin
83
175
  ##
84
176
  # Processes all of the necessary files
85
177
  def process
86
- @posts.each_parallel {|p| p.process}
87
178
  @posts.sort!
88
- @gens.each_parallel {|g| g.process}
179
+ @gens.sort!
89
180
 
90
- self.build_tags
91
- self.build_categories
92
- self.build_archive
181
+ @posts.each do |post|
182
+ @tags << post if post.data['tags']
183
+ @categories << post if post.data['category']
184
+ @archive << post
185
+ end
186
+ self
93
187
  end
94
188
 
95
189
  # @return [Hash] the payload for the layout engine
96
190
  def payload
97
- {
98
- 'site' => {
99
- 'author' => @config[:author],
100
- 'title' => @config[:title],
101
- 'description' => @config[:description],
102
- 'time_zone' => @config[:time_zone],
103
- 'created_at' => Time.now,
104
- 'posts' => @posts.collect{|i| i.to_hash},
105
- 'tags' => @tags.collect {|k, t| t.to_hash},
106
- 'categories' => @categories.collect {|k, t| t.to_hash},
107
- 'archive' => @archive.to_hash
108
- }
109
- }
110
- end
111
-
112
- # Creates tags from posts and adds them to @tags
113
- def build_tags
114
- @posts.each do |p|
115
- p.tags.each do |t|
116
- @tags[t].posts << p
117
- end
118
- end
119
- end
120
-
121
- # Create categories from posts and add to @categories
122
- def build_categories
123
- @posts.each do |p|
124
- @categories[p.category].posts << p unless p.category.nil?
125
- end
126
- end
127
-
128
- # @return [Hash] archive hash
129
- def build_archive
130
- @posts.each {|p| @archive.add_post(p)}
191
+ r = {'site' => @config}
192
+ r['site']['created_at'] = Time.now
193
+ r['site']['posts'] = @posts.collect{|i| i.to_hash}
194
+ r['site']['tags'] = @tags.to_hash
195
+ r['site']['categories'] = @categories.to_hash
196
+ r['site']['archive'] = @archive.to_hash
197
+ r
131
198
  end
132
199
 
133
200
  ##
134
201
  # Renders the files
135
202
  def render
136
- @posts.each_parallel {|p| p.render}
137
- @gens.each {|g| g.render}
203
+ @posts.each {|post| post.render}
204
+ @gens.each {|gen| gen.render}
205
+
206
+ self
138
207
  end
139
208
 
140
209
 
141
210
  ##
142
211
  # Writes the files
143
212
  def write
144
- @posts.each_parallel {|p| p.write}
145
- @gens.each_parallel {|g| g.write}
146
- @statics.each_parallel {|s| s.write}
213
+ @posts.each {|post| post.write}
214
+ @gens.each {|gen| gen.write}
215
+ @statics.each {|static| static.write}
147
216
 
148
217
  @archive.write
149
- self.write_tags
150
- self.write_categories
151
- end
152
-
153
- # Writes the necessary pages for tags, but only if the correct layouts are present
154
- def write_tags
155
- if @layouts['tag_index']
156
- write_path = File.join( @config[:root], 'tags', 'index.html' )
157
-
158
- tag_index = Gen.new(write_path, self)
159
- tag_index.layout = @layouts['tag_index']
160
-
161
- tag_index.render
162
- tag_index.write
163
- end
164
-
165
- if @layouts['tag_page']
166
- @tags.each do |n, tag|
167
- write_path = File.join( @config[:root], 'tags', tag.name, 'index.html' )
168
-
169
- payload = {:name => 'tag', :payload => tag.to_hash}
170
- tag_page = Gen.new(write_path, self, payload)
171
- tag_page.layout = @layouts['tag_page']
172
-
173
- tag_page.render
174
- tag_page.write
175
- end
176
- end
177
- end
178
-
179
- # Writes the necessary pages for categories, but only if the correct layouts are present
180
- def write_categories
181
- if @layouts['category_index']
182
- write_path = File.join( @config[:root], 'categories', 'index.html' )
183
-
184
- category_index = Gen.new(write_path, self)
185
- category_index.layout = @layouts['category_index']
186
-
187
- category_index.render
188
- category_index.write
189
- end
190
-
191
- if @layouts['category_page']
192
- @categories.each do |n, category|
193
- write_path = File.join( @config[:root], 'categories', category.name, 'index.html' )
194
-
195
- payload = {:name => 'category', :payload => category.to_hash}
196
- category_page = Gen.new(write_path, self, payload)
197
- category_page.layout = @layouts['category_page']
198
-
199
- category_page.render
200
- category_page.write
201
- end
202
- end
218
+ @tags.write
219
+ @categories.write
220
+ self
203
221
  end
204
222
 
205
223
 
206
- # @return [Bool]
224
+ # @param [String] path to test
225
+ # @return [Bool] whether the path points to a static
207
226
  def static?( path )
208
227
  !( layout?(path) || post?(path) || gen?(path) || ignored?(path) )
209
228
  end
210
229
 
211
- # @return [Bool]
230
+ # @param [String] path to test
231
+ # @return [Bool] whether the path points to a layout
212
232
  def layout?( path )
213
233
  path.include?('layouts/') && !ignored?(path)
214
234
  end
215
235
 
216
- # @return [Bool]
236
+ # @param [String] path to test
237
+ # @return [Bool] whether the path points to a post
217
238
  def post?( path )
218
239
  path.include?('posts/') && !ignored?(path)
219
240
  end
220
241
 
221
- # @return [Bool]
242
+ # @param [String] path to test
243
+ # @return [Bool] whether the path points to a gen
222
244
  def gen?( path )
223
245
  return false if post?(path) || layout?(path) || ignored?(path)
224
- return true if @config[:plugins][:generators].has_key? path.extension
246
+ return true if @plugins[:generators].has_key? path.to_p.extname[1..-1]
225
247
  return true if File.open(path, "r").read(3) == "---"
226
248
  false
227
249
  end
228
250
 
229
- # @return [Bool]
251
+ # @param [String] path to test
252
+ # @return [Bool] whether the path points to a file which should be ignored
230
253
  def ignored?( path )
231
- ignored = ['/options.yaml'] + @config[:exclude]
232
- ignored.collect! {|i| File.join(@config[:root], i)}
254
+ ignored = ['/options.yaml'] + @config['exclude']
255
+ ignored.collect! {|i| File.join(@root, i)}
233
256
  ignored.each do |i|
234
257
  return true if path.include? i
235
258
  end
@@ -2,25 +2,27 @@ module Henshin
2
2
 
3
3
  class Static
4
4
 
5
- attr_accessor :path, :site, :config, :content
5
+ attr_accessor :path, :site, :content
6
6
 
7
7
  def initialize( path, site )
8
8
  @path = path
9
9
  @site = site
10
- @config = site.config
11
- @content = File.read( path )
10
+ @content = File.read(path)
12
11
  end
13
12
 
14
13
  ##
15
14
  # Writes the file to the correct place
16
15
  def write
17
- write_path = File.join( config[:root], config[:target], @path[ config[:root].size..-1 ] )
18
-
19
- FileUtils.mkdir_p File.join( write_path.directory )
20
- file = File.new( File.join( write_path ), "w" )
21
- file.puts( @content )
16
+ FileUtils.mkdir_p(self.write_path.dirname)
17
+ file = File.new(self.write_path, "w")
18
+ file.puts(@content)
22
19
  end
23
20
 
21
+ # @return [Pathname] path to write the file
22
+ def write_path
23
+ rel = @path.relative_path_from(@site.root)
24
+ @site.target + File.join(@site.base, rel)[1..-1]
25
+ end
24
26
 
25
27
  def inspect
26
28
  "#<Static:#{@path}>"
@@ -1,22 +1,23 @@
1
- require 'rubygems'
2
1
  require 'test/unit'
3
2
  require 'shoulda'
4
3
  require 'rr'
5
4
 
6
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
7
- $LOAD_PATH.unshift(File.dirname(__FILE__))
8
- require 'henshin'
5
+ require File.dirname(__FILE__) + '/../lib/henshin'
9
6
 
10
7
  class Test::Unit::TestCase
11
8
 
12
9
  include RR::Adapters::TestUnit
13
10
 
11
+ def site_override
12
+ {'root' => root_dir, 'target' => target_dir}
13
+ end
14
+
14
15
  def root_dir
15
16
  File.join(File.dirname(__FILE__), 'site')
16
17
  end
17
18
 
18
19
  def target_dir
19
- "_site"
20
+ File.join(root_dir, "_site")
20
21
  end
21
22
 
22
23
  def remove_site
@@ -24,9 +25,20 @@ class Test::Unit::TestCase
24
25
  end
25
26
 
26
27
  def new_site
27
- override = {:root => File.join(File.dirname(__FILE__), 'site'), :target => '_site'}
28
- config = Henshin.configure(override)
29
- Henshin::Site.new(config)
28
+ Henshin::Site.new(site_override)
29
+ end
30
+
31
+
32
+
33
+ # Determines whether +instance+ is a +klass+ using #is_a?.
34
+ # This is similar to assert_instance_of except it doesn't
35
+ # have to be an instance of +klass+ just an instance of a
36
+ # decendant of +klass+.
37
+ #
38
+ # @param [Class] klass the klass to test for
39
+ # @param [Object] instance of the object to test
40
+ def assert_is_a(klass, instance)
41
+ assert instance.is_a?(klass), "Expected #{instance} to be a #{klass}"
30
42
  end
31
43
 
32
44
  end