Posted to tcl by dbohdan at Wed Nov 04 12:25:27 GMT 2020view pretty

#! /usr/bin/env tclsh
#
# A configuration deployment script.
# Requires: Tcl 8.6 or later, rsync(1).
#
# Copyright (c) 2020 D. Bohdan.
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

proc main home {
    cd [file dirname [info script]]

    set vars [dict create \
        hostname [exec hostname] \
    ]

    foreach src {
        config/awesome/
        config/gxkb/
        config/icewm/
        config/mc/
        config/tclsh/
        config/udiskie/
    } {
        sync $src {} --delete
    }

    foreach src {
        config/fish/
        config/mpv/
        gitconfig
        gitignore
        jimrc
        tclshrc
        tmux.conf
        xinitrc
        xscreensaver.template
    } {
        sync $src
    }
}


proc with-path {path script} {
    set prev [pwd]

    try {
        cd $path
        uplevel 1 $script
    } finally {
        cd $prev
    }
}


proc sync {src {dst {}} args} {
    upvar 1 home home
    upvar 1 vars vars

    if {$dst eq {}} {
        set dst $home/.$src
    }

    exec rsync -av {*}$args $src $dst >@ stdout 2>@ stderr

    foreach template [find $dst -iname *.template] {
        puts stderr [list expanding template $template]
        expand-template $template {} $vars
    }
}


proc find args {
    set paths [split [exec find {*}$args -print0] \0]

    lrange $paths 0 end-1
}


proc expand-template {src {dst {}} {vars {}}} {
    if {$dst eq {}} {
        set dst [file rootname $src]
    }

    set ch [open $src r]
    set template [read $ch]
    set contents [safe-eval [template::parse $template] $vars]

    set ch2 [open $dst w]
    puts -nonewline $ch2 $contents

    file delete $src

    close $ch2
    close $ch
}


proc safe-eval {script vars} {
    ::safe::interpCreate templateInterp
    dict for {k v} $vars {
        templateInterp eval [list set $k $v]
    }
    set result [templateInterp eval $script]
    ::safe::interpDelete templateInterp

    return $result
}


namespace eval template {}

# Convert a template into Tcl code.
proc template::parse template {
    set result {}
    set regExpr {^(.*?)<%(.*?)%>(.*)$}
    set listing "set _output {}\n"
    while {[regexp $regExpr $template match preceding token template]} {
        append listing [list append _output $preceding]\n

        switch -exact -- [string index $token 0] {
            = {
                set code [list [string range $token 1 end]]
                append listing [format {append _output [expr %s]} $code]
            }
            ! {
                set code [string range $token 1 end]
                append listing [format {append _output [%s]} $code]
            }
            default {
                append listing $token
            }
        }
        append listing \n
    }

    append listing [list append _output $template]\n

    return $listing
}


main $env(HOME)