rpub 0.1.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/.gitignore +8 -0
- data/.rspec +5 -0
- data/.travis.yml +5 -0
- data/.yardopts +6 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +51 -0
- data/Guardfile +5 -0
- data/HISTORY.md +5 -0
- data/LICENSE +0 -0
- data/README.md +182 -0
- data/Rakefile +11 -0
- data/bin/rpub +6 -0
- data/example/advanced/README +1 -0
- data/example/advanced/config.yml +11 -0
- data/example/advanced/layout.html +0 -0
- data/example/advanced/styles.css +0 -0
- data/example/simple/01-introduction.md +3 -0
- data/example/simple/02-foo.md +4 -0
- data/example/simple/03-bar.md +4 -0
- data/example/simple/config.yml +8 -0
- data/lib/rpub.rb +53 -0
- data/lib/rpub/book.rb +70 -0
- data/lib/rpub/chapter.rb +84 -0
- data/lib/rpub/commander.rb +14 -0
- data/lib/rpub/commands/base.rb +33 -0
- data/lib/rpub/commands/clean.rb +55 -0
- data/lib/rpub/commands/compile.rb +56 -0
- data/lib/rpub/commands/help.rb +37 -0
- data/lib/rpub/commands/main.rb +45 -0
- data/lib/rpub/commands/package.rb +45 -0
- data/lib/rpub/commands/preview.rb +56 -0
- data/lib/rpub/compilation_helpers.rb +43 -0
- data/lib/rpub/compressor.rb +46 -0
- data/lib/rpub/epub.rb +37 -0
- data/lib/rpub/epub/container.rb +14 -0
- data/lib/rpub/epub/content.rb +80 -0
- data/lib/rpub/epub/cover.rb +27 -0
- data/lib/rpub/epub/html_toc.rb +25 -0
- data/lib/rpub/epub/toc.rb +35 -0
- data/lib/rpub/hash_delegation.rb +23 -0
- data/lib/rpub/subclass_tracker.rb +53 -0
- data/lib/rpub/version.rb +3 -0
- data/lib/rpub/xml_file.rb +14 -0
- data/rpub.gemspec +52 -0
- data/spec/fixtures/clean/config.yml +2 -0
- data/spec/fixtures/clean/example.epub +0 -0
- data/spec/fixtures/clean/preview.html +0 -0
- data/spec/fixtures/no_files/config.yml +0 -0
- data/spec/fixtures/preview/a.md +1 -0
- data/spec/fixtures/preview/b.md +1 -0
- data/spec/fixtures/preview/config.yml +2 -0
- data/spec/rpub/book_spec.rb +62 -0
- data/spec/rpub/chapter_spec.rb +61 -0
- data/spec/rpub/commands/clean_spec.rb +46 -0
- data/spec/rpub/commands/main_spec.rb +26 -0
- data/spec/rpub/commands/preview_spec.rb +42 -0
- data/spec/rpub_spec.rb +7 -0
- data/spec/spec_helper.rb +21 -0
- data/support/layout.html +22 -0
- data/support/styles.css +66 -0
- metadata +310 -0
@@ -0,0 +1,46 @@
|
|
1
|
+
module Rpub
|
2
|
+
# Wrapper around a `ZipOutputStream` object provided by the `rubyzip` gem.
|
3
|
+
# This writes string contents straight into a zip file, without first saving
|
4
|
+
# them to disk.
|
5
|
+
class Compressor
|
6
|
+
|
7
|
+
# @return [ZipOutputStream]
|
8
|
+
attr_reader :zip
|
9
|
+
|
10
|
+
# Convenience method for opening a stream, allowing content to be written
|
11
|
+
# and finally closing the stream again.
|
12
|
+
def self.open(filename)
|
13
|
+
compressor = new(filename)
|
14
|
+
yield compressor
|
15
|
+
compressor.close
|
16
|
+
end
|
17
|
+
|
18
|
+
# @param [String] filename of the archive to write to disk
|
19
|
+
def initialize(filename)
|
20
|
+
@zip = Zip::ZipOutputStream.new(filename)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Close the zip stream and write the file to disk.
|
24
|
+
def close
|
25
|
+
zip.close
|
26
|
+
end
|
27
|
+
|
28
|
+
# Store a file in the archive without any compression.
|
29
|
+
#
|
30
|
+
# @param [String] filename under the which the data should be stored
|
31
|
+
# @param [#to_s] content to be compressed
|
32
|
+
def store_file(filename, content)
|
33
|
+
zip.put_next_entry filename, nil, nil, Zip::ZipEntry::STORED, Zlib::NO_COMPRESSION
|
34
|
+
zip.write content.to_s
|
35
|
+
end
|
36
|
+
|
37
|
+
# Store a file with maximum compression in the archive.
|
38
|
+
#
|
39
|
+
# @param [String] filename under the which the data should be stored
|
40
|
+
# @param [#to_s] content to be compressed
|
41
|
+
def compress_file(filename, content)
|
42
|
+
zip.put_next_entry filename, nil, nil, Zip::ZipEntry::DEFLATED, Zlib::BEST_COMPRESSION
|
43
|
+
zip.write content.to_s
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/rpub/epub.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
module Rpub
|
2
|
+
class Epub
|
3
|
+
attr_reader :book, :styles
|
4
|
+
|
5
|
+
def initialize(book, styles)
|
6
|
+
@book, @styles = book, styles
|
7
|
+
end
|
8
|
+
|
9
|
+
def manifest_in(target)
|
10
|
+
target.store_file 'mimetype', 'application/epub+zip'
|
11
|
+
target.compress_file 'META-INF/container.xml', Container.new
|
12
|
+
target.compress_file 'OEBPS/content.opf', Content.new(book)
|
13
|
+
target.compress_file 'OEBPS/toc.ncx', Toc.new(book)
|
14
|
+
target.compress_file 'OEBPS/styles.css', styles
|
15
|
+
if book.cover?
|
16
|
+
target.compress_file 'OEBPS/cover.html', Cover.new(book)
|
17
|
+
target.compress_file File.join('OEBPS', book.cover_image), File.read(book.cover_image)
|
18
|
+
end
|
19
|
+
if book.toc?
|
20
|
+
target.compress_file 'OEBPS/toc.html', toc { HtmlToc.new(book).render }
|
21
|
+
end
|
22
|
+
book.each do |chapter|
|
23
|
+
target.compress_file File.join('OEBPS', chapter.filename), chapter.to_html
|
24
|
+
end
|
25
|
+
book.images.each do |image|
|
26
|
+
target.compress_file File.join('OEBPS', image), File.read(image)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def toc
|
33
|
+
@body = yield
|
34
|
+
ERB.new(File.read(book.layout)).result(binding)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Rpub
|
2
|
+
class Epub
|
3
|
+
class Container < XmlFile
|
4
|
+
def render
|
5
|
+
xml.instruct!
|
6
|
+
xml.container :version => '1.0', :xmlns => 'urn:oasis:names:tc:opendocument:xmlns:container' do
|
7
|
+
xml.rootfiles do
|
8
|
+
xml.rootfile 'full-path' => 'OEBPS/content.opf', 'media-type' => 'application/oebps-package+xml'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Rpub
|
2
|
+
class Epub
|
3
|
+
class Content < XmlFile
|
4
|
+
attr_reader :book
|
5
|
+
|
6
|
+
MEDIA_TYPES = {
|
7
|
+
'png' => 'image/png',
|
8
|
+
'gif' => 'image/gif',
|
9
|
+
'jpg' => 'image/jpeg',
|
10
|
+
'svg' => 'image/svg+xml'
|
11
|
+
}
|
12
|
+
|
13
|
+
def initialize(book)
|
14
|
+
@book = book
|
15
|
+
super()
|
16
|
+
end
|
17
|
+
|
18
|
+
def render
|
19
|
+
xml.instruct!
|
20
|
+
xml.declare! :DOCTYPE, :package, :PUBLIC, '-//W3C//DTD XHTML 1.1//EN', 'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd'
|
21
|
+
xml.package 'xmlns' => 'http://www.idpf.org/2007/opf', 'unique-identifier' => 'BookId', 'version' => '2.0' do
|
22
|
+
xml.metadata 'xmlns:dc' => 'http://purl.org/dc/elements/1.1/', 'xmlns:opf' => 'http://www.idpf.org/2007/opf' do
|
23
|
+
xml.dc :language, book.language
|
24
|
+
xml.dc :title, book.title
|
25
|
+
xml.dc :creator, book.creator, 'opf:role' => 'aut'
|
26
|
+
xml.dc :publisher, book.publisher
|
27
|
+
xml.dc :subject, book.subject
|
28
|
+
xml.dc :identifier, book.uid, :id => 'BookId'
|
29
|
+
xml.dc :rights, book.rights
|
30
|
+
xml.dc :description, book.description
|
31
|
+
|
32
|
+
if book.cover?
|
33
|
+
xml.meta :name => 'cover', :content => 'cover-image'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
xml.manifest do
|
38
|
+
xml.item 'id' => 'ncx', 'href' => 'toc.ncx', 'media-type' => 'application/x-dtbncx+xml'
|
39
|
+
xml.item 'id' => 'css', 'href' => 'styles.css', 'media-type' => 'text/css'
|
40
|
+
|
41
|
+
if book.cover?
|
42
|
+
xml.item 'id' => 'cover', 'href' => 'cover.html', 'media-type' => 'application/xhtml+xml'
|
43
|
+
xml.item 'id' => 'cover-image', 'href' => book.cover_image, 'media-type' => guess_media_type(book.cover_image)
|
44
|
+
end
|
45
|
+
|
46
|
+
book.images.each do |image|
|
47
|
+
xml.item 'id' => File.basename(image), 'href' => image, 'media-type' => guess_media_type(image)
|
48
|
+
end
|
49
|
+
xml.item 'id' => 'toc', 'href' => 'toc.html', 'media-type' => 'application/xhtml+xml'
|
50
|
+
book.chapters.each do |chapter|
|
51
|
+
xml.item 'id' => chapter.id, 'href' => chapter.filename, 'media-type' => 'application/xhtml+xml'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
xml.spine 'toc' => 'ncx' do
|
56
|
+
if book.cover?
|
57
|
+
xml.itemref 'idref' => 'cover', 'linear' => 'no'
|
58
|
+
end
|
59
|
+
book.chapters.each do |chapter|
|
60
|
+
xml.itemref 'idref' => chapter.id
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
if book.cover?
|
65
|
+
xml.guide do
|
66
|
+
xml.reference :type => 'cover', :title => 'Cover', :href => 'cover.html'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def guess_media_type(filename)
|
75
|
+
MEDIA_TYPES.fetch(filename[/\.(gif|png|jpe?g|svg)$/, 1]) { 'image/png' }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Rpub
|
2
|
+
class Epub
|
3
|
+
class Cover < XmlFile
|
4
|
+
attr_reader :book
|
5
|
+
|
6
|
+
def initialize(book)
|
7
|
+
@book = book
|
8
|
+
super()
|
9
|
+
end
|
10
|
+
|
11
|
+
def render
|
12
|
+
xml.declare! :DOCTYPE, :html, :PUBLIC, '-//W3C//DTD XHTML 1.0 Strict//EN', 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'
|
13
|
+
xml.html :xmlns => 'http://www.w3.org/1999/xhtml' do
|
14
|
+
xml.head do
|
15
|
+
xml.title 'Cover'
|
16
|
+
xml.style 'img { max-width: 100%; }', :type => 'text/css'
|
17
|
+
end
|
18
|
+
xml.body do
|
19
|
+
xml.div :id => 'cover-image' do
|
20
|
+
xml.img :src => book.cover_image, :alt => book.title
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Rpub
|
2
|
+
class Epub
|
3
|
+
class HtmlToc < XmlFile
|
4
|
+
attr_reader :book
|
5
|
+
|
6
|
+
def initialize(book)
|
7
|
+
@book = book
|
8
|
+
super()
|
9
|
+
end
|
10
|
+
|
11
|
+
def render
|
12
|
+
xml.h1 'Table of Contents'
|
13
|
+
xml.div :class => 'toc' do
|
14
|
+
book.outline.each do |(filename, headings)|
|
15
|
+
headings.each do |heading|
|
16
|
+
xml.div :class => "level-#{heading.level}" do
|
17
|
+
xml.a heading.text, :href => [filename, heading.id].join('#')
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Rpub
|
2
|
+
class Epub
|
3
|
+
class Toc < XmlFile
|
4
|
+
attr_reader :book
|
5
|
+
|
6
|
+
def initialize(book)
|
7
|
+
@book = book
|
8
|
+
super()
|
9
|
+
end
|
10
|
+
|
11
|
+
def render
|
12
|
+
xml.instruct!
|
13
|
+
xml.declare! :DOCTYPE, :ncx, :PUBLIC, "-//W3C//DTD XHTML 1.1//EN", 'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd'
|
14
|
+
xml.ncx :xmlns => 'http://www.daisy.org/z3986/2005/ncx/', :version => '2005-1' do
|
15
|
+
xml.head do
|
16
|
+
xml.meta :name => 'dtb:uid', :content => @book.uid
|
17
|
+
xml.meta :name => 'dtb:depth', :content => '1'
|
18
|
+
xml.meta :name => 'dtb:totalPageCount', :content => '0'
|
19
|
+
xml.meta :name => 'dtb:maxPageNumber', :content => '0'
|
20
|
+
end
|
21
|
+
xml.docTitle { xml.text @book.title }
|
22
|
+
xml.navMap do
|
23
|
+
@book.chapters.each_with_index do |chapter, n|
|
24
|
+
xml.navPoint :id => chapter.id, :playOrder => n do
|
25
|
+
xml.navLabel { xml.text chapter.title }
|
26
|
+
xml.content :src => chapter.filename
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Rpub
|
2
|
+
# Delegate missing methods to keys in a Hash atribute on the current object.
|
3
|
+
module HashDelegation
|
4
|
+
def self.included(base)
|
5
|
+
base.extend ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def delegate_to_hash(attr)
|
10
|
+
define_method(:delegated_hash) { send attr }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def respond_to?(m)
|
15
|
+
super || delegated_hash.has_key?(m.to_s)
|
16
|
+
end
|
17
|
+
|
18
|
+
def method_missing(m, *args, &block)
|
19
|
+
return super unless respond_to? m
|
20
|
+
delegated_hash.fetch m.to_s
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Rpub
|
2
|
+
# Add tracking of subclasses to an existing class by extending it with
|
3
|
+
# SubclassTracker.
|
4
|
+
#
|
5
|
+
# This allows you to set an identifier in a subclass using the `identifier`
|
6
|
+
# macro, and find subclasses based on that value.
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
#
|
10
|
+
# class ParentClass
|
11
|
+
# extend SubclassTracker
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# class ChildClass < ParentClass
|
15
|
+
# identifier 'foo'
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# ParentClass.matching('foo') # => ChildClass
|
19
|
+
# ParentClass.matching('bar') # => raises SubclassTracker::NoSuchSubclass
|
20
|
+
#
|
21
|
+
# Note that you don't HAVE to set an identifier. If you don't, your child
|
22
|
+
# class will never be found by `#matching`.
|
23
|
+
module SubclassTracker
|
24
|
+
class NoSuchSubclass < StandardError
|
25
|
+
def initialize(subcommand)
|
26
|
+
super "Unrecognized identifier: #{subcommand}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Set or return the identifier for this class.
|
31
|
+
def identifier(id = nil)
|
32
|
+
return @identifier if id.nil?
|
33
|
+
@identifier = id
|
34
|
+
end
|
35
|
+
|
36
|
+
def inherited(child)
|
37
|
+
@subclasses ||= []
|
38
|
+
@subclasses << child
|
39
|
+
super
|
40
|
+
end
|
41
|
+
|
42
|
+
def each
|
43
|
+
@subclasses ||= []
|
44
|
+
@subclasses.each { |subclass| yield subclass }
|
45
|
+
end
|
46
|
+
|
47
|
+
def matching(identifier)
|
48
|
+
find { |subclass| subclass.identifier === identifier } or raise NoSuchSubclass, identifier
|
49
|
+
end
|
50
|
+
|
51
|
+
include Enumerable
|
52
|
+
end
|
53
|
+
end
|
data/lib/rpub/version.rb
ADDED
data/rpub.gemspec
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require './lib/rpub/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
# Metadata
|
6
|
+
s.name = 'rpub'
|
7
|
+
s.version = Rpub::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ['Arjan van der Gaag']
|
10
|
+
s.email = %q{arjan@arjanvandergaag.nl}
|
11
|
+
s.description = %q{an ePub generation library in Ruby}
|
12
|
+
s.homepage = %q{http://avdgaag.github.com/rpub}
|
13
|
+
s.summary = <<-EOS
|
14
|
+
rPub is a command-line tool that generates a collection of plain text input
|
15
|
+
files into an eBook in ePub format. It provides several related functions to
|
16
|
+
make working with ePub files a little easier:
|
17
|
+
|
18
|
+
* Generation of table of contents
|
19
|
+
* Tracking of references to tables or figures
|
20
|
+
* Validation of output file
|
21
|
+
* Packaging your eBook in an archive with additional README file
|
22
|
+
* Very simple version control
|
23
|
+
EOS
|
24
|
+
|
25
|
+
# Files
|
26
|
+
s.files = `git ls-files`.split("\n")
|
27
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
28
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
29
|
+
s.require_paths = ["lib"]
|
30
|
+
|
31
|
+
# Rdoc
|
32
|
+
s.rdoc_options = ['--charset=UTF-8']
|
33
|
+
s.extra_rdoc_files = [
|
34
|
+
'LICENSE',
|
35
|
+
'README.md',
|
36
|
+
'HISTORY.md'
|
37
|
+
]
|
38
|
+
|
39
|
+
# Dependencies
|
40
|
+
s.add_runtime_dependency 'typogruby'
|
41
|
+
s.add_runtime_dependency 'kramdown'
|
42
|
+
s.add_runtime_dependency 'rubyzip'
|
43
|
+
s.add_runtime_dependency 'builder'
|
44
|
+
s.add_development_dependency 'yard'
|
45
|
+
s.add_development_dependency 'rspec'
|
46
|
+
s.add_development_dependency 'rake'
|
47
|
+
s.add_development_dependency 'guard'
|
48
|
+
s.add_development_dependency 'guard-rspec'
|
49
|
+
s.add_development_dependency 'rb-fsevent'
|
50
|
+
s.add_development_dependency 'growl'
|
51
|
+
end
|
52
|
+
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1 @@
|
|
1
|
+
foo
|
@@ -0,0 +1 @@
|
|
1
|
+
bar
|