<b>=============== Summary ===============</b><br>
<br>
Change Set: normalize-paths<br>
Date: 9 July 2022<br>
Author: Christoph Thiede<br>
<br>
Adds interface for path normalization on FileDirectory that removes path navigators such as <font color="#800080">'.'</font> or <font color="#800080">'..'</font>. Tests new <font color="#000080">#withNormalizedPath</font> for Windows and Unix file directories. Applies path normlization in file dialogs so that entering <font color="#800080">'../'</font> in the input field will trigger a correct selection of the parent node in the tree.<br>
<br>
<b>=============== Diff ===============</b><br>
<br>
<b>DosFileDirectory class>>forPathParts: {instance creation} · ct 7/9/2022 19:43</b><br>
<font color="#FF0000">+ forPathParts: pathParts<br>
+ <br>
+ | fileName partsStream |<br>
+ fileName := String streamContents: [:stream |<br>
+ partsStream := pathParts readStream.<br>
+ (self isDriveLetterRoot: partsStream peek)<br>
+ ifTrue: [stream nextPutAll: partsStream next]<br>
+ ifFalse: ["UNC path/network drive" stream nextPutAll: self slash].<br>
+ partsStream do: [:part |<br>
+ stream<br>
+ nextPutAll: self slash;<br>
+ nextPutAll: part]].<br>
+ ^ self on: fileName</font><br>
<br>
<b>DosFileDirectory class>>isDrive: {platform specific} · ct 7/9/2022 17:17 (changed)</b><br>
isDrive: fullName<br>
<s><font color="#0000FF">- "Answer whether the given full name describes a 'drive', e.g., one of the root directories of a Win32 file system. We allow two forms here - the classic one where a drive is specified by a letter followed by a colon, e.g., 'C:', 'D:' etc. and the network share form starting with double-backslashes e.g., '\\server'."<br>
- ^ (fullName size = 2 and: [fullName first isLetter and: [fullName last = $:]])<br>
- or: [(fullName beginsWith: '\\') and: [(fullName occurrencesOf: $\) = 2]]<br>
</font></s><font color="#FF0000">+ "Answer whether the given full name describes a 'drive', e.g., one of the root directories of a Win32 file system. We allow two forms here - the classic one where a drive is specified by a letter followed by a colon, e.g., 'C:', 'D:' etc., and the UNC/network share form starting with double-backslashes e.g., '\\server' or '\\localhost\c$\Users'."<br>
+ <br>
+ ^ (self isDriveLetterRoot: fullName)<br>
+ or: [self isUNCRoot: fullName]</font><br>
<br>
<b>DosFileDirectory class>>isDriveLetterRoot: {platform specific} · ct 7/9/2022 17:16</b><br>
<font color="#FF0000">+ isDriveLetterRoot: fullName<br>
+ "e.g., 'C:', 'D:' etc."<br>
+ <br>
+ ^ fullName size = 2 and: [fullName first isLetter] and: [fullName second = $:]</font><br>
<br>
<b>DosFileDirectory class>>isUNCRoot: {platform specific} · ct 7/9/2022 17:17</b><br>
<font color="#FF0000">+ isUNCRoot: fullName<br>
+ "e.g., '\\server' or '\\localhost\c$\Users'"<br>
+ <br>
+ ^ (fullName beginsWith: '\\') and: [(fullName occurrencesOf: $\) = 2]</font><br>
<br>
<b>DosFileDirectory>>withNormalizedPath {path access} · ct 7/9/2022 19:55</b><br>
<font color="#FF0000">+ withNormalizedPath<br>
+ "Answer a similar instance to the receiver with a normalized path, i.e., all path navigators such as . or .. will be eliminated."<br>
+ <br>
+ | normalizedParts originalParts |<br>
+ originalParts := self pathParts.<br>
+ normalizedParts := OrderedCollection new: originalParts size.<br>
+ originalParts do: [:part |<br>
+ part<br>
+ caseOf:<br>
+ {[self class parentDirectoryNickname] -><br>
+ [normalizedParts size > 1 ifTrue:<br>
+ [normalizedParts removeLast]].<br>
+ [self class currentDirectoryNickname] -> []}<br>
+ otherwise: [normalizedParts add: part]].<br>
+ ^ self class forPathParts: normalizedParts</font><br>
<br>
<b>DosFileDirectoryTests>>testWithNormalizedPath {tests} · ct 7/9/2022 19:41</b><br>
<font color="#FF0000">+ testWithNormalizedPath<br>
+ <br>
+ FileDirectory activeDirectoryClass == DosFileDirectory ifFalse:[^self].<br>
+ <br>
+ self assert: 'C:\plonk\griffle' equals: (FileDirectory on: 'C:\plonk\griffle') withNormalizedPath pathName.<br>
+ <br>
+ self assert: 'C:\plonk\griffle' equals: (FileDirectory on: 'C:\.\plonk\.\.\griffle\.\') withNormalizedPath pathName.<br>
+ self assert: 'C:\plonk\wiffy' equals: (FileDirectory on: 'C:\..\..\plonk\..\plonk\griffle\..\wiffy\ziffy\..') withNormalizedPath pathName.<br>
+ <br>
+ self assert: '\\share\ziffy' equals: (FileDirectory on: '\\share\..\plonk\..\ziffy') withNormalizedPath pathName.</font><br>
<br>
<b>FileAbstractSelectionDialog>>directory: {directory tree} · ct 7/9/2022 18:58 (changed)</b><br>
directory: aFileDirectory <br>
"Set the path of the directory to be displayed in the directory tree pane"<br>
<br>
<s><font color="#0000FF">- directory := aFileDirectory<br>
</font></s><font color="#FF0000">+ | normalizedDirectory |<br>
+ directory := aFileDirectory.<br>
+ directory ifNil: [^ self].<br>
+ <br>
+ normalizedDirectory := directory withNormalizedPath.<br>
+ normalizedDirectory pathName = directory pathName<br>
+ ifFalse: [directory := normalizedDirectory].<br>
+ self flag: #workaround. "Avoid reassigning the directory as long as possible because PluggableTreeMorph does not perform equality comparison when reselecting items. See comment in #setDirectoryTo:."</font><br>
<br>
<b>FileDirectory class>>forPathParts: {instance creation} · ct 7/9/2022 17:12</b><br>
<font color="#FF0000">+ forPathParts: pathParts<br>
+ <br>
+ | fileName |<br>
+ fileName := String streamContents: [:stream |<br>
+ pathParts do: [:part |<br>
+ stream<br>
+ nextPutAll: self slash;<br>
+ nextPutAll: part]].<br>
+ ^ self on: fileName</font><br>
<br>
<b>FileDirectory>>= {comparing} · ct 7/9/2022 17:24 (changed)</b><br>
= aDirectory<br>
"Compare two FileDirectory instances."<br>
<br>
^(aDirectory isKindOf: FileDirectory) and: [<br>
<font color="#FF0000">+ self flag: #todo. "ct: Should we normalize paths here?"<br>
</font> (pathName asString <br>
compare: aDirectory pathName asString <br>
caseSensitive: (self isCaseSensitive or: [ aDirectory isCaseSensitive ])) = 2 ]<br>
<br>
<b>FileDirectory>>withNormalizedPath {path access} · ct 7/9/2022 19:55</b><br>
<font color="#FF0000">+ withNormalizedPath<br>
+ "Answer a similar instance to the receiver with a normalized path, i.e., all path navigators such as . or .. will be eliminated."<br>
+ <br>
+ | normalizedParts originalParts |<br>
+ originalParts := self pathParts.<br>
+ normalizedParts := OrderedCollection new: originalParts size.<br>
+ originalParts do: [:part |<br>
+ part<br>
+ caseOf:<br>
+ {[self class parentDirectoryNickname] -><br>
+ [normalizedParts size > 0 ifTrue:<br>
+ [normalizedParts removeLast]].<br>
+ [self class currentDirectoryNickname] -> []}<br>
+ otherwise: [normalizedParts add: part]].<br>
+ ^ self class forPathParts: normalizedParts</font><br>
<br>
<b>UnixFileDirectoryTests>>testWithNormalizedPath {tests} · ct 7/9/2022 19:46</b><br>
<font color="#FF0000">+ testWithNormalizedPath<br>
+ <br>
+ FileDirectory activeDirectoryClass == UnixFileDirectory ifFalse:[^self].<br>
+ <br>
+ self assert: '/media/plonk/griffle' equals: (FileDirectory on: '/media/plonk/griffle') withNormalizedPath pathName.<br>
+ <br>
+ self assert: '/media/plonk/griffle' equals: (FileDirectory on: '/media/./plonk/././griffle/./') withNormalizedPath pathName.<br>
+ self assert: '/media/plonk/wiffy' equals: (FileDirectory on: '/media/../../media/plonk/../plonk/griffle/../wiffy/ziffy/..') withNormalizedPath pathName.</font><br>
<br>
["normalize-paths.2.cs"]<br>
<br>
<font color="#808080">---<br>
</font><font color="#808080"><i>Sent from </i></font><font color="#808080"><i><a href="https://github.com/hpi-swa-lab/squeak-inbox-talk"><u><font color="#808080">Squeak Inbox Talk</font></u></a></i></font><br>
["normalize-paths.2.cs"]