fileutil_traverse(n)

πŸ“¦
version 2.0 Β· 2 versions
, in fedora 43, fedora 44, nixos 26.05, nixpkgs unstable

πŸ’… fileutil_traverse β€” the manual, but make it make sense

okay bestie, gather round, because we're talking about fileutil_traverse from the fileutil family (the manual page lives in section n, and it's the file utilities corner of the Tcllib world). this is iterative directory traversal and it is lowkey iconic. ✨


πŸ¦‹ NAME πŸ¦‹

fileutil_traverse β€” iterative directory traversal. that's it, that's the whole vibe. it walks your directory hierarchies for you so you don't have to. πŸšΆβ™€οΈπŸ’–


🌸 SYNOPSIS 🌸

package require Tcl 8.5 9

package require fileutil::traverse ?0.7?

package require fileutil

package require control

::fileutil::traverse ?objectName? path ?option value...?

$traverser command ?arg arg ...?

$traverser files

$traverser foreach filevar script

$traverser next filevar

like, look at her. so many ways to call her. main character energy fr fr. πŸ‘‘


✨ DESCRIPTION ✨

so this package hands you objects for doing programmable traversal of directory hierarchies. not just "list me the files", no ma'am β€” you get to program it. 🧚

the main command the package exports is this one:

  • ::fileutil::traverse ?objectName? path ?option value...? β€” this creates a brand new traversal object, and it comes with its own global Tcl command whose name is objectName. you use that command to do stuff to the traverser. and here's the cute part: if you pass the string %AUTO% as the objectName, the package just makes up a unique name for you. no thoughts, head empty, name generated. πŸŽ€

about the options it recognizes β€” scroll down to the OPTIONS section, bestie. but important: all of those options can only be set during the creation of the traversal object. changing them later? not possible. attempting it throws errors. it's giving "you had one chance and you used it." πŸ’€

the object command has this general shape:

$traverser command ?arg arg ...?

the command plus its arguments decide exactly what the object does. every time. periodt.

and here are the commands a traversal object can do:

  • $traverser files β€” the most high-level method of the bunch. invoke it and you get back a list of the names of all files and directories matching the traverser's current configuration. one call, whole list, slay. πŸ“‹βœ¨
  • $traverser foreach filevar script β€” the high-level files method above is literally built on top of this mid-level one. invoke it and it finds everything matching the current config, then runs script for each path. the path currently being looked at gets stored in the variable named by filevar. and get this: both the variable and the script live in / are executed in the context of the caller of the method. in files, the script's whole job is just saving the found paths into the list that gets returned. humble but crucial. 🫢
  • $traverser next filevar β€” the lowest-level interface to the traverser, the core that every higher method is built on. invoke it and it returns a boolean: did it find a path matching the current config (True) or not (False)? if it did find one, that path is stored into the variable named by filevar, in the context of the caller.

the foreach method is literally just calling this one in a loop until it returns False. it's exposed so that you can also incrementally traverse a directory hierarchy in an event-based manner. yes, that's a real capability. yes, it's cool. 😌

🚨 okay important note, listen up: the traverser does follow symbolic links β€” except when doing so would make it enter a link-cycle. in other words, she takes care not to lose herself in infinite loops when she runs into circular link structures. she is self-aware. πŸ’… but also note: even links that are not followed will still appear in the result. so they show up, they just don't get chased. noted. πŸ“

πŸŽ€ OPTIONS πŸŽ€

  • -prefilter command_prefix β€” this callback runs for directories. its result decides whether the traverser recurses into that directory or not. default behaviour is to always recurse into all directories (she's trusting like that). the callback gets invoked with a single argument β€” the absolute path of the directory β€” and it has to return a boolean value: True when the directory passes the filter, False when it doesn't. πŸšͺ✨
  • -filter command_prefix β€” this callback runs for all paths. its result decides whether the current path is a valid result and gets returned by next. default is to accept all paths as valid (no gatekeeping here). the callback is invoked with a single argument, the absolute path to check, and must return a boolean: True when the path passes the filter, False when it doesn't. πŸ•΅οΈβ™€οΈ
  • -errorcmd command_prefix β€” this callback runs for all paths the traverser has trouble with. like, she couldn't change into them, couldn't get their status, etc. default is to just ignore any such problems (we love a chill queen). the callback is invoked with two arguments: the absolute path where the error occurred, and the error message. errors thrown by the filter callbacks get handled through this callback too. but heads up: errors thrown by the error callback itself are not caught and ignored β€” they're allowed to pass to the caller, i.e. however invoked the next. and any other results from the callback? ignored. 🫠

😭 WARNINGS AND INCOMPATIBILITIES 😭

  • 0.4.4 β€” in this version, the traverser's broken system for handling symlinks was replaced with one that works correctly and properly enumerates all the legal non-cyclic paths under a base directory. glow-up complete. ✨

and while it's correct now, it also means certain pathological directory hierarchies with cross-linked sym-links will now take about O(n**2) time to enumerate β€” whereas the original broken code managed O(n) because of its brokenness. so yeah, correctness has a price tag. 😭

a concrete example, and honestly an extreme case: the "/sys" hierarchy under Linux, where some hundred devices exist under both "/sys/devices" and "/sys/class" with the two sub-hierarchies linking to the other, generating millions of legal paths to enumerate. the structure, reduced to three devices, roughly looks like this:

  /sys/class/tty/tty0 --> ../../dev/tty0
  /sys/class/tty/tty1 --> ../../dev/tty1
  /sys/class/tty/tty2 --> ../../dev/tty1

  /sys/dev/tty0/bus
  /sys/dev/tty0/subsystem --> ../../class/tty
  /sys/dev/tty1/bus
  /sys/dev/tty1/subsystem --> ../../class/tty
  /sys/dev/tty2/bus
  /sys/dev/tty2/subsystem --> ../../class/tty

when you have to handle a hierarchy this pathological, the recommendation is to use the -prefilter option to stop the traverser from following symbolic links. like so, bestie:

package require fileutil::traverse

proc NoLinks {fileName} {
    if {[string equal [file type $fileName] link]} {
        return 0
    }
    return 1
}

fileutil::traverse T /sys/devices -prefilter NoLinks
T foreach p {
    puts $p
}
T destroy

look at that. filter says "no links, we're not doing that today." iconic. πŸ›‘


πŸ› BUGS, IDEAS, FEEDBACK πŸ›

this document, and the package it describes, will undoubtedly contain bugs and other problems. like, obviously. it's software. please report such in the category fileutil of the Tcllib Trackers at http://core.tcl.tk/tcllib/reportlist. and please also report any ideas for enhancements you may have for either the package and/or the documentation β€” we want them, fr fr. πŸ’Œ

when proposing code changes, please provide unified diffs, i.e. the output of diff -u. that's the format. don't be mysterious about it. πŸ“„

and note further that attachments are strongly preferred over inlined patches. attachments can be made by going to the Edit form of the ticket immediately after its creation, and then using the left-most button in the secondary navigation bar. yeah, that button. the left-most one. you got this. 🫢


πŸ”‘ KEYWORDS πŸ”‘

  • directory traversal
  • traversal

(short list, big responsibilities.) πŸ’…


🌈 CATEGORY 🌈

Programming tools πŸ› οΈβœ¨


and that's the whole page, bestie!! this beauty is from tcllib, version 0.7, dated per the page footer β€” so go forth, traverse those directories, follow those symlinks responsibly (or don't, use -prefilter), and remember: she does not lose herself in infinite loops. be more like her. mwah. πŸ’–πŸŽ€πŸ¦‹πŸŒΈπŸ’…

πŸ“¦ where this page comes from