{ This file is part of logfilter. Copyright (C) 2015 Andreas Schneider logfilter is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. logfilter is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with logfilter. If not, see . } unit UApp; {$mode objfpc}{$H+} {$modeswitch advancedrecords} {.$define debugmatches} interface uses Classes, SysUtils, CustApp, math, fgl, RegExpr, crt, UFilter; type { THighlight } THighlight = record FGColor: Byte; BGColor: Byte; Start: Integer; Length: Integer; class operator =(A, B: THighlight): Boolean; end; THighlights = specialize TFPGList; { TLogFilterApplication } TLogFilterApplication = class(TCustomApplication) protected FLineFilters: TLineFilters; FCurrentLineFilter: TLineFilter; FCommandMatcher: TRegExpr; FCommandFileName: String; FLogFileName: String; procedure DoRun; override; procedure ProcessCommand(ACommand, AParams: String); procedure WriteContent(AContent: String; AFilters: TFilterList; AGroupRanges: TGroupRanges); public constructor Create(TheOwner: TComponent); override; destructor Destroy; override; procedure WriteHelp; virtual; end; implementation { THighlight } class operator THighlight. = (A, B: THighlight): Boolean; begin Result := (A.Start = B.Start) and (A.Length = B.Length); end; function CompareHighlights(const AHL1: THighlight; const AHL2: THighlight): Integer; begin if AHL1.Start = AHL2.Start then begin Result := AHL1.Length - AHL2.Length; end else Result := AHL1.Start - AHL2.Start; end; { TLogFilterApplication } procedure TLogFilterApplication.DoRun; var commandFile: TextFile; logFile: TextFile; groupRanges: TGroupRanges; lineFilter: TLineFilter; line: String; begin if HasOption('h', 'help') or not HasOption('c', 'commandfile') then begin WriteHelp; Terminate; Exit; end; FCommandFileName := GetOptionValue('c', 'commandfile'); if not FileExists(FCommandFileName) then begin Writeln('Commandfile not found: ', FCommandFileName); ExitCode := 1; Terminate; Exit; end; AssignFile(commandFile, FCommandFileName); Reset(commandFile); // Parse command file first while not EOF(commandFile) do begin Readln(commandFile, line); if FCommandMatcher.Exec(line) then ProcessCommand(FCommandMatcher.Match[1], FCommandMatcher.Match[3]); end; CloseFile(commandFile); if not FileExists(FLogFileName) then begin Writeln('Logfile not found: ', FLogFileName); ExitCode := 1; Terminate; Exit; end; AssignFile(logFile, FLogFileName); Reset(logFile); // Filter log while not EOF(logFile) do begin Readln(logFile, line); for lineFilter in FLineFilters do begin if lineFilter.Matches(line, groupRanges) then WriteContent(line, lineFilter.Filters, groupRanges); end; end; CloseFile(logFile); Terminate; end; procedure TLogFilterApplication.ProcessCommand(ACommand, AParams: String); var highlightFilter: THighlightFilter; begin case LowerCase(ACommand) of 'filter': begin FCurrentLineFilter := TLineFilter.Create(AParams); FLineFilters.Add(FCurrentLineFilter); end; 'highlight': begin highlightFilter := THighlightFilter.Create(AParams); FCurrentLineFilter.Filters.Add(highlightFilter); end; 'file': begin FLogFileName := AParams; end; end; end; procedure TLogFilterApplication.WriteContent(AContent: String; AFilters: TFilterList; AGroupRanges: TGroupRanges); var i: Integer; matchPos, offset, lastPos: Integer; highlights: THighlights; highlight: THighlight; group: Byte; begin highlights := THighlights.Create; offset := 0; lastPos := 1; for i := 0 to AFilters.Count - 1 do begin if AFilters[i].Expression.Exec(AContent) then begin repeat // We need these values anyway. matchPos := AFilters[i].Expression.MatchPos[0]; offset := AFilters[i].Expression.MatchLen[0]; group := AFilters[i].Group; if group < Length(AGroupRanges) then begin if (matchPos + offset < AGroupRanges[group].StartIdx) or (matchPos > AGroupRanges[group].EndIdx) then continue; //Pointless; nothing we can do here highlight.Start := Max(AGroupRanges[group].StartIdx, matchPos); highlight.Length := Min(AGroupRanges[group].EndIdx - highlight.Start, highlight.Start + offset - matchPos); {$ifdef debugmatches} writeln(' Highlight: ', highlight.Start, ', ', highlight.Length); writeln(' MatchPos: ', matchPos, ', StartIdx: ', AGroupRanges[group].StartIdx); writeln(' Offset: ', offset, ', EndIdx: ', AGroupRanges[group].EndIdx); {$endif} end else begin highlight.Start := matchPos; highlight.Length := offset; end; highlight.FGColor := AFilters[i].FGColor; highlight.BGColor := AFilters[i].BGColor; highlights.Add(highlight); until not AFilters[i].Expression.ExecNext; end; end; highlights.Sort(@CompareHighlights); // Sanitize highlights for i := 0 to highlights.Count - 2 do begin if (highlights[i].Start + highlights[i].Length) > highlights[i+1].Start then begin highlight := highlights[i]; highlight.Length := highlights[i+1].Start - highlights[i].Start; highlights[i] := highlight; end; end; for highlight in highlights do begin matchPos := highlight.Start; offset := highlight.Length; write(Copy(AContent, lastPos, matchPos - lastPos)); if highlight.FGColor < $FF then TextColor(highlight.FGColor); if highlight.BGColor < $FF then TextBackground(highlight.BGColor); write(Copy(AContent, matchPos, offset)); NormVideo; lastPos := matchPos + offset; end; writeln(Copy(AContent, lastPos, Length(AContent))); highlights.Free; end; constructor TLogFilterApplication.Create(TheOwner: TComponent); begin inherited Create(TheOwner); FLineFilters := TLineFilters.Create; FCommandMatcher := TRegExpr.Create('^(\w+)( (.*)|)$'); // 1 = command, 3 = OPTIONAL params end; destructor TLogFilterApplication.Destroy; begin FLineFilters.Free; FCommandMatcher.Free; inherited Destroy; end; procedure TLogFilterApplication.WriteHelp; begin Writeln('Usage: ', ExtractFileName(ExeName), ' [options]'); Writeln; Writeln('Options:'); Writeln(' -c --commandfile='); Writeln(' specifies the filename with filter commands'); Writeln(' -f --logfile='); Writeln(' specifies the logfile to be parsed'); Writeln(' -h --help'); Writeln(' show this help screen'); end; end.