(**************************************************************************** * WANT - A build management tool. * * Copyright (c) 1995-2003 Juancarlo Anez, Caracas, Venezuela. * * All rights reserved. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation; either * * version 2.1 of the License, or (at your option) any later version. * * * * This library 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 * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public * * License along with this library; if not, write to the Free Software * * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ****************************************************************************) { @brief @author Juancarlo Aņez } unit JalPaths; interface uses SysUtils, Classes, JalStrings; const WildChars = '?*'; InvalidPathChars = string(';'{$IFDEF LINUX} + ':' {$ENDIF}); {$IFDEF LINUX} SystemPathDelimiter: string = '/'; {$ELSE} SystemPathDelimiter: string = '\'; {$ENDIF} type EPathException = class(Exception); EFileOpException = class(EPathException); TFileAttribute = ( ReadOnly, {= $00000001 } Hidden, {= $00000002 } SysFile, {= $00000004 } VolumeID, {= $00000008 } Directory, {= $00000010 } Archive, {= $00000020 } NoFile {= -1} ); TFileAttributes = set of TFileAttribute; const AnyFileAttribute = [ReadOnly..Archive]; type IPath = interface; TIPaths = array of IPath; TPath = type string; TPaths = TStringDynArray; IPath = interface ['{EB2D9F52-739B-4939-B8F8-0A7FC638251B}'] function Concat(Other :IPath) :IPath; overload; function Concat(Other :string) :IPath; overload; function Length :Word; function Server :string; function Drive :string; function Directory :string; function Resource :string; function Super :IPath; function Attributes :TFileAttributes; function SystemAttributes :Longint; function IsAbsolute :boolean; function IsWindowsPath :boolean; function IsDirectory :boolean; function AsString :string; function AsLocalPath :string; function Move(FromBase, ToBase :IPath) :IPath; function Split :TStringDynArray; function Time :TDateTime; function SystemTime :Longint; function Find(Pattern: string; IncludeAttr: TFileAttributes = AnyFileAttribute; ExcludeAttr: TFileAttributes = [] ): TIPaths; procedure MakeDir; function GetPath :string; procedure SetPath(const Value: string); property path :string read GetPath write SetPath; end; function NewPath(const Rep :string = '') :IPath; function CurrentDir: IPath; function FileAttributesToSystemAttributes(const Attr: TFileAttributes):Byte; function SystemAttributesToFileAttributes(Attr: Integer) :TFileAttributes; function TimeToSystemFileTime(const Time: TDateTime):Integer; implementation type TPathImpl = class(TInterfacedObject, IPath) protected _Path :string; function GetPath :string; procedure SetPath(const Value: string); procedure FindPaths( S :TStrings; Pattern : string; IncludeAttr: TFileAttributes; ExcludeAttr: TFileAttributes ); public constructor Create(Rep :string); function Concat(Other :IPath) :IPath; overload; function Concat(Other :string) :IPath; overload; function Length :Word; function Server :string; function Drive :string; function Directory :string; function Resource :string; function Super :IPath; function Attributes :TFileAttributes; function SystemAttributes :Longint; function IsAbsolute :boolean; function IsWindowsPath :boolean; function IsDirectory :boolean; function AsString :string; function AsLocalPath :string; function Time :TDateTime; function SystemTime :Longint; function Move(FromBase, ToBase :IPath) :IPath; function Split :TStringDynArray; procedure MakeDir; function Find(Pattern: string; IncludeAttr: TFileAttributes = AnyFileAttribute; ExcludeAttr: TFileAttributes = [] ): TIPaths; property path :string read GetPath write SetPath; end; function NewPath(const Rep :string) :IPath; begin Result := TPathImpl.Create(Rep); end; function CurrentDir: IPath; begin Result := NewPath(SysUtils.GetCurrentDir); end; function FileAttributesToSystemAttributes(const Attr: TFileAttributes): Byte; begin Result := Byte(Attr); end; function SystemAttributesToFileAttributes(Attr: Integer): TFileAttributes; begin Result := TFileAttributes(Byte(Attr)); end; procedure AssertIsSystemIndependentPath(const Path: TPath); begin if Pos(SystemPathDelimiter, Path) <> 0 then raise EPathException.Create( Format( '"%s" looks like a system path. Expected a system independent one.', [Path]) ); end; function TimeToSystemFileTime(const Time: TDateTime):Integer; begin Result := DateTimeToFileDate(Time); end; function StringsToIPaths(S: TStrings): TIPaths; var i: Integer; begin SetLength(Result, S.Count); for i := 0 to S.Count-1 do Result[i] := NewPath(S[i]); end; procedure CheckPath(Path :TPath); var i :Integer; begin for i := 1 to Length(InvalidPathChars) do begin if Pos(InvalidPathChars[i], Path) <> 0 then raise EPathException.Create('invalid path chars in ' + Path); end; end; function ToPath(SystemPath: TPath; const BasePath: TPath= ''): TPath; begin CheckPath(SystemPath); CheckPath(BasePath); Result := SystemPath; if BasePath <> '' then Result := ExtractRelativePath(BasePath, Result); if (Length(Result) >= 2) and (Result[2] = ':') and (Result[1] in ['a'..'z', 'A'..'Z']) then begin Result[1] := LowerCase(''+Result[1])[1]; Result := SystemPathDelimiter + Result; end; Result := StringReplace(Result, SystemPathDelimiter, '/', [rfReplaceAll]); end; function SuperPath(const Path: TPath): TPath; var p : Integer; begin AssertIsSystemIndependentPath(Path); if (Path = '.') or (Path = '') then Result := '..' else begin Result := Path; p := LastDelimiter('/', Result); if p = Length(Result) then begin Result := Copy(Result, 1, p-1); p := LastDelimiter('/', Result); end; Result := Copy(Result, 1, p-1); end; end; function PathDrive(const Path: TPath): string; var p: Integer; begin p := Pos(':', Path); Result := Copy(Path, p-1, 1); end; function PathServer(const Path: TPath): string; var P: string; begin if StrLeft(Path, 2) <> '//' then Result := '' else begin P := Copy(Path, 3, Length(Path)); Result := StrToken(P, '/'); end; end; function RemovePathDrive(Path: TPath):TPath; var p: Integer; begin Result := Path; p := Pos(':', Result); Delete(Result, 1, p); end; function RemovePathServer(Path: TPath):TPath; var Server: string; begin Result := Path; Server := PathServer(Path); if Server <> '' then Delete(Result, 1, 3 + Length(Server)); end; { TPathImpl } function TPathImpl.AsLocalPath: string; begin Result := Path; if (Drive <> '') and (StrLeft(Result, 1) = '/') then Delete(Result,1, 1); Result := StringReplace(Result, '/', SystemPathDelimiter, [rfReplaceAll]); if IsAbsolute then Result := ExpandFileName(Result); CheckPath(Result); end; function TPathImpl.AsString: string; begin Result := path; end; function TPathImpl.Concat(Other: string): IPath; begin Result := Concat(NewPath(Other)); end; function TPathImpl.Concat(Other: IPath): IPath; var Parts: TPaths; i : Integer; P1 : string; begin Parts := nil; if (Length = 0) or Other.IsAbsolute then Result := Other else if Other.Length = 0 then Result := Self else begin P1 := Self.AsString; if StrLast(P1) = '/' then Delete(P1, System.Length(P1), 1); Parts := Other.Split; for i := Low(Parts) to High(Parts) do begin if Parts[i] = '..' then P1 := SuperPath(P1) else if Parts[i] = '.' then // do nothing else P1 := P1 + '/' + Parts[i]; end; Result := NewPath(P1); end; end; constructor TPathImpl.Create(Rep: string); begin CheckPath(Rep); inherited Create; Self.path := Rep; end; function TPathImpl.Directory: string; begin Result := RemovePathDrive(RemovePathServer(Path)); end; function TPathImpl.Drive: string; var p: Integer; begin if not IsWindowsPath then Result := '' else begin p := Pos(':', Path); Result := Copy(Path, p-1, 1); end; end; function TPathImpl.Find(Pattern: string; IncludeAttr, ExcludeAttr: TFileAttributes): TIPaths; var S: TStringList; begin S := TStringList.Create; S.Sorted := True; try FindPaths(S, Pattern, IncludeAttr, ExcludeAttr); Result := StringsToIPaths(S); finally S.Free; end; end; procedure TPathImpl.FindPaths(S: TStrings; Pattern :string; IncludeAttr, ExcludeAttr: TFileAttributes); var Search: TSearchRec; SearchResult: Integer; Local :string; begin Local := Concat(Pattern).AsLocalPath; SearchResult := FindFirst(Local, faAnyFile, Search); try while SearchResult = 0 do begin if ((SystemAttributesToFileAttributes(Search.Attr) * IncludeAttr) <> []) and ((SystemAttributesToFileAttributes(Search.Attr) * ExcludeAttr) = []) and (Search.Name <> '.' ) and (Search.Name <> '..' ) then S.Add(ToPath(Search.Name, Self.Path)); SearchResult := FindNext(Search); end; finally FindClose(Search); end; end; function TPathImpl.IsAbsolute: boolean; begin Result := (System.Length(path) > 0) and (path[1] = '/') or (System.Length(path) >= 3) and (path[2] = ':') and (path[3] = '/'); end; function TPathImpl.IsWindowsPath: boolean; begin Result := Pos(':', Path) <> 0; end; function TPathImpl.Length: Word; begin Result := System.Length(path); end; function TPathImpl.Move(FromBase, ToBase: IPath): IPath; begin if (FromBase.path <> '') and (Pos(FromBase.path+'/', Self.Path) = 1) then Result := ToBase.Concat(Copy(Self.Path, 2+FromBase.Length, Self.Length)) else Result := ToBase.Concat(Path); end; function TPathImpl.Resource: string; var p :Integer; begin p := LastDelimiter('/', Path); if p < Length then Result := Copy(Path, p+1, Length) else Result := Path; end; function TPathImpl.Server: string; var P: string; begin if StrLeft(Path, 2) <> '//' then Result := '' else begin P := Copy(Path, 3, Length); Result := StrToken(P, '/'); end; end; procedure TPathImpl.SetPath(const Value: string); begin _Path := ToPath(Value); end; function TPathImpl.Split: TStringDynArray; begin Result := StringToArray(path, '/'); end; function TPathImpl.Super: IPath; var p : Integer; begin if (Path = '.') or (Path = '') then Result := NewPath('..') else begin p := LastDelimiter('/', Path); if p = Length then p := LastDelimiter('/', Copy(Path, 1, p-1)); Result := NewPath(Copy(Path, 1, p-1)); end; end; function TPathImpl.GetPath: string; begin Result := _Path; end; function TPathImpl.SystemTime: Longint; begin Result := SysUtils.FileAge(asLocalPath); if Result < 0 then Result := 0; end; function TPathImpl.Time: TDateTime; var STime: Longint; begin STime := SystemTime; if STime <= 0 then Result := 0 else Result := FileDateToDateTime(STime); end; function TPathImpl.IsDirectory: boolean; begin Result := (Attributes * [NoFile, JalPaths.Directory]) = [JalPaths.Directory]; end; function TPathImpl.Attributes: TFileAttributes; var Attr :Integer; begin Attr := SystemAttributes; if Attr < 0 then Result := [NoFile] else Result := TFileAttributes(Byte(Attr)); end; function TPathImpl.SystemAttributes: Longint; begin Result := Byte(SysUtils.FileGetAttr(asLocalPath)); end; procedure TPathImpl.MakeDir; begin if (Length > 0) and (Path[Length] <> ':') // Oops! Windows specific! and not IsDirectory then begin Super.MakeDir; SysUtils.CreateDir(asLocalPath); if not IsDirectory then raise EFileOpException.Create(Format('Could not create directory "%s"', [Path])); end; end; end.