tex_log_parser 1.0.0.pre.1 → 1.0.0.pre.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4278834a923e4825ac4e56bcd07e6b7eeb681643
4
- data.tar.gz: 38810767f39844e7ec8eac3b78a445f8ebdbf68c
3
+ metadata.gz: 062e87fb6cd767aacf4df4306f06812861c0f435
4
+ data.tar.gz: 36c8a772c8a16b79b112288a4673603050b181d2
5
5
  SHA512:
6
- metadata.gz: 701b97b4ab74385a29286fbb30055296ee417881f636f00638d692ece7b74a4fa47b3e68a567c22c403279709240abcd4fad45df80f82d4c6be95fa4a643ab2d
7
- data.tar.gz: 0aaa4a74adb27548e8e2487bbf2b3a8782481eb4cce192098b549e250ab39a4bb142ead0796eac745f5f940a3d90ee030274773d853ee7159cb940aad1a4f792
6
+ metadata.gz: d2dbe0b0507532726115f4c45652ba3d5d642db99ff43e9298a628f2fc3891d6e0b97e0fe4226cb91d1c81f8afa32be6bfc38093b2a6bb897edb6356e61eeb8f
7
+ data.tar.gz: 1251c33e7e6803f91f309b8d5920febfb45e6d0e4a14eb9c24bbaea5aa83a47f2fe290ae0d7d1b97e59d81170fc54ff71d35f7309c8741ba27afd9adb2cc7c03
data/README.md CHANGED
@@ -1,6 +1,20 @@
1
1
  # TeXLogParser
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/texlogparser.svg)](https://rubygems.org/gems/texlogparser)
3
+ [![Gem Version](https://badge.fury.io/rb/tex_log_parser.svg)](https://badge.fury.io/rb/tex_log_parser)
4
+ [![Yard docs](http://img.shields.io/badge/yard-docs-green.svg)](http://www.rubydoc.info/gems/tex_log_parser/1.0.0.pre.1) **˙**
5
+ [![Maintainability](https://api.codeclimate.com/v1/badges/748992a2c5f6570797d4/maintainability)](https://codeclimate.com/github/reitzig/texlogparser/maintainability)
6
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/748992a2c5f6570797d4/test_coverage)](https://codeclimate.com/github/reitzig/texlogparser/test_coverage) **˙**
4
7
  [![Travis build status](https://api.travis-ci.org/reitzig/texlogparser.svg?branch=master)](https://travis-ci.org/reitzig/texlogparser)
8
+ [![Inline docs](http://inch-ci.org/github/reitzig/texlogparser.svg?branch=master)](http://inch-ci.org/github/reitzig/texlogparser)
5
9
 
6
- TODO write
10
+ Eases the many pains around logs from (La)TeX engines.
11
+
12
+ ## Usage
13
+
14
+ * Use `texlogparser` on the CLI.
15
+ * Use `TexLogParser` from Ruby scripts.
16
+
17
+ ## Contributing
18
+
19
+ 1. TeXians: Report wrong parsing (w/ excerpt and full log incl expectations).
20
+ 2. Rubyists: Review API, documentation, gem setup, etc.
data/bin/texlogparser CHANGED
@@ -1,6 +1,75 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ if $PROGRAM_NAME == __FILE__
4
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
5
+ end
6
+
3
7
  require 'tex_log_parser'
8
+ require 'optparse'
9
+ require 'json'
10
+
11
+ # Defaults
12
+ options = {
13
+ debug: false,
14
+ format: :file_line,
15
+ input: STDIN,
16
+ output: STDOUT
17
+ }
18
+ formats = [:file_line, :json]
19
+
20
+ OptionParser.new do |opts|
21
+ opts.banner = 'Usage: texlogparser [options]'
22
+ opts.on('-d', '--debug', 'Output debug information') do
23
+ options[:debug] = true
24
+ end
25
+ opts.on('-f ENUM', '--format ENUM', formats,
26
+ 'Output format', "One of: #{formats.map { |e| e.to_s }.join(", ")}") do |format|
27
+ options[:format] = format
28
+ end
29
+ opts.on('-i', '--input PATH', 'Read input from PATH') do |path|
30
+ unless File.file?(path)
31
+ STDERR.puts("No such file: #{path}")
32
+ exit 1
33
+ end
34
+
35
+ options[:input] = path
36
+ end
37
+ opts.on('-o', '--output PATH', 'Write output to PATH') do |path|
38
+ options[:output] = path
39
+ end
40
+ opts.on_tail('-h', '--help', 'Show this message') do
41
+ puts opts
42
+ exit
43
+ end
44
+ opts.on_tail('-v', '--version', 'Show version') do
45
+ puts TexLogParser::VERSION
46
+ exit
47
+ end
48
+
49
+ begin
50
+ opts.parse!
51
+ rescue => e
52
+ STDERR.puts e
53
+ exit 1
54
+ end
55
+
56
+ if !options[:input].is_a?(String) && STDIN.tty?
57
+ STDERR.puts opts.help
58
+ exit 1
59
+ end
60
+ end
61
+
62
+ input = STDIN
63
+ input = File.open(options[:input], 'r') if options[:input].is_a?(String)
64
+ parser = TexLogParser.new(input, options)
65
+ messages = parser.parse
4
66
 
5
- # TODO read stdin, parse, write default format (file-line? json?) to stdout
6
- # TODO options for output format?
67
+ output = STDOUT
68
+ output = File.open(options[:output], 'w') if options[:output].is_a?(String)
69
+ case options[:format]
70
+ when :file_line
71
+ output.write(messages.map { |m| m.to_s }.join("\n\n"))
72
+ when :json
73
+ output.write(JSON.pretty_generate(messages))
74
+ end
75
+ output.close
@@ -0,0 +1,56 @@
1
+ class LogBuffer
2
+ # @param [Array<String>,IO] log
3
+ def initialize(log)
4
+ @buffer = []
5
+ @stream = nil
6
+
7
+ @buffer = log if log.is_a?(Array)
8
+ @stream = log if log.is_a?(IO) || log.is_a?(StringIO)
9
+ end
10
+
11
+ def empty?
12
+ @buffer.empty? && stream_is_done?
13
+ end
14
+
15
+ def first
16
+ self[0]
17
+ end
18
+
19
+ def find_index(starting_from = 0)
20
+ index = starting_from
21
+ element = self[index]
22
+
23
+ until element.nil?
24
+ return index if yield(element)
25
+ index += 1
26
+ element = self[index]
27
+ end
28
+
29
+ nil
30
+ end
31
+
32
+ def [](offset, length = nil)
33
+ base_length = length || 1
34
+ while offset + base_length > @buffer.size
35
+ return (length.nil? ? nil : @buffer[offset, @buffer.size]) if stream_is_done?
36
+ @buffer.push(@stream.readline.rstrip)
37
+ end
38
+
39
+ length.nil? ? @buffer[offset] : @buffer[offset, length]
40
+ end
41
+
42
+ def forward(offset = 1)
43
+ self[offset]
44
+ @buffer.slice!(0, offset)
45
+ end
46
+
47
+ def close
48
+ @stream.close unless @stream.nil? || @stream.closed?
49
+ end
50
+
51
+ private
52
+
53
+ def stream_is_done?
54
+ @stream.nil? || @stream.closed? || @stream.eof?
55
+ end
56
+ end
@@ -1,17 +1,24 @@
1
+ require 'json'
2
+
1
3
  # @attr [String] message
2
4
  # @attr [String,nil] source_file
3
5
  # @attr [Hash<Symbol, Int>,nil] source_lines
4
6
  # @attr [Hash<Symbol, Int>,nil] log_lines
5
7
  # @attr [true,false] preformatted
6
8
  # @attr [:error,:warning,:info,:debug] level
9
+ # @attr [Class] pattern
7
10
  class LogMessage
8
- def initialize(message:, source_file: nil, source_lines: nil, log_lines: nil, preformatted: false, level: :info)
11
+ def initialize(message:, source_file: nil, source_lines: nil, log_lines: nil,
12
+ preformatted: false, level: :info, pattern: nil)
9
13
  @message = message
10
14
  @source_file = source_file
11
15
  @source_lines = source_lines
12
16
  @log_lines = log_lines
13
17
  @preformatted = preformatted
14
18
  @level = level
19
+ @pattern = pattern
20
+
21
+ @debug = false # TODO: get option here
15
22
  end
16
23
 
17
24
  attr_accessor :message, :source_file, :source_lines, :log_lines,
@@ -19,14 +26,31 @@ class LogMessage
19
26
 
20
27
  def to_s
21
28
  lines = if @source_lines.nil?
22
- ""
29
+ ''
23
30
  else
24
31
  # @type [Hash<Symbol, Int>] @source_lines
25
- @source_lines.values.join("-")
32
+ ":#{@source_lines.values.uniq.join('-')}"
26
33
  end
34
+
35
+ message = @message
36
+ message = message.split("\n").map {|l| l.strip }.join("") unless @preformatted
37
+ message += "\nLog pattern: '#{@pattern}'" if @debug
38
+
27
39
  <<~MSG
28
- #{@source_file}:#{lines} #{@level.to_s.capitalize}
29
- #{@message}
40
+ #{@source_file}#{lines}: #{@level.to_s.upcase}
41
+ #{message}
30
42
  MSG
31
43
  end
44
+
45
+ def to_json(options={})
46
+ hash = {
47
+ level: @level,
48
+ source_file: @source_file,
49
+ source_lines: @source_lines,
50
+ message: @message,
51
+ log_lines: @log_lines,
52
+ preformatted: @preformatted
53
+ }
54
+ JSON.pretty_generate hash
55
+ end
32
56
  end
@@ -1,7 +1,22 @@
1
+ require "#{File.expand_path(__dir__)}/log_buffer.rb"
2
+
1
3
  # TODO: document
2
4
  module LogParser
3
- def initialize
5
+ attr_reader :messages
6
+
7
+ # TODO: document
8
+ # @param [Array<String>,IO,StringIO] log
9
+ # @param [Hash] options
10
+ def initialize(log, options)
4
11
  @files = []
12
+
13
+ @messages = []
14
+ @log_line_number = 0
15
+ @lines = LogBuffer.new(log)
16
+
17
+ @debug = options.fetch(:debug, false)
18
+
19
+ puts "Parsing from '#{log}'" if @debug
5
20
  end
6
21
 
7
22
  # @return [Array<LogPattern>]
@@ -18,57 +33,78 @@ module LogParser
18
33
  end
19
34
 
20
35
  # TODO: document
21
- # @param [Array<String>] log_lines
22
36
  # @return [Array<LogMessage>]
23
- def parse(log_lines)
24
- messages = []
37
+ def parse
38
+ raise 'Parser already ran!' unless @log_line_number.zero?
25
39
 
26
- log_line_number = 1
27
- until log_lines.empty?
28
- line = log_lines.first
40
+ @log_line_number = 1
41
+ until @lines.empty?
42
+ line = @lines.first
29
43
 
30
44
  if line.strip.empty?
31
- consumed_lines = 1
32
- else
33
- # Use the first pattern that matches. Let's hope that's a good heuristic.
34
- # If not, we'll have to let all competitors consume and see who wins --
35
- # which we'd decide how?
36
- matching_pattern = patterns.detect { |p| p.begins_at?(line) }
37
-
38
- if matching_pattern.nil?
39
- # In the hope that scope changes happen not on the same
40
- # line as messages. Gulp.
41
- scope_changes(log_lines.first).each { |op|
42
- if op == :pop
43
- left = @files.pop
44
- #puts "Finished file #{left.nil? ? "nil" : left}" # TODO: debug mode
45
- left
46
- else
47
- #puts "Entered file #{op}" # TODO: debug mode
48
- @files.push(op)
49
- end
50
- }
51
-
52
- # Try again with the next line
53
- consumed_lines = 1
54
- else
55
- # Apply the pattern, i.e. read the next message!
56
- # @type [LogMessage] message
57
- message, consumed_lines = matching_pattern.read(log_lines)
58
- message.log_lines = { from: log_line_number,
59
- to: log_line_number + consumed_lines - 1 }
60
- message.source_file ||= @files.last
61
-
62
- messages.push(message)
63
- end
45
+ remove_consumed_lines 1
46
+ next
64
47
  end
65
48
 
66
- # Forward the window
67
- log_lines.slice!(0, consumed_lines)
68
- log_line_number += consumed_lines
49
+ puts "\nLine: '#{line.strip}'" if @debug
50
+
51
+ # Use the first pattern that matches. Let's hope that's a good heuristic.
52
+ # If not, we'll have to let all competitors consume and see who wins --
53
+ # which we'd decide how?
54
+ matching_pattern = patterns.detect { |p| p.begins_at?(line) }
55
+
56
+ if matching_pattern.nil?
57
+ puts '- No pattern matches' if @debug
58
+ apply_scope_changes
59
+ else
60
+ puts "- Matched pattern: '#{matching_pattern.class}'" if @debug
61
+ consume_pattern(matching_pattern)
62
+ end
69
63
  end
70
64
 
71
- #puts "Filestack: #{@files}" # TODO: debug mode
65
+ puts "\nFiles that did not close: #{@files}" if @debug
66
+ @lines.close
72
67
  messages
73
68
  end
69
+
70
+ private
71
+
72
+ def remove_consumed_lines(i)
73
+ @lines.forward(i)
74
+ @log_line_number += i
75
+ end
76
+
77
+ def consume_pattern(pattern)
78
+ # Apply the pattern, i.e. read the next message!
79
+ begin
80
+ # @type [LogMessage] message
81
+ message, consumed_lines = pattern.read(@lines)
82
+ message.log_lines = { from: @log_line_number,
83
+ to: @log_line_number + consumed_lines - 1 }
84
+ message.source_file ||= @files.last
85
+
86
+ puts message if @debug
87
+ @messages.push(message)
88
+ remove_consumed_lines consumed_lines
89
+ rescue => e
90
+ puts "#{e}" if @debug
91
+ remove_consumed_lines 1
92
+ end
93
+ end
94
+
95
+ def apply_scope_changes
96
+ # In the hope that scope changes happen not on the same
97
+ # line as messages. Gulp.
98
+ scope_changes(@lines.first).each do |op|
99
+ if op == :pop
100
+ left = @files.pop
101
+ puts "- Finished source file: '#{left.nil? ? 'nil' : left}'" if @debug
102
+ else # op is file name
103
+ puts "- Entered source file: '#{op}'" if @debug
104
+ @files.push(op)
105
+ end
106
+ end
107
+
108
+ remove_consumed_lines 1
109
+ end
74
110
  end
@@ -29,11 +29,12 @@ module RegExpPattern
29
29
  # @option ending [Regexp] :pattern
30
30
  # @option ending [:match,:mismatch] :until
31
31
  # @option ending [true,false] :inclusive
32
- def initialize(start, ending = { pattern: ->(_) { /^\s+$/ },
32
+ def initialize(start, ending = { pattern: ->(_) { /^\s*$/ },
33
33
  until: :match,
34
34
  inclusive: false })
35
35
  @start = start
36
36
  @ending = ending
37
+ @debug = false # TODO: Get the option here
37
38
  end
38
39
 
39
40
  # TODO: document
@@ -50,16 +51,17 @@ module RegExpPattern
50
51
  end
51
52
 
52
53
  # TODO make failable (e.g. EOF)
53
- # @param [Array<String>] lines
54
+ # @param [LogBuffer] lines
54
55
  # @return [Array<(LogMessage, Int)>]
55
- def read(lines) # TODO: How useful is this? Not very, I'm afraid... there should be functions for source file, line, and level?
56
+ def read(lines)
56
57
  raise NotImplementedError if @ending.nil?
57
58
 
58
- ending = lines[1, lines.size].find_index { |l| ends_at?(l) }
59
- ending += 1 if @ending[:inclusive]
59
+ ending = lines.find_index(1) { |l| ends_at?(l) }
60
+ raise "Did not find end of message (pattern '#{self.class}')." if ending.nil?
61
+ ending -= 1 unless @ending[:inclusive]
60
62
 
61
63
  # Use ending+1 since ending is the index when we drop the first line!
62
- msg = LogMessage.new(message: lines[0, ending+1].join, preformatted: false, level: nil)
64
+ msg = LogMessage.new(message: lines[0, ending + 1].join("\n"), preformatted: true, level: nil)
63
65
  [msg, ending + 1]
64
66
  end
65
67
  end
@@ -44,7 +44,7 @@ class PrefixedMultiLinePattern
44
44
  msg.source_lines = { from: line, to: line }
45
45
  end
46
46
 
47
- # TODO: message itself contains useless line prefixes --> remove
47
+ # TODO: message itself contains useless line prefixes --> remove, preformatted = false
48
48
 
49
49
  [msg, consumed]
50
50
  end
@@ -0,0 +1,3 @@
1
+ class TexLogParser
2
+ VERSION = '1.0.0.pre.2'
3
+ end
@@ -1,4 +1,5 @@
1
1
  require 'tex_log_parser/log_parser'
2
+ require 'tex_log_parser/version'
2
3
  Dir["#{File.expand_path(__dir__)}/tex_log_parser/patterns/*.rb"].each { |p| require p }
3
4
 
4
5
  # TODO: document
@@ -10,32 +11,27 @@ class TexLogParser
10
11
  end
11
12
 
12
13
  def scope_changes(line)
13
- ops = case line
14
- when /^\s*\(([^()]*)\)\s+(.*)$/
15
- # A scope opened and closed immediately -- log it, then
16
- # continue with rest of the line (there can be multiple such
17
- # things in one line, see e.g. 000.log:656)
18
- [$1, :pop] + ($2.strip.empty? ? [] : scope_changes($2))
19
- when /^\s*\(([^()]+?)\s*$/
20
- # A scope opened and will be closed later.
21
- # Happens on a dedicated line
22
- [$1]
23
- when /^\s*(\)+)(.*)$/
24
- # Scopes close on a dedicated line, except if they don't (cf 000.log:624)
25
- # So we have to continue on the rest of the line. Uh oh.
26
- ([:pop] * $1.length) + scope_changes($2)
27
- when /\([^)]*$/
28
- # BROKEN_BY_LINEBREAKS
29
- # Bad linebreaks can cause trailing ) to spill over. Narf.
30
- # e.g. 000.log:502-503
31
- ["dummy"] # Compensate the bad pop that will happen next line.
32
- else
33
- []
34
- end
35
-
36
- # puts line # TODO: debug mode
37
- # puts ops.to_s
38
- # puts ""
39
- ops
14
+ case line
15
+ when /^\s*\(([^()]*)\)\s+(.*)$/
16
+ # A scope opened and closed immediately -- log it, then
17
+ # continue with rest of the line (there can be multiple such
18
+ # things in one line, see e.g. 000.log:656)
19
+ [$1, :pop] + ($2.strip.empty? ? [] : scope_changes($2))
20
+ when /^\s*\(([^()]+?)\s*$/
21
+ # A scope opened and will be closed later.
22
+ # Happens on a dedicated line
23
+ [$1]
24
+ when /^\s*(\)+)(.*)$/
25
+ # Scopes close on a dedicated line, except if they don't (cf 000.log:624)
26
+ # So we have to continue on the rest of the line. Uh oh.
27
+ ([:pop] * $1.length) + scope_changes($2)
28
+ when /\([^)]*$/
29
+ # BROKEN_BY_LINEBREAKS
30
+ # Bad linebreaks can cause trailing ) to spill over. Narf.
31
+ # e.g. 000.log:502-503
32
+ ["dummy"] # Compensate the bad pop that will happen next line.
33
+ else
34
+ []
35
+ end
40
36
  end
41
37
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tex_log_parser
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.pre.1
4
+ version: 1.0.0.pre.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Raphael Reitzig
@@ -14,44 +14,58 @@ dependencies:
14
14
  name: minitest
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: '5.10'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: '5.10'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: '12.3'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: '12.3'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: yard
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ">="
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: '0.9'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ">="
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
54
+ version: '0.9'
55
+ - !ruby/object:Gem::Dependency
56
+ name: json
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.1'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.1'
55
69
  description: Parses log files of (La)TeX engines
56
70
  email: 4246780+reitzig@users.noreply.github.com
57
71
  executables:
@@ -63,6 +77,7 @@ files:
63
77
  - README.md
64
78
  - bin/texlogparser
65
79
  - lib/tex_log_parser.rb
80
+ - lib/tex_log_parser/log_buffer.rb
66
81
  - lib/tex_log_parser/log_message.rb
67
82
  - lib/tex_log_parser/log_parser.rb
68
83
  - lib/tex_log_parser/log_pattern.rb
@@ -71,6 +86,7 @@ files:
71
86
  - lib/tex_log_parser/patterns/fontspec_error.rb
72
87
  - lib/tex_log_parser/patterns/prefixed_multi_line_pattern.rb
73
88
  - lib/tex_log_parser/patterns/runaway_parameter_error.rb
89
+ - lib/tex_log_parser/version.rb
74
90
  homepage: http://github.com/reitzig/texlogparser
75
91
  licenses:
76
92
  - MIT