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.
Files changed (61) hide show
  1. data/.gitignore +8 -0
  2. data/.rspec +5 -0
  3. data/.travis.yml +5 -0
  4. data/.yardopts +6 -0
  5. data/Gemfile +2 -0
  6. data/Gemfile.lock +51 -0
  7. data/Guardfile +5 -0
  8. data/HISTORY.md +5 -0
  9. data/LICENSE +0 -0
  10. data/README.md +182 -0
  11. data/Rakefile +11 -0
  12. data/bin/rpub +6 -0
  13. data/example/advanced/README +1 -0
  14. data/example/advanced/config.yml +11 -0
  15. data/example/advanced/layout.html +0 -0
  16. data/example/advanced/styles.css +0 -0
  17. data/example/simple/01-introduction.md +3 -0
  18. data/example/simple/02-foo.md +4 -0
  19. data/example/simple/03-bar.md +4 -0
  20. data/example/simple/config.yml +8 -0
  21. data/lib/rpub.rb +53 -0
  22. data/lib/rpub/book.rb +70 -0
  23. data/lib/rpub/chapter.rb +84 -0
  24. data/lib/rpub/commander.rb +14 -0
  25. data/lib/rpub/commands/base.rb +33 -0
  26. data/lib/rpub/commands/clean.rb +55 -0
  27. data/lib/rpub/commands/compile.rb +56 -0
  28. data/lib/rpub/commands/help.rb +37 -0
  29. data/lib/rpub/commands/main.rb +45 -0
  30. data/lib/rpub/commands/package.rb +45 -0
  31. data/lib/rpub/commands/preview.rb +56 -0
  32. data/lib/rpub/compilation_helpers.rb +43 -0
  33. data/lib/rpub/compressor.rb +46 -0
  34. data/lib/rpub/epub.rb +37 -0
  35. data/lib/rpub/epub/container.rb +14 -0
  36. data/lib/rpub/epub/content.rb +80 -0
  37. data/lib/rpub/epub/cover.rb +27 -0
  38. data/lib/rpub/epub/html_toc.rb +25 -0
  39. data/lib/rpub/epub/toc.rb +35 -0
  40. data/lib/rpub/hash_delegation.rb +23 -0
  41. data/lib/rpub/subclass_tracker.rb +53 -0
  42. data/lib/rpub/version.rb +3 -0
  43. data/lib/rpub/xml_file.rb +14 -0
  44. data/rpub.gemspec +52 -0
  45. data/spec/fixtures/clean/config.yml +2 -0
  46. data/spec/fixtures/clean/example.epub +0 -0
  47. data/spec/fixtures/clean/preview.html +0 -0
  48. data/spec/fixtures/no_files/config.yml +0 -0
  49. data/spec/fixtures/preview/a.md +1 -0
  50. data/spec/fixtures/preview/b.md +1 -0
  51. data/spec/fixtures/preview/config.yml +2 -0
  52. data/spec/rpub/book_spec.rb +62 -0
  53. data/spec/rpub/chapter_spec.rb +61 -0
  54. data/spec/rpub/commands/clean_spec.rb +46 -0
  55. data/spec/rpub/commands/main_spec.rb +26 -0
  56. data/spec/rpub/commands/preview_spec.rb +42 -0
  57. data/spec/rpub_spec.rb +7 -0
  58. data/spec/spec_helper.rb +21 -0
  59. data/support/layout.html +22 -0
  60. data/support/styles.css +66 -0
  61. 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
@@ -0,0 +1,3 @@
1
+ module Rpub
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,14 @@
1
+ module Rpub
2
+ class XmlFile
3
+ attr_reader :xml
4
+
5
+ def initialize
6
+ @xml = Builder::XmlMarkup.new :indent => 2
7
+ end
8
+
9
+ def to_s
10
+ render
11
+ xml.target!
12
+ end
13
+ end
14
+ end
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
+
@@ -0,0 +1,2 @@
1
+ ---
2
+ title: example
File without changes
File without changes
File without changes
@@ -0,0 +1 @@
1
+ foo
@@ -0,0 +1 @@
1
+ bar