Hi Chris,
Am Sa., 28. Okt. 2023 um 23:45 Uhr schrieb Chris Muller asqueaker@gmail.com:
The accepted answer there is for the FileSystem programming interface that is also available for Squeak, but not out of the box, contrary to Pharo. FileSystem has a convenient allChildren message that answers a collection with all the recursive subdirectories and files.
In Squeak, without loading any additional extensions, we only have the FileDirectory programming interface. It has similar messages, but apparently none as straightforward as allChildren.
#allChildren is just one of example of why FileSystem should be avoided. It's a wrong abstraction for accessing a file system at all. An enumerative API should be employed for accessing domains of indefinite size, like a file system. FileSystem is basically unusable on large file systems due to inefficient design.
Agreed that the enumerative API is the better idea *in general*. However the other Stack Overflow answer was already there, so the seemingly simple way had already been anchored. I wished to respond to that, and please mind that the asker stated that they are not a programmer of any kind. Under the circumstances I figured that simple solutions, or simpler-looking solutions, are preferred.
Disagreed that FileSystem were unusable on large file systems. It has two visitor classes for file system traversal, and these are used in allChildren etc. Of course one can use them directly as needed to do it better than eagerly collecting all the entries.
For example, there is a message called withAllSubdirectoriesCollect:, which evaluates a code block for every directory in the tree, and from the directory you can get all the contained files.
(UIManager default chooseDirectory: 'Select directory of the collection' from: FileDirectory default) "This will result in a
FileDirectory object unless you cancel the dialog." withAllSubdirectoriesCollect: [:eachDirectory | "eachDirectory is a FileDirectory object." eachDirectory fileEntries do: "This loops through all the files (not subdirectories) in eachDirectory." [:eachFileEntry | "eachFileEntry is a DirectoryEntry object." Transcript show: eachFileEntry fullName; cr]]
^^^ Loop within a loop. Conflicting concerns. #withAllSubdirectoriesCollect: is about collecting up and returning a (potentially huge) list of the valued sub directory's -- but the use case calls for enumerating FileEntry's. You found the method responsible for that, but...
You are right, I somehow ignored the "collect" in the message name.
But if this is about collecting up a potentially huge list, doesn't it suffer from the same concern as the above-discussed allChildren etc. do?
There is another message that also includes all the files in the
traversal directly, not only directories (so you do not need two nested blocks of code), but it seems a bit awkward to me: it is called directoryTreeDo: and it evaluates a code block for each file and directory. As an argument to this code block, it provides a list of the DirectoryEntries that lead to this file or directory... From the user perspective, that is a rather convoluted way to pass the path to the file or directory, in my opinion. But if you just look at the last entry of the provided list, you should be able to get all that you need for each file.
Here is an example:
(UIManager default chooseDirectory: 'Select directory of the collection' from: FileDirectory default) directoryTreeDo: [:each | Transcript show: each last fullName; cr]
`each last` in the block is the DirectoryEntry for the current file or directory. For example, you can seek out only the files by testing with `each last isFile`
... directoryTreeDo: [:each | each last isFile ifTrue: [Transcript show: each last fullName; cr]]
... you don't like it?
Well, it seems odd to me. It is not what I expected. When I looked at its interface I wondered whether it simply leaked some implementation detail to the user...
Would it be better if directoryTreeDo: were updated to #cull:cull: the current Entry, followed by the path? That way, you could write:
directoryTreeDo: [ : eachEntry | ... ]
if you wanted, or
directoryTreeDo: [ : eachEntry : path | ... ]
if you needed. Something more specific than "awkward" and "convoluted" would help. But the path needs to be accessible, though, to have sufficient use-case coverage.
Yeah, I see; the path may be the problem. The other day I didn't think about FileDirectory not having an abstraction for file paths; so I guess a list of entries is the closest thing that it could provide to get across a relative path. It is a path in the graph sense after all.
What I would have expected is not a list as the block argument, but a single object that allows me to access the current item, be it a file or directory. Since we have only FileDirectory and DirectoryEntry as abstractions, it would thus have to be the last DirectoryEntry. But that is not enough to transport the relative path without computing it again.
In your example, the : path would be the list of entries as we have it today, right? I think your proposal would adequately address my concern. But we cannot change the method like this because it would break backwards compatibility. Also I would not push for it since it was rather a minor cosmetic issue for me.
It isn't perfect, but much better than its reputation, IMO.
It gets the job done, sure. One thing that troubles me about it is that, if you are not familiar with it, it takes a while to find the functionality that you assume must exist because you know it from other systems. I assume that is why the asker posted the original question in the first place. (Although they also didn't find their answer in the FileSystem protocol, so in this case that was not much better.)
For example: almost everyone that I know speaks of "parent" directories. There is no message with "parent" in the protocol of FileDirectory. It is called containingDirectory instead. And taken on its own, that is a fine name, correct and clear. Just not what you might have expected it to be...
Kind regards, Jakob