texzip 0.1.8 → 0.1.9

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.
@@ -8,522 +8,520 @@ class TeXzip::Error < Exception; end
8
8
 
9
9
  class TeXzip::Project < HighLine
10
10
 
11
- PACKAGE_EXTENSIONS = %w(.sty .cls)
12
- IMAGE_EXTENSIONS = %w(.png .jpg .pdf .eps .pstex)
13
- TEXIMAGE_EXTENSIONS = %w(.pspdftex .pdf_t .pstex_t)
14
-
15
- class Quit < Exception; end
16
-
17
- attr_accessor :overwrite_all
18
-
19
- class FilePath
20
- def initialize(root_dir, file)
21
- @root_dir = Pathname.new(root_dir).expand_path
22
- @file = Pathname.new(file)
23
- @file = @root_dir.join(file).expand_path.relative_path_from(@root_dir)
24
- @out_file = @file
25
- end
26
-
27
- def set_output_directory dir
28
- @out_dir = Pathname.new(dir).expand_path
29
- end
30
-
31
- def set_plain_output_directory dir
32
- set_output_directory dir
33
- @out_file = @file.basename
34
- end
35
-
36
- def file
37
- @file
38
- end
39
-
40
- def out_file= file
41
- @out_file = Pathname.new(file)
42
- end
43
-
44
- def output_path
45
- @out_dir.join(@out_file).expand_path
46
- end
47
-
48
- def path
49
- @root_dir.join(@file).expand_path
50
- end
51
-
52
- def extname
53
- @file.extname
54
- end
55
-
56
- def hash
57
- path.to_s.hash
58
- end
59
-
60
- def eql?(file_path)
61
- path.to_s.eql? file_path.path.to_s
62
- end
63
- end
64
-
65
- def initialize( master_file )
66
- super()
67
- @tex_master_file = Pathname.new(master_file).expand_path
68
-
69
- # All possible include-paths for TeX
70
- @tex_dirs = [@tex_master_file.dirname]
71
- @tex_dirs.concat((ENV["TEXINPUTS"] || "").split(':').map{|d| Pathname.new(d)})
72
- @tex_dirs.map! &:expand_path
73
- @tex_dirs.uniq!
74
-
75
- @overwrite_all = false
76
-
77
- parse_files
78
- end
79
-
80
- def parse_files
81
- # The hash of all files, including the whole text.
82
- @tex_files = {}
83
- @image_files = Set.new
84
- @bib_files = Set.new
85
- @cites = Set.new
86
-
87
- # Read all files recursively
88
- unparsed_files = [@tex_master_file]
89
- until unparsed_files.empty?
90
- fname = unparsed_files.pop
91
- file = find_file( fname )
92
- if file.nil? then
93
- if PACKAGE_EXTENSIONS.include? File.extname(fname)
94
- next
95
- else
96
- raise TeXzip::Error, "Can't find file: #{fname}"
97
- end
98
- end
99
-
100
- unless @tex_files.has_key? file
101
- included_files = parse_file file
102
- unparsed_files.concat included_files
103
- end
104
- end
105
-
106
- unless @bib_files.empty?
107
- @bib = BibTeX::Bibliography.new
108
- @bib_files.each do |bib_file|
109
- bib = BibTeX.open(bib_file.path)
110
- bib.replace_strings
111
- @bib.add(bib.data)
112
- end
113
- else
114
- @bib = nil
115
- end
116
- end
117
-
118
- # Returns the master-file's path.
119
- def master_file
120
- @tex_master_file
121
- end
122
-
123
- # Returns a list of included tex and sty files.
124
- # @return [Array<Pathname>] Included tex files.
125
- def tex_files
126
- @tex_files.keys
127
- end
128
-
129
- # Returns a list of included image-files.
130
- # @return [Array<Pathname>] Included image files.
131
- def image_files
132
- @image_files.to_a
133
- end
134
-
135
- # Returns a list of included BibTeX-files.
136
- # @return [Array<Pathname>] Included BibTeX files.
137
- def bib_files
138
- @bib_files.to_a
139
- end
140
-
141
- # Returns a list of citations.
142
- # @return [Array<String>] Citations.
143
- def cites
144
- @cites.to_a
145
- end
146
-
147
- # Returns the full path for a certain file.
148
- #
149
- # The file is searched in the current directory as well as all
150
- # directories given by the environment variable +TEXINPUTS+
151
- #
152
- # @param [String] file The (relative) path of the file.
153
- # @param [Array<String>] extensions The (possible) file extensions.
154
- # @return [Pathname,nil] The path to the file if exist.
155
- def find_file( file, extensions = [] )
156
- extensions.unshift "" # the empty extension
157
- extensions.uniq!
158
-
159
- @tex_dirs.each do |d|
160
- extensions.each do |ext|
161
- file_path = d.join(file + ext).expand_path
162
- if File.file? file_path
163
- return FilePath.new(d, file + ext)
164
- end
165
- end
166
- end
167
- return nil
168
- end
169
-
170
- # Returns the full paths for all variants of a certain file.
171
- #
172
- # The files are searched in the current directory as well as all
173
- # directories given by the environment variable +TEXINPUTS+
174
- #
175
- # @param [String] file The base file-name.
176
- # @param [Array<String>] extensions The possible file-extensions.
177
- # @return [Array<Pathname>] All found files.
178
- def find_files( file, extensions )
179
- extensions.uniq!
180
-
181
- files = []
182
-
183
- extensions.each do |ext|
184
- @tex_dirs.each do |d|
185
- file_path = d.join(file + ext).expand_path
186
- if file_path.file?
187
- files << FilePath.new(d, file + ext)
188
- break
189
- end
190
- end
191
- end
192
-
193
- files
194
- end
195
-
196
- # Load and parse a single tex-file.
197
- #
198
- # The file is parsed for commands including images, BibTeX-files
199
- # and citations. The command along with the command's argument is
200
- # passed to the block. The block is assumed to return a list of
201
- # further tex-files to be parsed.
202
- #
203
- # @param [Pathname,String] file_name The name of the TeX-file to parse
204
- # @return [Array<String>] A list of included TeX-files.
205
- def parse_file file_name, &block
206
- text = nil
207
- File.open(file_name.path, "rb") do |f|
208
- text = f.read
209
- end
210
- @tex_files[file_name] = text
211
-
212
- block = method(:handle_command) unless block
213
-
214
- included_files = []
215
- text_without_comments = ""
216
- text.each_line do |line|
217
- comment_match = line.match /(?:\\)*%/
218
- if comment_match and (comment_match.end(0) - comment_match.begin(0)).odd?
219
- line = line[0...comment_match.end(0)]
220
- end
221
- text_without_comments.concat line
222
- end
223
-
224
- text_without_comments.scan(/\\(documentclass|usepackage|include|input|includegraphics|bibliography|cite)(?:\[[^\]]+\])?\{([^}]+)\}/) do |cmd, arg|
225
- new_files = block.call cmd, arg
226
- included_files.concat new_files if new_files
227
- end
228
- included_files
229
- end
230
-
231
- # Handles parsed commands.
232
- def handle_command command, argument
233
- case command
234
- when "includegraphics"
235
- add_image argument
236
- when "bibliography"
237
- argument.split(',').uniq.each{|f| add_bib f.strip}
238
- when "usepackage"
239
- return [argument + ".sty"]
240
- when "documentclass"
241
- return [argument + ".cls"]
242
- when "cite"
243
- @cites.merge argument.split(',').map(&:strip)
244
- else
245
- ext = File.extname(argument)
246
- if TEXIMAGE_EXTENSIONS.include?(ext)
247
- file = find_file(argument)
248
- unless file
249
- puts "WARNING: Can't find tex-image file #{argument}"
250
- return nil
251
- end
252
- dir = File.dirname(argument)
253
- parse_file file do |command, arg|
254
- if command == "includegraphics"
255
- add_image File.join(dir, arg)
256
- else
257
- raise TeXzip::Error, "Unexpected command '\\#{command}' in tex-image file: \\#{argument}"
258
- end
259
- nil
260
- end
261
- elsif ext != ".tex"
262
- argument += ".tex"
263
- end
264
- return [argument]
265
- end
266
- return nil
267
- end
268
-
269
- # Adds an image to the list of included images.
270
- # @param [String] image_file_name The path of the image-file
271
- def add_image image_file_name
272
- ext = File.extname(image_file_name)
273
- if ext == ""
274
- image_files = find_files( image_file_name, IMAGE_EXTENSIONS )
275
- else
276
- image_files = [find_file( image_file_name )].compact
277
- end
278
-
279
- if image_files.empty?
280
- puts "WARNING: Can't find included image #{image_file_name}"
281
- else
282
- @image_files.merge image_files
283
- end
284
- end
285
-
286
- # Adds a BibTeX-file to the list of included BibTeX-files.
287
- # @param [String] image_file_name The path of the BibTeX-file.
288
- def add_bib bib_file_name
289
- bib_file = find_file( bib_file_name, [".bib"] )
290
-
291
- if bib_file.nil?
292
- puts "WARNING: Can't find included BibTeX file #{bib_file_name}"
293
- else
294
- @bib_files.add bib_file
295
- end
296
- end
297
-
298
- def modify_files outdir, image_dir, plain_img, bibtex_file
299
- @output_directory = Pathname.new(outdir).expand_path
300
- @output_image_directory = @output_directory.join(Pathname.new(image_dir)).expand_path
301
- @output_bibtex = @output_directory.join(Pathname.new(bibtex_file)).expand_path
302
- @modified_files = {}
303
-
304
- plain_img_files = Hash.new{|h,k| h[k] = []}
305
-
306
- @tex_files.each_key do |file|
307
- if TEXIMAGE_EXTENSIONS.include? file.extname
308
- if plain_img
309
- file.set_plain_output_directory @output_image_directory
310
- plain_img_files[file.file.to_s] << file
311
- else
312
- file.set_output_directory @output_image_directory
313
- end
314
- else
315
- file.set_output_directory @output_directory
316
- end
317
- end
318
- @tex_files.each_pair do |file, text|
319
- @modified_files[file] = update_file file, text, plain_img
320
- end
321
-
322
- @image_files.each do |file|
323
- if plain_img
324
- file.set_plain_output_directory @output_image_directory
325
- plain_img_files[file.file.to_s] << file
326
- else
327
- file.set_output_directory @output_image_directory
328
- end
329
- end
330
-
331
- # maybe rename some files
332
- plain_img_files.each_pair do |fname, files|
333
- ext = File.extname(fname)
334
- next if files.size <= 1
335
- # TODO: not supported when updating references
336
- raise "Multiple images with the same name but different directories"
337
- cnt = 2
338
- files.each do |file|
339
- loop do
340
- new_fname = fname.basename.sub_ext("#{cnt}#{ext}")
341
- if plain_img_files.include?(new_fname.to_s)
342
- cnt += 1
343
- else
344
- file.out_file = new_fname
345
- break
346
- end
347
- end
348
- end
349
- end
350
-
351
- filter_bibtex
352
- end
353
-
354
- def update_file tex_file, text, plain
355
- ext = tex_file.path.extname
356
-
357
- new_text = ""
358
- text.each_line do |line|
359
- comment_match = line.match /(?:\\)*%/
360
- if comment_match and (comment_match.end(0) - comment_match.begin(0)).odd?
361
- comment = line[comment_match.end(0) .. -1]
362
- line = line[0...comment_match.end(0)]
363
- else
364
- comment = ""
365
- end
366
- new_line = line.gsub(/(\\(include|input|includegraphics|bibliography)(?:\[[^\]]+\])?)\{([^}]+)\}/) { |m|
367
- start = $1
368
- cmd = $2
369
- file = $3
370
- if cmd == "includegraphics"
371
- if TEXIMAGE_EXTENSIONS.include? ext
372
- file = File.join(tex_file.file.dirname, file)
373
- end
374
- new_file = @output_image_directory.join(Pathname.new(file)).relative_path_from(@output_directory)
375
- # TODO: this does not support multiple files with same name
376
- new_file = @output_image_directory.join(new_file.basename).relative_path_from(@output_directory) if plain
377
- elsif cmd == "bibliography"
378
- new_file = @output_bibtex.basename.to_s.gsub(/\.bib$/, '')
379
- else
380
- if TEXIMAGE_EXTENSIONS.include? File.extname(file)
381
- new_file = @output_image_directory.join(Pathname.new(file)).relative_path_from(@output_directory)
382
- # TODO: this does not support multiple files with same name
383
- new_file = @output_image_directory.join(new_file.basename).relative_path_from(@output_directory) if plain
384
- else
385
- new_file = @output_directory.join(Pathname.new(file)).relative_path_from(@output_directory)
386
- end
387
- end
388
- "#{start}{#{new_file}}"
389
- }
390
- new_text.concat new_line
391
- new_text.concat comment
392
- end
393
-
394
- return new_text
395
- end
396
-
397
- def filter_bibtex
398
- if @bib
399
- cites = @cites.to_a.map{|c| c.to_s}
400
- seen_cites = cites.to_set
401
- until cites.empty?
402
- cite = cites.pop
403
- entries = @bib[cite]
404
- if entries.nil?
405
- puts "WARNING: Can't find BibTeX-entry #{cite}"
406
- else
407
- entries = [entries] unless entries.kind_of? Array
408
- entries.each do |entry|
409
- crossref = entry["crossref"]
410
- if crossref
411
- crossref.split(',').map(&:strip).each do |ref|
412
- ref = ref.to_s
413
- cites << ref if seen_cites.add? ref
414
- end
415
- end
416
- end
417
- end
418
- end
419
-
420
- @bib = BibTeX::Bibliography.new.add(@bib.data.select{|entry| seen_cites.include? entry.key.to_s})
421
- end
422
- end
423
-
424
- def write_files( force = false )
425
- cwd = Pathname.getwd.expand_path
426
- write_data do |path, data|
427
- puts "Write file #{path.relative_path_from(cwd)}"
428
- FileUtils.mkdir_p path.dirname unless path.dirname.exist?
429
- if data.kind_of? Pathname
430
- FileUtils.copy data, path
431
- else
432
- File.open(path, "wb") do |f|
433
- f.write data
434
- end
435
- end
436
- end
437
- end
438
-
439
- def write_archive( archive_file, force = false )
440
- require 'ffi-libarchive'
441
-
442
- archive_file = Pathname.new(archive_file).expand_path
443
- return unless ask_overwrite(archive_file)
444
-
445
- compression = case File.basename(archive_file.to_s)
446
- when /\.tgz$/, /\.tar\.gz$/
447
- :gzip
448
- when /\.tbz2$/, /\.tar\.bz2$/
449
- :bzip2
450
- when /\.txz$/, /\.tar\.xz$/
451
- :xz
452
- when /\.tlzma$/, /\.tar\.lzma$/
453
- :lzma
454
- when /\.tZ$/, /\.tar\.Z$/
455
- :Z
456
- when /\.tar$/
457
- :none
458
- else
459
- raise TeXzip::Error, "Can't derive archive-type from file name #{archive_file}"
460
- end
461
-
462
- puts "Write archive #{archive_file.relative_path_from(Pathname.getwd)}"
463
- Archive.write_open_filename archive_file.to_s, compression, :tar do |ar|
464
- write_data true do |path, data|
465
- ar.add_entry do |e|
466
- e.pathname = path.relative_path_from(@output_directory).to_s
467
- if data.kind_of? Pathname
468
- e.copy_stat(data.to_s)
469
- File.open(data, "rb", &:read)
470
- else
471
- e.mode = 0644
472
- e.atime = Time.now
473
- e.mtime = Time.now
474
- e.filetype = :file
475
- data
476
- end
477
- end
478
- end
479
- end
480
- end
481
-
482
- def write_data( force = false, &block )
483
- raise ArgumentError, "Block required" unless block
484
-
485
- overwrite_all = force
486
- commands = []
487
-
488
- @modified_files.each_pair do |file, text|
489
- if force or ask_overwrite(file.output_path)
490
- commands << [file.output_path, text]
491
- end
492
- end
493
-
494
- @image_files.each do |file|
495
- if force or ask_overwrite(file.output_path)
496
- commands << [file.output_path, file.path]
497
- end
498
- end
499
-
500
- if @bib and (force or ask_overwrite(@output_bibtex))
501
- commands << [@output_bibtex, @bib.to_s]
502
- end
503
-
504
- commands.each do |path, data|
505
- block.call path, data
506
- end
507
- end
508
-
509
- def ask_overwrite file
510
- if !@overwrite_all and File.exist?(file)
511
- ask("File #{file.relative_path_from(Pathname.getwd)} exists. Overwrite? [Ynaq]") do |q|
512
- q.character = true
513
- q.validate = /[ynaq\r ]/
514
- q.case = :down
515
- q.overwrite = false
516
- q.answer_type = lambda{ |c|
517
- case c
518
- when "q"; raise Quit
519
- when "y"; true
520
- when "n"; false
521
- when "a"; @overwrite_all = true; true
522
- end
523
- }
524
- end
525
- else
526
- true
527
- end
528
- end
11
+ PACKAGE_EXTENSIONS = %w(.sty .cls).freeze
12
+ IMAGE_EXTENSIONS = %w(.png .jpg .pdf .eps .pstex).freeze
13
+ TEXIMAGE_EXTENSIONS = %w(.pspdftex .pdf_t .pstex_t).freeze
14
+
15
+ class Quit < Exception; end
16
+
17
+ attr_accessor :overwrite_all
18
+
19
+ class FilePath
20
+ def initialize(root_dir, file)
21
+ @root_dir = Pathname.new(root_dir).expand_path
22
+ @file = Pathname.new(file)
23
+ @file = @root_dir.join(file).expand_path.relative_path_from(@root_dir)
24
+ @out_file = @file
25
+ end
26
+
27
+ def set_output_directory(dir)
28
+ @out_dir = Pathname.new(dir).expand_path
29
+ end
30
+
31
+ def set_plain_output_directory(dir)
32
+ set_output_directory dir
33
+ @out_file = @file.basename
34
+ end
35
+
36
+ def file
37
+ @file
38
+ end
39
+
40
+ def out_file=(file)
41
+ @out_file = Pathname.new(file)
42
+ end
43
+
44
+ def output_path
45
+ @out_dir.join(@out_file).expand_path
46
+ end
47
+
48
+ def path
49
+ @root_dir.join(@file).expand_path
50
+ end
51
+
52
+ def extname
53
+ @file.extname
54
+ end
55
+
56
+ def hash
57
+ path.to_s.hash
58
+ end
59
+
60
+ def eql?(other)
61
+ path.to_s.eql? other.path.to_s
62
+ end
63
+ end
64
+
65
+ def initialize(master_file)
66
+ super()
67
+ @tex_master_file = Pathname.new(master_file).expand_path
68
+
69
+ # All possible include-paths for TeX
70
+ @tex_dirs = [@tex_master_file.dirname]
71
+ @tex_dirs.concat((ENV['TEXINPUTS'] || '').split(':').map {|d| Pathname.new(d)})
72
+ @tex_dirs.map!(&:expand_path)
73
+ @tex_dirs.uniq!
74
+
75
+ @overwrite_all = false
76
+
77
+ parse_files
78
+ end
79
+
80
+ def parse_files
81
+ # The hash of all files, including the whole text.
82
+ @tex_files = {}
83
+ @image_files = Set.new
84
+ @bib_files = Set.new
85
+ @cites = Set.new
86
+
87
+ # Read all files recursively
88
+ unparsed_files = [@tex_master_file]
89
+ until unparsed_files.empty?
90
+ fname = unparsed_files.pop
91
+ file = find_file(fname)
92
+ if file.nil? then
93
+ if PACKAGE_EXTENSIONS.include?(File.extname(fname))
94
+ next
95
+ else
96
+ raise TeXzip::Error, "Can't find file: #{fname}"
97
+ end
98
+ end
99
+
100
+ unless @tex_files.has_key?(file)
101
+ included_files = parse_file file
102
+ unparsed_files.concat included_files
103
+ end
104
+ end
105
+
106
+ if !@bib_files.empty?
107
+ @bib = nil
108
+ else
109
+ @bib = BibTeX::Bibliography.new
110
+ @bib_files.each do |bib_file|
111
+ bib = BibTeX.open(bib_file.path)
112
+ bib.replace_strings
113
+ @bib.add(bib.data)
114
+ end
115
+ end
116
+ end
117
+
118
+ # Returns the master-file's path.
119
+ def master_file
120
+ @tex_master_file
121
+ end
122
+
123
+ # Returns a list of included tex and sty files.
124
+ # @return [Array<Pathname>] Included tex files.
125
+ def tex_files
126
+ @tex_files.keys
127
+ end
128
+
129
+ # Returns a list of included image-files.
130
+ # @return [Array<Pathname>] Included image files.
131
+ def image_files
132
+ @image_files.to_a
133
+ end
134
+
135
+ # Returns a list of included BibTeX-files.
136
+ # @return [Array<Pathname>] Included BibTeX files.
137
+ def bib_files
138
+ @bib_files.to_a
139
+ end
140
+
141
+ # Returns a list of citations.
142
+ # @return [Array<String>] Citations.
143
+ def cites
144
+ @cites.to_a
145
+ end
146
+
147
+ # Returns the full path for a certain file.
148
+ #
149
+ # The file is searched in the current directory as well as all
150
+ # directories given by the environment variable +TEXINPUTS+
151
+ #
152
+ # @param [String] file The (relative) path of the file.
153
+ # @param [Array<String>] extensions The (possible) file extensions.
154
+ # @return [Pathname,nil] The path to the file if exist.
155
+ def find_file(file, extensions = [])
156
+ extensions.unshift '' # the empty extension
157
+ extensions.uniq!
158
+
159
+ @tex_dirs.each do |d|
160
+ extensions.each do |ext|
161
+ file_path = d.join(file + ext).expand_path
162
+ return FilePath.new(d, file + ext) if File.file?(file_path)
163
+ end
164
+ end
165
+ nil
166
+ end
167
+
168
+ # Returns the full paths for all variants of a certain file.
169
+ #
170
+ # The files are searched in the current directory as well as all
171
+ # directories given by the environment variable +TEXINPUTS+
172
+ #
173
+ # @param [String] file The base file-name.
174
+ # @param [Array<String>] extensions The possible file-extensions.
175
+ # @return [Array<Pathname>] All found files.
176
+ def find_files(file, extensions)
177
+ extensions.uniq!
178
+
179
+ files = []
180
+
181
+ extensions.each do |ext|
182
+ @tex_dirs.each do |d|
183
+ file_path = d.join(file + ext).expand_path
184
+ if file_path.file?
185
+ files << FilePath.new(d, file + ext)
186
+ break
187
+ end
188
+ end
189
+ end
190
+
191
+ files
192
+ end
193
+
194
+ # Load and parse a single tex-file.
195
+ #
196
+ # The file is parsed for commands including images, BibTeX-files
197
+ # and citations. The command along with the command's argument is
198
+ # passed to the block. The block is assumed to return a list of
199
+ # further tex-files to be parsed.
200
+ #
201
+ # @param [Pathname,String] file_name The name of the TeX-file to parse
202
+ # @return [Array<String>] A list of included TeX-files.
203
+ def parse_file(file_name, &block)
204
+ text = nil
205
+ File.open(file_name.path, 'rb') do |f|
206
+ text = f.read
207
+ end
208
+ @tex_files[file_name] = text
209
+
210
+ block = method(:handle_command) unless block
211
+
212
+ included_files = []
213
+ text_without_comments = ''
214
+ text.each_line do |line|
215
+ comment_match = line.match(/(?:\\)*%/)
216
+ if comment_match && (comment_match.end(0) - comment_match.begin(0)).odd?
217
+ line = line[0...comment_match.end(0)]
218
+ end
219
+ text_without_comments.concat line
220
+ end
221
+
222
+ text_without_comments.scan(/\\(documentclass|usepackage|include|input|includegraphics|bibliography|cite)(?:\[[^\]]+\])?\{([^}]+)\}/) do |cmd, arg|
223
+ new_files = block.call(cmd, arg)
224
+ included_files.concat(new_files) if new_files
225
+ end
226
+ included_files
227
+ end
228
+
229
+ # Handles parsed commands.
230
+ def handle_command(command, argument)
231
+ case command
232
+ when 'includegraphics'
233
+ add_image argument
234
+ when 'bibliography'
235
+ argument.split(',').uniq.each {|f| add_bib f.strip}
236
+ when 'usepackage'
237
+ return [argument + '.sty']
238
+ when 'documentclass'
239
+ return [argument + '.cls']
240
+ when 'cite'
241
+ @cites.merge argument.split(',').map(&:strip)
242
+ else
243
+ ext = File.extname(argument)
244
+ if TEXIMAGE_EXTENSIONS.include?(ext)
245
+ file = find_file(argument)
246
+ unless file
247
+ puts "WARNING: Can't find tex-image file #{argument}"
248
+ return nil
249
+ end
250
+ dir = File.dirname(argument)
251
+ parse_file file do |command, arg|
252
+ if command == 'includegraphics'
253
+ add_image File.join(dir, arg)
254
+ else
255
+ raise TeXzip::Error, "Unexpected command '\\#{command}' in tex-image file: \\#{argument}"
256
+ end
257
+ nil
258
+ end
259
+ elsif ext != '.tex'
260
+ argument += '.tex'
261
+ end
262
+ return [argument]
263
+ end
264
+ nil
265
+ end
266
+
267
+ # Adds an image to the list of included images.
268
+ # @param [String] image_file_name The path of the image-file
269
+ def add_image(image_file_name)
270
+ ext = File.extname(image_file_name)
271
+ image_files = if ext == ''
272
+ find_files(image_file_name, IMAGE_EXTENSIONS)
273
+ else
274
+ [find_file(image_file_name)].compact
275
+ end
276
+
277
+ if image_files.empty?
278
+ puts "WARNING: Can't find included image #{image_file_name}"
279
+ else
280
+ @image_files.merge image_files
281
+ end
282
+ end
283
+
284
+ # Adds a BibTeX-file to the list of included BibTeX-files.
285
+ # @param [String] image_file_name The path of the BibTeX-file.
286
+ def add_bib(bib_file_name)
287
+ bib_file = find_file(bib_file_name, ['.bib'])
288
+
289
+ if bib_file.nil?
290
+ puts "WARNING: Can't find included BibTeX file #{bib_file_name}"
291
+ else
292
+ @bib_files.add bib_file
293
+ end
294
+ end
295
+
296
+ def modify_files(outdir, image_dir, plain_img, bibtex_file)
297
+ @output_directory = Pathname.new(outdir).expand_path
298
+ @output_image_directory = @output_directory.join(Pathname.new(image_dir)).expand_path
299
+ @output_bibtex = @output_directory.join(Pathname.new(bibtex_file)).expand_path
300
+ @modified_files = {}
301
+
302
+ plain_img_files = Hash.new {|h,k| h[k] = []}
303
+
304
+ @tex_files.each_key do |file|
305
+ if TEXIMAGE_EXTENSIONS.include? file.extname
306
+ if plain_img
307
+ file.set_plain_output_directory @output_image_directory
308
+ plain_img_files[file.file.to_s] << file
309
+ else
310
+ file.set_output_directory @output_image_directory
311
+ end
312
+ else
313
+ file.set_output_directory @output_directory
314
+ end
315
+ end
316
+ @tex_files.each_pair do |file, text|
317
+ @modified_files[file] = update_file file, text, plain_img
318
+ end
319
+
320
+ @image_files.each do |file|
321
+ if plain_img
322
+ file.set_plain_output_directory @output_image_directory
323
+ plain_img_files[file.file.to_s] << file
324
+ else
325
+ file.set_output_directory @output_image_directory
326
+ end
327
+ end
328
+
329
+ # maybe rename some files
330
+ plain_img_files.each_pair do |fname, files|
331
+ ext = File.extname(fname)
332
+ next if files.size <= 1
333
+ # TODO: not supported when updating references
334
+ raise 'Multiple images with the same name but different directories'
335
+ cnt = 2
336
+ files.each do |file|
337
+ loop do
338
+ new_fname = fname.basename.sub_ext("#{cnt}#{ext}")
339
+ if plain_img_files.include?(new_fname.to_s)
340
+ cnt += 1
341
+ else
342
+ file.out_file = new_fname
343
+ break
344
+ end
345
+ end
346
+ end
347
+ end
348
+
349
+ filter_bibtex
350
+ end
351
+
352
+ def update_file(tex_file, text, plain)
353
+ ext = tex_file.path.extname
354
+
355
+ new_text = ''
356
+ text.each_line do |line|
357
+ comment_match = line.match(/(?:\\)*%/)
358
+ if comment_match && (comment_match.end(0) - comment_match.begin(0)).odd?
359
+ comment = line[comment_match.end(0)..-1]
360
+ line = line[0...comment_match.end(0)]
361
+ else
362
+ comment = ''
363
+ end
364
+ new_line = line.gsub(/(\\(include|input|includegraphics|bibliography)(?:\[[^\]]+\])?)\{([^}]+)\}/) do |m|
365
+ start = $1
366
+ cmd = $2
367
+ file = $3
368
+ if cmd == 'includegraphics'
369
+ if TEXIMAGE_EXTENSIONS.include? ext
370
+ file = File.join(tex_file.file.dirname, file)
371
+ end
372
+ new_file = @output_image_directory.join(Pathname.new(file)).relative_path_from(@output_directory)
373
+ # TODO: this does not support multiple files with same name
374
+ new_file = @output_image_directory.join(new_file.basename).relative_path_from(@output_directory) if plain
375
+ elsif cmd == 'bibliography'
376
+ new_file = @output_bibtex.basename.to_s.gsub(/\.bib$/, '')
377
+ elsif TEXIMAGE_EXTENSIONS.include?(File.extname(file))
378
+ new_file = @output_image_directory.join(Pathname.new(file)).relative_path_from(@output_directory)
379
+ # TODO: this does not support multiple files with same name
380
+ new_file = @output_image_directory.join(new_file.basename).relative_path_from(@output_directory) if plain
381
+ else
382
+ new_file = @output_directory.join(Pathname.new(file)).relative_path_from(@output_directory)
383
+ end
384
+ "#{start}{#{new_file}}"
385
+ end
386
+ new_text.concat new_line
387
+ new_text.concat comment
388
+ end
389
+
390
+ new_text
391
+ end
392
+
393
+ def filter_bibtex
394
+ if @bib
395
+ cites = @cites.to_a.map(&:to_s)
396
+ seen_cites = cites.to_set
397
+ until cites.empty?
398
+ cite = cites.pop
399
+ entries = @bib[cite]
400
+ if entries.nil?
401
+ puts "WARNING: Can't find BibTeX-entry #{cite}"
402
+ else
403
+ entries = [entries] unless entries.is_a?(Array)
404
+ entries.each do |entry|
405
+ crossref = entry['crossref']
406
+ if crossref
407
+ crossref.split(',').map(&:strip).each do |ref|
408
+ ref = ref.to_s
409
+ cites << ref if seen_cites.add? ref
410
+ end
411
+ end
412
+ end
413
+ end
414
+ end
415
+
416
+ @bib = BibTeX::Bibliography.new.add(@bib.data.select {|entry| seen_cites.include? entry.key.to_s})
417
+ end
418
+ end
419
+
420
+ def write_files(force = false)
421
+ cwd = Pathname.getwd.expand_path
422
+ write_data do |path, data|
423
+ puts "Write file #{path.relative_path_from(cwd)}"
424
+ FileUtils.mkdir_p path.dirname unless path.dirname.exist?
425
+ if data.is_a? Pathname
426
+ FileUtils.copy data, path
427
+ else
428
+ File.open(path, 'wb') do |f|
429
+ f.write data
430
+ end
431
+ end
432
+ end
433
+ end
434
+
435
+ def write_archive(archive_file, force = false)
436
+ require 'ffi-libarchive'
437
+
438
+ archive_file = Pathname.new(archive_file).expand_path
439
+ return unless ask_overwrite(archive_file)
440
+
441
+ compression = case File.basename(archive_file.to_s)
442
+ when /\.tgz$/, /\.tar\.gz$/
443
+ :gzip
444
+ when /\.tbz2$/, /\.tar\.bz2$/
445
+ :bzip2
446
+ when /\.txz$/, /\.tar\.xz$/
447
+ :xz
448
+ when /\.tlzma$/, /\.tar\.lzma$/
449
+ :lzma
450
+ when /\.tZ$/, /\.tar\.Z$/
451
+ :Z
452
+ when /\.tar$/
453
+ :none
454
+ else
455
+ raise TeXzip::Error, "Can't derive archive-type from file name #{archive_file}"
456
+ end
457
+
458
+ puts "Write archive #{archive_file.relative_path_from(Pathname.getwd)}"
459
+ Archive.write_open_filename archive_file.to_s, compression, :tar do |ar|
460
+ write_data true do |path, data|
461
+ ar.add_entry do |e|
462
+ e.pathname = path.relative_path_from(@output_directory).to_s
463
+ if data.is_a? Pathname
464
+ e.copy_stat(data.to_s)
465
+ File.open(data, 'rb', &:read)
466
+ else
467
+ e.mode = 0644
468
+ e.atime = Time.now
469
+ e.mtime = Time.now
470
+ e.filetype = :file
471
+ data
472
+ end
473
+ end
474
+ end
475
+ end
476
+ end
477
+
478
+ def write_data(force = false)
479
+ raise ArgumentError, 'Block required' unless block_given?
480
+
481
+ overwrite_all = force
482
+ commands = []
483
+
484
+ @modified_files.each_pair do |file, text|
485
+ if force || ask_overwrite(file.output_path)
486
+ commands << [file.output_path, text]
487
+ end
488
+ end
489
+
490
+ @image_files.each do |file|
491
+ if force || ask_overwrite(file.output_path)
492
+ commands << [file.output_path, file.path]
493
+ end
494
+ end
495
+
496
+ if @bib && (force || ask_overwrite(@output_bibtex))
497
+ commands << [@output_bibtex, @bib.to_s]
498
+ end
499
+
500
+ commands.each do |path, data|
501
+ yield(path, data)
502
+ end
503
+ end
504
+
505
+ def ask_overwrite(file)
506
+ if !@overwrite_all && File.exist?(file)
507
+ ask("File #{file.relative_path_from(Pathname.getwd)} exists. Overwrite? [Ynaq]") do |q|
508
+ q.character = true
509
+ q.validate = /[ynaq\r ]/
510
+ q.case = :down
511
+ q.overwrite = false
512
+ q.answer_type = lambda do |c|
513
+ case c
514
+ when 'q' then raise Quit
515
+ when 'y' then true
516
+ when 'n' then false
517
+ when 'a' then
518
+ @overwrite_all = true
519
+ true
520
+ end
521
+ end
522
+ end
523
+ else
524
+ true
525
+ end
526
+ end
529
527
  end