1
votes

I need a wildcard that would match only numbers. I tried FileExistsWildcard function from How to test using wildcards whether a file exists in Inno Setup:

FileExistsWildcard(ExpandConstant('{app}\sav[1-9]'))

But Pascal Script obviously doesn't work that way. Is there such a wildcard or should I write a custom function or something?

P.S. Is there a wildcard matching list for Inno Setup at all?

1

1 Answers

2
votes

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}'))