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
@@ -0,0 +1,242 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# Mimics the requirement to actually require plugins for
|
4
|
+
# them to be registered/work.
|
5
|
+
require 'mint/plugins/epub'
|
6
|
+
|
7
|
+
module Mint
|
8
|
+
describe Document do
|
9
|
+
describe "#chapters" do
|
10
|
+
it "splits a document's final text into chapters and maps onto IDs" do
|
11
|
+
# TODO: Clean up these long lines
|
12
|
+
chapters = Document.new('content.md').chapters
|
13
|
+
chapters[0].should =~ /This is just a test.*Paragraph number two/m
|
14
|
+
chapters[1].should =~ /Third sentence.*Fourth sentence/m
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe EPub do
|
20
|
+
describe "#after_publish" do
|
21
|
+
let(:document) do
|
22
|
+
Document.new 'content.md', :destination => 'directory'
|
23
|
+
end
|
24
|
+
|
25
|
+
before do
|
26
|
+
document.publish!
|
27
|
+
document_length = File.read('directory/content.html').length
|
28
|
+
EPub.after_publish(document)
|
29
|
+
|
30
|
+
# We're going to consider a document successfully split
|
31
|
+
# if its two chapters are less than half of it's length,
|
32
|
+
# not including the DOCTYPE/HTML chrome that we introduce
|
33
|
+
# into each split document (~150 characters)
|
34
|
+
@target_length = document_length / 2 + 200
|
35
|
+
end
|
36
|
+
|
37
|
+
after do
|
38
|
+
FileUtils.rm_r 'directory.epub'
|
39
|
+
end
|
40
|
+
|
41
|
+
it "does nothing if no destination is specified" do
|
42
|
+
invalid_document = Document.new 'content.md'
|
43
|
+
lambda do
|
44
|
+
EPub.after_publish(invalid_document)
|
45
|
+
end.should raise_error(InvalidDocumentError)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "replaces the monolithic published file with a packaged ePub file" do
|
49
|
+
File.exist?('directory/content.html').should be_false
|
50
|
+
File.exist?('directory.epub').should be_true
|
51
|
+
end
|
52
|
+
|
53
|
+
it "produces a valid ePub file" do
|
54
|
+
pending "need to integrate epubcheck script if found on system"
|
55
|
+
end
|
56
|
+
|
57
|
+
it "ensures all files were compressed using PKZIP" do
|
58
|
+
File.read('directory.epub')[0..1].should == 'PK'
|
59
|
+
end
|
60
|
+
|
61
|
+
context "when the ePub file is unzipped" do
|
62
|
+
before do
|
63
|
+
# Copy instead of moving to make test cleanup more
|
64
|
+
# predictable in nested contexts.
|
65
|
+
FileUtils.cp 'directory.epub', 'directory.zip'
|
66
|
+
|
67
|
+
# I will later replace this with my own EPub.unzip! function
|
68
|
+
# but don't want to get too distracted now.
|
69
|
+
`unzip -o directory.zip -d directory`
|
70
|
+
|
71
|
+
# EPub.unzip! 'directory.zip'
|
72
|
+
end
|
73
|
+
|
74
|
+
after do
|
75
|
+
FileUtils.rm_r 'directory.zip'
|
76
|
+
FileUtils.rm_r 'directory'
|
77
|
+
end
|
78
|
+
|
79
|
+
it "contains a META-INF directory" do
|
80
|
+
File.exist?('directory/META-INF/container.xml').should be_true
|
81
|
+
end
|
82
|
+
|
83
|
+
it "contains an OPS directory" do
|
84
|
+
File.exist?('directory/OPS').should be_true
|
85
|
+
end
|
86
|
+
|
87
|
+
it "contains a mimetype file" do
|
88
|
+
File.exist?('directory/mimetype').should be_true
|
89
|
+
File.read('directory/mimetype').chomp.should == 'application/epub+zip'
|
90
|
+
end
|
91
|
+
|
92
|
+
it "contains a container file that points to the OPF file" do
|
93
|
+
File.exist?('directory/META-INF/container.xml').should be_true
|
94
|
+
end
|
95
|
+
|
96
|
+
it "contains an OPF manifest with book metadata" do
|
97
|
+
File.exist?('directory/OPS/content.opf').should be_true
|
98
|
+
end
|
99
|
+
|
100
|
+
it "contains an NCX file with book spine and TOC" do
|
101
|
+
File.exist?('directory/OPS/toc.ncx').should be_true
|
102
|
+
end
|
103
|
+
|
104
|
+
it "splits the document into chapters" do
|
105
|
+
chapter1 = File.read 'directory/OPS/chapter-1.html'
|
106
|
+
chapter2 = File.read 'directory/OPS/chapter-2.html'
|
107
|
+
|
108
|
+
chapter1.length.should < @target_length
|
109
|
+
chapter2.length.should < @target_length
|
110
|
+
end
|
111
|
+
|
112
|
+
it "creates a stylesheet for all pages"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe "#split_on" do
|
117
|
+
it "returns a copy of the HTML text it is passed, grouping elements" do
|
118
|
+
Document.new('content.md').publish!
|
119
|
+
|
120
|
+
html_text = File.read 'content.html'
|
121
|
+
html_document = Nokogiri::HTML.parse(html_text)
|
122
|
+
|
123
|
+
chapters = EPub.split_on(html_document, 'h2')
|
124
|
+
|
125
|
+
expected_document = Nokogiri::HTML.parse <<-HTML
|
126
|
+
<div id='container'>
|
127
|
+
<div>
|
128
|
+
<h2>Header</h2>
|
129
|
+
<p>This is just a test.</p>
|
130
|
+
<p>Paragraph number two.</p>
|
131
|
+
</div>
|
132
|
+
|
133
|
+
<div>
|
134
|
+
<h2>Header 2</h2>
|
135
|
+
<p>Third sentence.</p>
|
136
|
+
<p>Fourth sentence.</p>
|
137
|
+
</div>
|
138
|
+
</div>
|
139
|
+
HTML
|
140
|
+
|
141
|
+
expected_chapters = expected_document.search 'div div'
|
142
|
+
|
143
|
+
cleanse(chapters).should == cleanse(expected_chapters)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
describe "#zip!" do
|
148
|
+
before do
|
149
|
+
FileUtils.mkdir 'directory'
|
150
|
+
|
151
|
+
files = {
|
152
|
+
first: 'First content',
|
153
|
+
second: 'Second content',
|
154
|
+
third: 'Third content'
|
155
|
+
}
|
156
|
+
|
157
|
+
files.each do |name, content|
|
158
|
+
File.open "directory/#{name}", 'w' do |f|
|
159
|
+
f << content
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
after do
|
165
|
+
Dir['directory*'].each {|dir| FileUtils.rm_r dir }
|
166
|
+
# FileUtils.rm 'directory.zip'
|
167
|
+
# FileUtils.rm_r 'directory'
|
168
|
+
end
|
169
|
+
|
170
|
+
# This is not a great test of Zip functionality,
|
171
|
+
# but I don't really care to spend time on this right now.
|
172
|
+
# Most of the details of the Zip file creation will be tested
|
173
|
+
# above.
|
174
|
+
it "compresses the named file into a directory" do
|
175
|
+
EPub.zip! 'directory'
|
176
|
+
File.exist?('directory.zip').should be_true
|
177
|
+
end
|
178
|
+
|
179
|
+
it "accepts an extension parameter" do
|
180
|
+
EPub.zip! 'directory', :extension => 'epub'
|
181
|
+
File.exist?('directory.epub').should be_true
|
182
|
+
end
|
183
|
+
|
184
|
+
it "creates a mimetype entry if specified" do
|
185
|
+
pending "a more robust Zip testing strategy"
|
186
|
+
EPub.zip! 'directory', :mimetype => 'text/epub'
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
describe "#create!" do
|
191
|
+
before do
|
192
|
+
EPub.should_receive(:create_from_template!).and_return
|
193
|
+
end
|
194
|
+
|
195
|
+
it "accepts a block for configuration options" do
|
196
|
+
lambda do
|
197
|
+
EPub.create! do |file|
|
198
|
+
file.type = 'container'
|
199
|
+
end
|
200
|
+
end.should_not raise_error
|
201
|
+
end
|
202
|
+
|
203
|
+
it "render a container file" do
|
204
|
+
EPub.should_receive(:container_defaults).once.and_return({})
|
205
|
+
EPub.create! do |file|
|
206
|
+
file.type = 'container'
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
it "render a content file" do
|
211
|
+
EPub.should_receive(:content_defaults).once.and_return({})
|
212
|
+
EPub.create! do |file|
|
213
|
+
file.type = 'content'
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
it "render a table of contents file" do
|
218
|
+
EPub.should_receive(:toc_defaults).once.and_return({})
|
219
|
+
EPub.create! do |file|
|
220
|
+
file.type = 'toc'
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
it "defaults to a type of 'container'" do
|
225
|
+
EPub.should_receive(:container_defaults).once.and_return({})
|
226
|
+
EPub.create!
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
describe "#create_chapters!" do
|
231
|
+
it "calls #create_chapter! for each chapter" do
|
232
|
+
EPub.should_receive(:create_chapter!).once.ordered
|
233
|
+
EPub.should_receive(:create_chapter!).once.ordered
|
234
|
+
EPub.create_chapters! ['text1', 'text2']
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def cleanse(dom)
|
239
|
+
dom.to_s.squeeze.chomp.gsub(/^\s/, '')
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Mint
|
4
|
+
describe Resource do
|
5
|
+
before do
|
6
|
+
@tmp_dir = Dir.getwd
|
7
|
+
@alternative_root = "#{@tmp_dir}/alternative-root"
|
8
|
+
@full_content_file = "#{@tmp_dir}/#{@content_file}"
|
9
|
+
@full_alt_content_file = "#{@alternative_root}/#{@content_file}"
|
10
|
+
end
|
11
|
+
|
12
|
+
shared_examples_for "all resources" do
|
13
|
+
subject { resource }
|
14
|
+
|
15
|
+
its(:root_directory_path) { should be_path(resource.root_directory) }
|
16
|
+
its(:source_file_path) { should be_path(resource.source_file) }
|
17
|
+
its(:source_directory_path) { should be_path(resource.source_directory) }
|
18
|
+
its(:destination_file_path) { should be_path(resource.destination_file) }
|
19
|
+
its(:destination_directory_path) do
|
20
|
+
should be_path(resource.destination_directory)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "when created with a relative path and no root" do
|
25
|
+
let(:resource) { Resource.new @content_file }
|
26
|
+
subject { resource }
|
27
|
+
|
28
|
+
its(:name) { should == 'content.html' }
|
29
|
+
its(:root) { should == @tmp_dir }
|
30
|
+
its(:source) { should == @content_file }
|
31
|
+
its(:source_file) { should == @full_content_file }
|
32
|
+
its(:source_directory) { should == @tmp_dir }
|
33
|
+
its(:destination) { should be_nil }
|
34
|
+
its(:destination_file) { should == "#{@tmp_dir}/content.html" }
|
35
|
+
its(:destination_directory) { should == @tmp_dir }
|
36
|
+
|
37
|
+
it_should_behave_like "all resources"
|
38
|
+
end
|
39
|
+
|
40
|
+
# TODO: This is wrong, need to rework this -- probably build slightly
|
41
|
+
# more comprehensive system in spec_helper for these edge cases,
|
42
|
+
# then build up compound variables in before block for this particular
|
43
|
+
# spec (which has more edge cases than most)
|
44
|
+
context "when created with a relative path and absolute root" do
|
45
|
+
let(:resource) { Resource.new @content_file, :root => @alternative_root }
|
46
|
+
subject { resource }
|
47
|
+
|
48
|
+
its(:name) { should == 'content.html' }
|
49
|
+
its(:root) { should == @alternative_root }
|
50
|
+
its(:source) { should == @content_file }
|
51
|
+
its(:source_file) { should == @full_alt_content_file }
|
52
|
+
its(:source_directory) { should == @alternative_root }
|
53
|
+
its(:destination) { should be_nil }
|
54
|
+
its(:destination_file) { should == "#{@alternative_root}/content.html" }
|
55
|
+
its(:destination_directory) { should == @alternative_root }
|
56
|
+
|
57
|
+
it_should_behave_like "all resources"
|
58
|
+
end
|
59
|
+
|
60
|
+
context "when created with an absolute path, no root" do
|
61
|
+
before do
|
62
|
+
# This is a use case we will only ever test here, so
|
63
|
+
# I'm not going to include it in the spec_helper
|
64
|
+
FileUtils.mkdir_p @alternative_root
|
65
|
+
File.open(@full_alt_content_file, 'w') do |f|
|
66
|
+
f << @content
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
let(:resource) { Resource.new @full_alt_content_file }
|
71
|
+
subject { resource }
|
72
|
+
|
73
|
+
its(:name) { should == 'content.html' }
|
74
|
+
its(:root) { should == @alternative_root }
|
75
|
+
its(:source) { should == @full_alt_content_file }
|
76
|
+
its(:source_file) { should == @full_alt_content_file}
|
77
|
+
its(:source_directory) { should == @alternative_root }
|
78
|
+
its(:destination) { should be_nil }
|
79
|
+
its(:destination_file) { should == "#{@alternative_root}/content.html" }
|
80
|
+
its(:destination_directory) { should == @alternative_root }
|
81
|
+
|
82
|
+
it_should_behave_like "all resources"
|
83
|
+
end
|
84
|
+
|
85
|
+
# The root should *not* override a source file absolute path but
|
86
|
+
# *should* affect the destination file path.
|
87
|
+
# I should also test this when neither the source nor the root
|
88
|
+
# are in Dir.getwd, which is the default root.
|
89
|
+
context "when created with an absolute path and root" do
|
90
|
+
let(:resource) { Resource.new @full_content_file,
|
91
|
+
:root => @alternative_root }
|
92
|
+
|
93
|
+
subject { resource }
|
94
|
+
|
95
|
+
its(:name) { should == 'content.html' }
|
96
|
+
its(:root) { should == @alternative_root }
|
97
|
+
its(:source) { should == @full_content_file }
|
98
|
+
its(:source_file) { should == @full_content_file }
|
99
|
+
its(:source_directory) { should == @tmp_dir }
|
100
|
+
its(:destination) { should be_nil }
|
101
|
+
its(:destination_file) { should == "#{@alternative_root}/content.html" }
|
102
|
+
its(:destination_directory) { should == @alternative_root }
|
103
|
+
|
104
|
+
it_should_behave_like "all resources"
|
105
|
+
end
|
106
|
+
|
107
|
+
context "when it's created with a block" do
|
108
|
+
let(:resource) do
|
109
|
+
Resource.new @content_file do |resource|
|
110
|
+
resource.root = @alternative_root
|
111
|
+
resource.destination = 'destination'
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
subject { resource }
|
116
|
+
|
117
|
+
its(:name) { should == 'content.html' }
|
118
|
+
its(:root) { should == @alternative_root }
|
119
|
+
its(:source) { should == @content_file }
|
120
|
+
its(:source_file) { should == @full_alt_content_file }
|
121
|
+
its(:source_directory) { should == @alternative_root }
|
122
|
+
its(:destination) { should == 'destination' }
|
123
|
+
|
124
|
+
its(:destination_file) do
|
125
|
+
should == "#{@alternative_root}/destination/content.html"
|
126
|
+
end
|
127
|
+
|
128
|
+
its(:destination_directory) do
|
129
|
+
should == "#{@alternative_root}/destination"
|
130
|
+
end
|
131
|
+
|
132
|
+
it_should_behave_like "all resources"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
require 'pathname'
|
2
2
|
require 'mint'
|
3
3
|
|
4
|
+
def delete_class(klass)
|
5
|
+
klass = nil
|
6
|
+
GC.start
|
7
|
+
sleep 1
|
8
|
+
end
|
9
|
+
|
4
10
|
RSpec::Matchers.define :be_in_directory do |name|
|
5
11
|
match {|resource| resource.source_directory =~ /#{name}/ }
|
6
12
|
end
|
@@ -41,10 +47,22 @@ RSpec.configure do |config|
|
|
41
47
|
@dynamic_style_file = 'dynamic.sass'
|
42
48
|
|
43
49
|
@content = <<-HERE
|
50
|
+
---
|
51
|
+
metadata: true
|
52
|
+
|
44
53
|
Header
|
45
54
|
------
|
46
55
|
|
47
56
|
This is just a test.
|
57
|
+
|
58
|
+
Paragraph number two.
|
59
|
+
|
60
|
+
Header 2
|
61
|
+
--------
|
62
|
+
|
63
|
+
Third sentence.
|
64
|
+
|
65
|
+
Fourth sentence.
|
48
66
|
HERE
|
49
67
|
|
50
68
|
@layout = <<-HERE
|
data/spec/style_spec.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Mint
|
4
|
+
describe Style do
|
5
|
+
before { @tmp_dir = Dir.getwd }
|
6
|
+
|
7
|
+
context "when it's created from a static file" do
|
8
|
+
let(:style) { Style.new @static_style_file }
|
9
|
+
subject { style }
|
10
|
+
|
11
|
+
its(:destination) { should be_nil }
|
12
|
+
its(:destination_file) { should == "#{@tmp_dir}/static.css" }
|
13
|
+
|
14
|
+
it { should_not be_rendered }
|
15
|
+
it "'renders' itself verbatim" do
|
16
|
+
style.render.should == File.read(@static_style_file)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "when it's created from a dynamic file" do
|
21
|
+
let(:style) { Style.new @dynamic_style_file }
|
22
|
+
subject { style }
|
23
|
+
|
24
|
+
its(:destination) { should be_nil }
|
25
|
+
its(:destination_file) { should == "#{@tmp_dir}/dynamic.css" }
|
26
|
+
|
27
|
+
it { should be_rendered }
|
28
|
+
it "renders itself from a templating language to Html" do
|
29
|
+
style.render.gsub("\n", " ").should ==
|
30
|
+
File.read(@static_style_file).gsub("\n", " ")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "when it's created with a specified destination" do
|
35
|
+
let(:style) { Style.new @static_style_file,
|
36
|
+
:destination => 'destination' }
|
37
|
+
subject { style }
|
38
|
+
|
39
|
+
its(:destination) { should == 'destination' }
|
40
|
+
its(:destination_file) do
|
41
|
+
should == "#{@tmp_dir}/destination/static.css"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# TODO: Create local-scope templates directory that I can test this with,
|
46
|
+
# and use it to beef up other specs (like document_spec and mint_spec)
|
47
|
+
# context "when it's created from a static template file" do
|
48
|
+
# let(:style) { Style.new(Mint.lookup_template(:static_test, :style)) }
|
49
|
+
# it "#destination" do
|
50
|
+
# style.destination.should be_nil
|
51
|
+
# end
|
52
|
+
|
53
|
+
# it "#destination_file" do
|
54
|
+
# style.destination_file.should ==
|
55
|
+
# Mint.path_for_scope(:local) + '/templates/static_test/style.css'
|
56
|
+
# end
|
57
|
+
# end
|
58
|
+
|
59
|
+
context "when it's created from a dynamic template file" do
|
60
|
+
let(:style) { Style.new(Mint.lookup_template(:default, :style)) }
|
61
|
+
subject { style }
|
62
|
+
|
63
|
+
its(:destination) { should == 'css' }
|
64
|
+
its(:destination_file) do
|
65
|
+
should == "#{Mint.root}/templates/default/css/style.css"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|