ebookie 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +91 -0
- data/lib/ebookie.rb +16 -0
- data/lib/ebookie/cli.rb +20 -0
- data/lib/ebookie/document.rb +16 -0
- data/lib/ebookie/document/base.rb +49 -0
- data/lib/ebookie/document/chapter.rb +26 -0
- data/lib/ebookie/document/config.rb +43 -0
- data/lib/ebookie/document/image.rb +29 -0
- data/lib/ebookie/rendering.rb +10 -0
- data/lib/ebookie/rendering/base.rb +156 -0
- data/lib/ebookie/rendering/epub.rb +60 -0
- data/lib/ebookie/rendering/mobi.rb +31 -0
- data/lib/ebookie/rendering/pdf.rb +104 -0
- data/lib/ebookie/templates/epub/META-INF/container.xml +6 -0
- data/lib/ebookie/templates/epub/OEBPS/chapter.erb +12 -0
- data/lib/ebookie/templates/epub/OEBPS/content.opf.erb +50 -0
- data/lib/ebookie/templates/epub/OEBPS/epub.css +68 -0
- data/lib/ebookie/templates/epub/OEBPS/toc.ncx.erb +21 -0
- data/lib/ebookie/templates/epub/mimetype +1 -0
- data/lib/ebookie/templates/pdf/cover.html.erb +9 -0
- data/lib/ebookie/templates/pdf/document.html.erb +16 -0
- data/lib/ebookie/templates/pdf/pdf.css +642 -0
- data/lib/ebookie/version.rb +3 -0
- metadata +270 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8cbd3543662f1c4abeff2153fae912695093d4c0
|
4
|
+
data.tar.gz: be917c2395058c019bbf02fabfd3da7c6b8ddc19
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3c5e6c82e1fc905b30cd9aff20300bc98e5d25dfa55b97e6da2ee8d5d4881a25762a3ac5edbddf8e66772568be856be94d0dbc257a3af95f1ad7ede451ee6730
|
7
|
+
data.tar.gz: 8c99693dd10cb8ee9c2eefb69f2f14b2ea5869cf2aec369d821a343ba7b81f28b572745983774e155bbaae22ff59f20c29a919f42d5e6d9000f7d4e1f4903e77
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 jordanandree
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
# Ebookie
|
2
|
+
|
3
|
+
Generate PDF, ePub, and Mobi eBooks
|
4
|
+
|
5
|
+
[Example project](./example)
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'ebookie'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install ebookie
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
Create a new document:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
document = Ebookie::Document.new "My eBook" # Title
|
27
|
+
```
|
28
|
+
|
29
|
+
Configure it:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
document.configure do |config|
|
33
|
+
config.cover = './path/to/cover.png' # Cover image
|
34
|
+
config.destination = './ebook/' # Ouput for ePub, Mobi, PDF
|
35
|
+
config.subject = "Introductions"
|
36
|
+
config.source = "http://google.com"
|
37
|
+
end
|
38
|
+
```
|
39
|
+
|
40
|
+
Add a chapter:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
# You can pass a string for the content
|
44
|
+
document.chapter 'Getting Started', "All about how to get started"
|
45
|
+
|
46
|
+
# You can also give it an html file to read
|
47
|
+
document.chapter 'Getting Started', Pathname.new("path/to/myfile.html")
|
48
|
+
```
|
49
|
+
|
50
|
+
Add an image:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
# Relative or absolute path for image to be copied
|
54
|
+
document.image './path/to/image.png'
|
55
|
+
|
56
|
+
# Chapters can reference images with html:
|
57
|
+
document.chapter "My Cool Image", "<img src='image.png' alt='image' />"
|
58
|
+
```
|
59
|
+
|
60
|
+
Render the document:
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
# ePub
|
64
|
+
document.render_epub
|
65
|
+
|
66
|
+
# Mobi
|
67
|
+
document.render_mobi
|
68
|
+
|
69
|
+
# PDF
|
70
|
+
document.render_pdf
|
71
|
+
```
|
72
|
+
|
73
|
+
#### Install templates for customization:
|
74
|
+
|
75
|
+
```bash
|
76
|
+
$ ebookie install ./path/to/templatees
|
77
|
+
```
|
78
|
+
|
79
|
+
Then configure the document:
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
document.config.template = './path/to/templates'
|
83
|
+
```
|
84
|
+
|
85
|
+
## Contributing
|
86
|
+
|
87
|
+
1. Fork it ( http://github.com/mailchimp/ebookie/fork )
|
88
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
89
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
90
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
91
|
+
5. Create new Pull Request
|
data/lib/ebookie.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
module Ebookie
|
2
|
+
require "ebookie/version"
|
3
|
+
require "ebookie/document"
|
4
|
+
require "ebookie/rendering"
|
5
|
+
require "logger"
|
6
|
+
|
7
|
+
def self.logger
|
8
|
+
@logger ||= Logger.new(STDOUT)
|
9
|
+
if ENV['LOG_LEVEL'] && log_level = Logger.const_get(ENV['LOG_LEVEL'])
|
10
|
+
@logger.level = log_level
|
11
|
+
else
|
12
|
+
@logger.level = Logger::WARN
|
13
|
+
end
|
14
|
+
@logger
|
15
|
+
end
|
16
|
+
end
|
data/lib/ebookie/cli.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require "thor"
|
2
|
+
require "pathname"
|
3
|
+
|
4
|
+
module Ebookie
|
5
|
+
class Cli < Thor
|
6
|
+
include Thor::Actions
|
7
|
+
add_runtime_options!
|
8
|
+
|
9
|
+
desc "install DIRECTORY", "install template files to DIRECTORY"
|
10
|
+
def install(directory)
|
11
|
+
exec_dir = Pathname.new(Dir.pwd)
|
12
|
+
templates_dir = Pathname.new(File.expand_path('../templates', __FILE__))
|
13
|
+
|
14
|
+
Ebookie::Cli.source_root templates_dir
|
15
|
+
|
16
|
+
directory templates_dir, exec_dir.join(directory)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "ebookie/document/config"
|
2
|
+
require "ebookie/document/base"
|
3
|
+
require "ebookie/document/chapter"
|
4
|
+
require "ebookie/document/image"
|
5
|
+
|
6
|
+
module Ebookie
|
7
|
+
module Document
|
8
|
+
class << self
|
9
|
+
|
10
|
+
def new(*args)
|
11
|
+
Base.new(*args)
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Ebookie
|
2
|
+
module Document
|
3
|
+
class Base
|
4
|
+
|
5
|
+
attr_reader :chapters, :images, :config
|
6
|
+
|
7
|
+
def initialize(title)
|
8
|
+
@chapters = []
|
9
|
+
@images = []
|
10
|
+
|
11
|
+
@config = Config.new
|
12
|
+
@config.title = title
|
13
|
+
end
|
14
|
+
|
15
|
+
def configure(&block)
|
16
|
+
yield @config
|
17
|
+
end
|
18
|
+
|
19
|
+
def chapter(title, content)
|
20
|
+
@chapters << Chapter.new(title, content)
|
21
|
+
end
|
22
|
+
|
23
|
+
def image(file)
|
24
|
+
@images << Image.new(file)
|
25
|
+
end
|
26
|
+
|
27
|
+
def render_pdf
|
28
|
+
Rendering::Pdf.new(self).render
|
29
|
+
end
|
30
|
+
|
31
|
+
def render_epub
|
32
|
+
Rendering::Epub.new(self).render
|
33
|
+
end
|
34
|
+
|
35
|
+
def render_mobi
|
36
|
+
Rendering::Mobi.new(self).render
|
37
|
+
end
|
38
|
+
|
39
|
+
def method_missing(meth, *args)
|
40
|
+
if @config.respond_to?(meth)
|
41
|
+
@config.send(meth, *args)
|
42
|
+
else
|
43
|
+
super
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Ebookie
|
2
|
+
module Document
|
3
|
+
class Chapter
|
4
|
+
|
5
|
+
attr_accessor :title, :content
|
6
|
+
|
7
|
+
def initialize(title, content)
|
8
|
+
@title = title
|
9
|
+
@content = content
|
10
|
+
end
|
11
|
+
|
12
|
+
def content
|
13
|
+
if @content.is_a? Pathname
|
14
|
+
@content.read
|
15
|
+
else
|
16
|
+
@content
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def slug
|
21
|
+
title.downcase.strip.gsub(' ', '-').gsub(/[^\w-]/, '')
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Ebookie
|
2
|
+
module Document
|
3
|
+
class Config
|
4
|
+
|
5
|
+
attr_accessor :title
|
6
|
+
attr_accessor :creator
|
7
|
+
attr_accessor :publisher
|
8
|
+
attr_accessor :rights
|
9
|
+
attr_accessor :subject
|
10
|
+
attr_accessor :language
|
11
|
+
attr_accessor :source
|
12
|
+
attr_accessor :date
|
13
|
+
attr_accessor :destination
|
14
|
+
attr_accessor :cover
|
15
|
+
attr_accessor :template
|
16
|
+
|
17
|
+
def date
|
18
|
+
@date || Time.now.strftime("%F")
|
19
|
+
end
|
20
|
+
|
21
|
+
def language
|
22
|
+
@language || "en-US"
|
23
|
+
end
|
24
|
+
|
25
|
+
def cover=(path)
|
26
|
+
if File.exists?(path) || path.match(/http[s]?:\/\//)
|
27
|
+
if %w(.png .pdf).include? File.extname(path)
|
28
|
+
@cover = Pathname.new(path)
|
29
|
+
else
|
30
|
+
Ebookie.logger.warn "Cover file is not a valid"
|
31
|
+
end
|
32
|
+
else
|
33
|
+
Ebookie.logger.warn "Cover file does not exist"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def slug
|
38
|
+
title.downcase.strip.gsub(' ', '-').gsub(/[^\w-]/, '')
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Ebookie
|
2
|
+
module Document
|
3
|
+
class Image
|
4
|
+
|
5
|
+
attr_reader :file
|
6
|
+
|
7
|
+
def initialize(filepath)
|
8
|
+
self.file = filepath
|
9
|
+
end
|
10
|
+
|
11
|
+
def file=(path)
|
12
|
+
if File.exists?(path) || path.match(/http[s]?:\/\//)
|
13
|
+
if File.extname(path) == '.png'
|
14
|
+
@file = Pathname.new(path)
|
15
|
+
else
|
16
|
+
Ebookie.logger.warn "Image file is not a valid png for '#{path}'"
|
17
|
+
end
|
18
|
+
else
|
19
|
+
Ebookie.logger.warn "Image file does not exist for '#{path}'"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def basename
|
24
|
+
File.basename(file) if file
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
require "ostruct"
|
2
|
+
require "borrower"
|
3
|
+
require "erb"
|
4
|
+
|
5
|
+
module Ebookie
|
6
|
+
module Rendering
|
7
|
+
class Base
|
8
|
+
IMAGE_SRC_REGEX = /src=['|"]((\/?.+\/)*)?(.+?[\.]+.+?)['|"]/xi
|
9
|
+
|
10
|
+
attr_reader :document
|
11
|
+
|
12
|
+
def initialize(document)
|
13
|
+
@document = document
|
14
|
+
|
15
|
+
after_initialize if respond_to?(:after_initialize)
|
16
|
+
end
|
17
|
+
|
18
|
+
class << self
|
19
|
+
def inherited(subclass)
|
20
|
+
subclass.class_eval do
|
21
|
+
attr_reader :settings
|
22
|
+
end
|
23
|
+
|
24
|
+
subclass.instance_eval do
|
25
|
+
define_method :settings do
|
26
|
+
@@settings.send(format) || {}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def set(key, val)
|
32
|
+
@@settings ||= OpenStruct.new
|
33
|
+
|
34
|
+
format_options = @@settings.send(format) || {}
|
35
|
+
@@settings.send "#{format}=", format_options.merge({key => val})
|
36
|
+
end
|
37
|
+
|
38
|
+
def format
|
39
|
+
self.name.split("::").last.downcase
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def render
|
44
|
+
throw "Output path required" unless document.destination
|
45
|
+
|
46
|
+
FileUtils.mkdir_p(tmp_dir) unless File.exists?(tmp_dir)
|
47
|
+
|
48
|
+
create_paths if settings.keys.include?(:paths) && settings[:paths]
|
49
|
+
copy_files if settings.keys.include?(:files) && settings[:files]
|
50
|
+
copy_images if document.images.any? && settings[:images_dir]
|
51
|
+
|
52
|
+
FileUtils.mkdir_p(document.destination) unless File.exists?(document.destination)
|
53
|
+
|
54
|
+
process!
|
55
|
+
return output_path
|
56
|
+
end
|
57
|
+
|
58
|
+
def template_file(path)
|
59
|
+
custom_template_dir = Pathname.new(document.template) if document.template
|
60
|
+
|
61
|
+
if document.template && File.exists?(custom_template_dir.join(format, path))
|
62
|
+
custom_template_dir.join(format, path)
|
63
|
+
else
|
64
|
+
template_dir.join(path)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def render_erb_to_file(template, filepath, locals={})
|
69
|
+
locals.merge! document: document, renderer: self
|
70
|
+
|
71
|
+
locals_struct = OpenStruct.new(locals).instance_eval { binding }
|
72
|
+
contents = ERB.new(File.read(template)).result(locals_struct)
|
73
|
+
|
74
|
+
write_contents_to_file(contents, filepath)
|
75
|
+
end
|
76
|
+
|
77
|
+
def tmp_dir
|
78
|
+
Pathname.new(File.expand_path("../../../../tmp/#{document.config.slug}/#{format}", __FILE__))
|
79
|
+
end
|
80
|
+
|
81
|
+
def template_dir
|
82
|
+
Pathname.new(File.expand_path("../../templates/#{format}", __FILE__))
|
83
|
+
end
|
84
|
+
|
85
|
+
def format
|
86
|
+
self.class.format
|
87
|
+
end
|
88
|
+
|
89
|
+
def output_path
|
90
|
+
Pathname.new(document.destination).join("#{document.config.slug}.#{format}").to_s
|
91
|
+
end
|
92
|
+
|
93
|
+
def sanitize_html(html)
|
94
|
+
{
|
95
|
+
/’|‘/ => "'",
|
96
|
+
/”|“|“|”/ => "\"",
|
97
|
+
"’" => "'",
|
98
|
+
":" => ":",
|
99
|
+
"⌘" => "⌘"
|
100
|
+
}.each do |k,v|
|
101
|
+
html.gsub! k, v
|
102
|
+
end
|
103
|
+
|
104
|
+
html
|
105
|
+
end
|
106
|
+
|
107
|
+
def clean_images(html, new_path)
|
108
|
+
html.each_line do |line|
|
109
|
+
old_line = line.dup
|
110
|
+
matches = line.match(IMAGE_SRC_REGEX).to_a
|
111
|
+
next unless matches.any?
|
112
|
+
|
113
|
+
# remove folder
|
114
|
+
line.gsub! matches[2], "" if matches[2]
|
115
|
+
|
116
|
+
# set our folder
|
117
|
+
line.gsub! matches[3], new_path.join(matches[3]).to_s
|
118
|
+
|
119
|
+
html.gsub! old_line, line
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
def create_paths
|
126
|
+
settings[:paths].each do |path|
|
127
|
+
FileUtils.mkdir_p tmp_dir.join(path)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def copy_images
|
132
|
+
document.images.each do |image|
|
133
|
+
borrow image.file.to_s, to: tmp_dir.join(settings[:images_dir], image.basename)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def copy_files
|
138
|
+
settings[:files].each do |file|
|
139
|
+
if File.extname(file) == '.erb' && ext = File.extname(file)
|
140
|
+
render_erb_to_file template_file(file), tmp_dir.join(file.gsub(ext, ''))
|
141
|
+
else
|
142
|
+
FileUtils.cp template_file(file), tmp_dir.join(file)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def write_contents_to_file(contents, filepath, mode="w+")
|
148
|
+
File.open(filepath, mode) do |handle|
|
149
|
+
handle.write(contents)
|
150
|
+
handle.close
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|