Updated 2016-07-13 13:15:02 by dbohdan
What tcl-augeas
Where https://github.com/dbohdan/tcl-augeas
Description A binary Tcl extension that provides bindings for the Augeas configuration editing tool.
Platforms Linux, FreeBSD, OpenBSD.
Prerequisites Tcl 8.5 or newer, Augeas 0.10 or newer.
Updated 2016-07-13
License MIT

Includes a build script that uses values from tcl::pkgconfig to build and install the extension in a way that works on across several different Linux distributions as well as on FreeBSD and OpenBSD.

Sample code  edit

package require augeas

set id [::augeas::init /]
foreach x [::augeas::match $id {/files/etc/hosts/*}] {
    set ipaddr [::augeas::get $id $x/ipaddr]
    set hostname [::augeas::get $id $x/canonical]
    puts [format %-30s%s $hostname $ipaddr]
}
::augeas::close $id

On the default installation of Fedora 21 with no extra hosts added this produces the output of
localhost.localdomain         127.0.0.1
localhost6.localdomain6       ::1

Dung Fork  edit

chw 2016-07-11: verrry versatile! Couldn't resist to make a little regedit like read-only toy and call it "Dung Fork" in memory of King Augeias, maybe an ancient acronym for "All Useful Garbage Extracted Is A String". Warning however: on my CentOS 6 system opening /files/etc/services takes centuries but this certainly isn't Tcl's fault.

dbohdan 2016-07-12: Cool stuff! You could grow a configuration management GUI tool on top of it if you let the user edit the values, record his actions and spit out Augeas or Tcl + tcl-augeas scripts.

domcleal 2016-07-12: @chw, the /etc/services performance ought to be a lot better in Augeas 1.5.0 which contains some optimisations for iterating and calling aug_get on every entry.

chw 2016-07-12: @domcleal, yes that's exactly my observation, too. CentOS 6 is at Augeas 1.0, but Ubuntu 16.04 is at 1.2.0 which is an order of magnitude faster. @dbohdan: thanks for this useful invention. Please review my somewhat changed tcl-augeas variant which I gladly integrated into undroidwish, the source code is in http://www.androwish.org/index.html/dir?name=undroid/tcl-augeas. However, to make a full scale editor from it is a Herculean task requiring a hero, two rivers, root, and a better name for the tool.

dbohdan 2016-07-13: chw, I haven't tried compiling it but at a glace the Autoconf port looks reasonable. As for storing augeas objects in a Tcl_HashTable, I like that! I've integrated your changes into the master branch on GitHub.

Screenshot

Code

# Dung Fork -- use augeas to browse system info from /etc etc
# 0.1.0

package require augeas

set ::AUG [augeas::init /]
set ::ABORT 0
array set ::AUGCACHE {}

proc treeview_focus {w} {
    if {![::tk::FocusOK $w]} return
    set item [$w focus]
    if {$item eq ""} {
        catch {
            set item [lindex [$w children {}] 0]
            $w focus $item
            $w see $item
            ttk::treeview::select.choose.browse $w $item
        }
    }
}

ttk::style configure Treeview -rowheight \
    [expr {[font metrics TkDefaultFont -linespace] + 4}]

bind Treeview <FocusIn> {treeview_focus %W}

proc augeas_match pat {
    if {[info exists ::AUGCACHE($pat)]} {
        return $::AUGCACHE($pat)
    }
    set ::AUGCACHE($pat) [augeas::match $::AUG $pat]
}

proc fill_roots {tree} {
    set ::ABORT 0
    foreach dir [lsort -dictionary [augeas::match $::AUG /*]] {
        fill_tree $tree [$tree insert {} end -text $dir \
                             -values [list $dir {} "directory"]]
    }
}

proc fill_tree {tree node {open 0} {dobusy 1}} {
    if {![string match "*irectory" [$tree set $node type]]} return
    if {$open < 0} {
        $tree item $node -open 0
        return
    }
    # already processed?
    if {[$tree set $node type] ne "directory"} return
    if {$dobusy} {
        set count0 0
        set ::ABORT 0
        catch {tk busy hold .}
        update
        set oldfocus [focus]
        catch {
            focus ._Busy
            bind ._Busy <Escape> {set ::ABORT 1}
            bind ._Busy <Any-Key> break
        }
        upvar 0 count0 count
    } else {
        upvar 1 count0 count
    }
    set path [$tree set $node fullpath]
    $tree delete [$tree children $node]
    set list {}
    set toopen {}
    set list [lsort -dictionary [augeas_match "$path/*"]]
    foreach f $list {
        if {[catch {augeas::get $::AUG $f} data]} continue
        set sublist [augeas_match $f/*]
        if {[llength $sublist]} {
            set id [$tree insert $node end -text [file tail $f] \
                        -values [list $f $data "directory"]]
            # make this node openable
            $tree insert $id 0 -text dummy ;# a dummy
            if {$open > 0} {
                lappend toopen $id
            }
        } else {
            set id [$tree insert $node end -text [file tail $f] \
                        -values [list $f $data "file"]]
        }
        incr count
        if {$count > 100} {
            set count 0
            update
        }
        if {$::ABORT} break
    }
    if {!$::ABORT} {
        # stop from rerunning on the current node
        $tree set $node type "processedDirectory"
    }
    if {($open > 0) && [llength $list]} {
        $tree item $node -open 1
    }
    foreach id $toopen {
        fill_tree $tree $id $open 0
    }
    if {$dobusy} {
        focus $oldfocus
        catch {tk busy forget .}
        set ::ABORT 0
    }
}

proc dung_fork {} {
    wm title . "Dung Fork"
    ttk::treeview .tree -columns {fullpath value type} -displaycolumns value \
        -yscroll [list .scroll set] -selectmode browse -show {headings tree}
    ttk::scrollbar .scroll -orient vertical -command [list .tree yview]
    .tree heading \#0 -text Path
    .tree heading value -text Value
    bind .tree <<TreeviewOpen>> {fill_tree %W [%W focus]}
    bind .tree <<TreeviewClose>> {fill_tree %W [%W focus] -1}
    grid .tree -row 0 -column 0 -sticky nsew
    grid .scroll -row 0 -column 1 -sticky ns
    grid columnconfigure . 0 -weight 1
    grid rowconfigure . 0 -weight 1
    fill_roots .tree
}

dung_fork