eeepub3 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +4 -0
- data/LICENSE +0 -0
- data/README.md +4 -0
- data/Rakefile +22 -0
- data/eeepub3.gemspec +24 -0
- data/lib/eeepub.rb +15 -0
- data/lib/eeepub/container_item.rb +116 -0
- data/lib/eeepub/easy.rb +100 -0
- data/lib/eeepub/maker.rb +156 -0
- data/lib/eeepub/ncx.rb +67 -0
- data/lib/eeepub/ocf.rb +131 -0
- data/lib/eeepub/opf.rb +154 -0
- data/spec/eeepub/container_item_spec.rb +20 -0
- data/spec/eeepub/easy_spec.rb +66 -0
- data/spec/eeepub/maker_spec.rb +131 -0
- data/spec/eeepub/ncx_spec.rb +80 -0
- data/spec/eeepub/ocf_spec.rb +50 -0
- data/spec/eeepub/opf_spec.rb +267 -0
- data/spec/eeepub_spec.rb +4 -0
- data/spec/spec_helper.rb +14 -0
- metadata +147 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e37fdd0df050b43117eb43439f4f2dba67cc9f96
|
4
|
+
data.tar.gz: 7e5a05e05a17afbdcd72c3c3a6ed2269c16c51d9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e1587a96ace135f869eb4494d2c087e5dac8aedf7977140fa1bfe54469e12a3eb3bede7e099823ec7875d60a52d48c6f7af6527d1b859d421c9cb494135f0c97
|
7
|
+
data.tar.gz: 43d500fe967d569fbba17c71712eb6dfed33119cb6ca6dffd03c61808b939574659e7ab59d9e6ff07bd10eee880f318e5e179ee44087c8d8f39f12bb1438c5b9
|
data/Gemfile
ADDED
data/LICENSE
ADDED
File without changes
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rspec/core/rake_task'
|
6
|
+
RSpec::Core::RakeTask.new do |t|
|
7
|
+
t.rspec_opts = ['--color']
|
8
|
+
end
|
9
|
+
rescue LoadError => e
|
10
|
+
puts "RSpec not installed"
|
11
|
+
end
|
12
|
+
|
13
|
+
task :default => :spec
|
14
|
+
|
15
|
+
begin
|
16
|
+
require 'yard'
|
17
|
+
YARD::Rake::YardocTask.new
|
18
|
+
rescue LoadError
|
19
|
+
task :yardoc do
|
20
|
+
abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
|
21
|
+
end
|
22
|
+
end
|
data/eeepub3.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "eeepub3"
|
5
|
+
s.version = "0.0.1"
|
6
|
+
s.platform = Gem::Platform::RUBY
|
7
|
+
s.authors = ["bubaz"]
|
8
|
+
s.email = ["s.bubovich@gmail.com"]
|
9
|
+
s.homepage = "http://github.com/bubaz/eeepub3"
|
10
|
+
s.summary = %q{ePub generator}
|
11
|
+
s.description = %q{EeePub is a Ruby ePub generator.}
|
12
|
+
|
13
|
+
s.files = `git ls-files`.split("\n")
|
14
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
15
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
|
18
|
+
s.add_dependency "builder"
|
19
|
+
s.add_dependency "rubyzip"
|
20
|
+
s.add_development_dependency "rspec"
|
21
|
+
s.add_development_dependency "nokogiri"
|
22
|
+
s.add_development_dependency "rr"
|
23
|
+
s.add_development_dependency "simplecov"
|
24
|
+
end
|
data/lib/eeepub.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'eeepub/container_item'
|
2
|
+
require 'eeepub/opf'
|
3
|
+
require 'eeepub/ocf'
|
4
|
+
require 'eeepub/ncx'
|
5
|
+
require 'eeepub/maker'
|
6
|
+
require 'eeepub/easy'
|
7
|
+
|
8
|
+
module EeePub
|
9
|
+
# Make ePub
|
10
|
+
#
|
11
|
+
# @param [Proc] block the block for initialize EeePub::Maker
|
12
|
+
def self.make(&block)
|
13
|
+
EeePub::Maker.new(&block)
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'builder'
|
2
|
+
|
3
|
+
module EeePub
|
4
|
+
# Abstract base class for container item of ePub. Provides some helper methods.
|
5
|
+
#
|
6
|
+
# @abstract
|
7
|
+
class ContainerItem
|
8
|
+
class << self
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
# Set default value to attribute
|
13
|
+
#
|
14
|
+
# @param [Symbol] name the attribute name
|
15
|
+
# @param [Object] default the default value
|
16
|
+
def default_value(name, default)
|
17
|
+
instance_variable_name = "@#{name}"
|
18
|
+
define_method(name) do
|
19
|
+
self.instance_variable_get(instance_variable_name) ||
|
20
|
+
self.instance_variable_set(instance_variable_name, default)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Define alias of attribute accessor
|
25
|
+
#
|
26
|
+
# @param [Symbol] name the attribute name as alias
|
27
|
+
# @param [Symbol] name the attribute name as source
|
28
|
+
def attr_alias(name, src)
|
29
|
+
alias_method name, src
|
30
|
+
alias_method :"#{name}=", :"#{src}="
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# @param [Hash<Symbol, Object>] values the hash of symbols and objects for attributes
|
35
|
+
def initialize(values)
|
36
|
+
set_values(values)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Set values for attributes
|
40
|
+
#
|
41
|
+
# @param [Hash<Symbol, Object>] values the hash of symbols and objects for attributes
|
42
|
+
def set_values(values)
|
43
|
+
values.each do |k, v|
|
44
|
+
self.send(:"#{k}=", v)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Convert to xml of container item
|
49
|
+
#
|
50
|
+
# @return [String] the xml of container item
|
51
|
+
def to_xml
|
52
|
+
out = ""
|
53
|
+
builder = Builder::XmlMarkup.new(:target => out, :indent => 2)
|
54
|
+
builder.instruct!
|
55
|
+
build_xml(builder)
|
56
|
+
out
|
57
|
+
end
|
58
|
+
|
59
|
+
# Save as container item
|
60
|
+
#
|
61
|
+
# @param [String] filepath the file path for container item
|
62
|
+
def save(filepath)
|
63
|
+
File.open(filepath, 'w') do |file|
|
64
|
+
file << self.to_xml
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
# Guess media type from file name
|
71
|
+
#
|
72
|
+
# @param [String] filename the file name
|
73
|
+
# @return [String] the media-type
|
74
|
+
def guess_media_type(filename)
|
75
|
+
ext = File.extname(filename)
|
76
|
+
mimes = {
|
77
|
+
'.png' => 'image/png',
|
78
|
+
'.js' => 'application/x-javascript',
|
79
|
+
'.html' => 'application/xhtml+xml',
|
80
|
+
'.jpeg' => 'image/jpeg',
|
81
|
+
'.jpg' => 'image/jpeg',
|
82
|
+
'.gif' => 'image/gif',
|
83
|
+
'.svg' => 'image/svg+xml',
|
84
|
+
'.ncx' => 'application/x-dtbncx+xml',
|
85
|
+
'.css' => 'text/css',
|
86
|
+
'.less' => 'text/plain', #explicit not text/css because of validation failure
|
87
|
+
'.mp4' => 'video/mp4',
|
88
|
+
'.mp3' => 'audio/mpeg',
|
89
|
+
'.webm' => 'video/webm',
|
90
|
+
'.woff' => 'application/font-woff',
|
91
|
+
'.otf' => 'application/vnd.ms-opentype',
|
92
|
+
'.ttf' => 'application/vnd.ms-opentype',
|
93
|
+
'.eot' => 'application/vnd.ms-opentype',
|
94
|
+
'.db' =>'application/octet-stream', #explicit,
|
95
|
+
'.nes'=>'text/plain; charset=x-user-defined', #explicit
|
96
|
+
'.txt'=>'text/plain'
|
97
|
+
}
|
98
|
+
|
99
|
+
mimes[ext] || 'application/octet-stream'
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
# Convert options for xml attributes
|
104
|
+
#
|
105
|
+
# @param [Hash<Symbol, Object>] hash the hash of symbols and objects for xml attributes
|
106
|
+
# @return [Hash<String, Object>] the options for xml attributes
|
107
|
+
def convert_to_xml_attributes(hash)
|
108
|
+
result = {}
|
109
|
+
hash.each do |k, v|
|
110
|
+
key = k.to_s.gsub('_', '-').to_sym
|
111
|
+
result[key] = v
|
112
|
+
end
|
113
|
+
result
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
data/lib/eeepub/easy.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'tmpdir'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module EeePub
|
5
|
+
# The class to make ePub more easily
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# epub = EeePub::Easy.new do
|
9
|
+
# title 'sample'
|
10
|
+
# creator 'jugyo'
|
11
|
+
# identifier 'http://example.com/book/foo', :scheme => 'URL'
|
12
|
+
# uid 'http://example.com/book/foo'
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# epub.sections << ['1. foo', <<HTML]
|
16
|
+
# <?xml version="1.0" encoding="UTF-8"?>
|
17
|
+
# <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
18
|
+
# <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja">
|
19
|
+
# <head>
|
20
|
+
# <title>foo</title>
|
21
|
+
# </head>
|
22
|
+
# <body>
|
23
|
+
# <p>
|
24
|
+
# foo foo foo foo foo foo
|
25
|
+
# </p>
|
26
|
+
# </body>
|
27
|
+
# </html>
|
28
|
+
# HTML
|
29
|
+
#
|
30
|
+
# epub.assets << 'image.png'
|
31
|
+
#
|
32
|
+
# epub.save('sample.epub')
|
33
|
+
class Easy < EeePub::Maker
|
34
|
+
attr_reader :sections, :assets
|
35
|
+
|
36
|
+
# @param [Proc] block the block for initialize
|
37
|
+
def initialize(&block)
|
38
|
+
@sections = []
|
39
|
+
@assets = []
|
40
|
+
super
|
41
|
+
end
|
42
|
+
|
43
|
+
# Save as ePub file
|
44
|
+
#
|
45
|
+
# @param [String] filename the ePub file name to save
|
46
|
+
def save(filename)
|
47
|
+
Dir.mktmpdir do |dir|
|
48
|
+
prepare(dir)
|
49
|
+
|
50
|
+
NCX.new(
|
51
|
+
:uid => @uid,
|
52
|
+
:title => @titles[0],
|
53
|
+
:nav => @nav
|
54
|
+
).save(File.join(dir, @ncx_file))
|
55
|
+
|
56
|
+
OPF.new(
|
57
|
+
:title => @titles,
|
58
|
+
:identifier => @identifiers,
|
59
|
+
:creator => @creators,
|
60
|
+
:publisher => @publishers,
|
61
|
+
:date => @dates,
|
62
|
+
:language => @languages,
|
63
|
+
:subject => @subjects,
|
64
|
+
:description => @descriptions,
|
65
|
+
:rights => @rightss,
|
66
|
+
:relation => @relations,
|
67
|
+
:manifest => @files.map{|i| File.basename(i)},
|
68
|
+
:ncx => @ncx_file
|
69
|
+
).save(File.join(dir, @opf_file))
|
70
|
+
|
71
|
+
OCF.new(
|
72
|
+
:dir => dir,
|
73
|
+
:container => @opf_file
|
74
|
+
).save(filename)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def prepare(dir)
|
81
|
+
filenames = []
|
82
|
+
sections.each_with_index do |section, index|
|
83
|
+
filename = File.join(dir, "section_#{index}.html")
|
84
|
+
File.open(filename, 'w') { |file| file.write section[1] }
|
85
|
+
filenames << filename
|
86
|
+
end
|
87
|
+
|
88
|
+
assets.each do |file|
|
89
|
+
FileUtils.cp(file, dir)
|
90
|
+
end
|
91
|
+
|
92
|
+
files(filenames + assets)
|
93
|
+
nav(
|
94
|
+
[sections, filenames].transpose.map do |section, filename|
|
95
|
+
{:label => section[0], :content => File.basename(filename)}
|
96
|
+
end
|
97
|
+
)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
data/lib/eeepub/maker.rb
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
require 'tmpdir'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module EeePub
|
5
|
+
# The class to make ePub easily
|
6
|
+
#
|
7
|
+
# Note on unique identifiers:
|
8
|
+
#
|
9
|
+
# At least one 'identifier' must be the unique identifer represented by the name
|
10
|
+
# given to 'uid' and set via the hash option :id => {name}. The default name for
|
11
|
+
# uid is 'BookId' and doesn't need to be specified explicitly. If no identifier is
|
12
|
+
# marked as the unique identifier, the first one give will be chosen.
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# epub = EeePub.make do
|
16
|
+
# title 'sample'
|
17
|
+
# creator 'jugyo'
|
18
|
+
# publisher 'jugyo.org'
|
19
|
+
# date '2010-05-06'
|
20
|
+
# uid 'BookId'
|
21
|
+
# identifier 'http://example.com/book/foo', :scheme => 'URL', :id => 'BookId'
|
22
|
+
#
|
23
|
+
# files ['/path/to/foo.html', '/path/to/bar.html']
|
24
|
+
# nav [
|
25
|
+
# {:label => '1. foo', :content => 'foo.html', :nav => [
|
26
|
+
# {:label => '1.1 foo-1', :content => 'foo.html#foo-1'}
|
27
|
+
# ]},
|
28
|
+
# {:label => '1. bar', :content => 'bar.html'}
|
29
|
+
# ]
|
30
|
+
# end
|
31
|
+
# epub.save('sample.epub')
|
32
|
+
class Maker
|
33
|
+
[
|
34
|
+
:title,
|
35
|
+
:creator,
|
36
|
+
:publisher,
|
37
|
+
:date,
|
38
|
+
:language,
|
39
|
+
:subject,
|
40
|
+
:description,
|
41
|
+
:rights,
|
42
|
+
:relation
|
43
|
+
].each do |name|
|
44
|
+
class_eval <<-DELIM
|
45
|
+
def #{name}(value)
|
46
|
+
@#{name}s ||= []
|
47
|
+
@#{name}s << value
|
48
|
+
end
|
49
|
+
DELIM
|
50
|
+
end
|
51
|
+
|
52
|
+
[
|
53
|
+
:uid,
|
54
|
+
:files,
|
55
|
+
:nav,
|
56
|
+
:cover,
|
57
|
+
:ncx_file,
|
58
|
+
:opf_file,
|
59
|
+
:guide
|
60
|
+
].each do |name|
|
61
|
+
define_method(name) do |arg|
|
62
|
+
instance_variable_set("@#{name}", arg)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def identifier(id, options)
|
67
|
+
@identifiers ||= []
|
68
|
+
@identifiers << {:value => id, :scheme => options[:scheme], :id => options[:id]}
|
69
|
+
end
|
70
|
+
|
71
|
+
# @param [Proc] block the block for initialize
|
72
|
+
def initialize(&block)
|
73
|
+
@files ||= []
|
74
|
+
@nav ||= []
|
75
|
+
@ncx_file ||= 'toc.ncx'
|
76
|
+
@opf_file ||= 'content.opf'
|
77
|
+
|
78
|
+
instance_eval(&block) if block_given?
|
79
|
+
end
|
80
|
+
|
81
|
+
# Save as ePub file
|
82
|
+
#
|
83
|
+
# @param [String] filename the ePub file name to save
|
84
|
+
def save(filename)
|
85
|
+
create_epub.save(filename)
|
86
|
+
end
|
87
|
+
|
88
|
+
# instead of saving to file, output the file contents.
|
89
|
+
# important for serving on-the-fly doc creation from
|
90
|
+
# web interface where we don't want to allow file system
|
91
|
+
# writes (Heroku, et al.)
|
92
|
+
def render
|
93
|
+
create_epub.render
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def create_epub
|
99
|
+
@uid ||= 'BookId'
|
100
|
+
unique_identifier = @identifiers.select{ |i| i[:id] == @uid }.first
|
101
|
+
unless unique_identifier
|
102
|
+
unique_identifier = @identifiers.first
|
103
|
+
unique_identifier[:id] = @uid
|
104
|
+
end
|
105
|
+
dir = Dir.mktmpdir
|
106
|
+
@files.each do |file|
|
107
|
+
case file
|
108
|
+
when String
|
109
|
+
FileUtils.cp(file, dir)
|
110
|
+
when Hash
|
111
|
+
file_path, dir_path = *file.first
|
112
|
+
dest_dir = File.join(dir, dir_path)
|
113
|
+
FileUtils.mkdir_p(dest_dir)
|
114
|
+
FileUtils.cp(file_path, dest_dir)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
NCX.new(
|
119
|
+
:uid => @identifiers.select{ |i| i[:id] == @uid }.first,
|
120
|
+
:title => @titles[0],
|
121
|
+
:nav => @nav
|
122
|
+
).save(File.join(dir, @ncx_file))
|
123
|
+
|
124
|
+
OPF.new(
|
125
|
+
:title => @titles,
|
126
|
+
:unique_identifier => @uid,
|
127
|
+
:identifier => @identifiers,
|
128
|
+
:creator => @creators,
|
129
|
+
:publisher => @publishers,
|
130
|
+
:date => @dates,
|
131
|
+
:language => @languages,
|
132
|
+
:subject => @subjects,
|
133
|
+
:description => @descriptions,
|
134
|
+
:rights => @rightss,
|
135
|
+
:cover => @cover,
|
136
|
+
:relation => @relations,
|
137
|
+
:manifest => @files.map{|file|
|
138
|
+
case file
|
139
|
+
when String
|
140
|
+
File.basename(file)
|
141
|
+
when Hash
|
142
|
+
file_path, dir_path = *file.first
|
143
|
+
File.join(dir_path, File.basename(file_path))
|
144
|
+
end
|
145
|
+
},
|
146
|
+
:ncx => @ncx_file,
|
147
|
+
:guide => @guide
|
148
|
+
).save(File.join(dir, @opf_file))
|
149
|
+
|
150
|
+
OCF.new(
|
151
|
+
:dir => dir,
|
152
|
+
:container => @opf_file
|
153
|
+
)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|