mint 0.2.9 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +45 -283
- data/bin/mint +10 -10
- data/bin/mint-epub +23 -0
- data/features/plugins/epub.feature +23 -0
- data/features/publish.feature +73 -0
- data/features/support/env.rb +1 -1
- data/lib/mint.rb +1 -0
- data/lib/mint/commandline.rb +3 -3
- data/lib/mint/document.rb +46 -5
- data/lib/mint/helpers.rb +65 -4
- data/lib/mint/mint.rb +6 -7
- data/lib/mint/plugin.rb +136 -0
- data/lib/mint/plugins/epub.rb +292 -0
- data/lib/mint/version.rb +1 -1
- data/plugins/templates/epub/container.haml +5 -0
- data/plugins/templates/epub/content.haml +36 -0
- data/plugins/templates/epub/layout.haml +6 -0
- data/plugins/templates/epub/title.haml +11 -0
- data/plugins/templates/epub/toc.haml +26 -0
- data/spec/commandline_spec.rb +91 -0
- data/spec/document_spec.rb +48 -9
- data/spec/helpers_spec.rb +231 -0
- data/spec/layout_spec.rb +6 -0
- data/spec/mint_spec.rb +94 -0
- data/spec/plugin_spec.rb +457 -0
- data/spec/plugins/epub_spec.rb +242 -0
- data/spec/resource_spec.rb +135 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/style_spec.rb +69 -0
- metadata +103 -34
- data/features/mint_document.feature +0 -48
- data/features/step_definitions/mint_steps.rb +0 -1
data/spec/document_spec.rb
CHANGED
@@ -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
|
data/spec/layout_spec.rb
ADDED
data/spec/mint_spec.rb
ADDED
@@ -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
|
data/spec/plugin_spec.rb
ADDED
@@ -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
|