class Rake4LaTeX::TeX_Statistic

Get an overview on status of a generated TeX-Project.

end

Constants

Defaults_texmessages2hash

Define the defaults for TeX_Statistic#texmessages2hash

end

FILEDATA_STRUCT

Container for some data

  • count: numbers of open ( in file, needed to detect end of file

  • logcontent: Extract of the log-file, contains only the parts for the actual source file

  • logstart: Line in log-file

PAGE_PATTERN

Regexp to detect new pages in result file. New pages looks like ā€œ[1.1nā€ or [9.9]

Attributes

stat[R]

Hash with a status overview on the logs of the TeX-project.

texmessages[R]

Array with TeXMessage-object. Contains messages of the TeX-log.

Public Class Methods

new( filename ) click to toggle source

Define the main file for the log-analyse.

# File lib/rake4latex/tex_statistic.rb, line 93
def initialize( filename )
  @stat = {}
  @texmessages = []

  @texfile = filename.ext('tex')
  if ! File.exist?(@texfile)
    @stat['_'] ||= {}
    @stat['_'][:tex]  = "No TeX File #{@texfile} found"
  end

  #Analyse TeX-log
  if File.exist?(@texfile.ext('log'))
    @texmessages = log_analyse_content( File.read(@texfile.ext('log')),nil,nil)
    @stat[@texfile.ext('log')] = texmessages2hash(:messages => @texmessages) #option , :page_info ??
  else
    @stat[@texfile.ext('log')] ||= { :errors => "No log-file #{@texfile.ext('log')} found" }
  end #File.exist?(@texfile.ext('log'))
  
  #Analyse BibTeX-log
  if File.exist?(@texfile.ext('blg'))
    @stat[@texfile.ext('blg')] = blg_analyse(@texfile.ext('blg'))
  end #File.exist?(@texfile.ext('log'))
  
  #Analyse index-logs (multiple possible with splitindex)
  #Example for dirpattern: testdokument{-*,}.ilg
  #
  #Missing: Logs from glossaries.sty
  Dir["#{@texfile.ext}{-*,}".ext('ilg')].each{|ilg_filename|
    @stat[ilg_filename] = ilg_analyse(ilg_filename)
  }
  
end

Public Instance Methods

blg_analyse(logfilename) click to toggle source

Analyse a bibTeX-log.

end

# File lib/rake4latex/tex_statistic.rb, line 315
def blg_analyse(logfilename)
    stat = {}
    File.readlines(logfilename).each{ |logline|
      case logline
        when /Database file #(.*): (.*)/
          stat[:source_information] ||= [] << "Database file #{$2} used"
        when  /Warning--I didn't find a database entry for "(.*)"/
          ( stat[:warnings] ||= [] ) << "Databaseentry #{$1} missing"
        when  /Warning--Empty definition in (.*)/
          ( stat[:warnings] ||= [] ) << "Empty definition #{$1}"
        when  /Warning--(.*)/
          ( stat[:warnings] ||= [] ) << "#{$1}"
        when  /I couldn't open (.*) file (.*)/
          ( stat[:errors] ||= [] ) << "#{$1} #{$2} not found"
        when  /I found no (.*) commands---while reading file(.*)/
          ( stat[:warnings] ||= [] ) << "found no #{$1} in #{$2}"                     
        when  /(.*)---line (.*) of file(.*)/
          #line-number ist unsinnig
          ( stat[:errors] ||= [] ) << "#{$1} in #{$3}"
      end
    }
    stat
end
ilg_analyse(logfilename) click to toggle source

Analyse the makeindex-log.

end

# File lib/rake4latex/tex_statistic.rb, line 341
def ilg_analyse(logfilename)
    stat = {}
    error = nil
    File.readlines(logfilename).each{ |logline|
      if error  #Index error announced in previous line
        ( stat[:errors] ||= [] ) << "#{logline.chomp.sub(/.*--/, "")} #{error}"
        error = nil
      else
        case logline
        when /Scanning input file (.*)\...done \((.*) entries accepted, (.*) rejected\)./
          #~ stat[:source_information] ||= [] << "input file #{$1} (#{$2} entries accepted, #{$3} rejected)"
          stat[:source_information] ||= []
          stat[:source_information] << "Input file: #{$1}"
          stat[:source_information] << "Entries accepted: #{$2}"
          stat[:source_information] << "Entries rejected: #{$3}"
        #~ when /done \((.*) entries accepted, (.*) rejected\)./
          #~ result[:rejected] += $2.to_i() if $2.to_i() > 0
        when /!! Input index error \(file = (.*), line = (.*)\):/
          #Error-message on next line
          error = "(file #{$1} line #{$2}: #{logline})"
        end
      end #if error
    }
    stat
end
log_analyse_by_file(logfilename) click to toggle source

Analyse the TeX-log file.

The log-text is splitted into files and pages and analysed separatly.

# File lib/rake4latex/tex_statistic.rb, line 148
  def log_analyse_by_file(logfilename)

    if ! File.exist?(logfilename)
      return [
        TeXMessage.new(:latex_errors, nil, nil, "No log-file #{logfilename} found", {}, 0, logfilename)
      ]
    end

    log = File.read(logfilename)
    stat = {}
    texmessages = []
    

    #Analyse log for page and source file
    page = nil  #store the actual page
    file   = nil  #store the actual file
    filestack = []  #
    filelist = { nil => FILEDATA_STRUCT.new(nil, 0, 0, 1, '' ) }
    logline = 0
    #~ filecount = Hash.new(0) #numbers of open ( in file, needed to detect end of file
    #~ filestartpage = {}  #Page, where the source is started.
    #~ filecontent = {nil => ''}#content of a file. The content of sub-includes are not part of this content.
    
    # inputs looks like "(./filename_en.toc \n" or "(./filename.lot)"
    file_pattern = %r{\((?:\.|c:)\/.*?[\)\n]}
    
    #Splitt at the file- and page-patterns and at ( and )
    #If the ( ) are balanced, then we can detect a file end with )
    #
    log.split(/(#{file_pattern}|\(|\)|#{PAGE_PATTERN})/ ).each{|x|
      logline += x.count("\n")
      case x #x[0]
        when file_pattern
          if x[-1,1] == ')'
            #~ puts "open and close #{file} on page #{page} #{filestack.inspect}" if file =~ /^\./
            filelist[file].logcontent << x
          else
            filelist[file].logcontent << x
            file = x.strip.sub(/\(/,'')
            filestack << file
            filelist[file] = FILEDATA_STRUCT.new(file, page, logline, 1, '' )
            #~ puts "open #{file} on page #{page} #{filestack.inspect}" if file =~ /^\./
          end
        when PAGE_PATTERN
          page = $1
#~ if x =~ /76/
#~ puts x.inspect
#~ puts page.inspect
#~ puts logline
#~ puts file
#~ end
          filelist[file].logcontent << x
          #Add page to all open log collection.
          #Without this, we would ignore all pages between the insertion of an include.
          filelist.each{| name, filedata | filedata.logcontent << "\n#{x} ##ADDED to log #{logline + 1}##\n" }
        when '('
          filelist[file].count+= 1
          filelist[file].logcontent << x
        when ')'
          filelist[file].count = filelist[file].count - 1
          if filelist[file].count == 0
            filestack.pop
            #~ puts "close #{file} on page #{page} #{filestack.inspect}" if file =~ /^\./
            texmessages.concat(log_analyse_content(filelist[file].logcontent, filelist[file].name, filelist[file].startpage, filelist[file].logstart ))
            if ! stat.empty?
              #hier stats sammeln (je vor/hinter input gab es meldungen)
              #~ puts '============'
              #~ puts file
              #~ puts stat.to_yaml
            end
            #~ filecontent.delete(file)
            filelist[file].logcontent = ''
            file = filestack.last
          else
            filelist[file].logcontent << x
          end
        else
          filelist[file].logcontent << x
        end
      #~ puts "%6s : %s" % [page, file ]
    }
    #Get unclear messages (top file?)
    texmessages.concat(log_analyse_content(filelist[nil].logcontent, filelist[nil].name, filelist[nil].startpage, filelist[nil].logstart ))
    #~ puts filestack.inspect
    texmessages
  end
log_analyse_content(log, file, startpage, logstart = 0) click to toggle source

Analyse the content of an extract of the TeX-log file and collect the messages as TeXMessage-objects.

file, startpage and logstart are only needed for the splitted analyse to get the initial values.

end

# File lib/rake4latex/tex_statistic.rb, line 240
def log_analyse_content(log, file, startpage, logstart = 0)
  page = startpage 
  messages =[]
  linecorrection = logstart + 1
  get_next_lines = nil
  log.split("\n").each_with_index{|logline, loc_lineno|
    logline.strip!
    #Check if the message is continued on next line
    case get_next_lines
      when Numeric  #Counter to add the next x lines to the previous message
        messages.last << logline
        if get_next_lines ==1
          get_next_lines = nil
        else
          get_next_lines = get_next_lines - 1
        end
        next
      when Regexp
        if logline =~ get_next_lines
          messages.last << $~.post_match
          messages.last.line = $1 if logline =~ /on input line (\d+)/
          next
        else
          get_next_lines = nil
        end
    end
    lineno = loc_lineno + linecorrection
    case logline
      when PAGE_PATTERN
        page = $1
        if logline =~ /##ADDED to log (\d+)##/
          linecorrection = $1.to_i - loc_lineno - 1 #resync the log line number
        end
      when /Package (.*) Error: (.*)/
        messages << TeXMessage.new(:package_errors, lineno, $2, {:package => $1 }, page, file)
      when /LaTeX Error:\s*(.*)/
        messages << TeXMessage.new(:latex_errors, lineno, $1, {}, page, file)
      when /Error:/
        messages << TeXMessage.new(:error, lineno, logline, {}, page, file)
      when /Package (.*) Warning: (.*)/
        get_next_lines = /\(#{$1}\)\s*/
        messages << TeXMessage.new(:package_warnings, lineno, $2, {:package => $1 }, page, file)
      when /LaTeX Warning:\s*(.*)/
        messages << TeXMessage.new(:latex_warnings, lineno, $1, {}, page, file)
      when /Warning/
        messages << TeXMessage.new(:warnings, lineno, logline, {}, page, file)
      when /Package (.*) Info: (.*)/ #A lot of messages
        messages << TeXMessage.new(:package_info, lineno, $2, {:package => $1 }, page, file)
      when /Output written on (.*) \((\d*) pages?, (\d*) bytes\)/
        raise "Double output in #{logfilename}" if stat[:output]
        messages << TeXMessage.new(:output, lineno, 
                                  "Output written on #{$1} (#{$2} pages)", {
                                  :name => $1, :pages => $2, :bytes => $3, :kbytes => ($3.to_i / 1024 )}, 
                                  page, file)
      when /Overfull \\hbox \((.*) too wide\) in paragraph at lines (.*)/ 
        #~ messages << TeXMessage.new(:overfull_boxes, lineno, "#{$1} at lines #{$2}", {:wide => $1.to_i }, page, file, $2)
        messages << TeXMessage.new(:overfull_boxes, lineno, logline, {:wide => $1.to_i }, page, file, $2)
        get_next_lines = 1
      when /Underfull (.*box) \(badness (.*)\) (detected|in paragraph) at lines? (.*)/
        #~ messages << TeXMessage.new(:underfull_boxes, lineno, "badness #{$2} at lines #{$4}", {:badness => $2.to_i }, page, file, $4)
        messages << TeXMessage.new(:underfull_boxes, lineno, logline, {:badness => $2.to_i }, page, file, $4)
        get_next_lines = 1  #oder bis / []/
      #fixme...
      when /^!\s*(.*)/ #Undefined control sequence.
        messages << TeXMessage.new(:latex_errors, lineno, $1, {}, page, file)        
      else
        #~ puts " #{logline}"
      end
  }
  messages
end
overview() click to toggle source

Build a more complex overview. Contains:

  • Summary (method stat_summary)

  • All errors and warnings from TeX and the tools.

  • A detailed analyses for the TeX-log (including some buge in the analyse).

end

# File lib/rake4latex/tex_statistic.rb, line 526
def overview()
  overview = []
  overview << "Log-overview for #{@texfile}"
  overview << stat_summary.map{|e| "  #{e}"} 
  overview << stat.to_yaml
  #And the TeX-errors in details (buggy)
  overview << "========\n   Detailed analyse of #{@texfile.ext('log')}\n========"
  overview << "###########\n##The following list may contain wrong line assignments\n###########"
  overview << texmessages2hash({
                :messages => log_analyse_by_file(@texfile.ext('log')),
                :page_info => true,   #add page information
                :loglineno_info => true,
                :nextlines_info => true,
                :source_info => true, #add source code information, only in log_analyse_by_file_page
              }).to_yaml
  overview.join("\n")
end
stat_summary() click to toggle source

Build a very quick overview on actual status.

Each tool gets one line.

Example:

- "Document: 14 errors, 15 warnings, 44 overfull boxes, 70 underfull boxes (testdocument.log)"
- "Bibliography: 1 error (testdocument.blg)"
- "Index: 21 entries (testdocument-changes.ilg)"
- "Index: 38 entries (testdocument-BMEcat.ilg)"
- "Index: 1 error (testdocument-idx.ilg)"
- "Index: 139 entries (testdocument-Fields.ilg)"

end

# File lib/rake4latex/tex_statistic.rb, line 427
def stat_summary()
  result = []
  @stat.each{|key, values|
    stat = {}
    values.each{|logkey,logvalues|
      stat[key] ||= []
      case logkey
        when :source_information, :output
          logvalues.each{|mess|
            case mess
              when /Entries accepted: (\d*)/
                stat[:ok] = $1.to_i unless $1 == '0'
              when /Entries rejected: (\d*)/
                stat[:reject] = $1.to_i unless $1 == '0'
              when /Database file (.+) used/
                ( stat[:source] ||= [] ) << $1
              #~ when /Output written on .* \((\d*) pages, (\d*) bytes\)/
              when /Output written on .* \((\d*) page/
                stat[:pages] = $1.to_i
              when /Input file:/
              else
                #~ puts mess.inspect
            end
          }
        when String
          #~ stat[key] = logkey
          puts logkey.inspect
          raise "?Undefined String-Handling in stat_overview"
        else  #count messages
          stat[logkey] = logvalues.size
        end
    } #values
    
    #Build summary line
    summary = []
    if stat[:pages]
      summary << "#{stat[:pages]} page" 
      summary.last << "s" if stat[:pages] > 1
    end
    if stat[:source]
      summary << "Used #{stat[:source].join(', ')}" 
    end
    errors = (stat[:error] ? stat[:error] : 0 ) + 
                (stat[:latex_errors] ? stat[:latex_errors] : 0 ) + 
                (stat[:package_errors] ? stat[:package_errors] : 0 )
    if errors > 0
      summary << "#{errors} error" 
      summary.last << 's' if errors > 1
    end
    case stat[:ok]
      when 0, nil #no report
      when 1; summary << "#{stat[:ok]} entry" 
      else; summary << "#{stat[:ok]} entries" 
    end
    case stat[:reject]
      when 0, nil
      when 1; summary << "#{stat[:reject]} rejected entry" 
      else; summary << "#{stat[:reject]} rejected entries"       
    end
    
    #fixme: stat[:package_warnings] counts packages with warnings, not number of warnings
    warnings = (stat[:warnings] ? stat[:warnings] : 0 ) + 
                    (stat[:latex_warnings] ? stat[:latex_warnings] : 0 ) + 
                    (stat[:package_warnings] ? stat[:package_warnings] : 0 )
    if warnings > 0
      summary << "#{warnings} warning" 
      summary.last << 's' if warnings > 1
    end
    if stat[:overfull_boxes]
      summary << "#{stat[:overfull_boxes]} overfull box"
      summary.last << 'es' if stat[:overfull_boxes] > 1
    end
    if stat[:underfull_boxes]
      summary << "#{stat[:underfull_boxes]} underfull box" 
      summary.last << 'es' if stat[:underfull_boxes] > 1
    end
    
    #Put summary to result-array
    #The result contains a sort-number, which is deleted in the end
    case key
      when /log\Z/; result << "%-13s: %s (%s)" % [ '1Document', summary.join(', '), key]
      when /blg\Z/; result << "%-13s: %s (%s)" % [ '2Bibliography', summary.join(', '), key]
      when /ilg\Z/; result <<  "%-13s: %s (%s)" % [ '3Index', summary.join(', '), key]
      else
        #fixme
        puts "#{__FILE__}##{__LINE__}: Unknown #{key}"
        result << "?? #{key}: #{summary.join(', ') }"
    end      
  } #@stat
  #Retrun the result in the wanted order, but without the sort-number.
  result.sort.map{|e| e[1..-1] }
end
texmessages2hash( options ) click to toggle source

Take messages and convert them into a hash for quick analyses.

Options is a hash with flags, which informations should be added. Defaults can be seen in Defaults_texmessages2hash.

end

# File lib/rake4latex/tex_statistic.rb, line 384
def texmessages2hash( options )
  options = Defaults_texmessages2hash.dup.merge(options)
  options[:messages] = @texmessages if options[:messages] == :texmessages

  #Return-Hash.
  #Collects the datas.
  stat = {}
  options[:messages].sort.each{|mess|
    text = [ mess.message ]
    if mess.source and options[:source_info]
      text <<  " (#{mess.source}#{mess.line ? ":#{mess.line}" : '' })"
    end
    text <<  " [page#{mess.page}]" if mess.page and options[:page_info]
    text <<  " [log##{mess.logline}]" if options[:loglineno_info]
    text <<  " - #{mess.nextlines}" if options[:nextlines_info]
    text = text.join()
      
    case mess.type
      when :package_info
      next
    when :package_warnings, :package_errors
      stat[mess.type] ||= {}
      (stat[mess.type][mess.details[:package]] ||= [] ) << text
    else
    ( stat[mess.type] ||= [] ) << text
    end
  }
  stat
end
to_s() click to toggle source

Return a quick overview. See stat_overview()

end

# File lib/rake4latex/tex_statistic.rb, line 547
def to_s()
  stat_overview()
end