rpub 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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