Inno Setup - 整数或 Set/Range 通配符?

Inno Setup - Integer or Set/Range wildcard?

我需要一个只匹配数字的通配符。我尝试了 FileExistsWildcard 来自 How to test using wildcards whether a file exists in Inno Setup 的函数:

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

但 Pascal Script 显然不能那样工作。有这样的通配符还是我应该写一个自定义函数什么的?

P.S。 Inno Setup 是否有通配符匹配列表?

The @TLama's FileExistsWildcard function internally uses Inno Setup FindFirst function, which in turn internally uses Windows FindFirstFile function.

和Windows在其通配符中仅支持*?。 range/set 模式 [a-z] 只是 *nix 的东西。

所以这不是 Pascal(脚本)限制。这是一个 Windows 限制。


实现支持所有 ?*[a-z] 的通用匹配函数并不容易。

我尝试实现了一个与Windows匹配(FindFirstFile)兼容但支持集合模式(包括范围集合)的匹配函数。

我没有确定 Windows 如何处理掩码和文件名中的 . 的确切规则。所以我的匹配函数在这方面的表现并不完全相同。否则,我相信,它是相同的。它支持 [abc] 集模式以及范围集模式 [a-z],或任何组合,如 [_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;

像这样使用它:

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;

对于您的特定需求,您还可以使用简单的 ad-hoc 函数,例如:

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;

像这样使用它:

SpecialFileExists(ExpandConstant('{app}'))