invisiblellama-repub 0.2.1
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 +4 -0
- data/History.txt +3 -0
- data/README.txt +95 -0
- data/Rakefile +30 -0
- data/TODO.txt +2 -0
- data/bin/repub +24 -0
- data/lib/repub.rb +46 -0
- data/lib/repub/app.rb +42 -0
- data/lib/repub/app/builder.rb +200 -0
- data/lib/repub/app/fetcher.rb +162 -0
- data/lib/repub/app/logger.rb +52 -0
- data/lib/repub/app/options.rb +173 -0
- data/lib/repub/app/parser.rb +139 -0
- data/lib/repub/app/profile.rb +91 -0
- data/lib/repub/app/utility.rb +57 -0
- data/lib/repub/epub.rb +3 -0
- data/lib/repub/epub/container.rb +28 -0
- data/lib/repub/epub/content.rb +153 -0
- data/lib/repub/epub/toc.rb +139 -0
- data/lib/repub/mobi/.githidden +0 -0
- data/test/epub/test_container.rb +15 -0
- data/test/epub/test_content.rb +56 -0
- data/test/epub/test_toc.rb +29 -0
- data/test/test_builder.rb +8 -0
- data/test/test_fetcher.rb +36 -0
- data/test/test_logger.rb +76 -0
- data/test/test_parser.rb +32 -0
- metadata +139 -0
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
3
|
+
module Repub
|
4
|
+
class App
|
5
|
+
module Profile
|
6
|
+
|
7
|
+
PROFILE_KEYS = %w[css encoding fixup helper metadata remove rx selectors].map {|k| k.to_sym}
|
8
|
+
|
9
|
+
def load_profile(name = nil)
|
10
|
+
name ||= 'default'
|
11
|
+
profile = Profile.new
|
12
|
+
profile[name] ||= {}
|
13
|
+
PROFILE_KEYS.each { |key| options[key] = profile[name][key] if profile[name][key] }
|
14
|
+
profile.save
|
15
|
+
profile[name]
|
16
|
+
end
|
17
|
+
|
18
|
+
def write_profile(name = nil)
|
19
|
+
name ||= 'default'
|
20
|
+
profile = Profile.new
|
21
|
+
profile[name] ||= {}
|
22
|
+
PROFILE_KEYS.each { |key| profile[name][key] = options[key] }
|
23
|
+
profile.save
|
24
|
+
profile[name]
|
25
|
+
end
|
26
|
+
|
27
|
+
def dump_profile(name = nil)
|
28
|
+
name ||= 'default'
|
29
|
+
profile = Profile.new
|
30
|
+
if p = profile[name]
|
31
|
+
keys = p.keys.map{|k| k.to_s }.sort.map{|k| k.to_sym }
|
32
|
+
keys.each do |k|
|
33
|
+
v = p[k]
|
34
|
+
next if v.nil? || (v.respond_to?(:empty?) && v.empty?)
|
35
|
+
case k
|
36
|
+
when :selectors
|
37
|
+
printf("%4s%-6s\n", '', k)
|
38
|
+
selector_keys = v.keys.map{|k| k.to_s }.sort.map{|k| k.to_sym }
|
39
|
+
selector_keys.each { |sk| printf("%8s%-12s %s\n", '', sk, v[sk]) }
|
40
|
+
when :remove
|
41
|
+
printf("%4s%-6s\n", '', k)
|
42
|
+
v.each { |rk| printf("%20s %s\n", '', rk) }
|
43
|
+
when :rx
|
44
|
+
printf("%4s%-6s\n", '', k)
|
45
|
+
v.each { |rk| printf("%20s %s\n", '', rk) }
|
46
|
+
when :metadata
|
47
|
+
printf("%4s%-6s\n", '', k)
|
48
|
+
metadata_keys = v.keys.map{|k| k.to_s }.sort.map{|k| k.to_sym }
|
49
|
+
metadata_keys.each { |mk| printf("%8s%-12s %s\n", '', mk, v[mk]) }
|
50
|
+
else
|
51
|
+
printf("%4s%-16s %s\n", '', k, v)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def list_profiles
|
58
|
+
profile = Profile.new
|
59
|
+
if profile.empty?
|
60
|
+
puts "No saved profiles."
|
61
|
+
else
|
62
|
+
profile.keys.sort.each do |name|
|
63
|
+
puts "#{name}:"
|
64
|
+
dump_profile(name)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class Profile < DelegateClass(Hash)
|
70
|
+
def initialize
|
71
|
+
@profiles = YAML.load_file(Profile.path)
|
72
|
+
rescue
|
73
|
+
ensure
|
74
|
+
@profiles ||= {}
|
75
|
+
super(@profiles)
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.path
|
79
|
+
File.join(App.data_path, 'profiles')
|
80
|
+
end
|
81
|
+
|
82
|
+
def save
|
83
|
+
File.open(Profile.path, 'w') do |f|
|
84
|
+
YAML.dump(@profiles, f)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'tmpdir'
|
2
|
+
|
3
|
+
# Convenience method to avoid long ifs with nil? and empty?
|
4
|
+
# If receiver is nil or empty? is _true_, returns _value_
|
5
|
+
# Otherwise, returns self
|
6
|
+
#
|
7
|
+
class Object
|
8
|
+
def if_blank(value)
|
9
|
+
self.nil? || self.empty? ? value : self
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Windows still has one-click installer based on Ruby 1.8.6
|
14
|
+
# Add this for compatibility (from Ruby 1.8.7 tmpdir.rb)
|
15
|
+
#
|
16
|
+
if not Dir.respond_to? :mktmpdir
|
17
|
+
class << Dir
|
18
|
+
def mktmpdir(prefix_suffix=nil, tmpdir=nil)
|
19
|
+
case prefix_suffix
|
20
|
+
when nil
|
21
|
+
prefix = "d"
|
22
|
+
suffix = ""
|
23
|
+
when String
|
24
|
+
prefix = prefix_suffix
|
25
|
+
suffix = ""
|
26
|
+
when Array
|
27
|
+
prefix = prefix_suffix[0]
|
28
|
+
suffix = prefix_suffix[1]
|
29
|
+
else
|
30
|
+
raise ArgumentError, "unexpected prefix_suffix: #{prefix_suffix.inspect}"
|
31
|
+
end
|
32
|
+
tmpdir ||= Dir.tmpdir
|
33
|
+
t = Time.now.strftime("%Y%m%d")
|
34
|
+
n = nil
|
35
|
+
begin
|
36
|
+
path = "#{tmpdir}/#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}"
|
37
|
+
path << "-#{n}" if n
|
38
|
+
path << suffix
|
39
|
+
Dir.mkdir(path, 0700)
|
40
|
+
rescue Errno::EEXIST
|
41
|
+
n ||= 0
|
42
|
+
n += 1
|
43
|
+
retry
|
44
|
+
end
|
45
|
+
|
46
|
+
if block_given?
|
47
|
+
begin
|
48
|
+
yield path
|
49
|
+
ensure
|
50
|
+
FileUtils.remove_entry_secure path
|
51
|
+
end
|
52
|
+
else
|
53
|
+
path
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/repub/epub.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'builder'
|
3
|
+
|
4
|
+
module Repub
|
5
|
+
module Epub
|
6
|
+
|
7
|
+
class Container
|
8
|
+
def to_xml
|
9
|
+
out = ''
|
10
|
+
builder = Builder::XmlMarkup.new(:target => out, :indent => 4)
|
11
|
+
builder.instruct!
|
12
|
+
builder.container :xmlns => "urn:oasis:names:tc:opendocument:xmlns:container", :version => "1.0" do
|
13
|
+
builder.rootfiles do
|
14
|
+
builder.rootfile 'full-path' => "content.opf", 'media-type' => "application/oebps-package+xml"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
out
|
18
|
+
end
|
19
|
+
|
20
|
+
def save(path = 'container.xml')
|
21
|
+
File.open(path, 'w') do |f|
|
22
|
+
f << to_xml
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'builder'
|
3
|
+
|
4
|
+
module Repub
|
5
|
+
module Epub
|
6
|
+
|
7
|
+
class Content
|
8
|
+
|
9
|
+
def initialize(uid)
|
10
|
+
@metadata = Metadata.new('Untitled', 'en', uid, Date.today.to_s)
|
11
|
+
@css_counter = 0
|
12
|
+
@img_counter = 0
|
13
|
+
@html_counter = 0
|
14
|
+
@manifest_items = []
|
15
|
+
@spine_items = []
|
16
|
+
@manifest_items << ContentItem.new('ncx', 'toc.ncx', 'application/x-dtbncx+xml')
|
17
|
+
end
|
18
|
+
|
19
|
+
class Metadata < Struct.new(
|
20
|
+
:title,
|
21
|
+
:language,
|
22
|
+
:identifier,
|
23
|
+
:date,
|
24
|
+
:subject,
|
25
|
+
:description,
|
26
|
+
:relation,
|
27
|
+
:creator,
|
28
|
+
:publisher,
|
29
|
+
:rights
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_reader :metadata
|
34
|
+
|
35
|
+
def add_page_template(href = 'page-template.xpgt', id = 'pt')
|
36
|
+
@manifest_items << ContentItem.new(id, href, 'application/vnd.adobe-page-template+xml')
|
37
|
+
end
|
38
|
+
|
39
|
+
def add_stylesheet(href, id = nil)
|
40
|
+
@manifest_items << ContentItem.new(id || "css_#{@css_counter += 1}", href, 'text/css')
|
41
|
+
end
|
42
|
+
|
43
|
+
def add_image(href, id = nil)
|
44
|
+
image_type = case(href.strip.downcase)
|
45
|
+
when /.*\.(jpeg|jpg)$/
|
46
|
+
'image/jpeg'
|
47
|
+
when /.*\.png$/
|
48
|
+
'image/png'
|
49
|
+
when /.*\.gif$/
|
50
|
+
'image/gif'
|
51
|
+
when /.*\.svg$/
|
52
|
+
'image/svg+xml'
|
53
|
+
else
|
54
|
+
raise 'Unsupported image type'
|
55
|
+
end
|
56
|
+
@manifest_items << ContentItem.new(id || "img_#{@img_counter += 1}", href, image_type)
|
57
|
+
end
|
58
|
+
|
59
|
+
def add_document(href, id = nil)
|
60
|
+
manifest_item = ContentItem.new(id || "item_#{@html_counter += 1}", href, 'application/xhtml+xml')
|
61
|
+
@manifest_items << manifest_item
|
62
|
+
@spine_items << manifest_item
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_xml
|
66
|
+
out = ''
|
67
|
+
builder = Builder::XmlMarkup.new(:target => out, :indent => 4)
|
68
|
+
builder.instruct!
|
69
|
+
builder.package :xmlns => "http://www.idpf.org/2007/opf",
|
70
|
+
'unique-identifier' => "dcidid",
|
71
|
+
'version' => "2.0" do
|
72
|
+
metadata_to_xml(builder)
|
73
|
+
manifest_to_xml(@manifest_items, builder)
|
74
|
+
spine_to_xml(@spine_items, builder)
|
75
|
+
end
|
76
|
+
out
|
77
|
+
end
|
78
|
+
|
79
|
+
def save(path = 'content.opf')
|
80
|
+
File.open(path, 'w') do |f|
|
81
|
+
f << to_xml
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def metadata_to_xml(builder)
|
88
|
+
builder.metadata 'xmlns:dc' => "http://purl.org/dc/elements/1.1/",
|
89
|
+
'xmlns:dcterms' => "http://purl.org/dc/terms/",
|
90
|
+
'xmlns:xsi' => "http://www.w3.org/2001/XMLSchema-instance",
|
91
|
+
'xmlns:opf' => "http://www.idpf.org/2007/opf" do
|
92
|
+
# Required elements
|
93
|
+
builder.dc :title do
|
94
|
+
builder << @metadata.title
|
95
|
+
end
|
96
|
+
builder.dc :language, 'xsi:type' => "dcterms:RFC3066" do
|
97
|
+
builder << @metadata.language
|
98
|
+
end
|
99
|
+
builder.dc :identifier, :id => 'dcidid', 'opf:scheme' => 'URI' do
|
100
|
+
builder << @metadata.identifier
|
101
|
+
end
|
102
|
+
# Optional elements
|
103
|
+
builder.dc :subject do
|
104
|
+
builder << @metadata.subject
|
105
|
+
end if @metadata.subject
|
106
|
+
builder.dc :description do
|
107
|
+
builder << @metadata.description
|
108
|
+
end if @metadata.description
|
109
|
+
builder.dc :relation do
|
110
|
+
builder << @metadata.relation
|
111
|
+
end if @metadata.relation
|
112
|
+
builder.dc :creator do
|
113
|
+
builder << @metadata.creator
|
114
|
+
end if @metadata.creator
|
115
|
+
builder.dc :publisher do
|
116
|
+
builder << @metadata.publisher
|
117
|
+
end if @metadata.publisher
|
118
|
+
builder.dc :date do
|
119
|
+
builder << @metadata.date.to_s
|
120
|
+
end if @metadata.date
|
121
|
+
builder.dc :rights do
|
122
|
+
builder << @metadata.rights
|
123
|
+
end if @metadata.rights
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
class ContentItem < Struct.new(
|
128
|
+
:id,
|
129
|
+
:href,
|
130
|
+
:media_type
|
131
|
+
)
|
132
|
+
end
|
133
|
+
|
134
|
+
def manifest_to_xml(manifest_items, builder)
|
135
|
+
builder.manifest do
|
136
|
+
manifest_items.each do |i|
|
137
|
+
builder.item :id => i[:id], :href => i[:href], 'media-type' => i[:media_type]
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def spine_to_xml(spine_items, builder)
|
143
|
+
builder.spine do
|
144
|
+
spine_items.each do |i|
|
145
|
+
builder.itemref :idref => i[:id]
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'builder'
|
3
|
+
|
4
|
+
module Repub
|
5
|
+
module Epub
|
6
|
+
|
7
|
+
class Toc
|
8
|
+
|
9
|
+
def initialize(uid)
|
10
|
+
@head = Head.new(uid)
|
11
|
+
@doc_title = DocTitle.new('Untitled')
|
12
|
+
@nav_map = NavMap.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def title
|
16
|
+
@doc_title.text
|
17
|
+
end
|
18
|
+
|
19
|
+
def title=(text)
|
20
|
+
@doc_title = DocTitle.new(text)
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :nav_map
|
24
|
+
|
25
|
+
def to_xml
|
26
|
+
out = ''
|
27
|
+
builder = Builder::XmlMarkup.new(:target => out, :indent => 4)
|
28
|
+
builder.instruct!
|
29
|
+
builder.declare! :DOCTYPE, :ncx, :PUBLIC, "-//NISO//DTD ncx 2005-1//EN", "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd"
|
30
|
+
builder.ncx :xmlns => "http://www.daisy.org/z3986/2005/ncx/", :version => "2005-1" do
|
31
|
+
@nav_map.calc_depth_and_play_order
|
32
|
+
@head.depth = @nav_map.depth
|
33
|
+
@head.to_xml(builder)
|
34
|
+
@doc_title.to_xml(builder)
|
35
|
+
@nav_map.to_xml(builder)
|
36
|
+
end
|
37
|
+
out
|
38
|
+
end
|
39
|
+
|
40
|
+
def save(path = 'toc.ncx')
|
41
|
+
File.open(path, 'w') do |f|
|
42
|
+
f << to_xml
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class Head < Struct.new(
|
47
|
+
:uid
|
48
|
+
)
|
49
|
+
|
50
|
+
attr_accessor :depth
|
51
|
+
|
52
|
+
def to_xml(builder)
|
53
|
+
builder.head do
|
54
|
+
builder.meta :name => "dtb:uid", :content => self.uid
|
55
|
+
builder.meta :name => "dtb:depth", :content => @depth
|
56
|
+
builder.meta :name => "dtb:totalPageCount", :content => 0
|
57
|
+
builder.meta :name => "dtb:dtb:maxPageNumber", :content => 0
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class DocTitle < Struct.new(
|
63
|
+
:text
|
64
|
+
)
|
65
|
+
|
66
|
+
def to_xml(builder)
|
67
|
+
builder.docTitle do
|
68
|
+
builder.text self.text
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class NavMap
|
74
|
+
class NavPoint < Struct.new(
|
75
|
+
:title,
|
76
|
+
:src
|
77
|
+
)
|
78
|
+
|
79
|
+
def initialize(title, src)
|
80
|
+
super
|
81
|
+
@@last_play_order = 0
|
82
|
+
@play_order = 0
|
83
|
+
@child_points = []
|
84
|
+
end
|
85
|
+
|
86
|
+
def add_nav_point(title, src)
|
87
|
+
nav_point = NavPoint.new(title, src)
|
88
|
+
@child_points << nav_point
|
89
|
+
nav_point
|
90
|
+
end
|
91
|
+
|
92
|
+
def to_xml(builder)
|
93
|
+
builder.navPoint :id => "navPoint-#{@play_order}", :playOrder => @play_order do
|
94
|
+
builder.navLabel do
|
95
|
+
builder.text self.title
|
96
|
+
end
|
97
|
+
builder.content :src => self.src
|
98
|
+
@child_points.each { |child_point| child_point.to_xml(builder) }
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def calc_depth_and_play_order(nav_map, depth)
|
103
|
+
nav_map.depth = depth
|
104
|
+
@play_order = @@last_play_order += 1
|
105
|
+
@child_points.each { |child_point| child_point.calc_depth_and_play_order(nav_map, depth + 1) }
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def initialize
|
110
|
+
@nav_points = []
|
111
|
+
@depth = 1
|
112
|
+
end
|
113
|
+
|
114
|
+
def add_nav_point(title, src)
|
115
|
+
nav_point = NavPoint.new(title, src)
|
116
|
+
@nav_points << nav_point
|
117
|
+
nav_point
|
118
|
+
end
|
119
|
+
|
120
|
+
attr_reader :depth
|
121
|
+
|
122
|
+
def depth=(value)
|
123
|
+
@depth = value if value > @depth
|
124
|
+
end
|
125
|
+
|
126
|
+
def calc_depth_and_play_order
|
127
|
+
@nav_points.each { |nav_point| nav_point.calc_depth_and_play_order(self, 1) }
|
128
|
+
end
|
129
|
+
|
130
|
+
def to_xml(builder)
|
131
|
+
builder.navMap do
|
132
|
+
@nav_points.each { |nav_point| nav_point.to_xml(builder) }
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
end
|