mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-08-30 15:50:25 +02:00
codetools: FilenameIsMatching: support for nested {}, multiple *, UTF-8 and special chars, issue #27393
git-svn-id: trunk@47711 -
This commit is contained in:
parent
91b63d9feb
commit
e5885ee170
@ -1548,18 +1548,19 @@ begin
|
|||||||
Result:='';
|
Result:='';
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function FilenameIsMatching(const Mask, Filename: string;
|
function FilenameIsMatching(const Mask, Filename: string; MatchExactly: boolean
|
||||||
MatchExactly: boolean): boolean;
|
): boolean;
|
||||||
(*
|
(*
|
||||||
check if Filename matches Mask
|
check if Filename matches Mask
|
||||||
if MatchExactly then the complete Filename must match, else only the
|
if MatchExactly then the complete Filename must match, else only the
|
||||||
start
|
start
|
||||||
|
|
||||||
Filename matches exactly or is a file/directory in a subdirectory of mask
|
Filename matches exactly or is a file/directory in a subdirectory of mask.
|
||||||
Mask can contain the wildcards * and ? and the set operator {,}
|
Mask can contain the wildcards * and ? and the set operator {,}.
|
||||||
The wildcards will _not_ match PathDelim
|
The wildcards will *not* match PathDelim.
|
||||||
|
You can nest the {} sets.
|
||||||
If you need the asterisk, the question mark or the PathDelim as character
|
If you need the asterisk, the question mark or the PathDelim as character
|
||||||
just put the SpecialChar character in front of it.
|
just put the SpecialChar character in front of it (e.g. #*, #? #/).
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
/abc matches /abc, /abc/p, /abc/xyz/filename
|
/abc matches /abc, /abc/p, /abc/xyz/filename
|
||||||
@ -1569,206 +1570,209 @@ function FilenameIsMatching(const Mask, Filename: string;
|
|||||||
/abc/x*z/www matches /abc/xz/www, /abc/xyz/www, /abc/xAAAz/www
|
/abc/x*z/www matches /abc/xz/www, /abc/xyz/www, /abc/xAAAz/www
|
||||||
but not /abc/x/z/www
|
but not /abc/x/z/www
|
||||||
/abc/x#*z/www matches /abc/x*z/www, /abc/x*z/www/ttt
|
/abc/x#*z/www matches /abc/x*z/www, /abc/x*z/www/ttt
|
||||||
|
|
||||||
/a{b,c,d}e matches /abe, /ace, /ade
|
/a{b,c,d}e matches /abe, /ace, /ade
|
||||||
|
*.p{as,p,} matches a.pas unit1.pp b.p but not b.inc
|
||||||
|
*.{p{as,p,},inc} matches a.pas unit1.pp b.p b.inc but not c.lfm
|
||||||
*)
|
*)
|
||||||
|
{off $DEFINE VerboseFilenameIsMatching}
|
||||||
|
|
||||||
function FindDirectoryStart(const AFilename: string;
|
function Check(MaskP, FileP: PChar): boolean;
|
||||||
CurPos: integer): integer;
|
|
||||||
begin
|
|
||||||
Result:=CurPos;
|
|
||||||
while (Result<=length(AFilename))
|
|
||||||
and (AFilename[Result]=PathDelim) do
|
|
||||||
inc(Result);
|
|
||||||
end;
|
|
||||||
|
|
||||||
function FindDirectoryEnd(const AFilename: string; CurPos: integer): integer;
|
|
||||||
begin
|
|
||||||
Result:=CurPos;
|
|
||||||
while (Result<=length(AFilename)) do begin
|
|
||||||
if AFilename[Result]=SpecialChar then
|
|
||||||
inc(Result,2)
|
|
||||||
else if (AFilename[Result]=PathDelim) then
|
|
||||||
break
|
|
||||||
else
|
|
||||||
inc(Result);
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
function CharsEqual(c1, c2: char): boolean;
|
|
||||||
begin
|
|
||||||
{$ifdef CaseInsensitiveFilenames}
|
|
||||||
Result:=(FPUpChars[c1]=FPUpChars[c2]);
|
|
||||||
{$else}
|
|
||||||
Result:=(c1=c2);
|
|
||||||
{$endif}
|
|
||||||
end;
|
|
||||||
|
|
||||||
var
|
var
|
||||||
DirStartMask, DirEndMask,
|
Level: Integer;
|
||||||
DirStartFile, DirEndFile,
|
MaskStart: PChar;
|
||||||
BracketMaskPos, BracketFilePos: integer;
|
FileStart: PChar;
|
||||||
StopChar: LongInt;
|
|
||||||
Fits: Boolean;
|
|
||||||
|
|
||||||
function TryNextOr: boolean;
|
|
||||||
begin
|
begin
|
||||||
|
{$IFDEF VerboseFilenameIsMatching}
|
||||||
|
debugln([' Check Mask="',MaskP,'" FileP="',FileP,'"']);
|
||||||
|
{$ENDIF}
|
||||||
Result:=false;
|
Result:=false;
|
||||||
if BracketMaskPos<1 then exit;
|
|
||||||
repeat
|
repeat
|
||||||
inc(DirStartMask);
|
case MaskP^ of
|
||||||
if DirStartMask>=DirEndMask then exit; // error, missing }
|
#0:
|
||||||
if Mask[DirStartMask]=SpecialChar then begin
|
begin
|
||||||
// special char -> next char is normal char
|
// the whole Mask fits the start of Filename
|
||||||
inc(DirStartMask);
|
// trailing PathDelim in FileP are ok
|
||||||
end else if Mask[DirStartMask]='}' then begin
|
{$IFDEF VerboseFilenameIsMatching}
|
||||||
// bracket found (= end of Or operator)
|
debugln([' Check END Mask="',MaskP,'" FileP="',FileP,'"']);
|
||||||
// -> filename does not match
|
{$ENDIF}
|
||||||
|
if FileP^=#0 then exit(true);
|
||||||
|
if FileP^<>PathDelim then exit(false);
|
||||||
|
while FileP^=PathDelim do inc(FileP);
|
||||||
|
Result:=(FileP^=#0) or (not MatchExactly);
|
||||||
exit;
|
exit;
|
||||||
end else if Mask[DirStartMask]=',' then begin
|
|
||||||
// next Or found
|
|
||||||
// -> reset filename position and compare
|
|
||||||
inc(DirStartMask);
|
|
||||||
DirStartFile:=BracketFilePos;
|
|
||||||
exit(true);
|
|
||||||
end;
|
end;
|
||||||
until false;
|
SpecialChar:
|
||||||
end;
|
|
||||||
|
|
||||||
begin
|
begin
|
||||||
//debugln(['[FilenameIsMatching] Mask="',Mask,'" Filename="',Filename,'" MatchExactly=',MatchExactly]);
|
// match on character
|
||||||
Result:=false;
|
{$IFDEF VerboseFilenameIsMatching}
|
||||||
if (Filename='') then exit;
|
debugln([' Check specialchar Mask="',MaskP,'" FileP="',FileP,'"']);
|
||||||
if (Mask='') then begin
|
{$ENDIF}
|
||||||
Result:=true; exit;
|
inc(MaskP);
|
||||||
|
if MaskP^=#0 then exit;
|
||||||
|
if MaskP^<>FileP^ then exit;
|
||||||
|
inc(MaskP);
|
||||||
|
inc(FileP);
|
||||||
|
end;
|
||||||
|
PathDelim:
|
||||||
|
begin
|
||||||
|
// match PathDelim(s) or end of filename
|
||||||
|
{$IFDEF VerboseFilenameIsMatching}
|
||||||
|
debugln([' Check PathDelim Mask="',MaskP,'" FileP="',FileP,'"']);
|
||||||
|
{$ENDIF}
|
||||||
|
if not (FileP^ in [#0,PathDelim]) then exit;
|
||||||
|
// treat several PathDelim as one
|
||||||
|
while MaskP^=PathDelim do inc(MaskP);
|
||||||
|
while FileP^=PathDelim do inc(FileP);
|
||||||
end;
|
end;
|
||||||
// test every directory
|
|
||||||
DirStartMask:=1;
|
|
||||||
DirStartFile:=1;
|
|
||||||
repeat
|
|
||||||
// find start of directories
|
|
||||||
DirStartMask:=FindDirectoryStart(Mask,DirStartMask);
|
|
||||||
DirStartFile:=FindDirectoryStart(Filename,DirStartFile);
|
|
||||||
// find ends of directories
|
|
||||||
DirEndMask:=FindDirectoryEnd(Mask,DirStartMask);
|
|
||||||
DirEndFile:=FindDirectoryEnd(Filename,DirStartFile);
|
|
||||||
//debugln(' Compare "',copy(Mask,DirStartMask,DirEndMask-DirStartMask),'"',
|
|
||||||
// ' "',copy(Filename,DirStartFile,DirEndFile-DirStartFile),'"');
|
|
||||||
// compare directories
|
|
||||||
BracketMaskPos:=0;
|
|
||||||
while (DirStartMask<DirEndMask) do begin
|
|
||||||
//debugln(['FilenameIsMatching ',DirStartMask,' ',Mask[DirStartMask],' - ',DirStartFile,' ',Pchar(Filename)[DirStartFile-1]]);
|
|
||||||
case Mask[DirStartMask] of
|
|
||||||
'?':
|
'?':
|
||||||
if DirStartFile<DirEndFile then begin
|
begin
|
||||||
inc(DirStartMask);
|
// match any one character, but PathDelim
|
||||||
inc(DirStartFile);
|
{$IFDEF VerboseFilenameIsMatching}
|
||||||
continue;
|
debugln([' Check any one char Mask="',MaskP,'" FileP="',FileP,'"']);
|
||||||
end else begin
|
{$ENDIF}
|
||||||
if not TryNextOr then exit;
|
if FileP^ in [#0,PathDelim] then exit;
|
||||||
|
inc(MaskP);
|
||||||
|
inc(FileP,UTF8CharacterLength(FileP));
|
||||||
end;
|
end;
|
||||||
'*':
|
'*':
|
||||||
begin
|
begin
|
||||||
inc(DirStartMask);
|
// match 0 or more characters, but PathDelim
|
||||||
Fits:=false;
|
{$IFDEF VerboseFilenameIsMatching}
|
||||||
if (DirStartMask=DirEndMask) then begin
|
debugln([' Check any chars Mask="',MaskP,'" FileP="',FileP,'"']);
|
||||||
Fits:=true;
|
{$ENDIF}
|
||||||
end else begin
|
while MaskP^='*' do inc(MaskP);
|
||||||
StopChar:=DirStartMask;
|
repeat
|
||||||
if (BracketMaskPos>0) and (Mask[StopChar] in [',','}']) then begin
|
if Check(MaskP,FileP) then exit(true);
|
||||||
while (StopChar<DirEndMask) and (Mask[StopChar]<>'}') do
|
if FileP^ in [#0,PathDelim] then exit;
|
||||||
inc(StopChar);
|
inc(FileP);
|
||||||
inc(StopChar);
|
until false;
|
||||||
end;
|
|
||||||
if StopChar>=DirEndMask then
|
|
||||||
Fits:=true
|
|
||||||
else begin
|
|
||||||
while (DirStartFile<DirEndFile)
|
|
||||||
and (not CharsEqual(Filename[DirStartFile],Mask[StopChar]))
|
|
||||||
do
|
|
||||||
inc(DirStartFile);
|
|
||||||
Fits:=DirStartFile<DirEndFile;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
if (not Fits) and (not TryNextOr) then exit;
|
|
||||||
continue;
|
|
||||||
end;
|
end;
|
||||||
'{':
|
'{':
|
||||||
if BracketMaskPos<1 then begin
|
begin
|
||||||
inc(DirStartMask);
|
// OR options separated by comma
|
||||||
BracketMaskPos:=DirStartMask;
|
{$IFDEF VerboseFilenameIsMatching}
|
||||||
BracketFilePos:=DirStartFile;
|
debugln([' Check { Mask="',MaskP,'" FileP="',FileP,'"']);
|
||||||
continue;
|
{$ENDIF}
|
||||||
end else begin
|
inc(MaskP);
|
||||||
// treat as normal character
|
repeat
|
||||||
|
if Check(MaskP,FileP) then begin
|
||||||
|
{$IFDEF VerboseFilenameIsMatching}
|
||||||
|
debugln([' Check { option fits -> end']);
|
||||||
|
{$ENDIF}
|
||||||
|
exit(true);
|
||||||
|
end;
|
||||||
|
{$IFDEF VerboseFilenameIsMatching}
|
||||||
|
debugln([' Check { skip to next option ...']);
|
||||||
|
{$ENDIF}
|
||||||
|
// skip to next option in MaskP
|
||||||
|
Level:=1;
|
||||||
|
repeat
|
||||||
|
case MaskP^ of
|
||||||
|
#0: exit;
|
||||||
|
SpecialChar:
|
||||||
|
begin
|
||||||
|
inc(MaskP);
|
||||||
|
if MaskP^=#0 then exit;
|
||||||
|
inc(MaskP);
|
||||||
|
end;
|
||||||
|
'{': inc(Level);
|
||||||
|
'}':
|
||||||
|
begin
|
||||||
|
dec(Level);
|
||||||
|
if Level=0 then exit; // no option fits
|
||||||
end;
|
end;
|
||||||
',':
|
',':
|
||||||
if BracketMaskPos>0 then begin
|
if Level=1 then break;
|
||||||
// Bracket operator fits complete
|
|
||||||
// -> skip rest of Bracket operator
|
|
||||||
repeat
|
|
||||||
inc(DirStartMask);
|
|
||||||
if DirStartMask>=DirEndMask then exit; // error, missing }
|
|
||||||
if Mask[DirStartMask]=SpecialChar then begin
|
|
||||||
// special char -> next char is normal char
|
|
||||||
inc(DirStartMask);
|
|
||||||
end else if Mask[DirStartMask]='}' then begin
|
|
||||||
// bracket found (= end of Or operator)
|
|
||||||
inc(DirStartMask);
|
|
||||||
break;
|
|
||||||
end;
|
end;
|
||||||
|
inc(MaskP);
|
||||||
|
until false;
|
||||||
|
{$IFDEF VerboseFilenameIsMatching}
|
||||||
|
debugln([' Check { next option: "',MaskP,'"']);
|
||||||
|
{$ENDIF}
|
||||||
|
inc(MaskP)
|
||||||
until false;
|
until false;
|
||||||
BracketMaskPos:=0;
|
|
||||||
continue;
|
|
||||||
end else begin
|
|
||||||
// treat as normal character
|
|
||||||
end;
|
end;
|
||||||
'}':
|
'}':
|
||||||
if BracketMaskPos>0 then begin
|
begin
|
||||||
// Bracket operator fits complete
|
{$IFDEF VerboseFilenameIsMatching}
|
||||||
inc(DirStartMask);
|
debugln([' Check } Mask="',MaskP,'" FileP="',FileP,'"']);
|
||||||
BracketMaskPos:=0;
|
{$ENDIF}
|
||||||
continue;
|
inc(MaskP);
|
||||||
end else begin
|
end;
|
||||||
// treat as normal character
|
',':
|
||||||
|
begin
|
||||||
|
// OR option fits => continue behind the {}
|
||||||
|
{$IFDEF VerboseFilenameIsMatching}
|
||||||
|
debugln([' Check Skipping to end of {} Mask="',MaskP,'" ...']);
|
||||||
|
{$ENDIF}
|
||||||
|
Level:=1;
|
||||||
|
repeat
|
||||||
|
inc(MaskP);
|
||||||
|
case MaskP^ of
|
||||||
|
#0: exit;
|
||||||
|
SpecialChar:
|
||||||
|
begin
|
||||||
|
inc(MaskP);
|
||||||
|
if MaskP^=#0 then exit;
|
||||||
|
inc(MaskP);
|
||||||
|
end;
|
||||||
|
'{': inc(Level);
|
||||||
|
'}':
|
||||||
|
begin
|
||||||
|
dec(Level);
|
||||||
|
if Level=0 then break;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
if Mask[DirStartMask]=SpecialChar then begin
|
until false;
|
||||||
// special char -> next char is normal char
|
{$IFDEF VerboseFilenameIsMatching}
|
||||||
inc(DirStartMask);
|
debugln([' Check Skipped to end of {} Mask="',MaskP,'"']);
|
||||||
if (DirStartMask>=DirEndMask) then exit;
|
{$ENDIF}
|
||||||
|
inc(MaskP);
|
||||||
end;
|
end;
|
||||||
// compare char
|
#128..#255:
|
||||||
if (DirStartFile<DirEndFile)
|
begin
|
||||||
and CharsEqual(Mask[DirStartMask],Filename[DirStartFile]) then begin
|
// match UTF-8 characters
|
||||||
inc(DirStartMask);
|
{$IFDEF VerboseFilenameIsMatching}
|
||||||
inc(DirStartFile);
|
debugln([' Check UTF-8 chars Mask="',MaskP,'" FileP="',FileP,'"']);
|
||||||
end else begin
|
{$ENDIF}
|
||||||
// chars different
|
MaskStart:=MaskP;
|
||||||
if (BracketMaskPos=0) or (not TryNextOr) then begin
|
FileStart:=FileP;
|
||||||
// filename does not match
|
while not (MaskP^ in [#0,SpecialChar,PathDelim,'?','*','{',',','}']) do
|
||||||
|
begin
|
||||||
|
if FileP^ in [#0,PathDelim] then exit;
|
||||||
|
inc(MaskP,UTF8CharacterLength(MaskP));
|
||||||
|
inc(FileP,UTF8CharacterLength(FileP));
|
||||||
|
end;
|
||||||
|
if CompareFilenames(MaskStart,MaskP-MaskStart,FileStart,FileP-FileStart)<>0 then
|
||||||
exit;
|
exit;
|
||||||
end;
|
end;
|
||||||
|
else
|
||||||
|
// match ASCII characters
|
||||||
|
repeat
|
||||||
|
case MaskP^ of
|
||||||
|
#0,SpecialChar,PathDelim,'?','*','{',',','}': break;
|
||||||
|
{$IFDEF CaseInsensitiveFilenames}
|
||||||
|
'a'..'z','A'..'Z':
|
||||||
|
if FPUpChars[MaskP^]<>FPUpChars[FileP^] then exit;
|
||||||
|
{$ENDIF}
|
||||||
|
else
|
||||||
|
if MaskP^<>FileP^ then exit;
|
||||||
end;
|
end;
|
||||||
|
inc(MaskP);
|
||||||
|
inc(FileP);
|
||||||
|
until false;
|
||||||
end;
|
end;
|
||||||
if BracketMaskPos>0 then exit;
|
until false;
|
||||||
if (DirStartMask<DirEndmask) or (DirStartFile<DirEndFile) then exit;
|
|
||||||
// find starts of next directories
|
|
||||||
DirStartMask:=DirEndMask+1;
|
|
||||||
DirStartFile:=DirEndFile+1;
|
|
||||||
until (DirStartFile>length(Filename)) or (DirStartMask>length(Mask));
|
|
||||||
|
|
||||||
DirStartMask:=FindDirectoryStart(Mask,DirStartMask);
|
|
||||||
|
|
||||||
// check that complete mask matches
|
|
||||||
Result:=(DirStartMask>length(Mask));
|
|
||||||
|
|
||||||
if MatchExactly then begin
|
|
||||||
DirStartFile:=FindDirectoryStart(Filename,DirStartFile);
|
|
||||||
// check that the complete Filename matches
|
|
||||||
Result:=(Result and (DirStartFile>length(Filename)));
|
|
||||||
end;
|
end;
|
||||||
//debugl(' [FilenameIsMatching] Result=',Result,' ',DirStartMask,',',length(Mask),' ',DirStartFile,',',length(Filename));
|
|
||||||
|
begin
|
||||||
|
if Filename='' then exit(false);
|
||||||
|
if Mask='' then exit(true);
|
||||||
|
{$IFDEF VerboseFilenameIsMatching}
|
||||||
|
debugln(['FilenameIsMatching2 Mask="',Mask,'" File="',Filename,'" Exactly=',MatchExactly]);
|
||||||
|
{$ENDIF}
|
||||||
|
|
||||||
|
Result:=Check(PChar(Mask),PChar(Filename));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function FindNextDirectoryInFilename(const Filename: string;
|
function FindNextDirectoryInFilename(const Filename: string;
|
||||||
|
@ -331,6 +331,28 @@ begin
|
|||||||
t('/a{b,c,d}e','/ade',false,true);
|
t('/a{b,c,d}e','/ade',false,true);
|
||||||
t('/a{b,c,d}e','/aze',true,false);
|
t('/a{b,c,d}e','/aze',true,false);
|
||||||
t('/a{b,c,d}e','/aze',false,false);
|
t('/a{b,c,d}e','/aze',false,false);
|
||||||
|
|
||||||
|
// {*.pas,*.pp,*.p,*.inc,Makefile.fpc} matches math.pp
|
||||||
|
t('{*.pas,*.pp,*.p,*.inc,Makefile.fpc}','math.pp',true,true);
|
||||||
|
|
||||||
|
t('{*.pas}','Generics.Collections.pas',true,true);
|
||||||
|
t('{*.pas,*.pp}','Generics.Collections.pas',true,true);
|
||||||
|
t('{*.pas,*.pp,*.p}','Generics.Collections.pas',true,true);
|
||||||
|
t('{*.pas,*.pp,*.p,*.inc}','Generics.Collections.pas',true,true);
|
||||||
|
t('{*.pas,*.pp,*.p,*.inc,Makefile.fpc}','Generics.Collections.pas',true,true);
|
||||||
|
|
||||||
|
// *{.p{as,p,}} matches a.pas unit1.pp b.p but not b.inc
|
||||||
|
t('*{.p{as,p,}}','a.pas',true,true);
|
||||||
|
t('*{.p{as,p,}}','unit1.pp',true,true);
|
||||||
|
t('*{.p{as,p,}}','b.p',true,true);
|
||||||
|
t('*{.p{as,p,}}','b.inc',true,false);
|
||||||
|
|
||||||
|
// *.{p{as,p,},inc} matches a.pas unit1.pp b.p b.inc but not c.lfm
|
||||||
|
t('*.{p{as,p,},inc}','a.pas',true,true);
|
||||||
|
t('*.{p{as,p,},inc}','unit1.pp',true,true);
|
||||||
|
t('*.{p{as,p,},inc}','b.p',true,true);
|
||||||
|
t('*.{p{as,p,},inc}','b.inc',true,true);
|
||||||
|
t('*.{p{as,p,},inc}','c.lfm',true,false);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
initialization
|
initialization
|
||||||
|
Loading…
Reference in New Issue
Block a user