tex_log_parser 1.0.0.pre.2 → 1.0.0.pre.7
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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/bin/texlogparser +9 -11
- data/lib/logger.rb +17 -0
- data/lib/tex_log_parser/log_buffer.rb +7 -1
- data/lib/tex_log_parser/log_message.rb +13 -12
- data/lib/tex_log_parser/log_parser.rb +69 -51
- data/lib/tex_log_parser/log_pattern.rb +10 -9
- data/lib/tex_log_parser/patterns/bad_hbox_warning.rb +2 -2
- data/lib/tex_log_parser/patterns/file_line_error.rb +4 -4
- data/lib/tex_log_parser/patterns/fontspec_error.rb +2 -2
- data/lib/tex_log_parser/patterns/prefixed_multi_line_pattern.rb +3 -3
- data/lib/tex_log_parser/patterns/runaway_parameter_error.rb +2 -2
- data/lib/tex_log_parser/version.rb +4 -2
- data/lib/tex_log_parser.rb +11 -6
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1771cd7fe895030d25eb1487e9af597b0617e5ff
|
4
|
+
data.tar.gz: acb8d3e74d985533efd9e45b613dfcf086aaf75d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 740853668e14c12939f5284030a9adce6cc43e3eca3db4f837a3220d01a31734adaed14e87379fd16962472337c67a0148f38642f444672e7d4c40f84ead69b4
|
7
|
+
data.tar.gz: c47346069d27184a711e51b66c63ac9a2c55f622f08f1b227173baa4808571909eafddddfab3c4493f338b8dab2be9068b3b9f939b69aba076f8972d4b32b6a4
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# TeXLogParser
|
2
2
|
|
3
3
|
[](https://badge.fury.io/rb/tex_log_parser)
|
4
|
-
[](http://www.rubydoc.info/gems/tex_log_parser/1.0.0.pre.
|
4
|
+
[](http://www.rubydoc.info/gems/tex_log_parser/1.0.0.pre.2) **˙**
|
5
5
|
[](https://codeclimate.com/github/reitzig/texlogparser/maintainability)
|
6
6
|
[](https://codeclimate.com/github/reitzig/texlogparser/test_coverage) **˙**
|
7
7
|
[](https://travis-ci.org/reitzig/texlogparser)
|
data/bin/texlogparser
CHANGED
@@ -1,29 +1,26 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
if $PROGRAM_NAME == __FILE__
|
4
|
-
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
5
|
-
end
|
2
|
+
# frozen_string_literal: true
|
6
3
|
|
7
4
|
require 'tex_log_parser'
|
5
|
+
require 'tex_log_parser/version'
|
8
6
|
require 'optparse'
|
9
7
|
require 'json'
|
10
8
|
|
11
9
|
# Defaults
|
12
10
|
options = {
|
13
|
-
debug: false,
|
14
11
|
format: :file_line,
|
15
12
|
input: STDIN,
|
16
13
|
output: STDOUT
|
17
14
|
}
|
18
|
-
formats = [
|
15
|
+
formats = %i[file_line json]
|
19
16
|
|
20
17
|
OptionParser.new do |opts|
|
21
18
|
opts.banner = 'Usage: texlogparser [options]'
|
22
19
|
opts.on('-d', '--debug', 'Output debug information') do
|
23
|
-
|
20
|
+
Logger.debugging = true
|
24
21
|
end
|
25
22
|
opts.on('-f ENUM', '--format ENUM', formats,
|
26
|
-
'Output format', "One of: #{formats.map
|
23
|
+
'Output format', "One of: #{formats.map(&:to_s).join(', ')}") do |format|
|
27
24
|
options[:format] = format
|
28
25
|
end
|
29
26
|
opts.on('-i', '--input PATH', 'Read input from PATH') do |path|
|
@@ -48,7 +45,7 @@ OptionParser.new do |opts|
|
|
48
45
|
|
49
46
|
begin
|
50
47
|
opts.parse!
|
51
|
-
rescue => e
|
48
|
+
rescue StandardError => e
|
52
49
|
STDERR.puts e
|
53
50
|
exit 1
|
54
51
|
end
|
@@ -68,8 +65,9 @@ output = STDOUT
|
|
68
65
|
output = File.open(options[:output], 'w') if options[:output].is_a?(String)
|
69
66
|
case options[:format]
|
70
67
|
when :file_line
|
71
|
-
output.write(messages.map
|
68
|
+
output.write(messages.map(&:to_s).join("\n\n"))
|
69
|
+
# TODO: print summary?
|
72
70
|
when :json
|
73
71
|
output.write(JSON.pretty_generate(messages))
|
74
72
|
end
|
75
|
-
output.close
|
73
|
+
output.close
|
data/lib/logger.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# A simple helper, probably to be replaced by a proper logging library
|
4
|
+
# at some point.
|
5
|
+
#
|
6
|
+
# @attr [true,false] debug
|
7
|
+
class Logger
|
8
|
+
class << self
|
9
|
+
attr_accessor :debugging
|
10
|
+
|
11
|
+
# Logs the given message to STDOUT if `debug` is true.
|
12
|
+
# @param [String] message
|
13
|
+
def debug(message)
|
14
|
+
puts message if debugging || !ENV['DEBUG'].nil?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class LogBuffer
|
2
4
|
# @param [Array<String>,IO] log
|
3
5
|
def initialize(log)
|
@@ -12,6 +14,10 @@ class LogBuffer
|
|
12
14
|
@buffer.empty? && stream_is_done?
|
13
15
|
end
|
14
16
|
|
17
|
+
def buffer_size
|
18
|
+
@buffer.size
|
19
|
+
end
|
20
|
+
|
15
21
|
def first
|
16
22
|
self[0]
|
17
23
|
end
|
@@ -53,4 +59,4 @@ class LogBuffer
|
|
53
59
|
def stream_is_done?
|
54
60
|
@stream.nil? || @stream.closed? || @stream.eof?
|
55
61
|
end
|
56
|
-
end
|
62
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'json'
|
2
4
|
|
3
5
|
# @attr [String] message
|
@@ -17,8 +19,6 @@ class LogMessage
|
|
17
19
|
@preformatted = preformatted
|
18
20
|
@level = level
|
19
21
|
@pattern = pattern
|
20
|
-
|
21
|
-
@debug = false # TODO: get option here
|
22
22
|
end
|
23
23
|
|
24
24
|
attr_accessor :message, :source_file, :source_lines, :log_lines,
|
@@ -33,8 +33,8 @@ class LogMessage
|
|
33
33
|
end
|
34
34
|
|
35
35
|
message = @message
|
36
|
-
message = message.split("\n").map
|
37
|
-
message += "\nLog pattern: '#{@pattern}'" if
|
36
|
+
message = message.split("\n").map(&:strip).join(' ') unless @preformatted
|
37
|
+
message += "\nLog pattern: '#{@pattern}'" if Logger.debugging
|
38
38
|
|
39
39
|
<<~MSG
|
40
40
|
#{@source_file}#{lines}: #{@level.to_s.upcase}
|
@@ -42,15 +42,16 @@ class LogMessage
|
|
42
42
|
MSG
|
43
43
|
end
|
44
44
|
|
45
|
-
def to_json(
|
45
|
+
def to_json(_options = {})
|
46
46
|
hash = {
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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
53
|
}
|
54
|
+
hash[:pattern] = @pattern if Logger.debugging
|
54
55
|
JSON.pretty_generate hash
|
55
56
|
end
|
56
|
-
end
|
57
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# TODO: document
|
4
4
|
module LogParser
|
@@ -6,17 +6,15 @@ module LogParser
|
|
6
6
|
|
7
7
|
# TODO: document
|
8
8
|
# @param [Array<String>,IO,StringIO] log
|
9
|
-
# @param [Hash]
|
10
|
-
def initialize(log,
|
9
|
+
# @param [Hash] _options
|
10
|
+
def initialize(log, _options = {})
|
11
11
|
@files = []
|
12
12
|
|
13
13
|
@messages = []
|
14
|
-
@log_line_number =
|
14
|
+
@log_line_number = 1
|
15
15
|
@lines = LogBuffer.new(log)
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
puts "Parsing from '#{log}'" if @debug
|
17
|
+
Logger.debug "Parsing from '#{log}'"
|
20
18
|
end
|
21
19
|
|
22
20
|
# @return [Array<LogPattern>]
|
@@ -24,72 +22,92 @@ module LogParser
|
|
24
22
|
raise NotImplementedError
|
25
23
|
end
|
26
24
|
|
27
|
-
# @param [String]
|
25
|
+
# @param [String] _line
|
28
26
|
# @return [Array<String,:pop>] A list of new scopes this line enters (strings)
|
29
27
|
# and leaves (`:pop`).
|
30
28
|
# Read stack operations from left to right.
|
31
|
-
def scope_changes(
|
29
|
+
def scope_changes(_line)
|
32
30
|
raise NotImplementedError
|
33
31
|
end
|
34
32
|
|
33
|
+
def empty?
|
34
|
+
@lines.empty?
|
35
|
+
end
|
36
|
+
|
35
37
|
# TODO: document
|
36
38
|
# @return [Array<LogMessage>]
|
37
39
|
def parse
|
38
|
-
|
40
|
+
skip_empty_lines
|
41
|
+
until empty?
|
42
|
+
parse_next_lines
|
43
|
+
skip_empty_lines
|
44
|
+
end
|
39
45
|
|
40
|
-
@
|
41
|
-
|
42
|
-
line = @lines.first
|
46
|
+
@messages
|
47
|
+
end
|
43
48
|
|
44
|
-
|
45
|
-
remove_consumed_lines 1
|
46
|
-
next
|
47
|
-
end
|
49
|
+
private
|
48
50
|
|
49
|
-
|
51
|
+
def skip_empty_lines
|
52
|
+
@lines.first
|
50
53
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
matching_pattern = patterns.detect { |p| p.begins_at?(line) }
|
54
|
+
first_nonempty_line = @lines.find_index { |line| /[^\s]/ =~ line }
|
55
|
+
remove_consumed_lines(first_nonempty_line || @lines.buffer_size)
|
56
|
+
end
|
55
57
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
58
|
+
# TODO: document
|
59
|
+
# @return [LogMessage,nil]
|
60
|
+
def parse_next_lines
|
61
|
+
raise 'Parse already done!' if @lines.empty?
|
62
|
+
|
63
|
+
line = @lines.first
|
64
|
+
Logger.debug "\nLine: '#{line.strip}'"
|
65
|
+
msg = nil
|
66
|
+
|
67
|
+
# Use the first pattern that matches. Let's hope that's a good heuristic.
|
68
|
+
# If not, we'll have to let all competitors consume and see who wins --
|
69
|
+
# which we'd decide how?
|
70
|
+
matching_pattern = patterns.detect { |p| p.begins_at?(line) }
|
71
|
+
|
72
|
+
if matching_pattern.nil?
|
73
|
+
Logger.debug '- No pattern matches'
|
74
|
+
apply_scope_changes
|
75
|
+
else
|
76
|
+
Logger.debug "- Matched pattern: '#{matching_pattern.class}'"
|
77
|
+
msg = consume_pattern(matching_pattern)
|
78
|
+
@messages.push(msg) unless msg.nil?
|
63
79
|
end
|
64
80
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
81
|
+
if @lines.empty?
|
82
|
+
@lines.close
|
83
|
+
Logger.debug "\nFiles that did not close: #{@files}"
|
84
|
+
end
|
69
85
|
|
70
|
-
|
86
|
+
msg
|
87
|
+
end
|
71
88
|
|
72
89
|
def remove_consumed_lines(i)
|
73
90
|
@lines.forward(i)
|
74
91
|
@log_line_number += i
|
75
92
|
end
|
76
93
|
|
94
|
+
# @return [LogMessage,nil]
|
77
95
|
def consume_pattern(pattern)
|
78
96
|
# Apply the pattern, i.e. read the next message!
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
97
|
+
|
98
|
+
# @type [LogMessage] message
|
99
|
+
message, consumed_lines = pattern.read(@lines)
|
100
|
+
message.log_lines = { from: @log_line_number,
|
101
|
+
to: @log_line_number + consumed_lines - 1 }
|
102
|
+
message.source_file ||= @files.last
|
103
|
+
|
104
|
+
Logger.debug message
|
105
|
+
remove_consumed_lines consumed_lines
|
106
|
+
return message
|
107
|
+
rescue StandardError => e
|
108
|
+
Logger.debug e.to_s
|
109
|
+
remove_consumed_lines 1
|
110
|
+
return nil
|
93
111
|
end
|
94
112
|
|
95
113
|
def apply_scope_changes
|
@@ -98,13 +116,13 @@ module LogParser
|
|
98
116
|
scope_changes(@lines.first).each do |op|
|
99
117
|
if op == :pop
|
100
118
|
left = @files.pop
|
101
|
-
|
119
|
+
Logger.debug "- Finished source file: '#{left.nil? ? 'nil' : left}'"
|
102
120
|
else # op is file name
|
103
|
-
|
121
|
+
Logger.debug "- Entered source file: '#{op}'"
|
104
122
|
@files.push(op)
|
105
123
|
end
|
106
124
|
end
|
107
125
|
|
108
126
|
remove_consumed_lines 1
|
109
127
|
end
|
110
|
-
end
|
128
|
+
end
|
@@ -1,18 +1,18 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# TODO: Document
|
4
4
|
module LogPattern
|
5
5
|
# TODO: Document
|
6
|
-
# @param [String]
|
6
|
+
# @param [String] _line
|
7
7
|
# @return [true,false]
|
8
|
-
def begins_at?(
|
8
|
+
def begins_at?(_line)
|
9
9
|
raise NotImplementedError
|
10
10
|
end
|
11
11
|
|
12
12
|
# TODO: Document
|
13
|
-
# @param [Array<String>]
|
13
|
+
# @param [Array<String>] _lines
|
14
14
|
# @return [Array<(LogMessage, Int)>]
|
15
|
-
def read(
|
15
|
+
def read(_lines)
|
16
16
|
raise NotImplementedError
|
17
17
|
end
|
18
18
|
end
|
@@ -34,7 +34,7 @@ module RegExpPattern
|
|
34
34
|
inclusive: false })
|
35
35
|
@start = start
|
36
36
|
@ending = ending
|
37
|
-
@
|
37
|
+
@ending = ending
|
38
38
|
end
|
39
39
|
|
40
40
|
# TODO: document
|
@@ -50,7 +50,7 @@ module RegExpPattern
|
|
50
50
|
match == (@ending[:until] == :match)
|
51
51
|
end
|
52
52
|
|
53
|
-
# TODO make failable (e.g. EOF)
|
53
|
+
# TODO: make failable (e.g. EOF)
|
54
54
|
# @param [LogBuffer] lines
|
55
55
|
# @return [Array<(LogMessage, Int)>]
|
56
56
|
def read(lines)
|
@@ -61,7 +61,8 @@ module RegExpPattern
|
|
61
61
|
ending -= 1 unless @ending[:inclusive]
|
62
62
|
|
63
63
|
# Use ending+1 since ending is the index when we drop the first line!
|
64
|
-
msg = LogMessage.new(message: lines[0, ending + 1].join("\n"),
|
64
|
+
msg = LogMessage.new(message: lines[0, ending + 1].join("\n"),
|
65
|
+
preformatted: true, level: nil, pattern: self.class)
|
65
66
|
[msg, ending + 1]
|
66
67
|
end
|
67
|
-
end
|
68
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Matches messages of this form:
|
4
4
|
#
|
@@ -9,7 +9,7 @@ class FileLineError
|
|
9
9
|
include RegExpPattern
|
10
10
|
|
11
11
|
def initialize
|
12
|
-
super(
|
12
|
+
super(%r{^(/?(?:.*?/)*[^/]+):(\d+):})
|
13
13
|
end
|
14
14
|
|
15
15
|
def read(lines)
|
@@ -18,10 +18,10 @@ class FileLineError
|
|
18
18
|
|
19
19
|
msg.source_file = @start_match[1]
|
20
20
|
line = @start_match[2].to_i
|
21
|
-
msg.source_lines = { from: line, to: line}
|
21
|
+
msg.source_lines = { from: line, to: line }
|
22
22
|
msg.preformatted = true
|
23
23
|
msg.level = :error
|
24
24
|
|
25
25
|
[msg, consumed]
|
26
26
|
end
|
27
|
-
end
|
27
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Matches messages of this form:
|
4
4
|
#
|
@@ -31,7 +31,7 @@ class PrefixedMultiLinePattern
|
|
31
31
|
msg.level = :info
|
32
32
|
else
|
33
33
|
# TODO: abort?
|
34
|
-
|
34
|
+
Logger.debug 'Unhandled message type!'
|
35
35
|
end
|
36
36
|
|
37
37
|
# source file from scope, parser does it
|
@@ -48,4 +48,4 @@ class PrefixedMultiLinePattern
|
|
48
48
|
|
49
49
|
[msg, consumed]
|
50
50
|
end
|
51
|
-
end
|
51
|
+
end
|
data/lib/tex_log_parser.rb
CHANGED
@@ -1,5 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
require 'tex_log_parser/log_buffer'
|
5
|
+
require 'tex_log_parser/log_message'
|
1
6
|
require 'tex_log_parser/log_parser'
|
2
|
-
require 'tex_log_parser/
|
7
|
+
require 'tex_log_parser/log_pattern'
|
3
8
|
Dir["#{File.expand_path(__dir__)}/tex_log_parser/patterns/*.rb"].each { |p| require p }
|
4
9
|
|
5
10
|
# TODO: document
|
@@ -16,22 +21,22 @@ class TexLogParser
|
|
16
21
|
# A scope opened and closed immediately -- log it, then
|
17
22
|
# continue with rest of the line (there can be multiple such
|
18
23
|
# things in one line, see e.g. 000.log:656)
|
19
|
-
[
|
24
|
+
[Regexp.last_match(1), :pop] + (Regexp.last_match(2).strip.empty? ? [] : scope_changes(Regexp.last_match(2)))
|
20
25
|
when /^\s*\(([^()]+?)\s*$/
|
21
26
|
# A scope opened and will be closed later.
|
22
27
|
# Happens on a dedicated line
|
23
|
-
[
|
28
|
+
[Regexp.last_match(1)]
|
24
29
|
when /^\s*(\)+)(.*)$/
|
25
30
|
# Scopes close on a dedicated line, except if they don't (cf 000.log:624)
|
26
31
|
# So we have to continue on the rest of the line. Uh oh.
|
27
|
-
([:pop] *
|
32
|
+
([:pop] * Regexp.last_match(1).length) + scope_changes(Regexp.last_match(2))
|
28
33
|
when /\([^)]*$/
|
29
34
|
# BROKEN_BY_LINEBREAKS
|
30
35
|
# Bad linebreaks can cause trailing ) to spill over. Narf.
|
31
36
|
# e.g. 000.log:502-503
|
32
|
-
[
|
37
|
+
['dummy'] # Compensate the bad pop that will happen next line.
|
33
38
|
else
|
34
39
|
[]
|
35
40
|
end
|
36
41
|
end
|
37
|
-
end
|
42
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
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.
|
4
|
+
version: 1.0.0.pre.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Raphael Reitzig
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-04-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|
@@ -76,6 +76,7 @@ files:
|
|
76
76
|
- LICENSE
|
77
77
|
- README.md
|
78
78
|
- bin/texlogparser
|
79
|
+
- lib/logger.rb
|
79
80
|
- lib/tex_log_parser.rb
|
80
81
|
- lib/tex_log_parser/log_buffer.rb
|
81
82
|
- lib/tex_log_parser/log_message.rb
|