The @TLama's FileExistsWildcard
function internally uses Inno Setup FindFirst
function, which in turn internally uses Windows FindFirstFile
function.
And Windows supports only *
and ?
in its wildcards. The range/set pattern [a-z]
is *nix thing only.
So it's not a Pascal (Script) limitation. It's a Windows limitation.
Implementing a generic matching function that supports all of ?
, *
and [a-z]
is not easy.
I've tried to implement a matching function that is compatible with Windows matching (FindFirstFile
) but supports a set pattern (including range set).
I was not identify exact rules how Windows treat .
in the mask and the filename. So my matching function does not behave exactly the same in that respect. Otherwise, I believe, it is identical. And it supports [abc]
set pattern as well as range set pattern [a-z]
, or any combination like [_0-9a-z]
.
function MatchesMaskEx(Mask: string; FileName: string): Boolean;
var
MaskI: Integer;
MaskC: Char;
FileNameI: Integer;
FileNameI2: Integer;
P: Integer;
Mask2: string;
EOSMatched: Boolean;
begin
Mask := LowerCase(Mask);
FileName := LowerCase(FileName);
MaskI := 1;
FileNameI := 1;
Result := True;
EOSMatched := False;
while (MaskI <= Length(Mask)) and Result do
begin
MaskC := Mask[MaskI];
if MaskC = '?' then
begin
{ noop, ? matches anything, even beyond end-of-string }
Inc(FileNameI);
end
else
if MaskC = '[' then
begin
if FileNameI > Length(FileName) then
begin
Result := False;
end
else
begin
P := Pos(']', Copy(Mask, MaskI + 1, Length(Mask) - MaskI));
if P = 0 then
begin
{ unclosed set - no match }
Result := False;
end
else
begin
Result := False;
P := P + MaskI;
Inc(MaskI);
while (MaskI < P) and (not Result) do
begin
MaskC := Mask[MaskI];
{ is it range (A-Z) ? }
if (MaskI + 2 < P) and (Mask[MaskI + 1] = '-') then
begin
MaskI := MaskI + 2;
end;
{ matching the range (or pseudo range A-A) }
if (MaskC <= FileName[FileNameI]) and
(FileName[FileNameI] <= Mask[MaskI]) then
begin
Inc(FileNameI);
Result := True;
MaskI := P - 1;
end;
Inc(MaskI);
end;
end;
end;
end
else
if MaskC = '*' then
begin
Mask2 := Copy(Mask, MaskI + 1, Length(Mask) - MaskI);
Result := False;
{ Find if the rest of the mask can match any remaining part }
{ of the filename => recursion }
for FileNameI2 := FileNameI to Length(FileName) + 1 do
begin
if MatchesMaskEx(
Mask2, Copy(FileName, FileNameI2, Length(FileName) - FileNameI2 + 1)) then
begin
Result := True;
MaskI := Length(Mask);
FileNameI := Length(FileName) + 1;
break;
end;
end;
end
else
begin
if (FileNameI <= Length(FileName)) and (FileName[FileNameI] = MaskC) then
begin
Inc(FileNameI);
end
else
begin
{ The dot can match EOS too, but only once }
if (MaskC = '.') and (FileNameI > Length(FileName)) and (not EOSMatched) then
begin
EOSMatched := True;
end
else
begin
Result := False;
end;
end;
end;
Inc(MaskI);
end;
if Result and (FileNameI <= Length(FileName)) then
begin
Result := False;
end;
end;
Use it like:
function FileExistsEx(Path: string): Boolean;
var
FindRec: TFindRec;
Mask: string;
begin
if FindFirst(AddBackslash(ExtractFilePath(Path)) + '*', FindRec) then
begin
Mask := ExtractFileName(Path);
try
repeat
if (FindRec.Attributes and FILE_ATTRIBUTE_DIRECTORY = 0) and
MatchesMaskEx(Mask, FindRec.Name) then
begin
Result := True;
Exit;
end;
until not FindNext(FindRec);
finally
FindClose(FindRec);
end;
end;
Result := False;
end;
For your specific needs, you can also use a simple ad-hoc function like:
function SpecialFileExists(Path: string): Boolean;
var
FindRec: TFindRec;
begin
if FindFirst(AddBackslash(Path) + '*', FindRec) then
begin
try
repeat
if (Length(FindRec.Name) = 4) and
(Copy(FindRec.Name, 1, 3) = 'sav') and
(FindRec.Name[4] >= '0') and (FindRec.Name[4] <= '9') then
begin
Result := True;
Exit;
end;
until not FindNext(FindRec);
finally
FindClose(FindRec);
end;
end;
Result := False;
end;
Use it like:
SpecialFileExists(ExpandConstant('{app}'))