#!/usr/bin/env tclsh # dsc -- damn small configuration manager # Copyright (C) 2025 Sergey Matveev # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . if {$argc == 0} { puts -nonewline stderr {Usage: dsc list [-v] [prefix] -- list all available options dsc add opt -- add /*/ section dsc del opt -- delete /*/ section dsc del "" -- delete the whole configuration, useful for import dsc has opt -- check if specified /*/ option exists dsc set opt value -- set option's value dsc set opt "" -- unset value, make it default one dsc set opt file.txtar -- export (whole by default) configuration dsc import @1] r+] puts $fh $v close $fh w set v [read $fh] if {[catch {close $fh}]} { puts -nonewline stderr $v exit 1 } return $v } proc assure-exists {opt} { global Stash if {! [file exists $Stash/$opt]} { puts stderr "not found" exit 1 } } proc txtar-fn {line} { if { [string length $line] > 6 && [string range $line 0 2] == "-- " && [string range $line end-2 end] == " --" } { return [string range $line 3 end-3] } return "" } set opt [lindex $argv 1] switch [lindex $argv 0] { list { set verbose n set prefix [lindex $argv 1] if {$argc > 1 && [lindex $argv 1] == "-v"} { set verbose y set prefix [lindex $argv 2] } foreach opt [walk $Schema d] { if {! [file exists $opt/title]} {continue} set name [string range $opt [expr {[string length $Schema] + 1}] end] if {$prefix != ""} { if {[string range $name 0 [string length $prefix]-1] != $prefix} { continue } } set v [fileread $opt/title] puts -nonewline "$name\t$v" if {$verbose} { if {[file exists $opt/descr]} { set lines [split [fileread $opt/descr] "\n"] set lines [lrange $lines 0 end-1] foreach line $lines { puts "\t$line" } } } } } add { set dir [file dirname $opt] set tail [run-checker $opt [file tail $opt]] set tail [string trimright $tail "\n"] file mkdir $Stash/$dir/$tail puts $dir/$tail } del { if {$opt != ""} { assure-exists $opt } file delete -force $Stash/$opt } set { if {[llength $argv] > 2} { set v [lindex $argv 2] if {$v == ""} { file delete $Stash/$opt exit } } file mkdir "$Stash/[file dirname $opt]" if {[is-bin $opt]} { set fh [open $Stash/$opt w] fconfigure $fh -translation binary fconfigure stdin -translation binary fcopy stdin $fh close $fh } else { set v [run-checker $opt $v] set fh [open $Stash/$opt w] puts -nonewline $fh $v close $fh } } has { assure-exists $opt } get-checker { puts [find-opt-schema $opt] } get { if {[file tail $opt] == "*"} { set opt [file dirname $opt] if {! [file exists $Stash/$opt]} { exit } set dirs [glob -directory $Stash/$opt -types d -tails -nocomplain -- *] foreach dir [lsort $dirs] { puts $dir } exit } if {[file exists $Stash/$opt]} { if {[is-bin $opt]} { set fh [open $Stash/$opt r] fconfigure $fh -translation binary fconfigure stdout -translation binary fcopy $fh stdout close $fh } else { puts -nonewline [fileread $Stash/$opt] } exit } puts -nonewline [run-checker $opt ""] } diff { set fh [file tempfile dirsSaved] foreach fn [walk $Saved/$opt d] { puts $fh [string range $fn [string length $Saved]+1 end] } close $fh set fh [file tempfile dirsStash] foreach fn [walk $Stash/$opt d] { puts $fh [string range $fn [string length $Stash]+1 end] } close $fh set fh [open |[list diff -u -L dirs -L dirs $dirsSaved $dirsStash] r] puts -nonewline [read $fh] catch {close $fh} file delete $dirsSaved file delete $dirsStash set fh [open |[list diff -urN $Saved/$opt $Stash/$opt] r] set prefixSaved "--- $Saved/" set prefixSavedLen [string length $prefixSaved] set prefixStash "+++ $Stash/" set prefixStashLen [string length $prefixStash] while {[gets $fh line] >= 0} { if {[string range $line 0 3] == "diff"} { continue } if {[string range $line 0 $prefixSavedLen-1] == $prefixSaved} { puts "--- [string range $line $prefixSavedLen end]" continue } if {[string range $line 0 $prefixStashLen-1] == $prefixStash} { puts "+++ [string range $line $prefixStashLen end]" continue } puts $line } catch {close $fh} } revert { catch {file delete -force $Stash/$opt} catch {file copy $Saved/$opt $Stash/$opt} } commit { file delete -force $Saved.bak set tmp $Saved.[expr {int(rand() * 1000000)}] file copy $Stash $tmp exec sync file rename $Saved $Saved.bak file rename $tmp $Saved file delete -force $Saved.bak } export { set dirs [walk $Saved/$opt d] if {$opt != ""} { set dirs [list $Saved/$opt {*}$dirs] } puts "-- .dirs --" foreach fn $dirs { puts [string range $fn [string length $Saved]+1 end] } foreach dir $dirs { foreach fn [walk $dir f] { set sfn [string range $fn [string length $Saved]+1 end] if {[is-bin $sfn]} { puts "-- $sfn:base64 --" set fh [open "|base64 $fn" r] fcopy $fh stdout close $fh } else { puts "-- $sfn --" set fh [open $fn] while {[gets $fh line] >= 0} { if {[txtar-fn $line] != ""} { set line "-- $line --" } puts $line } close $fh } } } } import { fconfigure stdin -translation binary while {[gets stdin line] >= 0} { set fn [txtar-fn $line] if {$fn != ""} { break } } if {$fn == ".dirs"} { while {[gets stdin line] >= 0} { set fn [txtar-fn $line] if {$fn == ""} { file mkdir $Stash/$fn } else { break } } } proc openfh {fn} { set bin no if {[string range $fn [expr {[string length $fn]-7}] end] == ":base64"} { set bin yes set fn [string range $fn 0 [expr {[string length $fn]-7-1}]] } global Stash file mkdir [file dirname $Stash/$fn] if {$bin} { set fh [open |[list base64 -d > $Stash/$fn] w] } else { set fh [open $Stash/$fn w] fconfigure $fh -translation binary } return $fh } set fh [openfh $fn] while {[gets stdin line] >= 0} { set fn [txtar-fn $line] if {$fn == ""} { puts $fh $line } elseif {[string range $fn 0 2] == "-- "} { puts $fh $fn } else { close $fh set fh [openfh $fn] } } close $fh exec sync } path { if {[file exists $Stash/$opt]} { puts [file normalize $Stash/$opt] } } }