ebook_generator 0.0.3 → 0.0.4
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.
- checksums.yaml +4 -4
- data/Changelog.md +5 -0
- data/README.md +2 -9
- data/ebook_generator.gemspec +3 -1
- data/lib/ebook_generator/version.rb +1 -1
- data/lib/ebook_generator/zip_file_processor.rb +48 -0
- data/lib/ebook_generator.rb +126 -146
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4430a1086ca77ff50348df43afafb4890e520053
|
4
|
+
data.tar.gz: 87a99f1c44cb867ad21eb46301343b92e5d12188
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c6bbf83277abbf49711c4db131f1a4391e4dc4e11a6f185bceac23e439f367bfb726dde392ea109d884fb55608865fe35aa90dc02cb8fd50feda8cc75666a9ef
|
7
|
+
data.tar.gz: 0c279a1b23d92c7b83c8c381a69da4ace0b6556f517f5cee84859a437b257a8ece13cb1507a8b76aea164eacc1d81cf167112455ff3926a95be6c3aee9c3e0a6
|
data/Changelog.md
CHANGED
data/README.md
CHANGED
@@ -27,22 +27,15 @@ Or install it yourself as:
|
|
27
27
|
## Usage
|
28
28
|
|
29
29
|
Generate the tables needed to process the ebooks:
|
30
|
-
|
31
30
|
`rails generate ebook_generator`
|
32
31
|
|
33
|
-
Migrate the
|
34
|
-
|
32
|
+
Migrate the database:
|
35
33
|
`rake db:migrate`
|
36
34
|
|
37
|
-
Require the ebook_generator module in your class:
|
38
|
-
|
39
|
-
`include 'EbookGenerator'`
|
40
|
-
|
41
35
|
Pass the id for the ebook you want to generate:
|
42
|
-
|
43
36
|
`EbookGenerator.generate_ebook(ebook.id)`
|
44
37
|
|
45
|
-
This will then generate an
|
38
|
+
This will then generate an ePub based on the values in the db and output to the /tmp folder.
|
46
39
|
|
47
40
|
## Feature roadmap
|
48
41
|
|
data/ebook_generator.gemspec
CHANGED
@@ -10,8 +10,10 @@ Gem::Specification.new do |s|
|
|
10
10
|
s.description = "A simple eBook (ePub) generator gem"
|
11
11
|
s.authors = ["Kris Quigley"]
|
12
12
|
s.email = 'kris@krisquigley.co.uk'
|
13
|
-
s.homepage =
|
13
|
+
s.homepage =
|
14
|
+
'https://github.com/krisquigley/ebook_generator'
|
14
15
|
|
16
|
+
s.add_runtime_dependency 'rails', '>= 4.0.0'
|
15
17
|
s.add_runtime_dependency 'friendly_id', '>= 5.0.3'
|
16
18
|
s.add_runtime_dependency 'pg', '>= 0.17.1'
|
17
19
|
s.add_runtime_dependency 'redcarpet', '>= 3.0.0'
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'zip'
|
2
|
+
|
3
|
+
# This is a simple example which uses rubyzip to
|
4
|
+
# recursively generate a zip file from the contents of
|
5
|
+
# a specified directory. The directory itself is not
|
6
|
+
# included in the archive, rather just its contents.
|
7
|
+
#
|
8
|
+
# Usage:
|
9
|
+
# directoryToZip = "/tmp/input"
|
10
|
+
# outputFile = "/tmp/out.zip"
|
11
|
+
# zf = ZipFileGenerator.new(directoryToZip, outputFile)
|
12
|
+
# zf.write()
|
13
|
+
class ZipFileProcessor
|
14
|
+
|
15
|
+
# Initialize with the directory to zip and the location of the output archive.
|
16
|
+
def initialize(inputDir, outputFile)
|
17
|
+
@inputDir = inputDir
|
18
|
+
@outputFile = outputFile
|
19
|
+
end
|
20
|
+
|
21
|
+
# Zip the input directory.
|
22
|
+
def write()
|
23
|
+
entries = Dir.entries(@inputDir); entries.delete("."); entries.delete("..")
|
24
|
+
io = Zip::File.open(@outputFile, Zip::File::CREATE);
|
25
|
+
|
26
|
+
writeEntries(entries, "", io)
|
27
|
+
io.close();
|
28
|
+
end
|
29
|
+
|
30
|
+
# A helper method to make the recursion work.
|
31
|
+
private
|
32
|
+
def writeEntries(entries, path, io)
|
33
|
+
|
34
|
+
entries.each { |e|
|
35
|
+
zipFilePath = path == "" ? e : File.join(path, e)
|
36
|
+
diskFilePath = File.join(@inputDir, zipFilePath)
|
37
|
+
puts "Deflating " + diskFilePath
|
38
|
+
if File.directory?(diskFilePath)
|
39
|
+
io.mkdir(zipFilePath)
|
40
|
+
subdir =Dir.entries(diskFilePath); subdir.delete("."); subdir.delete("..")
|
41
|
+
writeEntries(subdir, zipFilePath, io)
|
42
|
+
else
|
43
|
+
io.get_output_stream(zipFilePath) { |f| f.puts(File.open(diskFilePath, "rb").read())}
|
44
|
+
end
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
data/lib/ebook_generator.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
require "ebook_generator/version"
|
2
|
+
require "ebook_generator/zip_file_processor"
|
3
|
+
require "builder"
|
2
4
|
|
3
5
|
module EbookGenerator
|
4
6
|
|
@@ -7,170 +9,136 @@ module EbookGenerator
|
|
7
9
|
end
|
8
10
|
|
9
11
|
# Move writing of files into its own class that accepts an array
|
10
|
-
def self.
|
11
|
-
|
12
|
+
def self.make_dirs(paths)
|
13
|
+
paths.each do |path|
|
14
|
+
Dir.mkdir(path, 0777) unless File.exists?(path)
|
15
|
+
end
|
12
16
|
end
|
13
17
|
|
14
|
-
def self.
|
15
|
-
metainf_path = path + "/META-INF"
|
18
|
+
def self.generate_container(path)
|
16
19
|
|
17
|
-
|
20
|
+
file = File.new(path + "/container.xml", "wb")
|
21
|
+
xm = Builder::XmlMarkup.new(:target => file, :indent => 2)
|
22
|
+
xm.instruct!
|
23
|
+
xm.container("version" => "1.0", "xmlns" => "urn:oasis:names:tc:opendocument:xmlns:container") {
|
24
|
+
xm.rootfiles {
|
25
|
+
xm.rootfile("full-path" => "OEBPS/content.opf", "media-type" => "application/oebps-package+xml")
|
26
|
+
}
|
27
|
+
}
|
18
28
|
|
19
|
-
|
20
|
-
<container version=\"1.0\" xmlns=\"urn:oasis:names:tc:opendocument:xmlns:container\">
|
21
|
-
<rootfiles>
|
22
|
-
<rootfile full-path=\"OEBPS/content.opf\" media-type=\"application/oebps-package+xml\"/>
|
23
|
-
</rootfiles>
|
24
|
-
</container>"
|
29
|
+
file.close
|
25
30
|
|
26
|
-
|
27
|
-
f.write(metainf)
|
28
|
-
end
|
31
|
+
end
|
29
32
|
|
30
|
-
|
31
|
-
mimetype_path = path + "/mimetype"
|
33
|
+
def self.generate_mimetype(path)
|
32
34
|
|
33
|
-
File.open(
|
34
|
-
f.write(
|
35
|
+
File.open(path, "w+") do |f|
|
36
|
+
f.write("application/epub+zip")
|
35
37
|
end
|
36
38
|
|
37
|
-
FileUtils.chmod 0755, mimetype_path
|
38
|
-
|
39
|
-
content_path = path + "/OEBPS"
|
40
|
-
|
41
|
-
Dir.mkdir(content_path) unless File.exists?(content_path)
|
42
|
-
|
43
|
-
Dir.mkdir(content_path + "/Text") unless File.exists?(content_path + "/Text")
|
44
|
-
|
45
|
-
Dir.mkdir(content_path + "/Styles") unless File.exists?(content_path + "/Styles")
|
46
|
-
|
47
|
-
root = Rails.root.to_s
|
48
|
-
|
49
|
-
FileUtils.cp "#{root}/app/ebook/style.css", content_path+"/Styles"
|
50
|
-
|
51
|
-
return content_path
|
52
|
-
|
53
39
|
end
|
54
40
|
|
55
|
-
def self.
|
56
|
-
|
57
|
-
<meta content=\"#{attrs.title}\" name=\"Title\" />
|
58
|
-
<meta content=\"#{attrs.title}\" name=\"Author\" />
|
59
|
-
<link href=\"../Styles/style.css\" rel=\"stylesheet\" type=\"text/css\" />"
|
60
|
-
return xml
|
41
|
+
def self.copy_style(path)
|
42
|
+
FileUtils.cp Rails.root.to_s + "/app/ebook/style.css", path
|
61
43
|
end
|
62
44
|
|
63
45
|
def self.generate_sections(path, attrs)
|
64
46
|
markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML, autolink: true, tables: true)
|
65
|
-
headers = generate_headers(attrs)
|
66
47
|
|
67
48
|
attrs.sections.each do |section|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
"
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
File.open(path + "/Section#{section.position}.html", "w+") do |f|
|
85
|
-
f.write(xml)
|
86
|
-
end
|
49
|
+
file = File.new(path + "/Section#{section.position}.html", "wb")
|
50
|
+
|
51
|
+
xm = Builder::XmlMarkup.new(:target => file, :indent => 2)
|
52
|
+
xm.instruct!
|
53
|
+
xm.declare! :DOCTYPE, :html, :PUBLIC, "-//W3C//DTD XHTML 1.1//EN", "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"
|
54
|
+
xm.html("xmlns" => "http://www.w3.org/1999/xhtml"){
|
55
|
+
xm.head {
|
56
|
+
xm.title { attrs.title }
|
57
|
+
xm.meta("content" => attrs.title, "name" => "Title")
|
58
|
+
xm.meta("content" => attrs.creator, "name" => "Author")
|
59
|
+
xm.link("href" => "../Styles/style.css", "rel" => "stylesheet", "type" => "text/css")
|
60
|
+
}
|
61
|
+
xm.body { |b| b << "<div id=\"#{section.title}\">" + markdown.render(section.content) + "</div>" }
|
62
|
+
}
|
63
|
+
|
64
|
+
file.close
|
87
65
|
end
|
88
66
|
end
|
89
67
|
|
90
68
|
def self.generate_content_opf(path, attrs)
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
xml += "</spine>
|
125
|
-
<guide />
|
126
|
-
</package>"
|
127
|
-
|
128
|
-
content_path = path + "/content.opf"
|
129
|
-
|
130
|
-
File.open(content_path, "w+") do |f|
|
131
|
-
f.write(xml)
|
132
|
-
end
|
133
|
-
|
134
|
-
FileUtils.chmod 0755, content_path
|
69
|
+
file = File.new(path + "/content.opf", "wb")
|
70
|
+
|
71
|
+
xm = Builder::XmlMarkup.new(:target => file, :indent => 2)
|
72
|
+
xm.instruct!
|
73
|
+
xm.package("xmlns" => "http://www.idpf.org/2007/opf", "unique-identifier" => "BookId", "version" => "2.0") {
|
74
|
+
xm.metadata("xmlns:dc" => "http://purl.org/dc/elements/1.1/", "xmlns:opf" => "http://www.idpf.org/2007/opf") {
|
75
|
+
xm.tag!("dc:identifier", attrs.id, "id" => "BookId", "opf:scheme" => "UUID")
|
76
|
+
xm.tag!("dc:title", attrs.title)
|
77
|
+
xm.tag!("dc:creator", attrs.creator, "opf:role" => "aut")
|
78
|
+
xm.tag!("dc:language", attrs.language)
|
79
|
+
xm.tag!("dc:date", attrs.updated_at, "opf:event" => "modification")
|
80
|
+
xm.tag!("dc:description", attrs.description)
|
81
|
+
xm.tag!("dc:publisher", attrs.publisher)
|
82
|
+
xm.tag!("dc:rights", attrs.rights)
|
83
|
+
xm.tag!("dc:subject", attrs.subject)
|
84
|
+
xm.tag!("dc:contributor", attrs.contributor, "opf:role" => "cov")
|
85
|
+
}
|
86
|
+
xm.manifest {
|
87
|
+
xm.item("href" => "toc.ncx", "id" => "ncx", "media-type" => "application/x-dtbncx+xml")
|
88
|
+
xm.item("href" => "Styles/style.css", "media-type" => "text/css")
|
89
|
+
attrs.sections.each do |section|
|
90
|
+
xm.item("href" => "Text/Section#{section.position}.html", "id" => "Section#{section.position}.html", "media-type" => "application/xhtml+xml")
|
91
|
+
end
|
92
|
+
}
|
93
|
+
xm.spine("toc" => "ncx") {
|
94
|
+
attrs.sections.each do |section|
|
95
|
+
xm.itemref("idref" => "Section#{section.position}.html")
|
96
|
+
end
|
97
|
+
}
|
98
|
+
xm.guide()
|
99
|
+
}
|
100
|
+
|
101
|
+
file.close
|
135
102
|
|
136
103
|
end
|
137
104
|
|
138
105
|
def self.generate_toc_ncx(path, attrs)
|
139
|
-
xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>
|
140
|
-
<!DOCTYPE ncx PUBLIC \"-//NISO//DTD ncx 2005-1//EN\"
|
141
|
-
\"http://www.daisy.org/z3986/2005/ncx-2005-1.dtd\">
|
142
|
-
<ncx xmlns=\"http://www.daisy.org/z3986/2005/ncx/\" version=\"2005-1\">
|
143
|
-
<head>
|
144
|
-
<meta content=\"urn:uuid:${attrs.id}\" name=\"dtb:uid\"/>
|
145
|
-
<meta content=\"2\" name=\"dtb:depth\"/>
|
146
|
-
<meta content=\"0\" name=\"dtb:totalPageCount\"/>
|
147
|
-
<meta content=\"0\" name=\"dtb:maxPageNumber\"/>
|
148
|
-
</head>
|
149
|
-
<docTitle>
|
150
|
-
<text>#{attrs.title}</text>
|
151
|
-
</docTitle>
|
152
|
-
<navMap>"
|
153
|
-
|
154
106
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
107
|
+
file = File.new(path + "/toc.ncx", "wb")
|
108
|
+
|
109
|
+
xm = Builder::XmlMarkup.new(:target => file, :indent => 2)
|
110
|
+
xm.instruct!
|
111
|
+
xm.declare! :DOCTYPE, :ncx, :PUBLIC, "-//NISO//DTD ncx 2005-1//EN", "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd"
|
112
|
+
xm.ncx("xmlns" => "http://www.daisy.org/z3986/2005/ncx/", "version" => "2005-1") {
|
113
|
+
xm.head {
|
114
|
+
xm.meta("content" => "urn:uuid:${attrs.id}", "name" => "dtb:uid")
|
115
|
+
xm.meta("content" => "2", "name" => "dtb:depth")
|
116
|
+
xm.meta("content" => "0", "name" => "dtb:totalPageCount")
|
117
|
+
xm.meta("content" => "0", "name" => "dtb:maxPageNumber")
|
118
|
+
}
|
119
|
+
xm.docTitle {
|
120
|
+
xm.text(attrs.title)
|
121
|
+
}
|
122
|
+
xm.navMap {
|
123
|
+
attrs.sections.each do |section|
|
124
|
+
xm.navPoint("id" => "navPoint-#{section.position}", "playOrder" => "#{section.position}") {
|
125
|
+
xm.navLabel {
|
126
|
+
xm.text(section.title)
|
127
|
+
}
|
128
|
+
xm.content("src" => "Text/Section#{section.position}.html")
|
129
|
+
}
|
130
|
+
end
|
131
|
+
}
|
132
|
+
}
|
133
|
+
|
134
|
+
file.close
|
163
135
|
|
164
|
-
|
165
|
-
</ncx>"
|
166
|
-
|
167
|
-
toc_path = path + "/toc.ncx"
|
136
|
+
end
|
168
137
|
|
169
|
-
|
170
|
-
|
138
|
+
def self.change_perms(files)
|
139
|
+
files.each do |file|
|
140
|
+
FileUtils.chmod 0755, file
|
171
141
|
end
|
172
|
-
|
173
|
-
FileUtils.chmod 0755, toc_path
|
174
142
|
end
|
175
143
|
|
176
144
|
def self.remove_tmp_dir(directory)
|
@@ -179,29 +147,41 @@ module EbookGenerator
|
|
179
147
|
|
180
148
|
def self.generate_ebook(ebook_id)
|
181
149
|
|
182
|
-
#
|
183
|
-
path = Rails.root.
|
184
|
-
|
150
|
+
# Set the root path of the ebook
|
151
|
+
path = Rails.root.to_s + "/tmp/#{ebook_id}"
|
152
|
+
|
153
|
+
# Make all required dirs
|
154
|
+
dirs = [path, path + "/META-INF", path + "/OEBPS", path + "/OEBPS/Text", path + "/OEBPS/Styles"]
|
155
|
+
make_dirs(dirs)
|
185
156
|
|
186
|
-
#
|
187
|
-
|
157
|
+
# Create container.xml
|
158
|
+
generate_container(path + "/META-INF")
|
159
|
+
|
160
|
+
# Create mimetype
|
161
|
+
generate_mimetype(path + "/mimetype")
|
162
|
+
|
163
|
+
# Move default stylesheet into styles folder
|
164
|
+
copy_style(path + "/OEBPS/Styles")
|
188
165
|
|
189
166
|
# loop through each section loading the reference header and saving as it's own section
|
190
167
|
attrs = Ebook.find(ebook_id)
|
191
|
-
generate_sections(
|
168
|
+
generate_sections(path + "/OEBPS/Text", attrs)
|
192
169
|
|
193
170
|
# generate toc based on the number of sections generated
|
194
|
-
generate_content_opf(
|
195
|
-
generate_toc_ncx(
|
171
|
+
generate_content_opf(path + "/OEBPS", attrs)
|
172
|
+
generate_toc_ncx(path + "/OEBPS", attrs)
|
173
|
+
|
174
|
+
# change permissions for files that need to be executable
|
175
|
+
files = [path + "/OEBPS/toc.ncx", path + "/OEBPS/content.opf", path + "/mimetype"]
|
176
|
+
change_perms(files)
|
196
177
|
|
197
178
|
# zip all files
|
198
179
|
zipfile_name = Rails.root.to_s + "/tmp/" + attrs.slug + ".epub"
|
199
|
-
|
200
|
-
zf = ZipFileProcessor.new(path.to_s, zipfile_name)
|
180
|
+
zf = ZipFileProcessor.new(path, zipfile_name)
|
201
181
|
zf.write
|
202
182
|
|
203
183
|
# Clean up the tmp dir
|
204
|
-
remove_tmp_dir(path
|
184
|
+
remove_tmp_dir(path + "/")
|
205
185
|
|
206
186
|
end
|
207
187
|
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ebook_generator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kris Quigley
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-04-
|
11
|
+
date: 2014-04-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 4.0.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 4.0.0
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: friendly_id
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -83,6 +97,7 @@ files:
|
|
83
97
|
- lib/ebook_generator/initializer.rb
|
84
98
|
- lib/ebook_generator/migration.rb
|
85
99
|
- lib/ebook_generator/version.rb
|
100
|
+
- lib/ebook_generator/zip_file_processor.rb
|
86
101
|
- lib/generators/ebook_generator_generator.rb
|
87
102
|
homepage: https://github.com/krisquigley/ebook_generator
|
88
103
|
licenses:
|