Split implementation into units

This commit is contained in:
Andreas Schneider 2015-09-24 14:06:37 +02:00
parent d46f442699
commit c9b6b19479
4 changed files with 397 additions and 283 deletions

242
UApp.pas Normal file
View File

@ -0,0 +1,242 @@
{
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 <http://www.gnu.org/licenses/>.
}
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<THighlight>;
{ TLogFilterApplication }
TLogFilterApplication = class(TCustomApplication)
protected
FLineFilters: TLineFilters;
FCurrentLineFilter: TLineFilter;
FCommandMatcher: TRegExpr;
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
AssignFile(commandFile, ParamStr(1));
Reset(commandFile);
AssignFile(logFile, ParamStr(2));
Reset(logFile);
// 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;
// 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(commandFile); //TODO: narrow scope!
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;
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
end;
end.

140
UFilter.pas Normal file
View File

@ -0,0 +1,140 @@
{
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 <http://www.gnu.org/licenses/>.
}
unit UFilter;
{$mode objfpc}{$H+}
{.$define debugmatches}
interface
uses
Classes, SysUtils, fgl, RegExpr;
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<THighlightFilter>;
{ TGroupRange }
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<TLineFilter>;
implementation
{ 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;
{ 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;
initialization
THighlightFilter.FilterExpression := TRegExpr.Create('( (FG|BG|Grp)(\d+))*$');
THighlightFilter.ParamExpression := TRegExpr.Create('(FG|BG|Grp)(\d+)');
finalization
THighlightFilter.FilterExpression.Free;
THighlightFilter.ParamExpression.Free;
end.

View File

@ -30,11 +30,19 @@
<FormatVersion Value="1"/>
</local>
</RunParams>
<Units Count="1">
<Units Count="3">
<Unit0>
<Filename Value="logfilter.pas"/>
<IsPartOfProject Value="True"/>
</Unit0>
<Unit1>
<Filename Value="UFilter.pas"/>
<IsPartOfProject Value="True"/>
</Unit1>
<Unit2>
<Filename Value="UApp.pas"/>
<IsPartOfProject Value="True"/>
</Unit2>
</Units>
</ProjectOptions>
<CompilerOptions>

View File

@ -19,293 +19,17 @@
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<THighlightFilter>;
{ THighlight }
THighlight = record
FGColor: Byte;
BGColor: Byte;
Start: Integer;
Length: Integer;
class operator =(A, B: THighlight): Boolean;
end;
THighlights = specialize TFPGList<THighlight>;
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<TLineFilter>;
{ 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;
//-----------------------------------------------------------
Classes, SysUtils, UApp, UFilter;
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;
Application: TLogFilterApplication;
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);
Application := TLogFilterApplication.Create(nil);
Application.Title := 'LogFilter';
Application.Run;
Application.Free;
end.