#!/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 get opt -- get option's value, maybe default one dsc get opt/* -- list /*/ sections dsc diff [prefix] -- show the difference between saved and stash dsc revert opt -- revert opt's configuration dsc commit -- commit (save) configuration dsc export >file.txtar -- export whole 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 } } 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 { set v [lindex $argv 2] if {$v == ""} { file delete $Stash/$opt exit } set v [run-checker $opt $v] file mkdir "$Stash/[file dirname $opt]" set fh [open $Stash/$opt w] puts -nonewline $fh $v close $fh } has { assure-exists $opt } get-checker { puts [find-checker $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]} { 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 file rename $Saved $Saved.bak file rename $tmp $Saved file delete -force $Saved.bak } export { set dirs [walk $Saved d] puts "-- .dirs --" foreach fn $dirs { puts [string range $fn [string length $Saved]+1 end] } foreach dir $dirs { foreach fn [walk $dir f] { puts "-- [string range $fn [string length $Saved]+1 end] --" puts -nonewline [fileread $fn] } } } import { set fn "" set lines [list] proc filewrite {fn v} { global Stash if {[llength $v] == 0} { return } if {$fn == ".dirs"} { foreach dir $v { file mkdir $Stash/$dir } return } file mkdir [file dirname $Stash/$fn] set fh [open $Stash/$fn w] puts $fh [join $v "\n"] close $fh } while {[gets stdin line] >= 0} { if { [string length $line] > 6 && [string range $line 0 2] == "-- " && [string range $line end-2 end] == " --" } { if {$fn != ""} { filewrite $fn $lines set lines [list] } set fn [string range $line 3 end-3] } else { lappend lines $line } } if {$fn != ""} { filewrite $fn $lines } } }