Updated 2013-12-01 02:37:42 by AMG

Although inspired by Cost, treeql is generally useful for any tree-like structure which has properties/attributes/keys.

This includes filesystems, xml and html documents, parse trees, etc.

This page contains some treeql adaptors.

   # this implements a treecl API for directories.

   package require snit

   snit::type dirtree {
       variable stat_names {}

       # Structural generators - used by apply

       # return all descendants of $node - redundant for treecl
       method descendants {node} {
        set children [$self children $node]
        foreach child $children {
            lappend children {*}[$self descendants $child]
        #puts stderr "$self descendants $node -> $children"
        return $children

       # return all immediate children of $node
       method children {node} {
        if {[catch {glob -nocomplain [file join $node *]} result]} {
            puts stderr "Error: $result"
        } else {
            #puts stderr "$self children $node -> $result"
            if {[string equal windows $::tcl_platform(platform)]} {
                # escape blanks in filenames
                regsub -all { } $result {\\ } result
            return $result

       # return next right sibling of $node
       method next {node} {
        set glob [$self children [file dirname $node]]
        set index [lsearch $glob [file tail $node]]
        if {$index == -1} {
            set result {}
        } else {
            set result [lindex $glob [expr {$index + 1}]]

        #puts stderr "$self next $node -> $result"

        return $result

       # return $node's parent
       method parent {node} {
        #puts stderr "$self parent $node -> [file dirname $node]"
        return [file dirname $node]

       # return left sibling of $node
       method previous {node} {
        set glob [$self children [file dirname $node]]
        set index [lsearch $glob [file tail $node]]
        if {$index == -1} {
            set result {}
        } else {
            set result [lindex $glob [expr {$index - 1}]]
        #puts stderr "$self previous $node -> $result"

        return $result

       # Property generators - used by apply

       # get value of attribute named $key
       method get {node key} {
        switch -glob -- $key {

            -* {
                # a file attribute
                return [file attributes $node $key]
            @name {
                # we create a pseudo key called @name
                # since we use name as node id, return that
                return $node

            default {
                # must be stat
                file stat $node stat
                return $stat($key)

       # get all $node attribute names whose keys match $glob (default *)
       method keys {node glob} {
        if {$stat_names == {}} {
            # cache the results of [file stat]
            # so we know what names stat returns
            file stat $node stat
            set stat_names [array get stat]

        set result {}
        set attrs [file attributes $node]
        lappend attrs {*}$stat_names @name ""

        foreach {attr val} $attrs {
            if {[string match $glob $attr]} {
                lappend result $attr
        return $result

       # set $node $key to $value
       method set {node attr val} {
        file attributes $node $attr $val

       # predicates - used by bool

       # $node has attribute $key
       method keyexists {node key} {
        return [expr {[lsearch [file attributes $node] $key] > -1}]

       # rootname - returns the doc root
       method rootname {} {
        return /

   if {[info script] == $argv0} {
       package require treeql

       set dir [dirtree %AUTO%] ;# create the directory shim

       set qd [treeql %AUTO% -tree $dir]        ;# create the tree query

       # start somewhere in the file system
       #$qd quote [file normalize /usr/lib/tclhttpd]
       $qd quote [file normalize ~]

       $qd descendants  ;# get all descendants of the starting point

       # from here on we use subquery - to preserve the current nodeset
       # (query would overwrite it.)

       puts "All my files: [$qd subquery withatt -owner $tcl_platform(user)]"

       puts "All file sizes: [$qd subquery get size]"
       puts "Files longer than 10K: [$qd subquery exprP {10240 <} size]"

       set age_y [clock scan "last year"]
       puts "Files older than a year: [$qd subquery exprP [list $age_y >] mtime]"

       set age_m [clock scan "last month"]
       puts "Files older than a month: [$qd subquery exprP [list $age_m >] mtime]"

       set age_f [clock scan "last fortnight"]
       puts "Files older than two weeks: [$qd subquery exprP [list $age_f >] mtime]"

       set age_w [clock scan "last week"]
       puts "Files older than a week: [$qd subquery exprP [list $age_w >] mtime]"

       # here we do a boolean query.
       puts "Files between one and two weeks old: [$qd subquery exprP [list $age_w >] mtime andq [list exprP [list $age_f < ] mtime]]"

UKo 2005-03-01: this doesn't work for me (besides the syntax error with catch, I have corrected). On windows I get the error:
  list element in braces followed by "\" instead of space

with a very lengthy errorInfo.

NH 2009-04-16: The error was caused by {expand} in the dirtree methods. I have changed this to {*} for Tcl 8.5+

See also Snit