{ 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 . } program logfilter; {$mode objfpc}{$H+} {$modeswitch advancedrecords} {.$define debugmatches} uses Classes, SysUtils, math, fgl, RegExpr, crt; type { THighlightFilter } THighlightFilter = class Expression: TRegExpr; FGColor: Byte; BGColor: Byte; Group: Byte; constructor Create(AString: String); destructor Destroy; override; class var FilterExpression: TRegExpr; ParamExpression: TRegExpr; end; TFilterList = specialize TFPGObjectList; { THighlight } THighlight = record FGColor: Byte; BGColor: Byte; Start: Integer; Length: Integer; class operator =(A, B: THighlight): Boolean; end; THighlights = specialize TFPGList; TGroupRange = record StartIdx: Integer; EndIdx: Integer; end; TGroupRanges = array of TGroupRange; { TLineFilter } TLineFilter = class constructor Create(AExpression: String); destructor Destroy; override; protected FExpression: TRegExpr; FFilters: TFilterList; public function Matches(ALine: String; var GroupRanges: TGroupRanges): Boolean; property Filters: TFilterList read FFilters; end; TLineFilters = specialize TFPGObjectList; { THighlight } class operator THighlight. = (A, B: THighlight): Boolean; begin Result := (A.Start = B.Start) and (A.Length = B.Length); end; { THighlightFilter } constructor THighlightFilter.Create(AString: String); begin FGColor := $FF; BGColor := $FF; Group := $FF; if FilterExpression.Exec(AString) then begin Expression := TRegExpr.Create(Copy(AString, 1, FilterExpression.MatchPos[0] - 1)); if ParamExpression.Exec(FilterExpression.Match[0]) then repeat if ParamExpression.Match[1] = 'FG' then FGColor := StrToInt(ParamExpression.Match[2]) else if ParamExpression.Match[1] = 'BG' then BGColor := StrToInt(ParamExpression.Match[2]) else if ParamExpression.Match[1] = 'Grp' then Group := StrToInt(ParamExpression.Match[2]); until not ParamExpression.ExecNext; end else Expression := TRegExpr.Create(AString); end; destructor THighlightFilter.Destroy; begin Expression.Free; inherited Destroy; 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; { TLineFilter } constructor TLineFilter.Create(AExpression: String); begin FExpression := TRegExpr.Create(AExpression); FFilters := TFilterList.Create; end; destructor TLineFilter.Destroy; begin FExpression.Free; FFilters.Free; inherited Destroy; end; function TLineFilter.Matches(ALine: String; var GroupRanges: TGroupRanges ): Boolean; var i: Integer; begin Result := FExpression.Exec(ALine); if Result then begin {$ifdef debugmatches}writeln(' Match: ', FExpression.Match[0]);{$endif} SetLength(GroupRanges, FExpression.SubExprMatchCount + 1); for i := 0 to FExpression.SubExprMatchCount do begin {$ifdef debugmatches}writeln(' [', i, ']: ', FExpression.Match[i]);{$endif} GroupRanges[i].StartIdx := FExpression.MatchPos[i]; GroupRanges[i].EndIdx := GroupRanges[i].StartIdx + FExpression.MatchLen[i]; end; end; end; //----------------------------------------------------------- var lineFilters: TLineFilters; currentLineFilter: TLineFilter; commandMatcher: TRegExpr; commandFile, logFile: TextFile; line: String; procedure ProcessCommand(ACommand, AParams: String); var highlightFilter: THighlightFilter; begin case LowerCase(ACommand) of 'filter': begin currentLineFilter := TLineFilter.Create(AParams); lineFilters.Add(currentLineFilter); end; 'highlight': begin highlightFilter := THighlightFilter.Create(AParams); currentLineFilter.Filters.Add(highlightFilter); end; end; end; procedure 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; var groupRanges: TGroupRanges; begin AssignFile(commandFile, ParamStr(1)); Reset(commandFile); AssignFile(logFile, ParamStr(2)); Reset(logFile); THighlightFilter.FilterExpression := TRegExpr.Create('( (FG|BG|Grp)(\d+))*$'); THighlightFilter.ParamExpression := TRegExpr.Create('(FG|BG|Grp)(\d+)'); lineFilters := TLineFilters.Create; commandMatcher := TRegExpr.Create('^(\w+)( (.*)|)$'); // 1 = command, 3 = OPTIONAL params try // Parse command file first while not EOF(commandFile) do begin Readln(commandFile, line); if commandMatcher.Exec(line) then ProcessCommand(commandMatcher.Match[1], commandMatcher.Match[3]); end; // Filter log while not EOF(logFile) do begin Readln(logFile, line); for currentLineFilter in lineFilters do begin if currentLineFilter.Matches(line, groupRanges) then WriteContent(line, currentLineFilter.Filters, groupRanges); end; end; finally commandMatcher.Free; lineFilters.Free; THighlightFilter.FilterExpression.Free; THighlightFilter.ParamExpression.Free; end; CloseFile(commandFile); //TODO: narrow scope! CloseFile(logFile); end.