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
data/lib/rpub/chapter.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
module Rpub
|
2
|
+
# Representation of a chapter in the book, from a single input file from the
|
3
|
+
# project directory. The Chapter object knows how to turn itself into HTML
|
4
|
+
# suitable for writing to the epub archive with the appropriate identifiers
|
5
|
+
# to be listed in the epub manifest files.
|
6
|
+
class Chapter
|
7
|
+
# @return [String] raw textual contents of this chapter
|
8
|
+
attr_reader :content
|
9
|
+
|
10
|
+
# @return [Fixnum] chapter number provided by its associated Book object
|
11
|
+
attr_reader :number
|
12
|
+
|
13
|
+
# @return [String] filename of the layout to use, to be passed directly to the Kramdown gem.
|
14
|
+
attr_reader :layout
|
15
|
+
|
16
|
+
def initialize(content, number, layout)
|
17
|
+
@content, @number, @layout = content, number, layout
|
18
|
+
@document = Kramdown::Document.new(content, KRAMDOWN_OPTIONS.merge(:template => layout))
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [String] Unique identifier for this chapter.
|
22
|
+
def uid
|
23
|
+
@uid ||= Digest::SHA1.hexdigest([content, id.to_s, layout].join)
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [String] XML-friendly slug for this chapter based on its number.
|
27
|
+
def id
|
28
|
+
@id ||= "chapter-#{number}"
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [String] content parsed to HTML by the markdown engine.
|
32
|
+
def to_html
|
33
|
+
@to_html ||= @document.to_html
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [String] name for the file in the zip to use, based on the title
|
37
|
+
def filename
|
38
|
+
@filename ||= id.to_s + '-' + title.gsub(/[^\w\.]/i, '-').squeeze('-').downcase.chomp('-') + '.html'
|
39
|
+
end
|
40
|
+
|
41
|
+
# Ordered headers for this chapter, each header as an object responding
|
42
|
+
# to #level and #text.
|
43
|
+
#
|
44
|
+
# @return [Array<#text,#level>] list of headers for this chapter
|
45
|
+
def outline
|
46
|
+
@outline ||= elements(:header).map do |element|
|
47
|
+
OpenStruct.new({
|
48
|
+
:level => element.options[:level],
|
49
|
+
:text => element_text(element),
|
50
|
+
:id => Kramdown::Converter::Html.send(:new, @document, { :auto_id_prefix => '' }).generate_id(element.options[:raw_text])
|
51
|
+
})
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# @return [Array<String>] list of all image references
|
56
|
+
def images
|
57
|
+
@images ||= elements(:img).map { |e| e.attr['src'] }
|
58
|
+
end
|
59
|
+
|
60
|
+
# @return [String] Text of the first heading in this chapter
|
61
|
+
def title
|
62
|
+
@title ||= begin
|
63
|
+
(heading = outline.first) ? heading.text : 'untitled'
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def element_text(element)
|
70
|
+
elements(:text, element).map { |e| e.value }.join
|
71
|
+
end
|
72
|
+
|
73
|
+
def elements(type, root = @document.root)
|
74
|
+
collector = lambda do |element|
|
75
|
+
element.children.select { |e|
|
76
|
+
e.type == type
|
77
|
+
} + element.children.map { |e|
|
78
|
+
collector.call(e)
|
79
|
+
}.flatten
|
80
|
+
end
|
81
|
+
collector.call(root)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Rpub
|
2
|
+
module Commander
|
3
|
+
def invoke(args = [])
|
4
|
+
subcommand, *options = args
|
5
|
+
Commands::Base.matching(subcommand).new(options).invoke
|
6
|
+
rescue SubclassTracker::NoSuchSubclass
|
7
|
+
Commands::Main.new(args).invoke
|
8
|
+
rescue NoConfiguration
|
9
|
+
abort 'The current directory does not look like an rpub project.'
|
10
|
+
end
|
11
|
+
|
12
|
+
extend self
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Rpub
|
2
|
+
module Commands
|
3
|
+
class Base
|
4
|
+
extend SubclassTracker
|
5
|
+
|
6
|
+
attr_reader :options
|
7
|
+
|
8
|
+
def initialize(options = [], stdout = $stdout)
|
9
|
+
@options, @stdout = options, stdout
|
10
|
+
end
|
11
|
+
|
12
|
+
def invoke
|
13
|
+
parser.parse!(options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def help
|
17
|
+
puts parser
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def parser
|
23
|
+
OptionParser.new
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def puts(*args)
|
29
|
+
@stdout.puts(*args)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Rpub
|
2
|
+
module Commands
|
3
|
+
class Clean < Base
|
4
|
+
include CompilationHelpers
|
5
|
+
|
6
|
+
identifier 'clean'
|
7
|
+
|
8
|
+
def invoke
|
9
|
+
super
|
10
|
+
remove create_book.filename
|
11
|
+
remove 'preview.html'
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def parser
|
17
|
+
OptionParser.new do |opts|
|
18
|
+
opts.banner = <<-EOS
|
19
|
+
Usage: rpub clean [-d]'
|
20
|
+
|
21
|
+
Clean up all generated files, such as the standard generated .epub
|
22
|
+
file, package files and preview files.
|
23
|
+
|
24
|
+
Options:
|
25
|
+
EOS
|
26
|
+
|
27
|
+
opts.separator ''
|
28
|
+
|
29
|
+
opts.on '-d', '--dry-run', 'Dry-run: only list files to be removed' do
|
30
|
+
@dry_run = true
|
31
|
+
end
|
32
|
+
|
33
|
+
opts.separator ''
|
34
|
+
opts.separator 'Generic options:'
|
35
|
+
opts.separator ''
|
36
|
+
|
37
|
+
opts.on_tail '-h', '--help', 'Display this message' do
|
38
|
+
puts opts
|
39
|
+
exit
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def remove(filename)
|
45
|
+
if File.exist?(filename)
|
46
|
+
unless @dry_run
|
47
|
+
File.unlink(filename)
|
48
|
+
else
|
49
|
+
puts filename
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Rpub
|
2
|
+
module Commands
|
3
|
+
class Compile < Base
|
4
|
+
include CompilationHelpers
|
5
|
+
|
6
|
+
identifier 'compile'
|
7
|
+
|
8
|
+
def invoke
|
9
|
+
super
|
10
|
+
book = create_book
|
11
|
+
Compressor.open(book.filename) do |zip|
|
12
|
+
Epub.new(book, File.read(styles)).manifest_in(zip)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def parser
|
19
|
+
OptionParser.new do |opts|
|
20
|
+
opts.banner = <<-EOS
|
21
|
+
Usage: rpub compile [options]
|
22
|
+
|
23
|
+
Compile your Markdown-formatted input files to a valid .epub output
|
24
|
+
file using the options described in config.yml. This will use the
|
25
|
+
layout.html and styles.css files in your project directory if
|
26
|
+
present.
|
27
|
+
|
28
|
+
Options:
|
29
|
+
EOS
|
30
|
+
opts.separator ''
|
31
|
+
|
32
|
+
opts.on '-l', '--layout FILENAME', 'Specify an explicit layout file to use' do |filename|
|
33
|
+
@layout = filename
|
34
|
+
end
|
35
|
+
|
36
|
+
opts.on '-s', '--styles FILENAME', 'Specify an explicit stylesheet file to use' do |filename|
|
37
|
+
@styles = filename
|
38
|
+
end
|
39
|
+
|
40
|
+
opts.on '-c', '--config FILENAME', 'Specify an explicit configuration file to use' do |filename|
|
41
|
+
@config_file = filename
|
42
|
+
end
|
43
|
+
|
44
|
+
opts.separator ''
|
45
|
+
opts.separator 'Generic options:'
|
46
|
+
opts.separator ''
|
47
|
+
|
48
|
+
opts.on_tail '-h', '--help', 'Display this message' do
|
49
|
+
puts opts
|
50
|
+
exit
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Rpub
|
2
|
+
module Commands
|
3
|
+
class Help < Base
|
4
|
+
identifier 'help'
|
5
|
+
|
6
|
+
def invoke
|
7
|
+
if options.empty?
|
8
|
+
Main.new.invoke
|
9
|
+
else
|
10
|
+
Base.matching(options.shift).new.help
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def parser
|
17
|
+
OptionParser.new do |opts|
|
18
|
+
opts.banner = <<-EOS
|
19
|
+
Usage: rpub help subcommand
|
20
|
+
|
21
|
+
Describe the usage and options for rpub subcommands.
|
22
|
+
|
23
|
+
Options:
|
24
|
+
EOS
|
25
|
+
opts.separator ''
|
26
|
+
opts.separator 'Generic options:'
|
27
|
+
opts.separator ''
|
28
|
+
|
29
|
+
opts.on_tail '-h', '--help', 'Display this message' do
|
30
|
+
puts opts
|
31
|
+
exit
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Rpub
|
2
|
+
module Commands
|
3
|
+
class Main < Base
|
4
|
+
|
5
|
+
def invoke
|
6
|
+
options << '-h' if options.empty?
|
7
|
+
super
|
8
|
+
raise InvalidSubcommand, options[0] unless options.empty?
|
9
|
+
end
|
10
|
+
|
11
|
+
protected
|
12
|
+
|
13
|
+
def parser
|
14
|
+
OptionParser.new do |opts|
|
15
|
+
opts.banner = <<-EOS
|
16
|
+
Usage: rpub [subcommand] [options]
|
17
|
+
|
18
|
+
Compile multiple Markdown-formatted input files into a machine-readable epub
|
19
|
+
file for distribution as an ebook.
|
20
|
+
|
21
|
+
Available subcommands:
|
22
|
+
|
23
|
+
compile
|
24
|
+
preview
|
25
|
+
clean
|
26
|
+
package
|
27
|
+
help
|
28
|
+
EOS
|
29
|
+
|
30
|
+
opts.separator ''
|
31
|
+
opts.separator 'Generic options:'
|
32
|
+
opts.separator ''
|
33
|
+
|
34
|
+
opts.on_tail '-v', '--version', 'Display version information' do
|
35
|
+
puts "rpub #{Rpub::VERSION}"
|
36
|
+
end
|
37
|
+
|
38
|
+
opts.on_tail '-h', '--help', 'Display command reference' do
|
39
|
+
puts opts
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Rpub
|
2
|
+
module Commands
|
3
|
+
class Package < Base
|
4
|
+
include CompilationHelpers
|
5
|
+
|
6
|
+
identifier 'package'
|
7
|
+
|
8
|
+
def invoke
|
9
|
+
super
|
10
|
+
Compile.new(options).invoke
|
11
|
+
return unless config.has_key?('package_file')
|
12
|
+
Compressor.open(config.fetch('package_file')) do |zip|
|
13
|
+
zip.store_file create_book.filename, File.read(create_book.filename)
|
14
|
+
config.fetch('package') { [] }.each do |file|
|
15
|
+
zip.compress_file file, File.read(file)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def parser
|
23
|
+
OptionParser.new do |opts|
|
24
|
+
opts.banner = <<-EOS
|
25
|
+
Usage: rpub package
|
26
|
+
|
27
|
+
Compile your ebook to an ePub file and package it into an archive together with
|
28
|
+
optional other files for easy distibution. You might want to include a README
|
29
|
+
file, a license or other promotion material.
|
30
|
+
|
31
|
+
Options:
|
32
|
+
EOS
|
33
|
+
opts.separator ''
|
34
|
+
opts.separator 'Generic options:'
|
35
|
+
opts.separator ''
|
36
|
+
|
37
|
+
opts.on_tail '-h', '--help', 'Display this message' do
|
38
|
+
puts opts
|
39
|
+
exit
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Rpub
|
2
|
+
module Commands
|
3
|
+
class Preview < Base
|
4
|
+
include CompilationHelpers
|
5
|
+
|
6
|
+
identifier 'preview'
|
7
|
+
|
8
|
+
def initialize(*args)
|
9
|
+
super
|
10
|
+
@filename = 'preview.html'
|
11
|
+
end
|
12
|
+
|
13
|
+
def invoke
|
14
|
+
super
|
15
|
+
return unless markdown_files.any?
|
16
|
+
concatenation = markdown_files.join("\n")
|
17
|
+
File.open(@filename, 'w') do |f|
|
18
|
+
f.write Kramdown::Document.new(concatenation, KRAMDOWN_OPTIONS.merge(:template => layout)).to_html
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def parser
|
25
|
+
OptionParser.new do |opts|
|
26
|
+
opts.banner = <<-EOS
|
27
|
+
Usage: rpub preview [options]
|
28
|
+
|
29
|
+
Generate a single-page HTML file for easy previewing of your content with the
|
30
|
+
layout and styles used when generating .epub files. By default, the output
|
31
|
+
file will be named "preview.html".
|
32
|
+
|
33
|
+
Options:
|
34
|
+
EOS
|
35
|
+
opts.separator ''
|
36
|
+
opts.on '-l', '--layout FILENAME', 'Specify an explicit layout file to use' do |filename|
|
37
|
+
@layout = filename
|
38
|
+
end
|
39
|
+
|
40
|
+
opts.on '-o', '--output FILENAME', 'Specify an explicit output file' do |filename|
|
41
|
+
@filename = filename
|
42
|
+
end
|
43
|
+
|
44
|
+
opts.separator ''
|
45
|
+
opts.separator 'Generic options:'
|
46
|
+
opts.separator ''
|
47
|
+
|
48
|
+
opts.on_tail '-h', '--help', 'Display this message' do
|
49
|
+
puts opts
|
50
|
+
exit
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Rpub
|
2
|
+
module CompilationHelpers
|
3
|
+
def create_book
|
4
|
+
book = Book.new(layout, config)
|
5
|
+
markdown_files.each(&book.method(:<<))
|
6
|
+
book
|
7
|
+
end
|
8
|
+
|
9
|
+
def markdown_files
|
10
|
+
@markdown_files ||= filter_exceptions(Dir['*.md']).sort.map(&File.method(:read))
|
11
|
+
end
|
12
|
+
|
13
|
+
def layout
|
14
|
+
@layout ||= own_or_support_file('layout.html')
|
15
|
+
end
|
16
|
+
|
17
|
+
def styles
|
18
|
+
@styles ||= own_or_support_file('styles.css')
|
19
|
+
end
|
20
|
+
|
21
|
+
def config
|
22
|
+
@config_file ||= begin
|
23
|
+
raise NoConfiguration unless File.exist?('config.yml')
|
24
|
+
YAML.load_file('config.yml') || {}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def filter_exceptions(filenames)
|
31
|
+
return filenames unless config.has_key?('ignore')
|
32
|
+
filenames.reject(&config['ignore'].method(:include?))
|
33
|
+
end
|
34
|
+
|
35
|
+
def own_or_support_file(filename)
|
36
|
+
if File.exists?(filename)
|
37
|
+
filename
|
38
|
+
else
|
39
|
+
Rpub.support_file(filename)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|