Class: Bookbinder::Package

Inherits:
Object
  • Object
show all
Defined in:
lib/bookbinder/package.rb

Direct Known Subclasses

EPUB, MP3Audiobook, MediaRipper, Openbook

Defined Under Namespace

Classes: EPUB, MP3Audiobook, MediaRipper, Openbook

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializePackage

Returns a new instance of Package.



57
58
59
60
61
62
63
# File 'lib/bookbinder/package.rb', line 57

def initialize
  @content_root = ''
  @file_aliases = {}
  @options = {}
  @files = {}
  reset_transforms
end

Instance Attribute Details

#content_rootObject

Returns the value of attribute content_root.



3
4
5
# File 'lib/bookbinder/package.rb', line 3

def content_root
  @content_root
end

#file_aliasesObject

Returns the value of attribute file_aliases.



3
4
5
# File 'lib/bookbinder/package.rb', line 3

def file_aliases
  @file_aliases
end

#file_systemObject

Returns the value of attribute file_system.



3
4
5
# File 'lib/bookbinder/package.rb', line 3

def file_system
  @file_system
end

#mapObject

Returns the value of attribute map.



3
4
5
# File 'lib/bookbinder/package.rb', line 3

def map
  @map
end

#optionsObject

Returns the value of attribute options.



3
4
5
# File 'lib/bookbinder/package.rb', line 3

def options
  @options
end

#warningsObject (readonly)

Returns the value of attribute warnings.



4
5
6
# File 'lib/bookbinder/package.rb', line 4

def warnings
  @warnings
end

Class Method Details

.build(path) ⇒ Object

Creates a new package of this class pointing at the given path (but not reading from it automatically).



40
41
42
43
44
# File 'lib/bookbinder/package.rb', line 40

def self.build(path)
  new.tap { |pkg|
    pkg.build(path)
  }
end

.read(path) ⇒ Object

Creates a new package of this class by reading content from a path.



50
51
52
53
54
# File 'lib/bookbinder/package.rb', line 50

def self.read(path)
  new.tap { |pkg|
    pkg.read(path)
  }
end

.recognize(path) ⇒ Object

In subclasses, return true if you can handle the path. We’ll iterate through all the descendent classes of Package and return the first matching class.



11
12
13
# File 'lib/bookbinder/package.rb', line 11

def self.recognize(path)
  # IMPLEMENT IN SUBCLASSES
end

.require_transforms(dir) ⇒ Object

This will require() all the .rb files within the given subdirectory of ‘bookbinder/transform’. So, for the EPUB package, you’d just call:

require_transforms('epub')

… to load all the EPUB-specific transforms.



23
24
25
26
27
28
29
# File 'lib/bookbinder/package.rb', line 23

def self.require_transforms(dir)
  Dir.glob(
    File.join(File.dirname(__FILE__), 'transform', dir, '*.rb')
  ).each { |rb|
    require("bookbinder/transform/#{dir}/#{File.basename(rb, '.rb')}")
  }
end

.transformsObject



32
33
34
# File 'lib/bookbinder/package.rb', line 32

def self.transforms
  [] # Implement in subclasses
end

Instance Method Details

#all_book_contentObject



133
134
135
136
137
138
139
# File 'lib/bookbinder/package.rb', line 133

def all_book_content
  book_content = []
  book_content += map['cover'].values  if map['cover']
  book_content += map['spine']  if map['spine']
  book_content += map['resources']  if map['resources']
  book_content
end

#apply_transform(mthd, transform_class) ⇒ Object

Applies the given transform by instantiating the class and invoking the specified method on it. ‘mthd` should be either `to_map` or `from_map`.

Runs all dependencies (that have not already run) first.



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/bookbinder/package.rb', line 226

def apply_transform(mthd, transform_class)
  run = @transformed[mthd] ||= []
  return  if run.include?(transform_class)
  run.push(transform_class)
  transform = transform_class.new
  transform.dependencies.each { |dep| apply_transform(mthd, dep) }
  if transform.respond_to?(mthd)
    # puts("[TRANSFORM] #{transform_class}##{mthd}")
    @transform = transform
    @transform_method = mthd
    @transform.send(mthd, self)
    @transform = nil
    @transform_method = nil
  end
end

#build(path) ⇒ Object

Prepares this package for mapping the contents of the given path.



69
70
71
72
# File 'lib/bookbinder/package.rb', line 69

def build(path)
  @map = {}
  @file_system = file_system_from_path(path)
end

#copy_to(path_or_file_system, content_root) ⇒ Object

Copy all components and resources in the map to the new file system, then creates a new temporary package based on this one and yields it.

We create a new temporary package rather than using the current one so that we can have a different file system (and a different content root if we want that, and fresh hashes for ‘files`, `file_aliases`, etc).



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/bookbinder/package.rb', line 113

def copy_to(path_or_file_system, content_root)
  dest_file_system = path_or_file_system
  if path_or_file_system.kind_of?(String)
    dest_file_system = file_system_from_path(dest_file_system)
  end
  all_book_content.each { |ref|
    rsrc = file(ref['path'])
    dest_path = ref['path']
    if content_root && !content_root.empty?
      dest_path = File.join(content_root, dest_path)
      dest_path = File.expand_path(dest_path, '/')[1..-1]
    end
    rsrc.copy_to(dest_file_system, dest_path)
  }
  self.class.new.tap { |pkg|
    pkg.import(@map, dest_file_system, content_root, @options)
  }
end

#export(package_class) ⇒ Object

Creates a new package of the given class, using our map and file_system, and returns it.



88
89
90
# File 'lib/bookbinder/package.rb', line 88

def export(package_class)
  package_class.new.import(map, file_system, content_root, options)
end

#file(path, root_path = @content_root) ⇒ Object

Returns a file object for the given path within this package – by default, the path is from this package’s content_root.



160
161
162
163
164
165
166
# File 'lib/bookbinder/package.rb', line 160

def file(path, root_path = @content_root)
  if full_path = file_path(path, root_path)
    @files[full_path] ||= Bookbinder::File.new(full_path, @file_system)
  else
    nil
  end
end

#file_path(path, root_path = @content_root) ⇒ Object

Returns the fully-resolved path to a file within this package, converting aliases and applying the content_root if appropriate.



172
173
174
175
176
177
178
179
180
181
# File 'lib/bookbinder/package.rb', line 172

def file_path(path, root_path = @content_root)
  path = file_aliases[path]  if file_aliases[path]
  if path.kind_of?(Symbol)
    nil
  else
    path = File.join(root_path, path)  if root_path && !root_path.empty?
    path = File.expand_path(path, '/')[1..-1]
    path
  end
end

#from_mapObject

This is the default “from_map” behavior, but feel free to override it in subclasses.



213
214
215
216
217
# File 'lib/bookbinder/package.rb', line 213

def from_map
  transforms.each { |transform_class|
    apply_transform(:from_map, transform_class)
  }
end

#if_file(*file_args) {|f| ... } ⇒ Object

Yields the file only if it exists.

Yields:

  • (f)


186
187
188
189
# File 'lib/bookbinder/package.rb', line 186

def if_file(*file_args)
  f = file(*file_args)
  yield(f)  if f && f.exists?
end

#import(map, file_system, content_root, options) ⇒ Object

Copies state: a map, a file-system, and a content root. That should be enough to be a fully independent package.



96
97
98
99
100
101
102
# File 'lib/bookbinder/package.rb', line 96

def import(map, file_system, content_root, options)
  @map = duplicate_map(map)
  @file_system = file_system
  @content_root = content_root
  @options = options.clone
  self
end

#read(path) ⇒ Object

Reads the path and generates a hash “map” of the book, returning it.



78
79
80
81
82
# File 'lib/bookbinder/package.rb', line 78

def read(path)
  build(path)
  to_map
  @map
end

#reset_transformsObject



243
244
245
246
# File 'lib/bookbinder/package.rb', line 243

def reset_transforms
  @warnings = []
  @transformed = {}
end

#save_open_filesObject

Any file that is marked ‘dirty’ (because it has been opened in ‘w’ mode) will be written out to its file-system.



262
263
264
# File 'lib/bookbinder/package.rb', line 262

def save_open_files
  @files.each_value { |file| file.save }
end

#to_mapObject

This is the default “to_map” behavior, but feel free to override it in subclasses.



203
204
205
206
207
# File 'lib/bookbinder/package.rb', line 203

def to_map
  transforms.each { |transform_class|
    apply_transform(:to_map, transform_class)
  }
end

#transformsObject

The list of transforms to be applied when mapping or writing this package.



195
196
197
# File 'lib/bookbinder/package.rb', line 195

def transforms
  self.class.transforms
end

#warn(msg) ⇒ Object



249
250
251
252
253
254
255
256
# File 'lib/bookbinder/package.rb', line 249

def warn(msg)
  @warnings.push({
    :transform => @transform.class,
    :method => @transform_method,
    :message => msg
  })
  nil
end

#write(path) ⇒ Object

Writes package to path. This is destructive! Anything already at the path will be replaced.



145
146
147
148
149
150
151
152
153
154
# File 'lib/bookbinder/package.rb', line 145

def write(path)
  tmp_path = Dir::Tmpname.create(File.basename(path)) { |tmp_path|
    raise Errno::EEXIST  if File.exists?(tmp_path)
  }
  dest_fs = file_system_from_path(tmp_path)
  write_to_file_system(dest_fs)
  dest_fs.close  if dest_fs.respond_to?(:close)
  FileUtils.rm_r(path)  if File.exists?(path)
  FileUtils.move(tmp_path, path)
end