<div id="__MailbirdStyleContent" style="font-size: 10pt;font-family: Arial;color: #000000;text-align: left" dir="ltr">
                                        Hi Christoph --<div><br></div><div>Sounds reasonable. Especially for comparison, right? Where else would you need a normalized path? User interface?</div><div><br></div><div>Is #forPathParts: a public or rather private interface? Maybe add commentary and change message category?</div><div><br></div><div>Best,</div><div>Marcel</div><div class="mb_sig"></div>
                                        <blockquote class="history_container" type="cite" style="border-left-style: solid;border-width: 1px;margin-top: 20px;margin-left: 0px;padding-left: 10px;min-width: 500px">
                        <p style="color: #AAAAAA; margin-top: 10px;">Am 09.07.2022 19:56:59 schrieb christoph.thiede@student.hpi.uni-potsdam.de <christoph.thiede@student.hpi.uni-potsdam.de>:</p><div style="font-family:Arial,Helvetica,sans-serif">
<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 <span style="color: #800080">'.'</span> or <span style="color: #800080">'..'</span>. Tests new <span style="color: #000080">#withNormalizedPath</span> for Windows and Unix file directories. Applies path normlization in file dialogs so that entering <span style="color: #800080">'../'</span> 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>
<span style="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</span><br>
<br>
<b>DosFileDirectory class>>isDrive: {platform specific} · ct 7/9/2022 17:17 (changed)</b><br>
isDrive: fullName<br>
<s><span style="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>
</span></s><span style="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]</span><br>
<br>
<b>DosFileDirectory class>>isDriveLetterRoot: {platform specific} · ct 7/9/2022 17:16</b><br>
<span style="color: #FF0000">+ isDriveLetterRoot: fullName<br>
+     "e.g., 'C:', 'D:' etc."<br>
+ <br>
+     ^ fullName size = 2 and: [fullName first isLetter] and: [fullName second = $:]</span><br>
<br>
<b>DosFileDirectory class>>isUNCRoot: {platform specific} · ct 7/9/2022 17:17</b><br>
<span style="color: #FF0000">+ isUNCRoot: fullName<br>
+     "e.g., '\\server' or '\\localhost\c$\Users'"<br>
+ <br>
+     ^ (fullName beginsWith: '\\') and: [(fullName occurrencesOf: $\) = 2]</span><br>
<br>
<b>DosFileDirectory>>withNormalizedPath {path access} · ct 7/9/2022 19:55</b><br>
<span style="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</span><br>
<br>
<b>DosFileDirectoryTests>>testWithNormalizedPath {tests} · ct 7/9/2022 19:41</b><br>
<span style="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.</span><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><span style="color: #0000FF">-     directory := aFileDirectory<br>
</span></s><span style="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:."</span><br>
<br>
<b>FileDirectory class>>forPathParts: {instance creation} · ct 7/9/2022 17:12</b><br>
<span style="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</span><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>
<span style="color: #FF0000">+         self flag: #todo. "ct: Should we normalize paths here?"<br>
</span>        (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>
<span style="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</span><br>
<br>
<b>UnixFileDirectoryTests>>testWithNormalizedPath {tests} · ct 7/9/2022 19:46</b><br>
<span style="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.</span><br>
<br>
["normalize-paths.2.cs"]<br>
<br>
<span style="color: #808080">---<br>
</span><span style="color: #808080"><i>Sent from </i></span><span style="color: #808080"><i><a href="https://github.com/hpi-swa-lab/squeak-inbox-talk"><u><span style="color: #808080">Squeak Inbox Talk</span></u></a></i></span><br>
["normalize-paths.2.cs"]
</div></blockquote></div>