#REFACTORING by cg
authorClaus Gittinger <cg@exept.de>
Fri, 13 May 2016 13:59:39 +0200
changeset 19821 1d873302be8b
parent 19820 18ad092be48d
child 19822 1af258da56af
#REFACTORING by cg class: Filename refactored the enumeration protocol to avoid duplicate code. New feature: GLOB path enumeration added:10 methods comment/format in: #components: #fullAlternativePathName #isCaseSensitive changed:8 methods
Filename.st
--- a/Filename.st	Fri May 13 13:56:18 2016 +0200
+++ b/Filename.st	Fri May 13 13:59:39 2016 +0200
@@ -1291,10 +1291,13 @@
 
 isCaseSensitive
     "return true, if filenames are case sensitive.
-     We ask the OS about this, to be independent here."
+     We ask the OS about this, to be independent here.
+     This is not really correct, as the sensitivity may depend on
+     the paricular mounted file system (NFS, for example).
+     So we need a query on the instance side"
 
     self isAbstract ifTrue:[
-	^ ConcreteClass isCaseSensitive
+        ^ ConcreteClass isCaseSensitive
     ].
 
     ^ OperatingSystem caseSensitiveFilenames
@@ -1536,7 +1539,7 @@
     |sep f vol rest components|
 
     self isAbstract ifTrue:[
-	^ ConcreteClass components:aString
+        ^ ConcreteClass components:aString
     ].
 
     "/ the following works on Unix & MSDOS (but not on openVMS)
@@ -1547,59 +1550,78 @@
     f := aString asFilename.
     vol := f volume.
     vol size ~~ 0 ifTrue:[
-	rest := f localPathName.
+        rest := f localPathName.
     ] ifFalse:[
-	rest := aString
+        rest := aString
     ].
 
     components := rest asCollectionOfSubstringsSeparatedBy:sep.
     (rest startsWith:sep) ifTrue:[
-	"first was a separator - root directory - restore"
-	(rest size > 1 and:[rest second = sep and:[vol isEmptyOrNil]]) ifTrue:[
-	    "keep \\ for windows network paths"
-	    components at:1 put:(String with:sep with:sep).
-	] ifFalse:[
-	    components at:1 put:sep asString.
-	].
+        "first was a separator - root directory - restore"
+        (rest size > 1 and:[rest second = sep and:[vol isEmptyOrNil]]) ifTrue:[
+            "keep \\ for windows network paths"
+            components at:1 put:(String with:sep with:sep).
+        ] ifFalse:[
+            components at:1 put:sep asString.
+        ].
     ].
     components := components select:[:each| each notEmpty].
 
     "/ prepend volume to first component (the root directory)
     vol size ~~ 0 ifTrue:[
-	components at:1 put:(vol , (components at:1)).
+        components at:1 put:(vol , (components at:1)).
     ].
     ^ components
 
     "
-     Filename components:'/foo/bar/baz'
-     Filename components:'/'
-     Filename components:'//'
-     Filename components:'foo/bar/baz'
-     Filename components:'foo/bar'
-     Filename components:'foo'
-     Filename components:'/foo'
-     Filename components:'//foo'
-     Filename components:''
-
-     Filename components:'\'
-     Filename components:'\foo'
-     Filename components:'\foo\'
-     Filename components:'\foo\bar'
-     Filename components:'\foo\bar\'
-     Filename components:'c:'
-     Filename components:'c:\'
-     Filename components:'c:\foo'
-     Filename components:'c:\foo\'
-     Filename components:'c:\foo\bar'
-     Filename components:'c:\foo\bar\'
-     Filename components:'\\idefix'
-     Filename components:'\\idefix\home'
-     Filename components:'\\idefix\home\bar'
+     Unix:
+     UnixFilename components:'/foo/bar/baz'
+     UnixFilename components:'/'
+     UnixFilename components:'//'
+     UnixFilename components:'foo/bar/baz'
+     UnixFilename components:'foo/bar'
+     UnixFilename components:'foo'
+     UnixFilename components:'/foo'
+     UnixFilename components:'//foo'
+     UnixFilename components:''
+
+     Windows:
+     PCFilename components:'\'
+     PCFilename components:'\foo'
+     PCFilename components:'\foo\'
+     PCFilename components:'\foo\bar'
+     PCFilename components:'\foo\bar\'
+     PCFilename components:'c:'
+     PCFilename components:'c:\'
+     PCFilename components:'c:\foo'
+     PCFilename components:'c:\foo\'
+     PCFilename components:'c:\foo\bar'
+     PCFilename components:'c:\foo\bar\'
+     PCFilename components:'\\idefix'
+     PCFilename components:'\\idefix\home'
+     PCFilename components:'\\idefix\home\bar'
     "
 
     "Modified: / 24.9.1998 / 19:10:52 / cg"
 !
 
+filesMatchingGLOB:pattern
+    "does a GLOB filename expansion.
+     Generates and returns a possibly empty list of files which match
+     the given glob pattern"
+
+    ^ OrderedCollection withCollectedContents:[:coll |
+        pattern asFilename filesMatchingGLOBDo:[:each | coll add:each]
+      ]
+
+    "
+     Filename filesMatchingGLOB:'./A*'
+     Filename filesMatchingGLOB:'/etc/A*'
+     Filename filesMatchingGLOB:'/*/A*'
+     '.' asFilename filesMatchingGLOB:'A*'
+    "
+!
+
 nameFromComponents:aCollectionOfDirectoryNames
     "return a filenameString from components given in aCollectionOfDirectoryNames.
      If the first component is the name of the root directory (i.e. '/'),
@@ -2176,16 +2198,8 @@
 directories
     "return a collection of directories contained in the directory represented by the receiver."
 
-    |collection|
-
-    collection := OrderedCollection new.
-    self directoryContentsAsFilenamesDo:[:eachFileOrDirectory |
-	eachFileOrDirectory isDirectory ifTrue:[
-	    collection add:eachFileOrDirectory.
-	].
-    ].
-
-    ^ collection
+    ^ OrderedCollection withCollectedContents:[:coll |
+        self directoriesDo:[:eachDirectory | coll add:eachDirectory]]
 
     "
      '.' asFilename directories.
@@ -2253,25 +2267,25 @@
     s := DirectoryStream directoryNamed:self osNameForDirectoryContents.
     "check for nil, in order to allow to proceed from an OpenError"
     s notNil ifTrue:[
-	[
-	    [s atEnd] whileFalse:[
-		|fn|
-
-		fn := s nextLine.
-		(fn ~= '.' and:[fn ~= '..']) ifTrue:[
-		    aBlock value:fn
-		].
-	    ].
-	] ensure:[
-	    s close.
-	].
+        [
+            [s atEnd] whileFalse:[
+                |fn|
+
+                fn := s nextLine.
+                (fn notNil and:[fn ~= '.' and:[fn ~= '..']]) ifTrue:[
+                    aBlock value:fn
+                ].
+            ].
+        ] ensure:[
+            s close.
+        ].
     ].
 
     "
      '.' asFilename directoryContentsDo:[:fn | Transcript showCR:fn].
      'doeSnotExIST' asFilename directoryContentsDo:[:fn | Transcript showCR:fn].
      [
-	'doeSnotExIST' asFilename directoryContentsDo:[:fn | Transcript showCR:fn].
+        'doeSnotExIST' asFilename directoryContentsDo:[:fn | Transcript showCR:fn].
      ] on:OpenError do:[:ex| ex proceed]
     "
 
@@ -2283,11 +2297,8 @@
     "return a collection of regular files
      contained in the directory represented by the receiver."
 
-    |collection|
-
-    collection := OrderedCollection new.
-    self filesDo:[:eachFileName | collection add:eachFileName].
-    ^ collection.
+    ^ OrderedCollection withCollectedContents:[:coll |
+        self filesDo:[:eachFileName | coll add:eachFileName]].
 
     "
      '.' asFilename files.
@@ -2314,19 +2325,140 @@
     "Modified: / 29-05-2007 / 12:02:46 / cg"
 !
 
+filesMatchingGLOB:pattern do:aBlock
+    "Interpreting pattern as a GLOB pattern,
+     evaluate aBlock for each file in me, which matches.
+     Returns the number of matches."
+
+    self assert:(pattern asFilename isAbsolute not).
+    ^ self filesMatchingGLOBComponents:(Filename components:pattern) do:aBlock
+    
+    "
+     '..' asFilename filesMatchingGLOB:'A*' do:[:fn | Transcript showCR:fn].
+     '../..' asFilename filesMatchingGLOB:'lib*/*.st' do:[:fn | Transcript showCR:fn].
+    "
+!
+
+filesMatchingGLOBComponents:patternComponents do:aBlock
+    "patternComponents is a component-collection with possible GLOB patterns.
+     Evaluate aBlock for each file in me, which matches.
+     Returns the number of matches"
+
+    |dirPath subComponents count|
+    
+    dirPath := patternComponents first.
+    subComponents := patternComponents copyFrom:2.
+
+    dirPath includesMatchCharacters ifFalse:[
+        | sub |
+        
+        sub := (self / dirPath).
+        subComponents isEmpty ifTrue:[
+            "/ I am a leaf
+            sub exists ifTrue:[
+                aBlock value:sub.
+                ^ 1.
+            ].    
+            ^ 0
+        ] ifFalse:[    
+            ^ sub filesMatchingGLOBComponents:subComponents do:aBlock
+        ].
+    ] ifTrue:[
+        count := 0.
+        subComponents isEmpty ifTrue:[
+            "/ I am a leaf
+            self isDirectory ifTrue:[
+                self filesMatching:dirPath do:[:eachMatchingFile |
+                    aBlock value:(self / eachMatchingFile).
+                    count := count + 1.
+                ]
+            ]
+        ] ifFalse:[    
+            self filesMatching:dirPath do:[:eachMatchingSubDir |
+                |sub|
+                sub := (self / eachMatchingSubDir).
+                sub isDirectory ifTrue:[
+                    count := count + (sub filesMatchingGLOBComponents:subComponents do:aBlock)
+                ].
+            ].
+        ].
+        ^ count
+    ].    
+
+    "
+     '/etc/A*' asFilename filesMatchingGLOBDo:[:fn | Transcript showCR:fn].
+     '../A*' asFilename filesMatchingGLOBDo:[:fn | Transcript showCR:fn].
+     '../../lib*/*.st' asFilename filesMatchingGLOBDo:[:fn | Transcript showCR:fn].
+    "
+!
+
+filesMatchingGLOBDo:aBlock
+    "Interpreting myself as a GLOB pattern,
+     evaluate aBlock for each file which matches."
+
+    |parts dirPath subComponents count top|
+    
+    parts := self components.
+    dirPath := parts first.
+    subComponents := parts copyFrom:2.
+
+    OpenError handle:[:ex |
+        ('%1 [info]: failed to open %2: %3'
+                bindWith:self class name
+                with:ex pathName 
+                with:ex description) infoPrintCR. 
+        self breakPoint:#cg.
+        ex proceed.
+    ] do:[
+        top := dirPath asFilename.
+
+        dirPath includesMatchCharacters ifFalse:[
+            top isAbsolute ifFalse:[
+                top := Filename currentDirectory construct:dirPath.
+            ].
+            ^ top filesMatchingGLOBComponents:subComponents do:aBlock
+        ].
+        
+        top isAbsolute ifFalse:[
+            top := Filename currentDirectory.
+        ].
+        count := 0.
+        top filesMatching:dirPath do:[:sub |
+            count := count + ((self / sub) filesMatchingGLOBComponents:subComponents do:aBlock)
+        ].
+        ^ count
+    ].
+    
+    "
+     '/etc/A*' asFilename filesMatchingGLOBDo:[:fn | Transcript showCR:fn].
+     '/etc/a*' asFilename filesMatchingGLOBDo:[:fn | Transcript showCR:fn].
+     '../A*' asFilename filesMatchingGLOBDo:[:fn | Transcript showCR:fn].
+     '../../lib*/*.st' asFilename filesMatchingGLOBDo:[:fn | Transcript showCR:fn].
+     '../../lib*/[A-D]*.st' asFilename filesMatchingGLOBDo:[:fn | Transcript showCR:fn].
+     '../../*/[A-D]*.st' asFilename filesMatchingGLOBDo:[:fn | Transcript showCR:fn].
+     '../../*/*/[A-D]*.st' asFilename filesMatchingGLOBDo:[:fn | Transcript showCR:fn].
+     '../../*java*/*/[A-D]*.st' asFilename filesMatchingGLOBDo:[:fn | Transcript showCR:fn].
+     '../../*java*/*/Ary.st' asFilename filesMatchingGLOBDo:[:fn | Transcript showCR:fn].
+     '/*/A*' asFilename filesMatchingGLOBDo:[:fn | Transcript showCR:fn].
+     '*/A*' asFilename filesMatchingGLOBDo:[:fn | Transcript showCR:fn].
+     '../*/A*' asFilename filesMatchingGLOBDo:[:fn | Transcript showCR:fn].
+     './*/A*' asFilename filesMatchingGLOBDo:[:fn | Transcript showCR:fn].
+     './*/*' asFilename filesMatchingGLOBDo:[:fn | Transcript showCR:fn].
+     '././A*' asFilename filesMatchingGLOBDo:[:fn | Transcript showCR:fn].
+    "
+!
+
 filesWithSuffix:suffix
     "return a collection of regular files (i.e. not subdirectories)
      with a given suffix which are contained in the directory
      represented by the receiver."
 
-    |collection|
-
-    collection := OrderedCollection new.
-    self filesWithSuffix:suffix do:[:eachFileName | collection add:eachFileName].
-    ^ collection.
+    ^ OrderedCollection withCollectedContents:[:coll |
+        self filesWithSuffix:suffix do:[:eachFileName | coll add:eachFileName]].
 
     "
      '.' asFilename filesWithSuffix:'so'.
+     'packages' asFilename filesWithSuffix:'so'.
     "
 !
 
@@ -4358,14 +4490,8 @@
      The pattern may be a simple matchPattern, or a set of
      multiple patterns separated by semicolons."
 
-    |matchers caseSensitive|
-
-    matchers := aPattern asCollectionOfSubstringsSeparatedBy:$;.
-    caseSensitive := self species isCaseSensitive.
-    ^ self directoryContents
-	select:[:name |
-		(matchers detect:[:p | p match:name caseSensitive:caseSensitive] ifNone:0) ~~ 0
-	       ]
+    ^ OrderedCollection withCollectedContents:[:coll |
+        self filesMatching:aPattern do:[:fn | coll add:fn]]
 
     "
      '/etc' asFilename filesMatching:'a*;c*'
@@ -4375,6 +4501,45 @@
     "Modified: / 3.8.1998 / 21:22:15 / cg"
 !
 
+filesMatching:aPattern caseSensitive:caseSensitive do:aBlock
+    "given the receiver, representing a directory;
+     evaluate aBlock for files which match a pattern.
+     The pattern may be a simple matchPattern, or a set of
+     multiple patterns separated by semicolons."
+
+    |matchers|
+
+    matchers := aPattern asCollectionOfSubstringsSeparatedBy:$;.
+    self directoryContentsDo:[:name |
+        (matchers contains:[:p | p match:name caseSensitive:caseSensitive]) ifTrue:[
+            aBlock value:name
+        ]
+    ].    
+
+    "
+     '/etc' asFilename filesMatching:'a*;c*' do:[:f | Transcript showCR:f]
+    "
+
+    "Created: / 15.4.1997 / 15:40:02 / cg"
+    "Modified: / 3.8.1998 / 21:22:15 / cg"
+!
+
+filesMatching:aPattern do:aBlock
+    "given the receiver, representing a directory;
+     evaluate aBlock for files which match a pattern.
+     The pattern may be a simple matchPattern, or a set of
+     multiple patterns separated by semicolons."
+
+    self filesMatching:aPattern caseSensitive:self species isCaseSensitive do:aBlock
+
+    "
+     '/etc' asFilename filesMatching:'a*;c*' do:[:f | Transcript showCR:f]
+    "
+
+    "Created: / 15.4.1997 / 15:40:02 / cg"
+    "Modified: / 3.8.1998 / 21:22:15 / cg"
+!
+
 filesMatchingWithoutDotDirs:aPattern
     "given the receiver, representing a directory;
      return a collection of files matching a pattern.
@@ -4382,17 +4547,57 @@
      The pattern may be a simple matchPattern, or a set of
      multiple patterns separated by semicolons."
 
-    |matchers caseSensitive|
+    ^ OrderedCollection withCollectedContents:[:coll |
+        self filesMatchingWithoutDotDirs:aPattern do:[:fn | coll add:fn]]
+
+    "
+     Filename currentDirectory filesMatching:'.*'
+     Filename currentDirectory filesMatchingWithoutDotDirs:'*.*'
+     '/etc' asFilename filesMatchingWithoutDotDirs:'*'
+    "
+
+    "Created: / 15.4.1997 / 12:52:10 / cg"
+    "Modified: / 3.8.1998 / 21:22:30 / cg"
+!
+
+filesMatchingWithoutDotDirs:aPattern caseSensitive:caseSensitive do:aBlock
+    "given the receiver, representing a directory;
+     evaluate aBlock for files matching a pattern.
+     Exclude '.' and '..'.
+     The pattern may be a simple matchPattern, or a set of
+     multiple patterns separated by semicolons."
+
+    |matchers|
 
     matchers := aPattern asCollectionOfSubstringsSeparatedBy:$;.
-    caseSensitive := self species isCaseSensitive.
-
-    ^ self directoryContents
-	select:[:name |
-		name ~= '.'
-		and:[name ~= '..'
-		and:[(matchers detect:[:p | p match:name caseSensitive:caseSensitive] ifNone:0) ~~ 0]]
-      ]
+
+    self directoryContentsDo:[:name |
+        (name ~= '.'
+            and:[ name ~= '..'
+            and:[ (matchers contains:[:p | p match:name caseSensitive:caseSensitive]) ]])
+        ifTrue:[
+            aBlock value:name
+        ]
+    ].
+    
+    "
+     Filename currentDirectory filesMatching:'M*'
+     '/etc' asFilename filesMatching:'[a-z]*'
+     '../../libbasic' asFilename filesMatching:'[A-D]*.st'
+    "
+
+    "Created: / 15.4.1997 / 12:52:10 / cg"
+    "Modified: / 3.8.1998 / 21:22:30 / cg"
+!
+
+filesMatchingWithoutDotDirs:aPattern do:aBlock
+    "given the receiver, representing a directory;
+     evaluate aBlock for files matching a pattern.
+     Exclude '.' and '..'.
+     The pattern may be a simple matchPattern, or a set of
+     multiple patterns separated by semicolons."
+
+    self filesMatchingWithoutDotDirs:aPattern caseSensitive:self species isCaseSensitive do:aBlock
 
     "
      Filename currentDirectory filesMatching:'M*'
@@ -4405,6 +4610,10 @@
 !
 
 fullAlternativePathName
+    "some filesystems (aka: windows) have alternative (short) filenames.
+     Those systems redefine this method to return it.
+     Otherwise, the same as the regular name is returned here"
+     
     ^ nameString
 !
 
@@ -5521,29 +5730,11 @@
      may be changed in the near future, to raise an exception instead.
      So users of this method better test for existing directory before.
      Notice:
-	this returns the file-names as strings;
-	see also #directoryContentsAsFilenames, which returns fileName instances."
-
-    |directoryStream contents|
-
-    contents := OrderedCollection new.
-    directoryStream := DirectoryStream directoryNamed:(self osNameForDirectoryContents).
-    directoryStream isNil ifTrue:[^ nil].
-
-    [
-	[directoryStream atEnd] whileFalse:[
-	    |entry|
-
-	    entry := directoryStream nextLine.
-	    (entry notNil and:[entry ~= '.' and:[entry ~= '..']]) ifTrue:[
-		contents add:entry
-	    ].
-	].
-    ] ensure:[
-	directoryStream close
-    ].
-
-    ^ contents.
+        this returns the file-names as strings;
+        see also #directoryContentsAsFilenames, which returns fileName instances."
+
+    ^ OrderedCollection withCollectedContents:[:coll |
+        self directoryContentsDo:[:each | coll add:each]] 
 
     "
      '.' asFilename directoryContents
@@ -5561,14 +5752,11 @@
      may be changed in the near future, to raise an exception instead.
      So users of this method better test for existing directory before.
      Notice:
-	this returns the file-names as fileName instances;
-	see also #directoryContents, which returns strings."
-
-    |names|
-
-    names := self directoryContents.
-    names isNil ifTrue:[^ nil].
-    ^ names collect:[:entry | self construct:entry].
+        this returns the file-names as fileName instances;
+        see also #directoryContents, which returns strings."
+
+    ^ OrderedCollection withCollectedContents:[:coll |
+        self directoryContentsAsFilenamesDo:[:each | coll add:each]] 
 
     "
      '.' asFilename directoryContentsAsFilenames