David Easton (DPE)19 March 2004 - This Snit based package provides the ability to read and write ini style config files.
Although it should work with any ini style config files, I wrote it to solve a particular problem where I wanted to read and write config files within a metakit virtual filesystem. The inifile parser did not work because it opens the file using access specifiers that did not seem to be supported within the vfs mounted with vfs::mk4::Mount.
The read method is used to read the config file into memory. The write method is used to write the config file from memory to disk. All other operations do not change the file on disk, but only the values in memory.
Limitations: There is no support for comments within a configuration file. i.e. Comments will be lost when a config file is written
As usual, feel free to edit, improve and use the code on this page.
DDG I was adding a dummy option because snit0.97 fails to load `$self configurelist $args' if no option was actually given.
WHD For the record, the correct solution is to delete the call "$self configurelist $args". If you have no options, there's no reason to call it.
DDG I agree. But the problem is that the error stack is a little bit ugly, people don't know what to do in order to fix it.
DPE 11 March 2005 I have deleted "$self configurelist $args" from the constructor and removed the dummy option as suggested by WHD. This is the change I had to make to Jigsaw Puzzler to work with a later version of snit.
################################################################################ # # Package: cfg # # Description: # Package of utilities for in-memory configuration file routines # # Provides the following functions: # # cfg create <name> - Create a cfg handle with name <name> # cfg create %AUTO% - Create a cfg handle # cfg list - List all open cfg handles # # <handle> read <filename> # <handle> write <filename> # <handle> destroy # <handle> show # # <handle> get <section> <key> # <handle> set <section> <key> <value> # <handle> delete <section> <key> # <handle> exists <section> <key> # # <handle> sections # <handle> section get <section> # <handle> section set <section> <key> <val> ?<key> <value>? ... # <handle> section delete <section> # <handle> section exists <section> # # Author: David Easton # Date: Nov 2003 # ################################################################################ package provide cfg 1.0 namespace eval cfg { package require snit snit::type cfg { typevariable cfgList [list] typemethod list {} { return $cfgList } # Used for efficient lreplace proc K {x y} { set x } # Define variable in which to store the cfg information variable data variable sectionList constructor {args} { lappend cfgList $self set sectionList [list] } destructor { if {[set i [lsearch $cfgList $self]] != -1} { set cfgList [lreplace $cfgList $i $i] } } # # Private methods # method AddSection {section} { if {[lsearch $sectionList $section] == -1} { lappend sectionList $section set data($section:keyList) [list] } } method DelSection {section} { if {[set i [lsearch $sectionList $section]] != -1} { set sectionList [lreplace $sectionList $i $i] foreach entry [array names data $section,*] { unset data($entry) } unset data($section:keyList) } } method AddKey {section key} { $self AddSection $section if {[lsearch $data($section:keyList) $key] == -1} { lappend data($section:keyList) $key } } method DelKey {section key} { if {[set i [lsearch $data($section:keyList) $key]] != -1} { set data($section:keyList) [lreplace $data($section:keyList) $i $i] unset data($section,$key) } } # # Public methods # method read {filename} { set retCode ok set section "" if {[catch {open $filename r} fileId]} { set retCode "error" } else { # Read whole file as a list set dataList [split [read -nonewline $fileId] \n] close $fileId # Now process the list foreach line $dataList { # Ignore blank lines if {[string trim $line] == ""} {continue} # Ignore comments if {[string match "#*" $line]} {continue} # Match sections if {[regexp {\[(.*)\]} $line junk section]} { # We have reached a new section } if {[string match "*=*" $line]} { foreach {key val} [split $line =] {break} $self set $section $key $val } } } return -code $retCode } method write {filename} { set retCode "ok" # Make parent directories if needed if {![info exists [file dirname $filename]]} { file mkdir [file dirname $filename] } # Open file for writing if {[catch {open $filename w} fid]} { set retCode "error" } else { foreach section $sectionList { puts $fid "\[$section\]" foreach key $data($section:keyList) { puts $fid "$key=$data($section,$key)" } puts $fid "" } flush $fid close $fid } } method sections {} { return $sectionList } method get {section key} { set value "" if [info exists data($section,$key)] { set value $data($section,$key) } return $value } method set {section key value} { if {![info exists data($section,$key)]} { $self AddKey $section $key } set data($section,$key) $value } method delete {section key} { $self DelKey $section $key } method exists {section key} { return [info exists data($section,$key)] } method show {} { foreach section $sectionList { puts "\[$section\]" foreach key $data($section:keyList) { puts "$key=$data($section,$key)" } puts "" } } method section {args} { if {[llength $args] < 2} { set message "wrong # args: should be \"$self section <option> <section> ?args?\"" return -code error $message } else { set cmd [lindex $args 0] set section [lindex $args 1] set args [lrange $args 2 end] } switch -- $cmd { get { set res [list] foreach key $data($section:keyList) { lappend res $key $data($section,$key) } return $res } set { set keyValList [concat $args] set message [list] foreach {key value} $keyValList { lappend message [$self set $section $key $value] } return $message } exists { if {[lsearch $sectionList $section] == -1} { return 0 } else { return 1 } } list { return $data($section:keyList) } delete { return [$self DelSection $section] } default { set message "bad option $cmd: must be get, set, exists, list, delete" return -code error $message } } } } }
Here is how it can be used:
package require cfg # Create a example.cfg config file set cfg [cfg::cfg create %AUTO%] $cfg set "General" attribute "This is my value" $cfg write example.cfg $cfg destroy # Read the config file set cfg2 [cfg::cfg create %AUTO%] $cfg2 read example.cfg puts "Value is: [$cfg2 get General attribute]" # Show the config file $cfg2 show # Now free it $cfg2 destroy