repub 0.3.0

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