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

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 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