texzip 0.1.8 → 0.1.9

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