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.
- 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
|