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.
@@ -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,3 @@
1
+ require 'repub/epub/container'
2
+ require 'repub/epub/content'
3
+ require 'repub/epub/toc'
@@ -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