--- /home/t/oz/mozart-source/mozart/trunk/mozart-stdlib/os/Path.oz	2007-02-07 07:09:26.000000000 +0100
+++ ./Path.oz	2007-02-12 14:29:52.000000000 +0100
@@ -23,6 +23,7 @@
    Readdir
    Extension DropExtension AddExtension
    MaybeAddPlatform
+   Rmdir
 
 prepare
    VS2S = VirtualString.toString
@@ -53,13 +54,32 @@
       end
    end
 
-   fun {IsSlashOrBackslash C} C==&/ orelse C==&\\ end
-   fun {IsSlash C} C==&/ end
+   fun {IsWinDelimiter C} C==&/ orelse C==&\\ end
+   fun {IsPosixDelimiter C} C==&/ end
 
-   fun {SplitWindows S} {Split S IsSlashOrBackslash} end
-   fun {SplitPosix   S} {Split S IsSlash} end
+   fun {SplitWindows S} {Split S IsWinDelimiter} end
+   fun {SplitPosix   S} {Split S IsPosixDelimiter} end
 
    fun {PathToString P} {P toString($)} end
+
+   %% computes full path from given path instance info record
+   fun {ComputeString INFO}
+      Delimiter = if INFO.windows then '\\' else '/' end 
+      Drive = if INFO.drive==unit then nil else [[INFO.drive &:]] end
+      Initial = if INFO.slashinitial then [Delimiter] else nil end
+      Body = if INFO.components==nil
+	     then nil
+	     else INFO.components.1 | {Map INFO.components.2 fun {$ X} Delimiter#X end}
+	     end
+      Final = if (INFO.slashfinal andthen INFO.exact) then [Delimiter] else nil end
+   in
+      {VS2S
+       {FoldL % concatenate to single VS
+	{FoldL % remove all nil
+	 [Drive Initial Body Final] Append nil}
+	fun {$ X Y} X#Y end ""}}
+   end
+   
 import
    OS Property
    Shell at 'Shell.ozf'
@@ -77,6 +97,23 @@
    class Path
       feat !IS_PATH:unit
       attr
+	 %% info is record of form
+% 	  unit(
+%	     %% path as string (with or without optional terminal slash according to Exact)
+% 	     string       : STR
+% 	     %% B, defaults to true on windows
+% 	     windows      : WIN
+% 	     %% Win drive letter or unit
+% 	     drive        : Drive 
+% 	     %% B, true for path starting with slash/backslash
+% 	     slashinitial : SlashInitial
+% 	     %% B, true for path ending with slash/backslash 
+% 	     slashfinal   : SlashFinal
+% 	     %% list of strings dir names (and possibly basename) without starting and ending slash/backslash 
+% 	     components   : Items
+%	     %% B, indicates whether terminal slash is printed when transforming path back to string
+%            exact: Exact	 
+% 	     )
 	 info
 
 	 %% newFromRecord(+R ?P) makes it easier to create instances of
@@ -94,45 +131,59 @@
       end
 
       %% init(...) is the main user-oriented constructor
-      
+      %%
+      %% [implementation changed: exact has only influence of computation of output string with ComputeString. However, the SlashFinal is set nevertheless always to true when input path ends with slash]
+      %%
+      %% [add to doc:] It is possible to indicate a directory by additing a terminal / or \ (depending on OS). However, this slash/backslash is removed in the string output when exact is set to false. 
       meth init(S windows:WIN<=IS_WINDOWS exact:Exact<=false)
 	 STR1 = {VS2S S}
-	 STR2 =
-	 if Exact then STR1 else
-	    case {Reverse STR1}
-	    of nil then nil
-	    [] H|T then
-	       Pred = if IS_WINDOWS then IsSlashOrBackslash else IsSlash end
-	    in
-	       if {Pred H} then
-		  {Reverse {List.dropWhile T Pred}}
-	       else
-		  STR1
-	       end
-	    end	       
-	 end
-	 Drive
-	 NonDrive
-	 Items Items2 Items3
-	 SlashInitial
-	 SlashFinal
+	 IsLastCharSlash	% aux: B, denoting terminal slash
+	 STR2			% STR1 without terminal slash
+	 Drive			% Win Drive letter
+	 NonDrive		% aux: Path after Drive
+	 Items			% aux: path components with SlashInitial indicator
+	 Items2			% path components 
+	 SlashInitial		
+	 SlashFinal		
       in
+	 %% set IsLastCharSlash and STR2: 
+	 %% if Exact=false and STR1 is neither nil nor can be '/', then STR2 is STR without terminal slash, otherwise STR2 = STR1
+	 if {LLength STR1} < 2  % test for '/' and nil
+	 then
+	    IsLastCharSlash = false
+	    STR2 = STR1
+	 else LastChar = {Reverse STR1}.1
+	 in
+	    IsLastCharSlash = ((WIN andthen {IsWinDelimiter LastChar})
+			       orelse
+			       ({Not WIN} andthen {IsPosixDelimiter LastChar}))
+	    STR2 = if {Not Exact} andthen IsLastCharSlash
+		   then {Reverse {Reverse STR1}.2}
+		   else STR1
+		   end
+	 end
+	 %% set Drive and NonDrive
 	 if WIN then
-	    case STR2
-	    of C|&:|L then Drive=C    NonDrive=L
+	    case STR1
+	    of DriveLetter|&:|L then Drive=DriveLetter    NonDrive=L
 	    []      L then Drive=unit NonDrive=L
 	    end
-	 else Drive=unit NonDrive=STR2 end
+	 else Drive=unit NonDrive=STR1 end
+	 %% set Items
 	 Items = {if WIN then SplitWindows else SplitPosix end
 		  NonDrive}
+	 %% set SlashInitial and Items2 
 	 case Items
 	 of nil|L then SlashInitial=true Items2=L
 	 else SlashInitial=false Items2=Items
 	 end
-	 case {Reverse Items2}
-	 of nil|L then SlashFinal=true Items3={Reverse L}
-	 else SlashFinal=false Items3=Items2
+	 %% set SlashFinal
+	 %% [changed: Split never returns nil as last element if path ends in single slash/backslash..]
+	 if IsLastCharSlash
+	 then SlashFinal=true
+	 else SlashFinal=false
 	 end
+	 %% create path instance 
 	 {self initFromRecord(
 		  unit(
 		     string       : STR2
@@ -140,83 +191,69 @@
 		     drive        : Drive
 		     slashinitial : SlashInitial
 		     slashfinal   : SlashFinal
-		     components   : Items3
+		     components   : Items2
+		     exact        : Exact
 		     ))}
       end
+
+      %% [!! tmp meth for debugging]
+      meth getInfo($) @info end
+      
       meth toString($) @info.string end
       meth toAtom($) {S2A @info.string} end
       meth length($) {LLength @info.string} end
       meth isAbsolute($) @info.slashinitial end
       meth isRelative($) {Not Path,isAbsolute($)} end
+      %% [missing in doc] Returns the string representation for the parent directory of self. 
+      meth dirnameString($)
+	 {{self dirname($)} toString($)}
+      end
+      %% [add to doc] if self contains only a single component (i.e. self is a plain file) then dirname returns nil.
       meth dirname($)
 	 INFO = @info
+	 COM = INFO.components
       in
-	 if INFO.slashfinal then
-	    {self newFromRecord({AdjoinAt INFO slashfinal false} $)}
-	 else
-	    COM = INFO.components
+	 if {LLength COM} == 1
+	 then {Make nil}
+	 else 
 	    STR
 	    INFO2 = {Adjoin INFO
 		     unit(
 			string       : STR
-			slashinitial :
-			   if COM==nil then false else INFO.slashinitial end
-			slashfinal   : false
+			slashfinal   :
+			   %% check for root
+			   if COM==nil then false else true end
 			components   :
 			   if COM==nil then nil
 			   else {Reverse {Reverse COM}.2} end
 			)}
 	 in
-	    Path,ComputeString(INFO2 STR)
+	    {ComputeString INFO2 STR}
 	    {self newFromRecord(INFO2 $)}
 	 end
       end
-      meth ComputeString(INFO $)
-	 INFO = @info
-	 DEV = INFO.drive
-	 INI = INFO.slashinitial
-	 FIN = INFO.slashfinal
-	 COM = INFO.components
-	 L1  = if DEV==unit then nil else [DEV &:] end
-	 L2  = if INI then L1#'/' else L1 end
-	 L3  = L2 # {FoldL COM
-		     fun {$ Accu C}
-			Accu#(if Accu==nil then C else Accu#'/'#C end)
-		     end nil}
-	 L4  = if FIN then L3#'/' else L3 end
-      in
-	 {VS2S L4}
-      end
+      %% [changed def: return plain string, not wrapped in a list]  
       meth basenameString($)
-	 INFO = @info
-      in
-	 if INFO.slashfinal then nil else
-	    case {Reverse INFO.components}
-	    of nil then nil
-	    [] H|_ then [H] end
-	 end
+	 {{self basename($)} toString($)}
       end
+      %% [changed def: UNIX basename always returns last component of path, even if path ends with dir]
       meth basename($)
 	 INFO = @info
-      in
-	 if INFO.slashfinal then
-	    {self new(nil $ window:INFO.windows)}
-	 else
-	    STR
-	    INFO2 = unit(
-		       string       : STR
+	 COM = INFO.components
+	 STR
+	 INFO2 = {Adjoin INFO
+		  unit(string       : STR
 		       drive        : unit
-		       slashinitial : false
-		       slashfinal   : false
+		       slashinitial :
+			  %% check for root case
+		          if COM==nil then INFO.slashinitial else false end
 		       components   :
-			  case {Reverse INFO.components}
-			  of nil then nil
-			  [] H|_ then [H] end
-		       windows      : INFO.windows)
-	 in
-	    Path,ComputeString(INFO2 STR)
-	    {self newFromRecord(INFO2 $)}
-	 end
+			  if COM==nil then nil
+			  else [{Reverse COM}.1] end
+		      )}
+      in
+	 {ComputeString INFO2 STR}
+	 {self newFromRecord(INFO2 $)}
       end
       meth exists($)
 	 try Path,stat(_) true catch _ then false end
@@ -224,12 +261,22 @@
       meth stat($)
 	 {OS.stat @info.string}
       end
+      %% [Add to doc] only works for existing file which is either absolute path or in current path. 
       meth isDir($)
 	 (Path,stat($)).type == 'dir'
       end
+      %% [missing in doc] Returns true if path ends in slash/backslash. This is not a foolproof method for checking for directories, but works also for non-existing pathes. 
+      meth isDir2($)
+	 @info.slashfinal orelse {self isRoot($)}
+      end
+      %% [Add to doc] only works for existing file which is either absolute path or in current path. 
       meth isFile($)
 	 (Path,stat($)).type == 'reg'
       end
+      %% [missing in doc] Returns true if path does NOT end in slash/backslash. This is not a foolproof method for checking for basenames, but works also for non-existing pathes. 
+      meth isFile2($)
+	 {Not @info.slashfinal}
+      end
       meth size($)
 	 (Path,stat($)).size
       end
@@ -237,6 +284,7 @@
 	 (Path,stat($)).mtime
       end
       meth GetInfo($) @info end
+      %% [add to doc:] If P2 [see doc, X here] is an absolute path, then return a path object only for P2. NB: resolve also 'appends' P2 to P [self] in case P is a regular file.  
       meth resolve(X $)
 	 P = {Make X}
       in
@@ -244,15 +292,14 @@
 	    INFO  = @info
 	    INFO2 = {P GetInfo($)}
 	    STR
-	    INFO3 = unit(
-		       string       : STR
-		       drive        : INFO.drive
-		       slashinitial : INFO.slashinitial
-		       slashfinal   : INFO2.slashfinal
-		       components   : {Append INFO.components INFO2.components}
-		       windows      : INFO.windows)
+	    INFO3 = {Adjoin INFO
+		     unit(
+			string       : STR
+			slashfinal   : INFO2.slashfinal
+			components   : {Append INFO.components INFO2.components}
+			)}
 	 in
-	    Path,ComputeString(INFO3 STR)
+	    {ComputeString INFO3 STR}
 	    {self newFromRecord(INFO3 $)}
 	 end
       end
@@ -280,12 +327,30 @@
 	 end
       end
       meth isRoot($)
-	 @info.components==nil
+	 @info.components==nil andthen @info.slashinitial
       end
       meth readdir($)
-	 for S in {OS.getDir @info.string} collect:COL do
-	    if S=="." orelse S==".." then skip else
-	       {COL Path,resolve(S $)}
+	 INFO = @info
+      in
+	 for S in {OS.getDir INFO.string} collect:COL do
+	    if S=="." orelse S==".." then skip
+	    else
+	       P_S = {Make S}
+	       STR_Result
+	       INFO_S = {P_S GetInfo($)}
+	       INFO_Result = {Adjoin INFO
+			      unit(string       : STR_Result
+				   slashfinal   : Slashfinal_Result
+				   components   : {Append INFO.components
+						   INFO_S.components})}
+	       %% if S is dir then set finalslash accordingly
+	       Slash = if INFO.exact then nil else
+			  if INFO.windows then '\\' else '/' end 
+		       end
+	       Slashfinal_Result = {OS.stat INFO.string#Slash#S}.type == 'dir'
+	    in
+	       {ComputeString INFO_Result STR_Result}
+	       {COL {self newFromRecord(INFO_Result $)}}
 	    end
 	 end
       end
@@ -300,9 +365,11 @@
       meth extension($)
 	 Path,SplitExtension(_ $)
       end
+      %% [change doc: orig doc does not reflect orig implementation] Returns a new path object of _basename_ of P with the extension of P, if any, omitted
       meth dropExtension($)
 	 {Path,dirname($) resolve(Path,SplitExtension($ _) $)}
       end
+      %% [change doc: orig doc does not reflect orig implementation] Returns a new path object of _basename_ of P which is like P but with the extension VS added. NB: the extension is also added to directory (e.g., adding 'txt' to root dir results in '/.txt').
       meth addExtension(Ext $)
 	 {Path,dirname($) resolve((Path,basenameString($))#'.'#Ext $)}
       end
@@ -341,7 +408,7 @@
    fun {Size       P} {{Make P} size($)} end
    fun {Mtime      P} {{Make P} mtime($)} end
    fun {Resolve P1 P2} {{{Make P1} resolve(P2 $)} toString($)} end
-   fun {Getcwd} {Make {OS.getCWD}} end
+   fun {Getcwd} {Make {OS.getCWD}#'/'} end
    proc {Mkdir     P} {{Make P} mkdir} end
    proc {Mkdirs    P} {{Make P} mkdirs} end
    fun {IsRoot     P} {{Make P} isRoot($)} end
