trex 1.0.1

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