tex_log_parser 1.0.0.pre.10 → 1.0.0.pre.14
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 +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
|
[](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
|
-
[](https://circleci.com/gh/reitzig/texlogparser)
|
8
8
|
[](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
|