ini.tcl

Go to the documentation of this file.
00001 /*  ini.tcl --*/
00002 /* */
00003 /*        Querying and modifying old-style windows configuration files (.ini)*/
00004 /* */
00005 /*  Copyright (c) 2003-2007    Aaron Faupell <afaupell@users.sourceforge.net>*/
00006 /* */
00007 /*  See the file "license.terms" for information on usage and redistribution*/
00008 /*  of this file, and for a DISCLAIMER OF ALL WARRANTIES.*/
00009 /*  */
00010 /*  RCS: @(#) $Id: ini.tcl,v 1.13 2007/08/20 21:03:17 andreas_kupries Exp $*/
00011 
00012 package provide inifile 0.2.1
00013 
00014 namespace ini {
00015     variable nexthandle; if {![info exists nexthandle]} { nexthandle =  0}
00016     variable commentchar; if {![info exists commentchar]} { commentchar =  \;}
00017 }
00018 
00019 ret  ::ini::open (type ini , optional mode =r+) {
00020     variable nexthandle
00021 
00022     if { ![regexp {^(w|r)\+?$} $mode] } {
00023         error "$mode is not a valid access mode"
00024     }
00025 
00026     ::set fh ini$nexthandle
00027     ::set tmp [::open $ini $mode]
00028     fconfigure $tmp -translation crlf
00029 
00030     namespace eval ::ini::$fh {
00031         variable data;     array set data     {}
00032         variable comments; array set comments {}
00033         variable sections; array set sections {}
00034     }
00035     ::set ::ini::${fh}::channel $tmp
00036     ::set ::ini::${fh}::file    [_normalize $ini]
00037     ::set ::ini::${fh}::mode    $mode
00038 
00039     incr nexthandle
00040     if { [string match "r*" $mode] } {
00041         _loadfile $fh
00042     }
00043     return $fh
00044 }
00045 
00046 /*  close the file and delete all stored info about it*/
00047 /*  this does not save any changes. see ::ini::commit*/
00048 
00049 ret  ::ini::close (type fh) {
00050     _valid_ns $fh
00051     ::close [::set ::ini::${fh}::channel]
00052     namespace delete ::ini::$fh
00053 }
00054 
00055 /*  write all changes to disk*/
00056 
00057 ret  ::ini::commit (type fh) {
00058     _valid_ns $fh
00059     namespace eval ::ini::$fh {
00060         if { $mode == "r" } {
00061             error "cannot write to read-only file"
00062         }
00063         ::close $channel
00064         ::set channel [::open $file w]
00065         ::set char $::ini::commentchar
00066         #seek $channel 0 start
00067         foreach sec [array names sections] {
00068             if { [info exists comments($sec)] } {
00069                 puts $channel "$char [join $comments($sec) "\n$char "]\n"
00070             }
00071             puts $channel "\[$sec\]"
00072             foreach key [lsort -dictionary [array names data [::ini::_globescape $sec]\000*]] {
00073                 ::set key [lindex [split $key \000] 1]
00074                 if {[info exists comments($sec\000$key)]} {
00075                     puts $channel "$char [join $comments($sec\000$key) "\n$char "]"
00076                 }
00077                 puts $channel "$key=$data($sec\000$key)"
00078             }
00079             puts $channel ""
00080         }
00081         catch { unset char sec key }
00082         close $channel
00083         ::set channel [::open $file r+]
00084     }
00085     return
00086 }
00087 
00088 /*  internal command to read in a file*/
00089 /*  see open and revert for public commands*/
00090 
00091 ret  ::ini::_loadfile (type fh) {
00092     namespace eval ::ini::$fh {
00093         ::set cur {}
00094         ::set com {}
00095         set char $::ini::commentchar
00096         seek $channel 0 start
00097 
00098         foreach line [split [read $channel] "\n"] {
00099             if { [string match "$char*" $line] } {
00100                 lappend com [string trim [string range $line [string length $char] end]]
00101             } elseif { [string match {\[*\]} $line] } {
00102                 ::set cur [string range $line 1 end-1]
00103                 if { $cur == "" } { continue }
00104                 ::set sections($cur) 1
00105                 if { $com != "" } {
00106                     ::set comments($cur) $com
00107                     ::set com {}
00108                 }
00109             } elseif { [string match {*=*} $line] } {
00110                 ::set line [split $line =]
00111                 ::set key [string trim [lindex $line 0]]
00112                 if { $key == "" || $cur == "" } { continue }
00113                 ::set value [string trim [join [lrange $line 1 end] =]]
00114                 if { [regexp "^(\".*\")\s+${char}(.*)$" $value -> 1 2] } {
00115                     set value $1
00116                     lappend com $2
00117                 }
00118                 ::set data($cur\000$key) $value
00119                 if { $com != "" } {
00120                     ::set comments($cur\000$key) $com
00121                     ::set com {}
00122                 }
00123             }
00124         }
00125         unset char cur com
00126         catch { unset line key value 1 2 }
00127     }
00128 }
00129 
00130 /*  internal command to escape glob special characters*/
00131 
00132 ret  ::ini::_globescape (type string) {
00133     return [string map {* \\* ? \\? \\ \\\\ \[ \\\[ \] \\\]} $string]
00134 }
00135 
00136 /*  internal command to check if a section or key is nonexistant*/
00137 
00138 ret  ::ini::_exists (type fh , type sec , type args) {
00139     if { ![info exists ::ini::${fh}::sections($sec)] } {
00140         error "no such section \"$sec\""
00141     }
00142     if { [llength $args] > 0 } {
00143         ::set key [lindex $args 0]
00144         if { ![info exists ::ini::${fh}::data($sec\000$key)] } {
00145             error "can't read key \"$key\""
00146         }
00147     }
00148 }
00149 
00150 /*  internal command to check validity of a handle*/
00151 
00152 if { [package vcompare [package provide Tcl] 8.4] < 0 } {
00153     ret  ::ini::_normalize (type path) {
00154     return $path
00155     }
00156     ret  ::ini::_valid_ns (type name) {
00157     variable ::ini::${name}::data
00158     if { ![info exists data] } {
00159         error "$name is not an open INI file"
00160     }
00161     }
00162 } else {
00163     ret  ::ini::_normalize (type path) {
00164     file normalize $path
00165     }
00166     ret  ::ini::_valid_ns (type name) {
00167     if { ![namespace exists ::ini::$name] } {
00168         error "$name is not an open INI file"
00169     }
00170     }
00171 }
00172 
00173 /*  get and set the ini comment character*/
00174 
00175 ret  commentchar ( optional new ={) } {
00176     if {$new != ""} {
00177         if {[string length $new] > 1} { error "comment char must be a single character" }
00178         :: ::ini = ::commentchar $new
00179     }
00180     return $::ini::commentchar
00181 }
00182 
00183 /*  return all section names*/
00184 
00185 ret  ::ini::sections (type fh) {
00186     _valid_ns $fh
00187     return [array names ::ini::${fh}::sections]
00188 }
00189 
00190 /*  return boolean indicating existance of section or key in section*/
00191 
00192 ret  ::ini::exists (type fh , type sec , optional key ={)} {
00193     _valid_ns $fh
00194     if { $key == "" } {
00195         return [info exists ::ini::${fh}::sections($sec)]
00196     }
00197     return [info exists ::ini::${fh}::data($sec\000$key)]
00198 }
00199 
00200 /*  return all key names of section*/
00201 /*  error if section is nonexistant*/
00202 
00203 ret  ::ini::keys (type fh , type sec) {
00204     _valid_ns $fh
00205     _exists $fh $sec
00206     ::set keys {}
00207     foreach x [array names ::ini::${fh}::data [_globescape $sec]\000*] {
00208         lappend keys [lindex [split $x \000] 1]
00209     }
00210     return $keys
00211 }
00212 
00213 /*  return all key value pairs of section*/
00214 /*  error if section is nonexistant*/
00215 
00216 ret  ::ini::get (type fh , type sec) {
00217     _valid_ns $fh
00218     _exists $fh $sec
00219     upvar 0 ::ini::${fh}::data data
00220     ::set r {}
00221     foreach x [array names data [_globescape $sec]\000*] {
00222         lappend r [lindex [split $x \000] 1] $data($x)
00223     }
00224     return $r
00225 }
00226 
00227 /*  return the value of a key*/
00228 /*  return default value if key or section is nonexistant otherwise error*/
00229 
00230 ret  ::ini::value (type fh , type sec , type key , optional default ={)} {
00231     _valid_ns $fh
00232     if {$default != "" && ![info exists ::ini::${fh}::data($sec\000$key)]} {
00233         return $default
00234     }
00235     _exists $fh $sec $key
00236     return [:: ::ini = ::${fh}::data($sec\000$key)]
00237 }
00238 
00239 /*  set the value of a key*/
00240 /*  new section or key names are created*/
00241 
00242 ret  ::ini::set (type fh , type sec , type key , type value) {
00243     _valid_ns $fh
00244     ::set sec [string trim $sec]
00245     ::set key [string trim $key]
00246     if { $sec == "" || $key == "" } {
00247         error "section or key may not be empty"
00248     }
00249     ::set ::ini::${fh}::data($sec\000$key) $value
00250     ::set ::ini::${fh}::sections($sec) 1
00251     return $value
00252 }
00253 
00254 /*  delete a key or an entire section*/
00255 /*  may delete nonexistant keys and sections*/
00256 
00257 ret  ::ini::delete (type fh , type sec , optional key ={)} {
00258     _valid_ns $fh
00259     if { $key == "" } {
00260         array un ::ini = ::${fh}::data [_globescape $sec]\000*
00261         array un ::ini = ::${fh}::sections [_globescape $sec]
00262     }
00263     catch {un ::ini = ::${fh}::data($sec\000$key)}
00264 }
00265 
00266 /*  read and set comments for sections and keys*/
00267 /*  may comment nonexistant sections and keys*/
00268 
00269 ret  ::ini::comment (type fh , type sec , type key , type args) {
00270     _valid_ns $fh
00271     upvar 0 ::ini::${fh}::comments comments
00272     ::set r $sec
00273     if { $key != "" } { append r \000$key }
00274     if { [llength $args] == 0 } {
00275         if { ![info exists comments($r)] } { return {} }
00276         return $comments($r)
00277     }
00278     if { [llength $args] == 1 && [lindex $args 0] == "" } {
00279         unset -nocomplain comments($r)
00280         return {}
00281     }
00282     # take care of any embedded newlines
00283     for {::set i 0} {$i < [llength $args]} {incr i} {
00284         ::set args [eval [list lreplace $args $i $i] [split [lindex $args $i] \n]]
00285     }
00286     eval [list lappend comments($r)] $args
00287 }
00288 
00289 /*  return the physical filename for the handle*/
00290 
00291 ret  ::ini::filename (type fh) {
00292     _valid_ns $fh
00293     return [::set ::ini::${fh}::file]
00294 }
00295 
00296 /*  reload the file from disk losing all changes since the last commit*/
00297 
00298 ret  ::ini::revert (type fh) {
00299     _valid_ns $fh
00300     namespace eval ::ini::$fh {
00301         array set data     {}
00302         array set comments {}
00303         array set sections {}
00304     }
00305     if { ![string match "w*" $mode] } {
00306         _loadfile $fh
00307     }
00308 }
00309 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1