tex_log_parser 1.0.0.pre.10 → 1.0.0.pre.14
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +1 -1
- data/bin/texlogparser +4 -4
- data/lib/log_parser/buffer.rb +129 -0
- data/lib/{tex_log_parser → log_parser}/log_parser.rb +35 -22
- data/lib/log_parser/logger.rb +33 -0
- data/lib/log_parser/message.rb +79 -0
- data/lib/log_parser/pattern.rb +124 -0
- data/lib/tex_log_parser/patterns/bad_hbox_warning.rb +20 -16
- data/lib/tex_log_parser/patterns/fatal_error_occurred.rb +21 -17
- data/lib/tex_log_parser/patterns/file_line_error.rb +24 -20
- data/lib/tex_log_parser/patterns/highlighted_messages.rb +92 -88
- data/lib/tex_log_parser/patterns/prefixed_multi_line_pattern.rb +48 -44
- data/lib/tex_log_parser/patterns/runaway_parameter_error.rb +21 -17
- data/lib/tex_log_parser/patterns/standard_error.rb +39 -35
- data/lib/tex_log_parser.rb +30 -9
- data/lib/version.rb +7 -0
- metadata +37 -9
- data/lib/logger.rb +0 -21
- data/lib/tex_log_parser/log_buffer.rb +0 -62
- data/lib/tex_log_parser/log_message.rb +0 -57
- data/lib/tex_log_parser/log_pattern.rb +0 -69
- data/lib/tex_log_parser/version.rb +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 9452fd78396c5094be2717ac70979ac88892678f73433966ee1ca79ba21fa73d
|
4
|
+
data.tar.gz: 0e72ee4dc90ca406c2ba557eb017bca1c1a6ae682f9b2847d7f391ac4ddf0483
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 52cf6f343c49d26fcc9e880301ea082e89f279a65a59ef1bd3c02c25d5810bf05de410224ec28008d55d64b295ea3168b683422b1a85e530a9e886507963da33
|
7
|
+
data.tar.gz: cda1bbf061fb89616784def652834b4020061d020be9c3874099a5bb7b7ed3b165f6343bf00398f3cbdbddae42cf8ab12b41d7dfc87929619c419f2fd56c32e5
|
data/README.md
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
[![Yard docs](http://img.shields.io/badge/yard-docs-green.svg)](http://www.rubydoc.info/gems/tex_log_parser/1.0.0.pre.2) **˙**
|
5
5
|
[![Maintainability](https://api.codeclimate.com/v1/badges/748992a2c5f6570797d4/maintainability)](https://codeclimate.com/github/reitzig/texlogparser/maintainability)
|
6
6
|
[![Test Coverage](https://api.codeclimate.com/v1/badges/748992a2c5f6570797d4/test_coverage)](https://codeclimate.com/github/reitzig/texlogparser/test_coverage) **˙**
|
7
|
-
[![
|
7
|
+
[![Circle CI](https://circleci.com/gh/reitzig/texlogparser.svg?style=svg)](https://circleci.com/gh/reitzig/texlogparser)
|
8
8
|
[![Inline docs](http://inch-ci.org/github/reitzig/texlogparser.svg?branch=master)](http://inch-ci.org/github/reitzig/texlogparser)
|
9
9
|
|
10
10
|
Eases the many pains around logs from (La)TeX engines.
|
data/bin/texlogparser
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require 'tex_log_parser'
|
5
|
-
require '
|
5
|
+
require 'version'
|
6
6
|
require 'optparse'
|
7
7
|
require 'json'
|
8
8
|
|
@@ -17,7 +17,7 @@ formats = %i[file_line json]
|
|
17
17
|
OptionParser.new do |opts|
|
18
18
|
opts.banner = 'Usage: texlogparser [options]'
|
19
19
|
opts.on('-d', '--debug', 'Output debug information') do
|
20
|
-
Logger.
|
20
|
+
LogParser::Logger.debug = true
|
21
21
|
end
|
22
22
|
opts.on('-f ENUM', '--format ENUM', formats,
|
23
23
|
'Output format', "One of: #{formats.map(&:to_s).join(', ')}") do |format|
|
@@ -39,7 +39,7 @@ OptionParser.new do |opts|
|
|
39
39
|
exit
|
40
40
|
end
|
41
41
|
opts.on_tail('-v', '--version', 'Show version') do
|
42
|
-
puts TexLogParser::VERSION
|
42
|
+
puts "#{File.basename(__FILE__)} #{TexLogParser::VERSION}"
|
43
43
|
exit
|
44
44
|
end
|
45
45
|
|
@@ -58,7 +58,7 @@ end
|
|
58
58
|
|
59
59
|
input = STDIN
|
60
60
|
input = File.open(options[:input], 'r') if options[:input].is_a?(String)
|
61
|
-
parser = TexLogParser.new(input
|
61
|
+
parser = TexLogParser.new(input)
|
62
62
|
messages = parser.parse
|
63
63
|
|
64
64
|
output = STDOUT
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LogParser
|
4
|
+
# Log buffers provide line after line from a given source,
|
5
|
+
# and only store as many lines as necessary:
|
6
|
+
#
|
7
|
+
# * Read lines from `log` lazily, i.e. only if {find_index} or {[]} are called.
|
8
|
+
# * Drop lines that are no longer needed, i.e. when {forward} is called.
|
9
|
+
class Buffer
|
10
|
+
# Creates a new buffer that reads lines from the given source.
|
11
|
+
#
|
12
|
+
# @param [Array<String>,IO,StringIO] log
|
13
|
+
# Where to read log lines from. Can be either an array of `String`,
|
14
|
+
# an `IO` (e.g. `STDIN`), or a `StringIO`.
|
15
|
+
def initialize(log)
|
16
|
+
@buffer = []
|
17
|
+
@stream = nil
|
18
|
+
|
19
|
+
@buffer = log if log.is_a?(Array)
|
20
|
+
@stream = log if log.is_a?(IO) || log.is_a?(StringIO)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Determines whether there are more lines in this log (buffer).
|
24
|
+
#
|
25
|
+
# @return [true,false]
|
26
|
+
def empty?
|
27
|
+
@buffer.empty? && stream_is_done?
|
28
|
+
end
|
29
|
+
|
30
|
+
# The current size of this buffer, that is the number of lines currently stored.
|
31
|
+
#
|
32
|
+
# @return [Integer]
|
33
|
+
def buffer_size
|
34
|
+
@buffer.size
|
35
|
+
end
|
36
|
+
|
37
|
+
# The first available line, if any.
|
38
|
+
#
|
39
|
+
# _Note:_ Will read from the source if necessary.
|
40
|
+
#
|
41
|
+
# @return [String,nil]
|
42
|
+
def first
|
43
|
+
self[0]
|
44
|
+
end
|
45
|
+
|
46
|
+
# Finds the first index of an element that fulfills a predicate.
|
47
|
+
#
|
48
|
+
# @param [Integer] starting_from
|
49
|
+
# The first index to check. That is, indices `(0..starting_from - 1)` are skipped.
|
50
|
+
# *Note:* Indices are relative to the start of the buffer, _not_ the start of the log!
|
51
|
+
#
|
52
|
+
# @yield Invokes a block as predicate over lines.
|
53
|
+
# @yieldparam [String] line
|
54
|
+
# A line of the log.
|
55
|
+
# @yieldreturn [true,false]
|
56
|
+
# `true` if (and only if) `line` fulfills the predicate.
|
57
|
+
#
|
58
|
+
# @return [Integer, nil]
|
59
|
+
# The first index of an element that fulfills the predicate, if any;
|
60
|
+
# otherwise, `nil`.
|
61
|
+
def find_index(starting_from = 0)
|
62
|
+
index = starting_from
|
63
|
+
element = self[index]
|
64
|
+
|
65
|
+
until element.nil?
|
66
|
+
return index if yield(element)
|
67
|
+
index += 1
|
68
|
+
element = self[index]
|
69
|
+
end
|
70
|
+
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
|
74
|
+
# Retrieves the next `length` many log lines starting from index `offset`.
|
75
|
+
#
|
76
|
+
# @param [Integer] offset
|
77
|
+
# The first index to retrieve.
|
78
|
+
# *Note:* Indices are relative to the start of the buffer, _not_ the start of the log!
|
79
|
+
# @param [Integer] length
|
80
|
+
# The number of elements to retrieve.
|
81
|
+
# @return [String,Array<String>]
|
82
|
+
# If `length` is set, returns the array of lines with indices in `(offset..offset+length-1)`.
|
83
|
+
# Otherwise, returns the line with index `offset`.
|
84
|
+
def [](offset, length = nil)
|
85
|
+
base_length = length || 1
|
86
|
+
while offset + base_length > @buffer.size
|
87
|
+
return (length.nil? ? nil : @buffer[offset, @buffer.size]) if stream_is_done?
|
88
|
+
@buffer.push(@stream.readline.rstrip)
|
89
|
+
end
|
90
|
+
|
91
|
+
length.nil? ? @buffer[offset] : @buffer[offset, length]
|
92
|
+
end
|
93
|
+
|
94
|
+
# Moves the front of this buffer forwards by `offset` elements.
|
95
|
+
#
|
96
|
+
# That is, the following code returns `true`:
|
97
|
+
# ```ruby
|
98
|
+
# before = buffer[offset]
|
99
|
+
# buffer.forward[offset]
|
100
|
+
# after = buffer.first
|
101
|
+
# before == after
|
102
|
+
# ```
|
103
|
+
#
|
104
|
+
# @param [Integer] offset
|
105
|
+
# The number of lines to drop.
|
106
|
+
# @return [void]
|
107
|
+
def forward(offset = 1)
|
108
|
+
self[offset]
|
109
|
+
@buffer.slice!(0, offset)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Closes the `IO` this buffer reads from, if any.
|
113
|
+
#
|
114
|
+
# @return [void]
|
115
|
+
def close
|
116
|
+
@stream.close unless @stream.nil? || @stream.closed?
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
# False if (and only if) this buffer reads from an `IO`,
|
122
|
+
# that `IO` is not closed, and has not yet reached the end.
|
123
|
+
#
|
124
|
+
# @return [true,false]
|
125
|
+
def stream_is_done?
|
126
|
+
@stream.nil? || @stream.closed? || @stream.eof?
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -1,29 +1,55 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'log_parser/logger'
|
4
|
+
require 'log_parser/buffer'
|
5
|
+
require 'log_parser/message'
|
6
|
+
require 'log_parser/pattern'
|
7
|
+
|
3
8
|
# TODO: document
|
4
9
|
module LogParser
|
5
10
|
attr_reader :messages
|
6
11
|
attr_reader :scope_changes_by_line if Logger.debug?
|
7
12
|
|
8
13
|
# TODO: document
|
14
|
+
# @return [Array<Message>]
|
15
|
+
def parse
|
16
|
+
skip_empty_lines
|
17
|
+
until empty?
|
18
|
+
parse_next_lines
|
19
|
+
skip_empty_lines
|
20
|
+
end
|
21
|
+
|
22
|
+
# TODO: Remove duplicates?
|
23
|
+
@messages
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
# Creates a new instance.
|
29
|
+
#
|
30
|
+
# This parser will read lines one by one from the given `log`.
|
31
|
+
# If it is an `IO` or `StringIO`, only those lines currently under investigation will be kept in memory.
|
32
|
+
#
|
9
33
|
# @param [Array<String>,IO,StringIO] log
|
10
|
-
#
|
11
|
-
def initialize(log
|
34
|
+
# A set of log lines that will be parsed.
|
35
|
+
def initialize(log)
|
12
36
|
@files = []
|
13
37
|
|
14
38
|
@messages = []
|
15
39
|
@log_line_number = 1
|
16
|
-
@lines =
|
40
|
+
@lines = LogParser::Buffer.new(log)
|
17
41
|
|
18
42
|
Logger.debug "Parsing from '#{log}'"
|
19
43
|
@scope_changes_by_line = {} if Logger.debug?
|
20
44
|
end
|
21
45
|
|
22
|
-
# @
|
46
|
+
# @abstract
|
47
|
+
# @return [Array<Pattern>]
|
23
48
|
def patterns
|
24
49
|
raise NotImplementedError
|
25
50
|
end
|
26
51
|
|
52
|
+
# @abstract
|
27
53
|
# @param [String] _line
|
28
54
|
# @return [Array<String,:pop>] A list of new scopes this line enters (strings)
|
29
55
|
# and leaves (`:pop`).
|
@@ -36,19 +62,6 @@ module LogParser
|
|
36
62
|
@lines.empty?
|
37
63
|
end
|
38
64
|
|
39
|
-
# TODO: document
|
40
|
-
# @return [Array<LogMessage>]
|
41
|
-
def parse
|
42
|
-
skip_empty_lines
|
43
|
-
until empty?
|
44
|
-
parse_next_lines
|
45
|
-
skip_empty_lines
|
46
|
-
end
|
47
|
-
|
48
|
-
# TODO: Remove duplicates?
|
49
|
-
@messages
|
50
|
-
end
|
51
|
-
|
52
65
|
private
|
53
66
|
|
54
67
|
def skip_empty_lines
|
@@ -59,7 +72,7 @@ module LogParser
|
|
59
72
|
end
|
60
73
|
|
61
74
|
# TODO: document
|
62
|
-
# @return [
|
75
|
+
# @return [Message,nil]
|
63
76
|
def parse_next_lines
|
64
77
|
raise 'Parse already done!' if @lines.empty?
|
65
78
|
|
@@ -91,16 +104,16 @@ module LogParser
|
|
91
104
|
|
92
105
|
def remove_consumed_lines(i)
|
93
106
|
@lines.forward(i)
|
94
|
-
@log_line_number
|
107
|
+
@log_line_number += i
|
95
108
|
|
96
|
-
@scope_changes_by_line[@log_line_number] = [] if Logger.debug? && i
|
109
|
+
@scope_changes_by_line[@log_line_number] = [] if Logger.debug? && i.positive?
|
97
110
|
end
|
98
111
|
|
99
|
-
# @return [
|
112
|
+
# @return [Message,nil]
|
100
113
|
def consume_pattern(pattern)
|
101
114
|
# Apply the pattern, i.e. read the next message!
|
102
115
|
|
103
|
-
# @type [
|
116
|
+
# @type [Message] message
|
104
117
|
message, consumed_lines = pattern.read(@lines)
|
105
118
|
message.log_lines = { from: @log_line_number,
|
106
119
|
to: @log_line_number + consumed_lines - 1 }
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LogParser
|
4
|
+
# A simple helper, probably to be replaced by a proper logging library
|
5
|
+
# at some point.
|
6
|
+
class Logger
|
7
|
+
class << self
|
8
|
+
# Switches debugging mode on and off.
|
9
|
+
#
|
10
|
+
# @param [true,false] flag
|
11
|
+
# @return [void]
|
12
|
+
def debug=(flag)
|
13
|
+
@debugging = flag
|
14
|
+
end
|
15
|
+
|
16
|
+
# Indicates whether we are debugging.
|
17
|
+
#
|
18
|
+
# @return [true,false]
|
19
|
+
# `true` if we are in debugging mode, `false` otherwise.
|
20
|
+
def debug?
|
21
|
+
@debugging || !ENV['DEBUG'].nil?
|
22
|
+
end
|
23
|
+
|
24
|
+
# Logs the given message to STDOUT if `debug?` is true.
|
25
|
+
#
|
26
|
+
# @param [String] message
|
27
|
+
# @return [void]
|
28
|
+
def debug(message)
|
29
|
+
puts message if debug?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module LogParser
|
6
|
+
# A single log message.
|
7
|
+
#
|
8
|
+
# @attr [String] message
|
9
|
+
# The actual message body from the log.
|
10
|
+
# @attr [String,nil] source_file
|
11
|
+
# The name of the source file this message originated from.
|
12
|
+
# `nil` if it could not be determined.
|
13
|
+
# @attr [Hash<Symbol, Int>,nil] source_lines
|
14
|
+
# A hash with keys `:from` and `:to` mapping to the first and last line index in `source_file` this message pertains to.
|
15
|
+
# `nil` if they could not be determined.
|
16
|
+
# @attr [Hash<Symbol, Int>,nil] log_lines
|
17
|
+
# A hash with keys `:from` and `:to` mapping to the first and last line index in the log file that this message covers.
|
18
|
+
# `nil` if they could not be determined.
|
19
|
+
# @attr [true,false] preformatted
|
20
|
+
# If `true`, `message` should be printed as-is.
|
21
|
+
# Otherwise, whitespace can be eliminated.
|
22
|
+
# @attr [:error,:warning,:info,:debug] level
|
23
|
+
# The severity of this message.
|
24
|
+
# @attr [Class] pattern
|
25
|
+
# The {LogParser::Pattern} this message was matched by.
|
26
|
+
class Message
|
27
|
+
def initialize(message:, source_file: nil, source_lines: nil, log_lines: nil,
|
28
|
+
preformatted: false, level: :info, pattern: nil)
|
29
|
+
@message = message
|
30
|
+
@source_file = source_file
|
31
|
+
@source_lines = source_lines
|
32
|
+
@log_lines = log_lines
|
33
|
+
@preformatted = preformatted
|
34
|
+
@level = level
|
35
|
+
@pattern = pattern
|
36
|
+
end
|
37
|
+
|
38
|
+
attr_accessor :message, :source_file, :source_lines, :log_lines,
|
39
|
+
:preformatted, :level
|
40
|
+
|
41
|
+
# Convert this message to a file-line string representation.
|
42
|
+
#
|
43
|
+
# @return [String]
|
44
|
+
def to_s
|
45
|
+
lines = if @source_lines.nil?
|
46
|
+
''
|
47
|
+
else
|
48
|
+
# @type [Hash<Symbol, Int>] @source_lines
|
49
|
+
":#{@source_lines.values.uniq.join('-')}"
|
50
|
+
end
|
51
|
+
|
52
|
+
message = @message
|
53
|
+
message = message.split("\n").map(&:strip).join(' ') unless @preformatted
|
54
|
+
message += "\nLog pattern: '#{@pattern}'" if Logger.debug?
|
55
|
+
|
56
|
+
<<~MSG
|
57
|
+
#{@source_file}#{lines}: #{@level.to_s.upcase}
|
58
|
+
#{message}
|
59
|
+
MSG
|
60
|
+
end
|
61
|
+
|
62
|
+
# Convert this message to JSON.
|
63
|
+
#
|
64
|
+
# @return [String]
|
65
|
+
# The JSON string representing this message.
|
66
|
+
def to_json(_options = {})
|
67
|
+
hash = {
|
68
|
+
level: @level,
|
69
|
+
source_file: @source_file,
|
70
|
+
source_lines: @source_lines,
|
71
|
+
message: @message,
|
72
|
+
log_lines: @log_lines,
|
73
|
+
preformatted: @preformatted
|
74
|
+
}
|
75
|
+
hash[:pattern] = @pattern if Logger.debug?
|
76
|
+
JSON.pretty_generate hash
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LogParser
|
4
|
+
# Represents a certain pattern log message. The model we use is that
|
5
|
+
# * a pattern tells you if a message starts in a given line, and
|
6
|
+
# * reads lines from there on until it ends.
|
7
|
+
# The result will be a {Message}.
|
8
|
+
module Pattern
|
9
|
+
# Checks if this message pattern matches the given line.
|
10
|
+
#
|
11
|
+
# @abstract
|
12
|
+
# @param [String] _line
|
13
|
+
# The log line currently under investigation.
|
14
|
+
# @return [true,false]
|
15
|
+
# `true` if (and only if) this pattern can parse a single message from the given line onwards.
|
16
|
+
def begins_at?(_line)
|
17
|
+
raise NotImplementedError
|
18
|
+
end
|
19
|
+
|
20
|
+
# Reads a message from the given lines.
|
21
|
+
#
|
22
|
+
# @abstract
|
23
|
+
# @param [LogParser::Buffer] _lines
|
24
|
+
# A source of log lines to read from.
|
25
|
+
# @return [Array<(Message, Int)>]
|
26
|
+
# An array of the message that was read, and the number of lines that it spans.
|
27
|
+
# @raise
|
28
|
+
# If no message end could be found among the given lines.
|
29
|
+
def read(_lines)
|
30
|
+
raise NotImplementedError
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# (see Pattern)
|
35
|
+
#
|
36
|
+
# This type of pattern is characterized mainly by two regular expressions:
|
37
|
+
# * One for matching the first line of a message, and
|
38
|
+
# * one for matching the last line (+- 1) of a message, which may depend on the first.
|
39
|
+
#
|
40
|
+
# @attr [Regexp] start
|
41
|
+
# Used to determine where a message starts.
|
42
|
+
# @see begins_at?
|
43
|
+
# @see initialize
|
44
|
+
# @attr [MatchData] start_match
|
45
|
+
# While a message is being processed, this attribute holds the match from the first line.
|
46
|
+
# @attr [Regexp] ending
|
47
|
+
# Used to determine where a message ends.
|
48
|
+
# @see ends_at?
|
49
|
+
# @see initialize
|
50
|
+
module RegExpPattern
|
51
|
+
include Pattern
|
52
|
+
|
53
|
+
# Creates a new instance.
|
54
|
+
#
|
55
|
+
# @param [Regexp] start
|
56
|
+
# Used to determine where a message starts.
|
57
|
+
# See also {begins_at?}.
|
58
|
+
# @param [Hash] ending
|
59
|
+
# Used to determine where a message ends.
|
60
|
+
# See also {ends_at?}.
|
61
|
+
# @option ending [Regexp] :pattern
|
62
|
+
# Describes either what all lines of a message look like, or matches the first line not part of the same message.
|
63
|
+
# @option ending [:match,:mismatch] :until
|
64
|
+
# Determines what is matched against `:pattern`.
|
65
|
+
# * If `:match`, messages end at the first line that _matches_ `:pattern`.
|
66
|
+
# * If `:mismatch`, messages end at the first line that does _not_ match `:pattern`.
|
67
|
+
# @option ending [true,false] :inclusive
|
68
|
+
# Determines whether the first line that (mis)matched `:pattern` should be part of the message.
|
69
|
+
def initialize(start, ending = { pattern: ->(_) { /^\s*$/ },
|
70
|
+
until: :match,
|
71
|
+
inclusive: false })
|
72
|
+
@start = start
|
73
|
+
@ending = ending
|
74
|
+
end
|
75
|
+
|
76
|
+
# Checks if this message pattern matches the given line,
|
77
|
+
# that is whether the `start` regexp (see {initialize}) matches it.
|
78
|
+
#
|
79
|
+
# @param [String] line
|
80
|
+
# The log line currently under investigation.
|
81
|
+
# @return [true,false]
|
82
|
+
# `true` if (and only if) this pattern can parse a single message from the given line onwards.
|
83
|
+
def begins_at?(line)
|
84
|
+
match = @start.match(line)
|
85
|
+
@start_match = match unless match.nil?
|
86
|
+
!match.nil?
|
87
|
+
end
|
88
|
+
|
89
|
+
# Reads a message from the given lines.
|
90
|
+
#
|
91
|
+
# @param [LogParser::Buffer] lines
|
92
|
+
# A source of log lines to read from.
|
93
|
+
# @return [Array<(Message, Int)>]
|
94
|
+
# An array of the message that was read, and the number of lines that it spans.
|
95
|
+
# @raise
|
96
|
+
# If no message end could be found among the given lines.
|
97
|
+
def read(lines)
|
98
|
+
ending = lines.find_index(1) { |l| ends_at?(l) }
|
99
|
+
raise "Did not find end of message (pattern '#{self.class}')." if ending.nil?
|
100
|
+
ending -= 1 unless @ending[:inclusive]
|
101
|
+
|
102
|
+
# Use ending+1 since ending is the index when we drop the first line!
|
103
|
+
msg = LogParser::Message.new(message: lines[0, ending + 1].join("\n"),
|
104
|
+
preformatted: true, level: nil,
|
105
|
+
pattern: self.class)
|
106
|
+
[msg, ending + 1]
|
107
|
+
end
|
108
|
+
|
109
|
+
protected
|
110
|
+
|
111
|
+
# Checks if the currently read message ends at the given line,
|
112
|
+
# that is whether the `ending[:pattern]` regexp (see {initialize}) matches it.
|
113
|
+
#
|
114
|
+
# @param [String] line
|
115
|
+
# The log line currently under investigation.
|
116
|
+
# @return [true,false]
|
117
|
+
# `true` if (and only if) the currently read message ends at the given line.
|
118
|
+
def ends_at?(line)
|
119
|
+
match = @ending[:pattern][@start_match].match(line)
|
120
|
+
@end_match = match unless match.nil?
|
121
|
+
!match.nil? == (@ending[:until] == :match)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -1,24 +1,28 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
class TexLogParser
|
4
|
+
# TODO: document
|
5
|
+
class BadHboxWarning
|
6
|
+
include LogParser::RegExpPattern
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
# Creates a new instance.
|
9
|
+
def initialize
|
10
|
+
super(/^(Over|Under)full \\hbox.*at lines (\d+)--(\d+)/,
|
11
|
+
{ pattern: ->(_) { /^\s*\[\]\s*$/ }, until: :match, inclusive: false }
|
12
|
+
)
|
13
|
+
end
|
12
14
|
|
13
|
-
|
14
|
-
|
15
|
-
|
15
|
+
# (see LogParser::RegExpPattern#read)
|
16
|
+
def read(lines)
|
17
|
+
# @type [Message] msg
|
18
|
+
msg, consumed = super(lines)
|
16
19
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
20
|
+
msg.source_lines = { from: @start_match[2].to_i,
|
21
|
+
to: @start_match[3].to_i }
|
22
|
+
msg.preformatted = true
|
23
|
+
msg.level = :warning
|
21
24
|
|
22
|
-
|
25
|
+
[msg, consumed]
|
26
|
+
end
|
23
27
|
end
|
24
28
|
end
|
@@ -1,25 +1,29 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
|
8
|
-
|
3
|
+
class TexLogParser
|
4
|
+
# Matches messages of this form:
|
5
|
+
#
|
6
|
+
# ! ==> Fatal error occurred, no output PDF file produced!
|
7
|
+
# Transcript written on plain.log.
|
8
|
+
class FatalErrorOccurred
|
9
|
+
include LogParser::RegExpPattern
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
# Creates a new instance.
|
12
|
+
def initialize
|
13
|
+
super(/^!\s+==>/,
|
14
|
+
{ pattern: ->(_) { /Transcript written/ }, until: :match, inclusive: true }
|
15
|
+
)
|
16
|
+
end
|
15
17
|
|
16
|
-
|
17
|
-
|
18
|
-
|
18
|
+
# (see LogParser::RegExpPattern#read)
|
19
|
+
def read(lines)
|
20
|
+
# @type [Message] msg
|
21
|
+
msg, consumed = super(lines)
|
19
22
|
|
20
|
-
|
21
|
-
|
23
|
+
msg.level = :error
|
24
|
+
msg.preformatted = false
|
22
25
|
|
23
|
-
|
26
|
+
[msg, consumed]
|
27
|
+
end
|
24
28
|
end
|
25
29
|
end
|
@@ -1,29 +1,33 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
|
9
|
-
|
3
|
+
class TexLogParser
|
4
|
+
# Matches messages of this form:
|
5
|
+
#
|
6
|
+
# ./plain.tex:31: Undefined control sequence.
|
7
|
+
# l.31 ...t contains some \ref{warnings} and \errors
|
8
|
+
# for testing.
|
9
|
+
class FileLineError
|
10
|
+
include LogParser::RegExpPattern
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
12
|
+
# Creates a new instance.
|
13
|
+
def initialize
|
14
|
+
super(%r{^(/?(?:.*?/)*[^/]+):(\d+):})
|
15
|
+
end
|
14
16
|
|
15
|
-
|
16
|
-
|
17
|
-
|
17
|
+
# (see LogParser::RegExpPattern#read)
|
18
|
+
def read(lines)
|
19
|
+
# @type [Message] msg
|
20
|
+
msg, consumed = super(lines)
|
18
21
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
22
|
+
msg.source_file = @start_match[1]
|
23
|
+
line = @start_match[2].to_i
|
24
|
+
msg.source_lines = { from: line, to: line }
|
25
|
+
msg.preformatted = true
|
26
|
+
msg.level = :error
|
24
27
|
|
25
|
-
|
28
|
+
msg.message.gsub!(@start, '')
|
26
29
|
|
27
|
-
|
30
|
+
[msg, consumed]
|
31
|
+
end
|
28
32
|
end
|
29
33
|
end
|