ini.tcl
Go to the documentation of this file.00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
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
00047
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
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
00089
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
00131
00132 ret ::ini::_globescape (type string) {
00133 return [string map {* \\* ? \\? \\ \\\\ \[ \\\[ \] \\\]} $string]
00134 }
00135
00136
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
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
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
00184
00185 ret ::ini::sections (type fh) {
00186 _valid_ns $fh
00187 return [array names ::ini::${fh}::sections]
00188 }
00189
00190
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
00201
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
00214
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
00228
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
00240
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
00255
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
00267
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
00290
00291 ret ::ini::filename (type fh) {
00292 _valid_ns $fh
00293 return [::set ::ini::${fh}::file]
00294 }
00295
00296
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