trex 1.0.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.
Files changed (3) hide show
  1. data/bin/trex +7 -0
  2. data/lib/trex.rb +949 -0
  3. metadata +51 -0
data/bin/trex ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+
4
+ require 'trex'
5
+
6
+ TReX.new(ARGV, STDIN).run
7
+
data/lib/trex.rb ADDED
@@ -0,0 +1,949 @@
1
+ # == Synopsis
2
+ # trex is script simplifying the compilation of latex files by creating
3
+ # proper human-readable error output with repeating patterns. Unlike the
4
+ # original latex output which is oververbosified.
5
+ #
6
+ # == Examples
7
+ # ./trex view # compiles and opens the pdf, equivalent to just ./trex
8
+ # ./trex clean # removes all generated files
9
+ # ./trex tex # compiles the latex sources
10
+ # ./trex count # gives different word approximations of the latex file
11
+ #
12
+ # == Usage
13
+ # trex [options] [view|compile|tex|clean]
14
+ # For help use: trex -h
15
+ #
16
+ # == Options
17
+ # -h, --help Displays help message
18
+ # -v, --version Display the version, then exit
19
+ # -q, --quiet Output as little as possible, overrides verbose
20
+ # -V, --verbose Output lists all errors, overrides quiet
21
+ # --figures Set the figures dir, default is './figures'
22
+ # --hack Open this script for direct changes in the sources
23
+ #
24
+ # == Authors
25
+ # Camillo Bruni, Toon Verwaest, Damien Pollet
26
+ #
27
+
28
+ # ============================================================================
29
+ # CHANGE THESE PROPERTIES FOR YOUR PROJECT
30
+ SOURCE = nil
31
+ FIGURES = 'figures' # not yet used
32
+ GARBAGE = 'log aux out blg pdfsync synctex.gz'
33
+
34
+ # ============================================================================
35
+
36
+ require 'optparse'
37
+ require 'ostruct'
38
+ require 'date'
39
+ require 'logger'
40
+ require 'open3'
41
+
42
+ #require 'ruby-debug19'
43
+ #Debugger.start(:post_mortem => true)
44
+ # Debugger.start()
45
+
46
+ # ============================================================================
47
+ #TODO track changed files and only recompile then...
48
+ class TReX
49
+ VERSION = '1.0.1'
50
+
51
+ attr_reader :options
52
+
53
+ # mapping of input command shortcuts and method names
54
+ VALID_ACTIONS = {
55
+ "view" => :view,
56
+ "compile" => :compile,
57
+ "tex" => :compile,
58
+ "count" => :count,
59
+ "clean" => :clean,
60
+ "bibtex" => :bibtex,
61
+ "bib" => :bibtex,
62
+ "v" => :view,
63
+ }
64
+
65
+ # ------------------------------------------------------------------------
66
+ def initialize(arguments, stdin)
67
+ @arguments = arguments
68
+ @stdin = stdin
69
+ @action = :compile
70
+ @opts = nil
71
+ # Set defaults
72
+ @options = OpenStruct.new
73
+ @options.verbose = false
74
+ @options.quiet = false
75
+ @options.interaction = 'nonstopmode'
76
+ @options.garbage = GARBAGE
77
+ @options.figures = FIGURES
78
+ end
79
+
80
+ # ------------------------------------------------------------------------
81
+ def source!(sourceFile)
82
+ @options.source = sourceFile
83
+ @options.sourceBase = sourceFile.sub(/\.\w+$/,'')
84
+ @options.output = @options.sourceBase + '.pdf'
85
+ end
86
+
87
+
88
+ # ------------------------------------------------------------------------
89
+ def check_source
90
+ return if File.exist?(@options.source)
91
+ if not @options.source.end_with? "tex"
92
+ @options.source += '.' if not @options.source.end_with? '.'
93
+ @options.source += 'tex'
94
+ self.source! @options.source
95
+ end
96
+ return if File.exist?(@options.source)
97
+ print "File doesn't exist: ".redb, @options.source, "\n"
98
+ exit 1
99
+ end
100
+
101
+ # ------------------------------------------------------------------------
102
+ def run
103
+ begin
104
+ if self.parsed_options? && self.arguments_valid?
105
+ self.process_arguments
106
+ self.process_command
107
+ else
108
+ self.output_usage
109
+ end
110
+ rescue Interrupt
111
+ puts ''
112
+ end
113
+ end
114
+
115
+ # ------------------------------------------------------------------------
116
+ def hack
117
+ editor = ENV['EDITOR'] || "vim"
118
+ cmd = "sudo #{editor} #{__FILE__}"
119
+ print cmd
120
+ exec(cmd)
121
+ end
122
+
123
+ # ------------------------------------------------------------------------
124
+ def clean
125
+ @options.garbage.split.each { |f|
126
+ `rm -f #{@options.sourceBase}.#{f}`
127
+ }
128
+ end
129
+
130
+ # ------------------------------------------------------------------------
131
+ def compile
132
+ self.check_commands
133
+ self.check_source
134
+ baseCommand = self.create_compile_baseCommand
135
+ # 1/4 first run interactive... ignoring cite/ref errors
136
+ formatter = TexOutputFormatter.new(`#{baseCommand}`,
137
+ @options.verbose, @options.quiet,
138
+ @options.source)
139
+ if not formatter.hasCitationWarnings? \
140
+ and not formatter.hasReferenceWarnings?
141
+ formatter.print unless @options.quiet
142
+ return true
143
+ end
144
+ # 2/4 bibtex on citation warnings
145
+ if formatter.hasCitationWarnings? and not self.bibtex \
146
+ and not @options.quiet
147
+ # obviously something went wrong in either compiling bibtex or the
148
+ # latex file itself. Print everything except for reference and
149
+ # citation warnings
150
+ errorGroups = formatter.errorGroups
151
+ errorGroups.delete formatter.referenceWarnings
152
+ errorGroups.delete formatter.citationWarnings
153
+ formatter.print
154
+ end
155
+ # 3/4 second run
156
+ if not system("#{baseCommand} > /dev/null 2>&1")
157
+ formatter.print
158
+ "\n! Could not create PDF !".error
159
+ return false
160
+ end
161
+ # 4/4 looking for ref/cite errors
162
+ formatter = TexOutputFormatter.new(`#{baseCommand}`,
163
+ @options.verbose, @options.quiet,
164
+ @options.source)
165
+ formatter.print if not @options.quiet
166
+ return true
167
+ end
168
+
169
+ # ------------------------------------------------------------------------
170
+ def bibtex
171
+ if not File.exist?(@options.sourceBase + '.aux')
172
+ return self.compile
173
+ end
174
+ warning = BibtexWarning.new()
175
+ warning.handle(`bibtex #{@options.sourceBase + '.aux'}`)
176
+ puts warning.to_s unless warning.empty?
177
+ return warning.empty?
178
+ end
179
+
180
+ # ------------------------------------------------------------------------
181
+ def view
182
+ self.compile
183
+ open = nil
184
+ if RUBY_PLATFORM.include? "linux"
185
+ 'gnome-open evince kpdf Xpdf'.split.each { |c|
186
+ if self.has_command? c
187
+ open = c
188
+ break
189
+ end
190
+ }
191
+ elsif RUBY_PLATFORM.include? "darwin"
192
+ open = 'open'
193
+ end
194
+ if open.nil?
195
+ "\n no command found to open #{ @options.output} !\n".error 1
196
+ end
197
+ system("#{open} #{@options.output}")
198
+ end
199
+
200
+ # ------------------------------------------------------------------------
201
+ def count
202
+ @options.quiet = true
203
+ self.compile
204
+ puts 'Sources'.red + ":"
205
+ self.print_wc_results `wc #{@options.source}`
206
+ puts 'PDFtoText'.red + ":"
207
+ #TODO create a tmp file instead of saving locally
208
+ self.print_wc_results `pdftotext -enc UTF-8 -nopgbrk #{@options.output} - | wc`
209
+ `rm -f #{@options.sourceBase}.txt`
210
+ if self.has_command? 'texcount'
211
+ puts 'texcount'.red + ":"
212
+ puts `texcount -relaxed -sum #{@options.source}`
213
+ end
214
+ end
215
+
216
+ def print_wc_results(result)
217
+ result = result.split()[0..2]
218
+ max = result.map{|l| l.size}.max
219
+ puts " #{result[0].rjust(max).yellow} lines"
220
+ puts " #{result[1].rjust(max).yellow} words"
221
+ puts " #{result[2].rjust(max).yellow} characters"
222
+
223
+ end
224
+
225
+ # ------------------------------------------------------------------------
226
+ protected
227
+
228
+ # Specify options
229
+ def option_parser
230
+ return @opts unless @opts.nil?
231
+
232
+ @opts = OptionParser.new do |opts|
233
+ opts.banner = "#{executable_name} [options] [view|compile|tex|clean]"
234
+
235
+ opts.separator ''
236
+ opts.separator 'Automate compilation of LaTeX documents, making the output human-readable.'
237
+ opts.separator ''
238
+
239
+ opts.on '--figures PATH', "Set the figures directory (default #{FIGURES})" do |path|
240
+ @options.figures = path
241
+ end
242
+ opts.on '-V', '--verbose', 'List all errors (overrides --quiet)' do
243
+ @options.verbose = true
244
+ end
245
+ opts.on '-q', '--quiet', 'Output as little as possible (overrides --verbose)' do
246
+ @options.quiet = true
247
+ end
248
+
249
+ opts.separator ''
250
+ opts.on '-h', '--help', 'Display this help message' do
251
+ self.output_help
252
+ exit 0
253
+ end
254
+ opts.on '-v', '--version', 'Show version' do
255
+ self.output_version
256
+ exit 0
257
+ end
258
+ opts.on '--hack', 'Open this script for direct changes in the sources' do
259
+ self.hack
260
+ end
261
+ end
262
+ end
263
+
264
+ def parsed_options?
265
+ option_parser.parse!(@arguments) rescue return false
266
+ self.process_options
267
+ true
268
+ end
269
+
270
+ # Performs post-parse processing on options
271
+ def process_options
272
+ @options.verbose = false if @options.quiet
273
+ end
274
+
275
+ # True if required arguments were provided
276
+ def arguments_valid?
277
+ return false if not @action and not VALID_ACTIONS.keys.include? @arguments[0]
278
+ true
279
+ end
280
+
281
+ # Setup the arguments
282
+ def process_arguments
283
+ if not @action or VALID_ACTIONS.keys.include? @arguments[0]
284
+ @action = VALID_ACTIONS[@arguments.shift]
285
+ end
286
+ if not @arguments.empty?
287
+ self.source! @arguments.shift
288
+ elsif not SOURCE.nil?
289
+ self.source! SOURCE
290
+ end
291
+ if @options.source.nil?
292
+ self.extract_source_from_current_dir
293
+ end
294
+ 'Source file not set!'.error 1 if @options.source.nil?
295
+ end
296
+
297
+ # ---------------------------------------------------------------------
298
+
299
+ def executable_name
300
+ File.basename(__FILE__)
301
+ end
302
+
303
+ def output_help
304
+ self.output_version
305
+ output_usage
306
+ end
307
+
308
+ def output_usage
309
+ puts @opts.help
310
+ end
311
+
312
+ def output_version
313
+ puts "#{executable_name} version #{VERSION}"
314
+ end
315
+
316
+ # ---------------------------------------------------------------------
317
+ def extract_source_from_current_dir
318
+ texFiles = Dir['*.tex']
319
+ texFiles = texFiles.select { |file|
320
+ File.read(file).match(/\\documentclass.*?\{.*?\}/)
321
+ }
322
+ return if texFiles.empty?
323
+ return self.source! texFiles.first if texFiles.length == 1
324
+ self.source! self.promptChoice texFiles
325
+ end
326
+
327
+ def promptChoice(args)
328
+ args.sort!
329
+ for i in 1..args.length
330
+ puts "[#{i}] #{args[i-1]}"
331
+ end
332
+ choice = nil
333
+ while choice.nil?
334
+ print "Which is the source file?[number]: "
335
+ choice = args[gets.chomp.to_i-1]
336
+ end
337
+ return choice
338
+ end
339
+
340
+ def check_commands
341
+ missing = []
342
+ 'pdflatex bibtex'.split.each { |c|
343
+ missing.push(c) unless self.has_command? c
344
+ }
345
+ if not missing.empty?
346
+ print 'Missing commands for running the script: '.redb
347
+ puts missing.join(', ')
348
+ if RUBY_PLATFORM.include? "linux"
349
+ puts 'install latex with your favorite package manager'
350
+ elsif RUBY_PLATFORM.include? "darwin"
351
+ puts 'install the latest latex from http://www.tug.org/mactex'
352
+ end
353
+ end
354
+ end
355
+
356
+ def process_command
357
+ self.send @action
358
+ end
359
+
360
+ # ---------------------------------------------------------------------
361
+ def has_command?(command)
362
+ # disable warnings during the script checking, so we get only
363
+ # errors in stderr. Warnings can cause problems during the
364
+ # detection, for example ( warning: Insecure world writable dir
365
+ # /some/path in PATH, mode 040777 )
366
+ @warn = $-v
367
+ $-v = nil
368
+ Open3.popen3(command+' --help') { |stdin, stdout, stderr|
369
+ return stderr.read.length == 0
370
+ }
371
+ $-v = @warn
372
+ return true
373
+ end
374
+
375
+ # ---------------------------------------------------------------------
376
+ def create_compile_baseCommand
377
+ "pdflatex -synctex=1 --interaction #{@options.interaction} '#{@options.source}'"
378
+ end
379
+
380
+ end
381
+
382
+ # ============================================================================
383
+
384
+ class TexOutputFormatter
385
+ attr_reader :errorGroups, :citationWarnings, :referenceWarnings
386
+ attr_writer :errorGroups
387
+
388
+ def initialize(texOutput, verbose=false, quiet=false, source=nil,
389
+ errorGroups=nil)
390
+ #TODO add arg check
391
+ @texOutput = texOutput
392
+ @source = source
393
+ @totalWarnings = 0
394
+ @parsed = false
395
+ @verbose = verbose
396
+ @quiet = quiet
397
+ @errorGroups = errorGroups
398
+ self.initialize_error_groups
399
+ end
400
+
401
+ def initialize_error_groups
402
+ interestingLimits = 50
403
+ interestingLimits = 0 if @quiet
404
+ uninterestingLimits = 0
405
+ uninterestingLimits = 50 if @verbose
406
+ return unless @errorGroups.nil?
407
+
408
+ @filestate = FilenameParser.new
409
+ @citationWarnings = CitationWarning.new(interestingLimits)
410
+ @referenceWarnings = ReferenceWarning.new(interestingLimits)
411
+ @errorGroups = [
412
+ PDFVersionMismatchWarning.new(uninterestingLimits),
413
+
414
+ TexWarning.new('Underfull lines',
415
+ /Underfull.*/,
416
+ /\(badness [0-9]+\) in \w+/,
417
+ uninterestingLimits),
418
+
419
+ TexWarning.new('Overfull lines',
420
+ /Overfull.*/,
421
+ /\w+ \(.*?\) in \w+/,
422
+ uninterestingLimits),
423
+
424
+ TexWarning.new('Float changes',
425
+ /Warning:.*?float specifier changed to/,
426
+ /float specifier .*/,
427
+ uninterestingLimits),
428
+
429
+ TexWarning.new('Package Warning',
430
+ /Package .* Warning/,
431
+ /.*/,
432
+ uninterestingLimits),
433
+
434
+
435
+ FontWarning.new(uninterestingLimits),
436
+
437
+ @citationWarnings, @referenceWarnings,
438
+
439
+ TooManyWarning.new(interestingLimits),
440
+
441
+ RepeatedPageNumberWarning.new(uninterestingLimits),
442
+
443
+ TexError.new('Undefined Control Sequence',
444
+ /! Undefined control /,
445
+ /\\.*/, interestingLimits),
446
+
447
+ TexError.new('LaTeX error', /! LaTeX Error/, /.*/,
448
+ interestingLimits),
449
+
450
+ MissingParenthesisWarning.new(@source, interestingLimits),
451
+
452
+ PharagraphEndedWarning.new(@source, interestingLimits),
453
+
454
+ TexWarning.new('File not Found',
455
+ /Warning: File/,
456
+ /[^ ]+ not found/,
457
+ interestingLimits),
458
+
459
+ MultipleLabelWarning.new(@source, interestingLimits),
460
+
461
+ TexError.new('Other Errors', /^! /, /.*/, interestingLimits),
462
+
463
+ @filestate,
464
+
465
+ OtherWarning.new,
466
+ ]
467
+ @errorGroups.each { |eg| eg.filestate = @filestate }
468
+ end
469
+
470
+ # ------------------------------------------------------------------------
471
+ def hasCitationWarnings?
472
+ self.parse unless @parsed
473
+ not @citationWarnings.empty?
474
+ end
475
+
476
+ def hasReferenceWarnings?
477
+ self.parse unless @parsed
478
+ not @referenceWarnings.empty?
479
+ end
480
+
481
+ # ------------------------------------------------------------------------
482
+
483
+ def add(handler)
484
+ handler.filestate = @filestate
485
+ @errorGroups.push handler
486
+ end
487
+
488
+ def print
489
+ self.parse unless @parsed
490
+ @errorGroups.each { |group|
491
+ if not group.empty?
492
+ Kernel.print group.to_s
493
+ end
494
+ }
495
+ end
496
+
497
+ # ------------------------------------------------------------------------
498
+ protected
499
+ def parse
500
+ lineNumberPattern = /lines ([0-9\-]+)/
501
+ @parsed = true
502
+ mergedLine = ""
503
+ lines = @texOutput.split "\n"
504
+ lines.each_with_index { |line,index|
505
+ #magic to unwrap the autowrapped lines from latex.. silly
506
+ mergedLine += line
507
+ if line.size == 79
508
+ next
509
+ elsif line.chomp.start_with? "! Undefined"
510
+ next
511
+ end
512
+ # start of the actual error parsing
513
+ line = mergedLine
514
+ handled = false
515
+ @errorGroups.each { |group|
516
+ break if group.handle(line, index, lines)
517
+ }
518
+ #Kernel.print line, "\n"
519
+ #Kernel.print line, "\n" if not handled
520
+ mergedLine = ""
521
+ }
522
+ end
523
+ end
524
+
525
+ # ============================================================================
526
+ class TexWarning
527
+ attr_reader :limit
528
+ attr_accessor :filestate
529
+
530
+ def initialize(name, pattern, printPattern=/.*/, limit=10,
531
+ additionaInputLines=0)
532
+ @name = name
533
+ @pattern = pattern
534
+ @printPattern = printPattern
535
+ @errors = []
536
+ @limit = limit
537
+ @maxLinesWidth = 0
538
+ @additionaInputLines = additionaInputLines
539
+ end
540
+
541
+ def handle(string, index=nil, lines=nil)
542
+ if not string.match(@pattern) or string.empty?
543
+ return false
544
+ end
545
+ string = self.expand_input(string, index, lines)
546
+ line = self.extract_line(string, index, lines)
547
+ self.add_error(line, string)
548
+ true
549
+ end
550
+
551
+ def expand_input(string, index, lines)
552
+ return string if @additionaInputLines == 0
553
+ to = [index + @additionaInputLines, lines.size].min
554
+ return lines[index..to].join("\n") + "\n"
555
+ end
556
+
557
+ def extract_line(string, index, lines)
558
+ # force 'line ..' first
559
+ if /line(?:s)? (?<line>[0-9\-]+)/ =~ string
560
+ return line
561
+ end
562
+ if /(?:line(?:s)? |l[^0-9])(?<line>[0-9\-]+)/ =~ string
563
+ return line
564
+ end
565
+ return "-"
566
+ end
567
+
568
+ def render_line(line)
569
+ (@filestate and @filestate.state or "") + line.to_s
570
+ end
571
+
572
+ def add_error(line, string)
573
+ return if self.has_error?(line, string) # avoid duplicate errors
574
+ line = self.render_line(line) unless line == "-"
575
+ @errors.push([line, string.to_s])
576
+ @maxLinesWidth = [@maxLinesWidth, line.size].max
577
+ end
578
+
579
+ def has_error?(line, string)
580
+ @errors.each { |e|
581
+ return true if e[0] == line and e[1] == string
582
+ }
583
+ false
584
+ end
585
+
586
+ def to_s
587
+ if self.empty?
588
+ return ""
589
+ end
590
+ self.sort
591
+ limit = [@limit, @errors.size].min
592
+ str = @name.redb + " [#{@errors.size}]: "
593
+ str += "\n" #if self.size > 1 and limit > 0
594
+ limit.times { |i|
595
+ str += " #{self.format_error(@errors[i][0], @errors[i][1])}\n"
596
+ }
597
+ if @limit < @errors.size and @limit != 0
598
+ str += " #{"...".ljust(@maxLinesWidth)} ...\n"
599
+ end
600
+ #str += "\n"
601
+ str
602
+ end
603
+
604
+ def format_error(line, string)
605
+ message = self.format_warning_message(line, string)
606
+ "#{line.ljust(@maxLinesWidth).yellow} #{message.to_s.chomp}"
607
+ end
608
+
609
+ def format_warning_message(line, string)
610
+ if @printPattern.instance_of? Proc
611
+ puts self.class, @name
612
+ return @printPattern.call(line, string)
613
+ end
614
+ matched = @printPattern.match string
615
+ return string if not matched
616
+ return matched
617
+ end
618
+
619
+ def sort
620
+ # magic from
621
+ # http://www.bofh.org.uk/2007/12/16/comprehensible-sorting-in-ruby
622
+ # for number sensible sorting
623
+ @errors.sort_by {|k|
624
+ k.to_s.split(/((?:(?:^|\s)[-+])?(?:\.\d+|\d+(?:\.\d+?(?:[eE]\d+)?(?:$|(?![eE\.])))?))/ms).
625
+ map {|v| Float(v) rescue v.downcase}
626
+ }
627
+ end
628
+
629
+ def size
630
+ @errors.size
631
+ end
632
+
633
+ def empty?
634
+ self.size == 0
635
+ end
636
+ end
637
+
638
+ class FilenameParser
639
+
640
+ def initialize
641
+ @nesting = []
642
+ end
643
+
644
+ def filestate=(state)
645
+ end
646
+
647
+ def handle(string, index, lines)
648
+ unless string =~ /^\([\.\/]/ || string =~ /^\[[^\]]/ || string =~ /^\)/
649
+ return false
650
+ end
651
+ self.parse(string)
652
+ return true
653
+ end
654
+
655
+ def parse(string)
656
+ while string and string != ""
657
+
658
+ if string =~ /^\s+(.*)/
659
+ string = $1
660
+ end
661
+
662
+ break unless string
663
+
664
+ if string =~ /^\[[^\]]+\](.*)/
665
+ string = $1
666
+ next
667
+ end
668
+
669
+ if string =~ /^(\)+)(.*)/
670
+ $1.size.times { @nesting.pop }
671
+ break unless $2
672
+ string = $2[1,$2.size]
673
+ next
674
+ end
675
+
676
+ if string =~ /^\(([^\(\)\[]+)(.*)/
677
+ @nesting.push($1)
678
+ string = $2
679
+ next
680
+ end
681
+
682
+ break
683
+ end
684
+ end
685
+
686
+ def empty?
687
+ return true
688
+ end
689
+
690
+ def state
691
+ if @nesting.size <= 1
692
+ return ""
693
+ end
694
+ return @nesting[1,@nesting.size].join("|") + ": "
695
+ end
696
+ end
697
+
698
+ class OtherWarning < TexWarning
699
+ def initialize(limits=10)
700
+ super('Other Warnings', /LaTeX Warning:/,
701
+ /[^(LaTeX Warning)].*/, limits)
702
+ end
703
+ end
704
+
705
+ class PDFVersionMismatchWarning < TexWarning
706
+ def initialize(limits=10)
707
+ super('PDF Version mismatches', /PDF version/, /found PDF .*/, limits)
708
+ end
709
+
710
+ def format_warning_message(line, string)
711
+ result = string.match(/found PDF version <(.*?)>.*?<(.*?)>/)
712
+ "found #{result[1]} instead of #{result[2]}"
713
+ end
714
+
715
+ def extract_line(string, index, lines)
716
+ /file (.*?)\):/.match(string)[1]
717
+ end
718
+ end
719
+
720
+ class TooManyWarning < TexWarning
721
+ def initialize(limits=10)
722
+ super('Too Many XYZ Warning', /Too many /, /^l\.[0-9]+ (.*)/, limits)
723
+ @additionaInputLines = 10
724
+ end
725
+
726
+ def format_warning_message(line, string)
727
+ @printPattern.match(string)[1]
728
+ end
729
+ end
730
+
731
+
732
+ class RepeatedPageNumberWarning < TexWarning
733
+ def initialize(limits=1)
734
+ super('PDF Repeated Page Number',
735
+ /destination with the same identifier \(name\{page\./, limits)
736
+ @additionaInputLines = 4
737
+ end
738
+
739
+ def format_warning_message(line, string)
740
+ match = /^l\.[0-9]+ (.*\n.*)/.match(string)[1]
741
+ match.gsub!(/\s*\n\s*/, '')
742
+ name = /^(.*)\)/.match(string)
743
+ if not name.nil?
744
+ name = name[1]
745
+ else
746
+ #fix this
747
+ end
748
+ if line.to_i == 0
749
+ prefix = "\n"
750
+ separator = ""
751
+ else
752
+ prefix = ""
753
+ separator = " "*Math.log(line.to_i).floor
754
+ end
755
+ "#{prefix} near: #{match}.\n#{separator} try using plainpages=false or pdfpagelabels in hyperref
756
+ see: http://en.wikibooks.org/wiki/LaTeX/Hyperlinks#Problems_with_Links_and_Pages"
757
+ end
758
+ end
759
+
760
+
761
+ class FontWarning < TexWarning
762
+ def initialize(limits=10)
763
+ super('Font Shape Warnings', /Font Warning: Font shape /, /.*/, limits, 1)
764
+ end
765
+ def format_warning_message(line, message)
766
+ message.sub(/\(Font\)\s+/, '').
767
+ sub("\n", ', ').
768
+ sub(", Font shape", ',').
769
+ match(/Font shape (.*?) on input/)[1]
770
+ end
771
+ end
772
+
773
+ class BibtexWarning < TexWarning
774
+ def initialize(limits=10)
775
+ super('Bibtex Warnings',
776
+ /I found no/,
777
+ /I f.*? command/, limits)
778
+ end
779
+ end
780
+
781
+ class CitationWarning < TexWarning
782
+ def initialize(limit=10)
783
+ super('Citation Undefined',
784
+ pattern=/Warning: Citation/,
785
+ printPattern=/Citation (?<citation>[^ ]+).*on page (?<page>[0-9]+) undefined/, limit=limit)
786
+ end
787
+
788
+ def format_warning_message(line, string)
789
+ matched = @printPattern.match string
790
+ "#{matched[:citation][1..-2]} (output page #{matched[:page]})"
791
+ end
792
+ end
793
+
794
+ class ReferenceWarning < TexWarning
795
+ def initialize(limits=1)
796
+ super('Reference Warnings',
797
+ /Warning: Reference/,
798
+ /[^ ]+ on page [0-9]+ undefined/, limits)
799
+ end
800
+ end
801
+
802
+ class MissingParenthesisWarning < TexWarning
803
+ def initialize(source, limits=10)
804
+ super('Missing Parenthesis',
805
+ /File ended /, /[^! ].*/,
806
+ limits)
807
+ @source = source
808
+ @sourceContents = File.read(source)
809
+ end
810
+
811
+ def extract_line(string, index, lines)
812
+ label = self.format_warning_message(0, string)
813
+ errorSource = Regexp.escape(lines[index - 1].strip()[0..-6])
814
+ errorSource.gsub!('\\ ', '\s*') # make sure we can match newlines
815
+ lineNumbers = []
816
+ matches = @sourceContents.match(errorSource)
817
+ (0..matches.size-1).each { |i|
818
+ to = matches.begin(i)
819
+ linenumber = @sourceContents[0..to].scan("\n").size + 1
820
+ lineNumbers.push(linenumber)
821
+ }
822
+ lineNumbers.join(',')
823
+ end
824
+ end
825
+
826
+ class PharagraphEndedWarning < TexWarning
827
+ def initialize(source, limits=10)
828
+ super('Runaway Argument',
829
+ /! Pharagraph ended /, /[^! ].*/,
830
+ limits)
831
+ @source = source
832
+ #@sourceContents = File.read(source)
833
+ end
834
+
835
+ def extract_line__(string, index, lines)
836
+ label = self.format_warning_message(0, string)
837
+ errorSource = Regexp.escape(lines[index - 1].strip()[0..-6])
838
+ errorSource.gsub!('\\ ', '\s*') # make sure we can match newlines
839
+ lineNumbers = []
840
+ matches = @sourceContents.match(errorSource)
841
+ (0..matches.size-1).each { |i|
842
+ to = matches.begin(i)
843
+ linenumber = @sourceContents[0..to].scan("\n").size + 1
844
+ lineNumbers.push(linenumber)
845
+ }
846
+ lineNumbers.join(',')
847
+ end
848
+ end
849
+
850
+ class MultipleLabelWarning < TexWarning
851
+ def initialize(source, limits=40)
852
+ super('Multiply defined Labels', /LaTeX Warning: Label.*? multiply/,
853
+ /.*/, limits)
854
+ @source = source
855
+ end
856
+
857
+ def format_warning_message(line, string)
858
+ string.match(/Label `(.*?)' /)[1]
859
+ end
860
+
861
+ def extract_line(string, index, lines)
862
+ label = self.format_warning_message(0, string)
863
+ `sed -ne /\label{#{label}}/= #{@source}`.split.join(',')
864
+ end
865
+
866
+ end
867
+
868
+ class TexError < TexWarning
869
+ def initialize(name, pattern, printPattern=/[^!].*/, limit=10)
870
+ super(name, pattern, printPattern, limit)
871
+ end
872
+
873
+ def extract_line(string, index, lines)
874
+ match = /(?:line(?:s)? |l.)([0-9\-]+( \\.*)?)/
875
+ if string =~ match
876
+ return $1.to_s
877
+ end
878
+ (index+1).upto(lines.size-1) { |i|
879
+ return "-" if lines[i].start_with? "!"
880
+ if lines[i] =~ match
881
+ return $1.to_s
882
+ end
883
+ }
884
+ return "-"
885
+ end
886
+ end
887
+ # ===========================================================================
888
+
889
+ def color_terminal?
890
+ [/linux/, /xterm.*/, /rxvt.*/].each { |m|
891
+ return true if ENV['TERM'] =~ m
892
+ }
893
+ return false
894
+ end
895
+
896
+ def colorize(text, color_code)
897
+ return "\033#{color_code}#{text}\033[0m" if color_terminal?()
898
+ return text
899
+ end
900
+
901
+ class String
902
+ def error(exitValue=nil)
903
+ puts self.redb
904
+ exit exitValue unless exitValue.nil?
905
+ end
906
+
907
+ def red
908
+ colorize(self, "[31m")
909
+ end
910
+
911
+ def redb
912
+ colorize(self, "[31;1m")
913
+ end
914
+
915
+ def green
916
+ colorize(self, "[32m")
917
+ end
918
+
919
+ def greenb
920
+ colorize(self, "[32;1m")
921
+ end
922
+
923
+ def yellow
924
+ colorize(self, "[33m")
925
+ end
926
+
927
+ def yellowb
928
+ colorize(self, "[33;1m")
929
+ end
930
+
931
+ def pink
932
+ colorize(self, "[35m")
933
+ end
934
+
935
+ def pinkb
936
+ colorize(self, "[35;1m")
937
+ end
938
+
939
+ def cyan
940
+ colorize(self, "[36m")
941
+ end
942
+
943
+ def cyanb
944
+ colorize(self, "[36;1m")
945
+ end
946
+
947
+ end
948
+
949
+ # vim: set ts=4 sw=4 ts=4 :
metadata ADDED
@@ -0,0 +1,51 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: trex
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Camillo Bruni
9
+ - Toon Verwaest
10
+ - Damien Pollet
11
+ autorequire:
12
+ bindir: bin
13
+ cert_chain: []
14
+ date: 2012-01-20 00:00:00.000000000 Z
15
+ dependencies: []
16
+ description: trex is script simplifying the compilation of latex files by creating
17
+ proper human-readable error output with repeating patterns. Unlike the original
18
+ latex output which is oververbosified.
19
+ email: camillorbuni@cxg.ch
20
+ executables:
21
+ - trex
22
+ extensions: []
23
+ extra_rdoc_files: []
24
+ files:
25
+ - lib/trex.rb
26
+ - bin/trex
27
+ homepage: https://github.com/dh83/trex
28
+ licenses: []
29
+ post_install_message:
30
+ rdoc_options: []
31
+ require_paths:
32
+ - lib
33
+ required_ruby_version: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ required_rubygems_version: !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ requirements: []
46
+ rubyforge_project:
47
+ rubygems_version: 1.8.11
48
+ signing_key:
49
+ specification_version: 3
50
+ summary: Automate compilation of LaTeX documents, making the output human-readable.
51
+ test_files: []