mint 0.2.9 → 0.5.0

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.
@@ -10,21 +10,19 @@ module Mint
10
10
  # We do have to test #style_destination derivatives. Those aren't
11
11
  # covered by resource_spec.rb.
12
12
  shared_examples_for "all documents" do
13
- # Convenience methods
14
-
15
- it "#stylesheet" do
16
- document.stylesheet.should ==
17
- Helpers.normalize_path(document.style_destination_file,
18
- document.destination_directory).to_s
19
- end
20
-
21
13
  # style_spec.rb ensures that our style generation goes as planned
22
14
  # However, we need to test layout generation because it should now
23
15
  # include our content
16
+ #
17
+ # This test doesn't cover any plugin transformations. Those
18
+ # transformations are covered in the Plugin spec.
24
19
  its(:content) { should =~ /<p>This is just a test.<\/p>/ }
20
+ its(:metadata) { should == { 'metadata' => true } }
25
21
 
26
22
  # Render output
27
-
23
+
24
+ # This test doesn't cover any plugin transformations. Those
25
+ # transformations are covered in the Plugin spec.
28
26
  it "renders its layout, injecting content inside" do
29
27
  document.render.should =~
30
28
  /.*<html>.*#{document.content}.*<\/html>.*/m
@@ -36,6 +34,8 @@ module Mint
36
34
 
37
35
  # Mint output
38
36
 
37
+ # These tests doesn't cover any plugin transformations. Those
38
+ # transformations are covered in the Plugin spec.
39
39
  it "writes its rendered style to #style_destination_file" do
40
40
  document.publish!
41
41
  document.style_destination_file_path.should exist
@@ -77,6 +77,8 @@ module Mint
77
77
  its(:layout) { should be_in_directory('default') }
78
78
  its(:style) { should be_in_directory('default') }
79
79
 
80
+ its(:stylesheet) { should == Mint.root + '/templates/default/css/style.css' }
81
+
80
82
  it_should_behave_like "all documents"
81
83
  end
82
84
 
@@ -110,6 +112,8 @@ module Mint
110
112
  its(:layout) { should be_in_directory('default') }
111
113
  its(:style) { should be_in_directory('default') }
112
114
 
115
+ its(:stylesheet) { should == 'styles/style.css' }
116
+
113
117
  it_should_behave_like "all documents"
114
118
  end
115
119
 
@@ -142,6 +146,8 @@ module Mint
142
146
  its(:layout) { should be_in_directory('default') }
143
147
  its(:style) { should be_in_directory('default') }
144
148
 
149
+ its(:stylesheet) { should == Mint.root + '/templates/default/css/style.css' }
150
+
145
151
  it_should_behave_like "all documents"
146
152
  end
147
153
 
@@ -181,7 +187,40 @@ module Mint
181
187
  its(:layout) { should be_in_directory('pro') }
182
188
  its(:style) { should be_in_directory('pro') }
183
189
 
190
+ its(:stylesheet) { should == 'styles/style.css' }
191
+
184
192
  it_should_behave_like "all documents"
185
193
  end
194
+
195
+ context "when dealing with metadata" do
196
+ let(:text) { "metadata: true\n\nReal text" }
197
+ describe ".metadata_chunk" do
198
+ it "extracts, but does not parse, metadata from text" do
199
+ Document.metadata_chunk(text).should == 'metadata: true'
200
+ end
201
+ end
202
+
203
+ describe ".metadata_from" do
204
+ it "parses a documents metadata if present" do
205
+ Document.metadata_from(text).should == { 'metadata' => true }
206
+ end
207
+
208
+ it "returns the empty string if a document has bad/no metadata" do
209
+ Document.metadata_from('No metadata here').should == {}
210
+ end
211
+ end
212
+
213
+ describe ".parse_metadata_from" do
214
+ it "separates text from its metadata if present" do
215
+ Document.parse_metadata_from(text).should ==
216
+ [{ 'metadata' => true }, 'Real text']
217
+ end
218
+
219
+ it "returns the entire text if no metadata is found" do
220
+ Document.parse_metadata_from('No metadata here').should ==
221
+ [{}, 'No metadata here']
222
+ end
223
+ end
224
+ end
186
225
  end
187
226
  end
@@ -0,0 +1,231 @@
1
+ require 'spec_helper'
2
+
3
+ module Mint
4
+ describe Helpers do
5
+ describe "#underscore" do
6
+ it "underscores class names per ActiveSupport conventions" do
7
+ Helpers.underscore('ClassName').should == 'class_name'
8
+ end
9
+
10
+ it "allows for camel case prefixes" do
11
+ Helpers.underscore('EPub').should == 'e_pub'
12
+ Helpers.underscore('EPub', :ignore_prefix => true).should == 'epub'
13
+ end
14
+
15
+ it "allows for namespace removal" do
16
+ Helpers.underscore('Mint::EPub',
17
+ :namespaces => true).should == 'mint/e_pub'
18
+ Helpers.underscore('Mint::EPub',
19
+ :namespaces => false).should == 'e_pub'
20
+ Helpers.underscore('Mint::EPub',
21
+ :namespaces => true,
22
+ :ignore_prefix => true).should == 'mint/epub'
23
+ end
24
+ end
25
+
26
+ describe "#slugize" do
27
+ it "downcases everything" do
28
+ Helpers.slugize('This could use FEWER CAPITALS').should ==
29
+ 'this-could-use-fewer-capitals'
30
+ end
31
+
32
+ it "parses 'and'" do
33
+ Helpers.slugize('You & me').should == 'you-and-me'
34
+ end
35
+
36
+ it "parses spaces" do
37
+ Helpers.slugize('You and me').should == 'you-and-me'
38
+ end
39
+
40
+ it "removes non-word/non-digits" do
41
+ Helpers.slugize('You // and :: me').should == 'you-and-me'
42
+ end
43
+
44
+ it "condenses multiple hyphens" do
45
+ Helpers.slugize('You-----and me').should == 'you-and-me'
46
+ end
47
+ end
48
+
49
+ describe "#symbolize" do
50
+ it "converts hyphens to underscores" do
51
+ Helpers.symbolize('you-and-me').should == :you_and_me
52
+ end
53
+ end
54
+
55
+ describe "#pathize" do
56
+ it "converts a String to a Pathname" do
57
+ Helpers.pathize("filename.md").should ==
58
+ Pathname.new("filename.md").expand_path
59
+ end
60
+
61
+ it "does not convert a Pathname" do
62
+ pathname = Pathname.new("filename.md")
63
+ Helpers.pathize(pathname).should == pathname.expand_path
64
+ end
65
+ end
66
+
67
+ describe "#symbolize_keys" do
68
+ it "turns all string keys in a flat map into symbols" do
69
+ flat_map = {
70
+ 'key1' => 'value1',
71
+ 'key2' => 'value2',
72
+ 'key3' => 'value3'
73
+ }
74
+
75
+ expected_map = {
76
+ key1: 'value1',
77
+ key2: 'value2',
78
+ key3: 'value3'
79
+ }
80
+
81
+ Helpers.symbolize_keys(flat_map).should == expected_map
82
+ end
83
+
84
+ it "recursively turns all string keys in a nested map into symbols" do
85
+ nested_map = {
86
+ 'key1' => 'value1',
87
+ 'key2' => 'value2',
88
+ 'key3' => 'value3',
89
+ 'key4' => {
90
+ 'nested_key1' => 'nested_value1',
91
+ 'nested_key2' => 'nested_value2'
92
+ }
93
+ }
94
+
95
+ expected_map = {
96
+ key1: 'value1',
97
+ key2: 'value2',
98
+ key3: 'value3',
99
+ key4: {
100
+ nested_key1: 'nested_value1',
101
+ nested_key2: 'nested_value2'
102
+ }
103
+ }
104
+
105
+ Helpers.symbolize_keys(nested_map).should == expected_map
106
+ end
107
+
108
+ it "recursively downcases all keys if specified" do
109
+ capitalized_map = {
110
+ 'Key1' => 'value1',
111
+ 'Key2' => 'value2',
112
+ 'Key3' => 'value3',
113
+ 'Key4' => {
114
+ 'Nested_key1' => 'nested_value1',
115
+ 'Nested_key2' => 'nested_value2'
116
+ }
117
+ }
118
+
119
+ expected_map = {
120
+ key1: 'value1',
121
+ key2: 'value2',
122
+ key3: 'value3',
123
+ key4: {
124
+ nested_key1: 'nested_value1',
125
+ nested_key2: 'nested_value2'
126
+ }
127
+ }
128
+
129
+ Helpers.symbolize_keys(capitalized_map, :downcase => true).should == expected_map
130
+ end
131
+ end
132
+
133
+ describe "#listify" do
134
+ it "joins a list of three or more with an ampersand, without the Oxford comma" do
135
+ Helpers.listify(['Alex', 'Chris', 'John']).should ==
136
+ 'Alex, Chris & John'
137
+ end
138
+
139
+ it "joins a list of two with an ampersand" do
140
+ Helpers.listify(['Alex', 'Chris']).should == 'Alex & Chris'
141
+ end
142
+
143
+ it "does not do anything to a list of one" do
144
+ Helpers.listify(['Alex']).should == 'Alex'
145
+ end
146
+ end
147
+
148
+ describe "#standardize" do
149
+ before do
150
+ @nonstandard = {
151
+ title: 'Title',
152
+ author: 'David',
153
+ editors: ['David', 'Jake'],
154
+ barcode: 'Unique ID'
155
+ }
156
+
157
+ @table = {
158
+ author: [:creators, :array],
159
+ editors: [:collaborators, :array],
160
+ barcode: [:uuid, :string]
161
+ }
162
+
163
+ @standard = {
164
+ title: 'Title',
165
+ creators: ['David'],
166
+ collaborators: ['David', 'Jake'],
167
+ uuid: 'Unique ID'
168
+ }
169
+ end
170
+
171
+ it "converts all nonstandard keys to standard ones" do
172
+ Helpers.standardize(@nonstandard,
173
+ :table => @table).should == @standard
174
+ end
175
+ end
176
+
177
+ describe "#normalize_path" do
178
+ it "handles two files in the same directory" do
179
+ path1 = '~/file1'
180
+ path2 = '~/file2'
181
+ Helpers.normalize_path(path1, path2).should ==
182
+ Pathname.new('../file1')
183
+ end
184
+
185
+ it "handles two files one directory apart" do
186
+ path1 = '~/file1'
187
+ path2 = '~/subdir/file2'
188
+ Helpers.normalize_path(path1, path2).should ==
189
+ Pathname.new('../../file1')
190
+ end
191
+
192
+ it "handles two files linked only at the directory root" do
193
+ path1 = '/home/david/file1'
194
+ path2 = '/usr/local/src/file2'
195
+ Helpers.normalize_path(path1, path2).should ==
196
+ Pathname.new('/home/david/file1')
197
+ end
198
+
199
+ it "returns nil for identical files" do
200
+ path1 = '~/file1'
201
+ path2 = '~/file1'
202
+ Helpers.normalize_path(path1, path2).should == Pathname.new('.')
203
+ end
204
+ end
205
+
206
+ describe "#update_yaml!" do
207
+ it "loads existing YAML data from file"
208
+ it "combines existing YAML data with new data and writes to file"
209
+ end
210
+
211
+ describe "#generate_temp_file!" do
212
+ before do
213
+ @file = Helpers.generate_temp_file! 'content.md'
214
+ @path = Pathname.new @file
215
+ end
216
+
217
+ it "creates a randomly named temp file" do
218
+ @path.should exist
219
+ end
220
+
221
+ it "creates a temp file with the correct name and extension" do
222
+ @path.basename.to_s.should =~ /content/
223
+ @path.extname.should == '.md'
224
+ end
225
+
226
+ it "fills the temp file with the specified content" do
227
+ @path.read.should =~ /This is just a test/
228
+ end
229
+ end
230
+ end
231
+ end
@@ -0,0 +1,6 @@
1
+ require 'spec_helper'
2
+
3
+ module Mint
4
+ describe Layout do
5
+ end
6
+ end
@@ -0,0 +1,94 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mint do
4
+ subject { Mint }
5
+ its(:root) { should == File.expand_path('../../../mint', __FILE__) }
6
+ its(:path) { should == ["#{Dir.getwd}/.mint", "~/.mint", Mint.root] }
7
+ its(:formats) { should include('md') }
8
+ its(:css_formats) { should include('sass') }
9
+ its(:templates) { should include(Mint.root + '/templates/default') }
10
+
11
+ its(:default_options) do
12
+ should == {
13
+ layout: 'default',
14
+ style: 'default',
15
+ destination: nil,
16
+ style_destination: nil
17
+ }
18
+ end
19
+
20
+ its(:directories) do
21
+ should == {
22
+ templates: 'templates',
23
+ config: 'config'
24
+ }
25
+ end
26
+
27
+ its(:files) do
28
+ should == {
29
+ syntax: Mint.directories[:config] + '/syntax.yaml',
30
+ config: Mint.directories[:config] + '/config.yaml'
31
+ }
32
+ end
33
+
34
+ it "creates a valid renderer" do
35
+ Mint.renderer(@content_file).should respond_to(:render)
36
+ end
37
+
38
+ it "chooses the appropriate path for scope" do
39
+ Mint.path_for_scope(:local).should == "#{Dir.getwd}/.mint"
40
+ Mint.path_for_scope(:user).should == '~/.mint'
41
+ Mint.path_for_scope(:global).should == Mint.root
42
+ end
43
+
44
+ it "looks up the correct template according to scope" do
45
+ Mint.lookup_template(:default, :layout).should be_in_template('default')
46
+ Mint.lookup_template(:default, :style).should be_in_template('default')
47
+ Mint.lookup_template(:pro, :layout).should be_in_template('pro')
48
+ Mint.lookup_template(:pro, :style).should be_in_template('pro')
49
+ Mint.lookup_template('layout.haml').should == 'layout.haml'
50
+ Mint.lookup_template('dynamic.sass').should == 'dynamic.sass'
51
+ end
52
+
53
+ it "finds the correct template according to scope" do
54
+ Mint.find_template('default', :layout).should be_in_template('default')
55
+ Mint.find_template('pro', :layout).should be_in_template('pro')
56
+ Mint.find_template('pro', :style).should be_in_template('pro')
57
+ end
58
+
59
+ it "decides whether or not a file is a template file" do
60
+ actual_template = Mint.lookup_template(:default, :layout)
61
+ fake_template = "#{Mint.root}/templates/default.css"
62
+ obvious_nontemplate = @dynamic_style_file
63
+
64
+ actual_template.should be_a_template
65
+ fake_template.should_not be_a_template
66
+ obvious_nontemplate.should_not be_a_template
67
+ end
68
+
69
+ it "properly guesses destination file names based on source file names" do
70
+ Mint.guess_name_from('content.md').should == 'content.html'
71
+ Mint.guess_name_from('content.textile').should == 'content.html'
72
+ Mint.guess_name_from('layout.haml').should == 'layout.html'
73
+ Mint.guess_name_from('dynamic.sass').should == 'dynamic.css'
74
+ end
75
+
76
+ context "before it publishes a document" do
77
+ let(:document) { Mint::Document.new @content_file }
78
+ subject { document }
79
+
80
+ its(:destination_file_path) { should_not exist }
81
+ its(:style_destination_file_path) { should_not exist }
82
+ end
83
+
84
+ # These are copied from document_spec.rb. I eventually want to move
85
+ # to this non-OO style of publishing, and this is the transition
86
+ context "when it publishes a document" do
87
+ let(:document) { Mint::Document.new @content_file }
88
+ before { Mint.publish! document }
89
+ subject { document }
90
+
91
+ its(:destination_file_path) { should exist }
92
+ its(:style_destination_file_path) { should exist }
93
+ end
94
+ end
@@ -0,0 +1,457 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mint do
4
+ # Remove unintended side effects of creating
5
+ # new plugins in other files.
6
+ before { Mint.clear_plugins! }
7
+ after { Mint.clear_plugins! }
8
+
9
+ describe ".plugins" do
10
+ it "returns all registered plugins" do
11
+ plugin = Class.new(Mint::Plugin)
12
+ Mint.plugins.should == [plugin]
13
+ end
14
+
15
+ it "returns an empty array if there are no registered plugins" do
16
+ Mint.plugins.should == []
17
+ end
18
+ end
19
+
20
+ describe ".activated_plugins" do
21
+ it "returns a list of plugins activated for a document"
22
+ it "returns a list of plugins activated for a set of documents"
23
+ it "returns a list of plugins activated for all documents"
24
+ end
25
+
26
+ describe ".register_plugin!" do
27
+ let(:plugin) { Class.new }
28
+
29
+ it "registers a plugin once" do
30
+ Mint.register_plugin! plugin
31
+ Mint.plugins.should == [plugin]
32
+ end
33
+
34
+ it "does not register a plugin more than once" do
35
+ Mint.register_plugin! plugin
36
+ lambda { Mint.register_plugin! plugin }.should_not change { Mint.plugins }
37
+ Mint.plugins.should == [plugin]
38
+ end
39
+ end
40
+
41
+ describe ".activate_plugin!" do
42
+ let(:plugin) { Class.new }
43
+
44
+ it "activates a plugin once" do
45
+ Mint.activate_plugin! plugin
46
+ Mint.activated_plugins.should == [plugin]
47
+ end
48
+
49
+ it "does not register a plugin more than once" do
50
+ Mint.activate_plugin! plugin
51
+ lambda { Mint.activate_plugin! plugin }.should_not change { Mint.activated_plugins }
52
+ Mint.activated_plugins.should == [plugin]
53
+ end
54
+ end
55
+
56
+ describe ".clear_plugins!" do
57
+ let(:plugin) { Class.new }
58
+
59
+ it "does nothing if no plugins are registered" do
60
+ lambda { Mint.clear_plugins! }.should_not raise_error
61
+ end
62
+
63
+ it "removes all registered plugins" do
64
+ Mint.register_plugin! plugin
65
+ lambda { Mint.clear_plugins! }.should change { Mint.plugins.length }.by(-1)
66
+ end
67
+
68
+ it "removes all activated plugins" do
69
+ Mint.activate_plugin! plugin
70
+ lambda { Mint.clear_plugins! }.should change { Mint.activated_plugins.length }.by(-1)
71
+ end
72
+ end
73
+
74
+ describe ".template_directory" do
75
+ let(:plugin) { Class.new(Mint::Plugin) }
76
+
77
+ it "gives access to a directory where template files can be stored" do
78
+ plugin.should_receive(:name).and_return('DocBook')
79
+ Mint.template_directory(plugin).should ==
80
+ Mint.root + '/plugins/templates/doc_book'
81
+ end
82
+ end
83
+
84
+ describe ".config_directory" do
85
+ let(:plugin) { Class.new(Mint::Plugin) }
86
+
87
+ it "gives access to a directory where template files can be stored" do
88
+ plugin.should_receive(:name).and_return('DocBook')
89
+ Mint.config_directory(plugin).should ==
90
+ Mint.root + '/plugins/config/doc_book'
91
+ end
92
+ end
93
+
94
+ describe ".commandline_options_file" do
95
+ let(:plugin) { Class.new(Mint::Plugin) }
96
+
97
+ it "gives access to a directory where template files can be stored" do
98
+ plugin.should_receive(:name).and_return('DocBook')
99
+ Mint.commandline_options_file(plugin).should ==
100
+ Mint.root + '/plugins/config/doc_book/syntax.yml'
101
+ end
102
+ end
103
+
104
+ [:before_render, :after_render].each do |callback|
105
+ describe ".#{callback}" do
106
+ let(:first_plugin) { Class.new(Mint::Plugin) }
107
+ let(:second_plugin) { Class.new(Mint::Plugin) }
108
+ let(:third_plugin) { Class.new(Mint::Plugin) }
109
+
110
+ context "when plugins are specified" do
111
+ before do
112
+ first_plugin.should_receive(callback).ordered.and_return('first')
113
+ second_plugin.should_receive(callback).ordered.and_return('second')
114
+ third_plugin.should_receive(callback).never
115
+ end
116
+
117
+ it "reduces .#{callback} across all specified plugins in order" do
118
+ plugins = [first_plugin, second_plugin]
119
+ Mint.send(callback, 'text', :plugins => plugins).should == 'second'
120
+ end
121
+ end
122
+
123
+ context "when plugins are activated, but no plugins are specified" do
124
+ before do
125
+ first_plugin.should_receive(callback).ordered.and_return('first')
126
+ second_plugin.should_receive(callback).ordered.and_return('second')
127
+ third_plugin.should_receive(callback).never
128
+ end
129
+
130
+ it "reduces .#{callback} across all activated plugins in order" do
131
+ Mint.activate_plugin! first_plugin
132
+ Mint.activate_plugin! second_plugin
133
+ Mint.send(callback, 'text').should == 'second'
134
+ end
135
+ end
136
+
137
+ context "when plugins are not specified" do
138
+ before do
139
+ first_plugin.should_receive(callback).never
140
+ second_plugin.should_receive(callback).never
141
+ third_plugin.should_receive(callback).never
142
+ end
143
+
144
+ it "returns the parameter text" do
145
+ Mint.send(callback, 'text').should == 'text'
146
+ end
147
+ end
148
+ end
149
+ end
150
+
151
+ describe ".after_publish" do
152
+ let(:first_plugin) { Class.new(Mint::Plugin) }
153
+ let(:second_plugin) { Class.new(Mint::Plugin) }
154
+ let(:third_plugin) { Class.new(Mint::Plugin) }
155
+
156
+ context "when plugins are specified" do
157
+ before do
158
+ first_plugin.should_receive(:after_publish).ordered
159
+ second_plugin.should_receive(:after_publish).ordered
160
+ third_plugin.should_receive(:after_publish).never
161
+ end
162
+
163
+ it "iterates across all specified plugins in order" do
164
+ plugins = [first_plugin, second_plugin]
165
+ Mint.after_publish('fake document', :plugins => plugins)
166
+ end
167
+ end
168
+
169
+ context "when plugins are activated, but no plugins are specified" do
170
+ before do
171
+ first_plugin.should_receive(:after_publish).ordered
172
+ second_plugin.should_receive(:after_publish).ordered
173
+ third_plugin.should_receive(:after_publish).never
174
+ end
175
+
176
+ it "iterates across all activated plugins in order" do
177
+ Mint.activate_plugin! first_plugin
178
+ Mint.activate_plugin! second_plugin
179
+ Mint.after_publish('fake document')
180
+ end
181
+ end
182
+
183
+ context "when plugins are not specified" do
184
+ before do
185
+ first_plugin.should_receive(:after_publish).never
186
+ second_plugin.should_receive(:after_publish).never
187
+ third_plugin.should_receive(:after_publish).never
188
+ end
189
+
190
+ it "does not iterate over any plugins" do
191
+ Mint.after_publish('fake document')
192
+ end
193
+ end
194
+ end
195
+
196
+ # TODO: Document expected document functionality changes related to plugins
197
+ describe Mint::Document do
198
+ context "when plugins are registered with Mint" do
199
+ describe "#content=" do
200
+ it "applies each registered plugin's before_render filter"
201
+ end
202
+
203
+ describe "#render" do
204
+ it "applies each registered plugin's after_render filter"
205
+ end
206
+
207
+ describe "#publish!" do
208
+ it "applies each registered plugin's after_publish filter"
209
+ end
210
+ end
211
+ end
212
+
213
+ describe Mint::Plugin do
214
+ # We have to instantiate these plugins in a before block,
215
+ # and not in a let block. Because lets are lazily evaluated,
216
+ # the first two tests in the '#inherited' suite will not
217
+ # pass.
218
+ before do
219
+ @first_plugin = Class.new(Mint::Plugin)
220
+ @second_plugin = Class.new(Mint::Plugin)
221
+ end
222
+
223
+ describe ".underscore" do
224
+ let(:plugin) { Class.new(Mint::Plugin) }
225
+
226
+ it "when anonymous, returns a random identifier"
227
+
228
+ it "when named, returns its name, underscored" do
229
+ plugin.should_receive(:name).and_return('EPub')
230
+ plugin.underscore.should == 'epub'
231
+ end
232
+ end
233
+
234
+ describe ".inherited" do
235
+ it "registers the subclass with Mint as a plugin" do
236
+ lambda do
237
+ Class.new(Mint::Plugin)
238
+ end.should change { Mint.plugins.length }.by(1)
239
+ end
240
+
241
+ it "preserves the order of subclassing" do
242
+ Mint.plugins.should == [@first_plugin, @second_plugin]
243
+ end
244
+
245
+ it "does not change the order of a plugin when it is monkey-patched" do
246
+ lambda do
247
+ @first_plugin.instance_eval do
248
+ def monkey_patch
249
+ end
250
+ end
251
+ end.should_not change { Mint.plugins }
252
+ end
253
+ end
254
+
255
+ describe ".commandline_options" do
256
+ let(:plugin) { Class.new(Mint::Plugin) }
257
+ before do
258
+ plugin.instance_eval do
259
+ def commandline_options
260
+ end
261
+ end
262
+ end
263
+
264
+ it "returns a hash of options the plugin can take, including constraints"
265
+ end
266
+
267
+ context "plugin callbacks" do
268
+ let(:plugin) { Class.new(Mint::Plugin) }
269
+
270
+ describe ".before_render" do
271
+ it "allows changes to the un-rendered content" do
272
+ plugin.instance_eval do
273
+ def before_render(text_document)
274
+ 'base'
275
+ end
276
+ end
277
+
278
+ plugin.before_render('text').should == 'base'
279
+ end
280
+ end
281
+
282
+ describe ".after_render" do
283
+ it "allows changes to the rendered HTML" do
284
+ plugin.instance_eval do
285
+ def after_render(html_document)
286
+ '<!doctype html>'
287
+ end
288
+ end
289
+
290
+ plugin.after_render('<html></html>').should == '<!doctype html>'
291
+ end
292
+ end
293
+
294
+ describe ".after_mint" do
295
+ let(:document) { Mint::Document.new 'content.md' }
296
+
297
+ it "allows changes to the document extension" do
298
+ plugin.instance_eval do
299
+ def after_publish(document)
300
+ document.name.gsub! /html$/, 'htm'
301
+ end
302
+ end
303
+
304
+ lambda do
305
+ plugin.after_publish(document)
306
+ end.should change { document.name.length }.by(-1)
307
+ end
308
+
309
+ it "allows splitting up the document into two, without garbage" do
310
+ plugin.instance_eval do
311
+ def after_publish(document)
312
+ content = document.content
313
+ fake_splitting_point = content.length / 2
314
+
315
+ first = content[0..fake_splitting_point]
316
+ second = content[fake_splitting_point..-1]
317
+
318
+ File.open 'first-half.html', 'w+' do |file|
319
+ file << first
320
+ end
321
+
322
+ File.open 'second-half.html', 'w+' do |file|
323
+ file << second
324
+ end
325
+
326
+ File.delete document.destination_file
327
+ end
328
+ end
329
+
330
+ document.publish! :plugins => [plugin]
331
+
332
+ File.exist?(document.destination_file).should be_false
333
+ File.exist?('first-half.html').should be_true
334
+ File.exist?('second-half.html').should be_true
335
+ end
336
+
337
+ it "allows changes to the style file" do
338
+ pending "figure out a better strategy for style manipulation"
339
+ document = Mint::Document.new 'content.md', :style => 'style.css'
340
+
341
+ plugin.instance_eval do
342
+ def after_publish(document)
343
+ # I'd like to take document.style_destination_file,
344
+ # but the current Mint API doesn't allow for this
345
+ # if we're setting the style via a concrete
346
+ # stylesheet in our current directory
347
+ style_source = document.style.source_file
348
+ style = File.read style_source
349
+ File.open style_source, 'w' do |file|
350
+ file << style.gsub(/#/, '.')
351
+ end
352
+ end
353
+ end
354
+
355
+ document.publish! :plugins => [plugin]
356
+
357
+ File.read(document.style.source_file).should =~ /\#container/
358
+ end
359
+
360
+ context "when the output is in the default directory" do
361
+ it "doesn't allow changes to the document directory" do
362
+ pending "figuring out the best way to prevent directory manipulation"
363
+ document = Mint::Document.new 'content.md'
364
+ plugin.instance_eval do
365
+ def after_publish
366
+ original = document.destination_directory
367
+ new = File.expand_path 'invalid'
368
+ FileUtils.mv original, new
369
+ document.destination = 'invalid'
370
+ end
371
+
372
+ lambda do
373
+ document.publish! :plugins => [plugin]
374
+ end.should raise_error(InvalidPluginAction)
375
+ end
376
+ end
377
+ end
378
+
379
+ context "when the output is a new directory" do
380
+ it "allows changes to the document directory" do
381
+ document = Mint::Document.new 'content.md', :destination => 'destination'
382
+ plugin.instance_eval do
383
+ def after_publish(document)
384
+ original = document.destination_directory
385
+ new = File.expand_path 'book'
386
+ FileUtils.mv original, new
387
+ document.destination = 'book'
388
+ end
389
+ end
390
+
391
+ document.publish! :plugins => [plugin]
392
+ File.exist?('destination').should be_false
393
+ File.exist?('book').should be_true
394
+ document.destination_directory.should == File.expand_path('book')
395
+ end
396
+
397
+ it "allows compression of the final output" do
398
+ require 'zip/zip'
399
+ require 'zip/zipfilesystem'
400
+
401
+ document = Mint::Document.new 'content.md', :destination => 'destination'
402
+ plugin.instance_eval do
403
+ def after_publish(document)
404
+ Zip::ZipOutputStream.open 'book.zip' do |zos|
405
+ # zos.put_next_entry('mimetype', nil, nil, Zip::ZipEntry::STORED)
406
+ # zos.puts 'text/epub'
407
+ zos.put_next_entry('chapter-1', nil, nil, Zip::ZipEntry::DEFLATED)
408
+ zos.puts File.read(document.destination_file)
409
+ end
410
+
411
+ FileUtils.mv 'book.zip', 'book.epub'
412
+ end
413
+ end
414
+
415
+ document.publish! :plugins => [plugin]
416
+
417
+ File.exist?('destination').should be_true
418
+ File.exist?('book.zip').should be_false
419
+ File.exist?('book.epub').should be_true
420
+
421
+ directory_size =
422
+ Dir["#{document.destination_directory}/**/*"].
423
+ flatten.
424
+ map {|file| File.stat(file).size }.
425
+ reduce(&:+)
426
+ compressed_size = File.stat('book.epub').size
427
+ directory_size.should > compressed_size
428
+ end
429
+ end
430
+
431
+ context "when the style output is a new directory" do
432
+ it "allows changes to the style directory" do
433
+ document = Mint::Document.new 'content.md', :style_destination => 'styles'
434
+ plugin.instance_eval do
435
+ def after_publish(document)
436
+ original = document.style_destination_directory
437
+ new = File.expand_path 'looks'
438
+ FileUtils.mv original, new
439
+ document.style_destination = 'looks'
440
+ end
441
+ end
442
+
443
+ document.publish! :plugins => [plugin]
444
+
445
+ File.exist?('styles').should be_false
446
+ File.exist?('looks').should be_true
447
+ document.style_destination_directory.should == File.expand_path('looks')
448
+ end
449
+
450
+ after do
451
+ FileUtils.rm_r File.expand_path('looks')
452
+ end
453
+ end
454
+ end
455
+ end
456
+ end
457
+ end