summaryrefslogtreecommitdiffstats
path: root/tcllib/modules/inifile
diff options
context:
space:
mode:
Diffstat (limited to 'tcllib/modules/inifile')
-rw-r--r--tcllib/modules/inifile/ChangeLog182
-rw-r--r--tcllib/modules/inifile/ini.man100
-rw-r--r--tcllib/modules/inifile/ini.tcl403
-rw-r--r--tcllib/modules/inifile/inifile.pcx89
-rw-r--r--tcllib/modules/inifile/inifile.test218
-rw-r--r--tcllib/modules/inifile/pkgIndex.tcl2
-rw-r--r--tcllib/modules/inifile/sample.ini5
-rw-r--r--tcllib/modules/inifile/test.ini15
8 files changed, 1014 insertions, 0 deletions
diff --git a/tcllib/modules/inifile/ChangeLog b/tcllib/modules/inifile/ChangeLog
new file mode 100644
index 0000000..8da18ed
--- /dev/null
+++ b/tcllib/modules/inifile/ChangeLog
@@ -0,0 +1,182 @@
+2013-02-01 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.15 ========================
+ *
+
+2012-01-05 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * ini.tcl: [Bug 3469006]: Followup to [Bug 3419727]. Fixed
+ * ini.man: the unscoped 'close' command left in the code.
+ * pkgIndex.tcl: Bumped version to 0.2.5
+
+2011-12-13 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.14 ========================
+ *
+
+2011-12-02 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * ini.tcl: [Bug 3419727]: Fixed the creative writing issue
+ * ini.man: with what should be local variables of _loadfile
+ * pkgIndex.tcl: and _commit. Generally reworked to use the
+ 'variable' command to bring object state into scope.
+ Bumped version to 0.2.4
+
+2011-01-24 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.13 ========================
+ *
+
+2009-12-07 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.12 ========================
+ *
+
+2008-12-12 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.11.1 ========================
+ *
+
+2008-10-16 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.11 ========================
+ *
+
+2008-06-14 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * inifile.pcx: New file. Syntax definitions for the public
+ commands of the inifile package.
+
+2008-05-10 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * ini.tcl: Fixed definition of procedure 'commentchar'. Was
+ * ini.man: defined global instead of in the '::ini' namespace.
+ * pkgIndex.tcl: This fixes [SF Tcllib Bug 1917035]. Bumped version
+ to 0.2.3.
+
+2008-03-14 Andreas Kupries <andreask@activestate.com>
+
+ * ini.tcl: Simplified the initialization code. Bumped version to
+ * ini.man: 0.2.2.
+ * pkgIndex.tcl:
+
+2007-09-12 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.10 ========================
+ *
+
+2007-08-20 Andreas Kupries <andreask@activestate.com>
+
+ * ini.man: Bumped package version to 0.2.1, due to bugfix in last
+ * ini.tcl: entry.
+ * pkgIndex.tcl:
+
+2007-08-16 Aaron Faupell <afaupell@users.sourceforge.net>
+
+ * ini.man: clarifications to ini::open and ini::remove
+ * ini.tcl fixed bug in ini::value where default value only
+ worked if the section didnt exist
+
+2007-03-21 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * ini.man: Fixed all warnings due to use of now deprecated
+ commands. Added a section about how to give feedback.
+
+2006-10-03 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.9 ========================
+ *
+
+2006-06-29 Aaron Faupell <afaupell@users.sourceforge.net>
+
+ * ini.tcl: added default value option for value command, and added
+ commentchar command. change in comment behavior
+ * ini.man: clarifications for open, commit, and comment commands; added
+ commentchar command
+
+2006-01-23 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * inifile.test: More boilerplate simplified via use of test support.
+
+2006-01-19 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * inifile.test: Hooked into the new common test support code.
+
+2005-17-11 Aaron Faupell <afaupell@users.sourceforge.net>
+
+ * ini.tcl: fixed bug causing empty ini files when opening
+ with w modes introduced on 2005-31-03
+
+2005-10-06 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.8 ========================
+ *
+
+2005-09-05 Pat Thoyts <patthoyts@users.sourceforge.net>
+
+ * ini.tcl: Fix for bug #1280529 - collision with global
+ * inifile.test: variable names. Added tests for these.
+
+2005-31-03 Aaron Faupell <afaupell@users.sourceforge.net>
+
+ * ini.tcl: fixed bug where ini files were corrupted when
+ saving a shorter version. due to not closing
+ and truncating file before writing.
+
+2004-10-05 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.7 ========================
+ *
+
+2004-05-23 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.6.1 ========================
+ *
+
+2004-03-06 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * inifile.test: Fixed [Tcllib SF Bug 899204] by (a) rewriting all
+ tests to be completely independent of each other and (b)
+ changing the mode when opening the test file to 'r'. It should
+ be noted that the write facilities of the module are not covered
+ by the testsuite. That is unfortunate.
+
+2004-02-15 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.6 ========================
+ *
+
+2004-02-10 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * inifile.test: It was easier to make this package useable for Tcl
+ * ini.tcl: 8.2 than excluding it from test for versions of Tcl
+ * ini.man: before 8.4. So that was done.
+
+2003-07-15 Andreas Kupries <andreask@pliers.activestate.com>
+
+ * ini.tcl: Got a rewritten system from Aaron.
+ * ini.man: Updated the documentation.
+
+ * infile.test: New testsuite for module.
+ * test.ini:
+
+2003-07-04 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * ini.tcl: Added a comment header for RCS id, copyright notices,
+ etc. Slight reformatting of the code. Slight code changes to
+ make 'procheck' complain less (proper import of variables into
+ the scope).
+
+ Documented possible bug.
diff --git a/tcllib/modules/inifile/ini.man b/tcllib/modules/inifile/ini.man
new file mode 100644
index 0000000..ce36ea1
--- /dev/null
+++ b/tcllib/modules/inifile/ini.man
@@ -0,0 +1,100 @@
+[vset VERSION 0.3]
+[comment {-*- tcl -*- doctools manpage}]
+[manpage_begin inifile n [vset VERSION]]
+[moddesc {Parsing of Windows INI files}]
+[titledesc {Parsing of Windows INI files}]
+[category {Text processing}]
+[require Tcl 8.2]
+[require inifile [opt [vset VERSION]]]
+[description]
+
+This package provides an interface for easy manipulation of Windows INI files.
+
+[para]
+
+[list_begin definitions]
+
+[call [cmd ::ini::open] [arg file] [opt "[option -encoding] [arg encoding]"] [opt [arg access]]]
+
+Opens an INI file and returns a handle that is used by other commands.
+[arg access] is the same as the first form (non POSIX) of the [const open]
+command, with the exception that mode [const a] is not supported. The
+default mode is [const r+].
+
+[para] The default [arg encoding] is the system encoding.
+
+
+[call [cmd ::ini::close] [arg ini]]
+
+Close the specified handle. If any changes were made and not written by
+[const commit] they are lost.
+
+[call [cmd ::ini::commit] [arg ini]]
+
+Writes the file and all changes to disk. The sections are written in
+arbitrary order. The keys in a section are written in alphabetical
+order. If the ini was opened in read only mode an error will be thrown.
+
+[call [cmd ::ini::revert] [arg ini]]
+
+Rolls all changes made to the inifile object back to the last
+committed state.
+
+[call [cmd ::ini::filename] [arg ini]]
+
+Returns the name of the file the [arg ini] object is associated with.
+
+[call [cmd ::ini::sections] [arg ini]]
+
+Returns a list of all the names of the existing sections in the file handle
+specified.
+
+[call [cmd ::ini::keys] [arg ini] [arg section]]
+
+Returns a list of all they key names in the section and file specified.
+
+[call [cmd ::ini::get] [arg ini] [arg section]]
+
+Returns a list of key value pairs that exist in the section and file specified.
+
+[call [cmd ::ini::exists] [arg ini] [arg section] [opt [arg key]]]
+
+Returns a boolean value indicating the existance of the specified section as a
+whole or the specified key within that section.
+
+[call [cmd ::ini::value] [arg ini] [arg section] [arg key] [opt [arg default]]]
+
+Returns the value of the named key and section. If specified,
+the default value will be returned if the key does not exist. If the key does
+not exist and no default is specified an error will be thrown.
+
+[call [cmd ::ini::set] [arg ini] [arg section] [arg key] [arg value]]
+
+Sets the value of the key in the specified section. If the section does not
+exist then a new one is created.
+
+[call [cmd ::ini::delete] [arg ini] [arg section] [opt [arg key]]]
+
+Removes the key or the entire section and all its keys. A section is not
+automatically deleted when it has no remaining keys.
+
+[call [cmd ::ini::comment] [arg ini] [arg section] [opt [arg key]] [opt [arg text]]]
+
+Reads and modifies comments for sections and keys. To write a section comment use an
+empty string for the [arg key]. To remove all comments use an empty string for [arg text].
+[arg text] may consist of a list of lines or one single line. Any embedded newlines in
+[arg text] are properly handled. Comments may be written to nonexistant
+sections or keys and will not return an error. Reading a comment from a nonexistant
+section or key will return an empty string.
+
+[call [cmd ::ini::commentchar] [opt char]]
+
+Reads and sets the comment character. Lines that begin with this character are treated as
+comments. When comments are written out each line is preceded by this character. The default
+is [const \;].
+
+[list_end]
+
+[vset CATEGORY inifile]
+[include ../doctools2base/include/feedback.inc]
+[manpage_end]
diff --git a/tcllib/modules/inifile/ini.tcl b/tcllib/modules/inifile/ini.tcl
new file mode 100644
index 0000000..938cb64
--- /dev/null
+++ b/tcllib/modules/inifile/ini.tcl
@@ -0,0 +1,403 @@
+# ini.tcl --
+#
+# Querying and modifying old-style windows configuration files (.ini)
+#
+# Copyright (c) 2003-2007 Aaron Faupell <afaupell@users.sourceforge.net>
+# Copyright (c) 2008-2012 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+#
+# See the file "license.terms" for information on usage and redistribution
+# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+#
+# RCS: @(#) $Id: ini.tcl,v 1.17 2012/01/05 21:04:55 andreas_kupries Exp $
+
+package provide inifile 0.3
+
+namespace eval ini {
+ variable nexthandle 0
+ variable commentchar \;
+}
+
+proc ::ini::open {ini args} {
+ variable nexthandle
+
+ while {[string match -* [::set opt [lindex $args 0]]]} {
+ switch -exact -- $opt {
+ -- {
+ ::set args [lrange $args 1 end]
+ break
+ }
+ -encoding {
+ ::set enc [lindex $args 1]
+ ::set args [lrange $args 2 end]
+ }
+ default {
+ return -code error \
+ -errorcode {INIFILE OPTION INVALID} \
+ "Invalid option $opt, expected -encoding"
+ }
+ }
+ }
+
+ ::set remainder [llength $args]
+ if {$remainder > 1} {
+ return -code error \
+ -errorcode {WRONG-ARGS INIFILE} \
+ "wrong\#args: should be \"ini::open ?-encoding E? ?mode?\""
+ } elseif {$remainder == 1} {
+ ::set mode [lindex $args 0]
+ } else {
+ ::set mode r+
+ }
+
+ if { ![regexp {^(w|r)\+?$} $mode] } {
+ return -code error \
+ -errorcode {INIFILE MODE INVALID} \
+ "$mode is not a valid access mode"
+ }
+
+ ::set fh ini$nexthandle
+ ::set tmp [::open $ini $mode]
+ fconfigure $tmp -translation crlf
+ if {[info exists enc]} {
+ if {[catch {
+ fconfigure $tmp -encoding $enc
+ } msg]} {
+ ::close $tmp
+ return -code error $msg
+ }
+ }
+
+ namespace eval ::ini::$fh {
+ variable data; array set data {}
+ variable comments; array set comments {}
+ variable sections; array set sections {}
+ }
+ ::set ::ini::${fh}::channel $tmp
+ ::set ::ini::${fh}::file [_normalize $ini]
+ ::set ::ini::${fh}::mode $mode
+
+ incr nexthandle
+ if { [string match "r*" $mode] } {
+ _loadfile $fh
+ }
+ return $fh
+}
+
+# close the file and delete all stored info about it
+# this does not save any changes. see ::ini::commit
+
+proc ::ini::close {fh} {
+ _valid_ns $fh
+ variable ::ini::${fh}::channel
+ ::close $channel
+ namespace delete ::ini::$fh
+ return
+}
+
+# write all changes to disk
+
+proc ::ini::commit {fh} {
+ _valid_ns $fh
+
+ variable ::ini::${fh}::data
+ variable ::ini::${fh}::comments
+ variable ::ini::${fh}::sections
+ variable ::ini::${fh}::channel
+ variable ::ini::${fh}::file
+ variable ::ini::${fh}::mode
+ variable commentchar
+
+ if { $mode == "r" } {
+ return -code error \
+ -errorcode {INIFILE READ-ONLY} \
+ "cannot write to read-only file"
+ }
+ ::close $channel
+ ::set channel [::open $file w]
+ ::set char $commentchar
+ #seek $channel 0 start
+ foreach sec [array names sections] {
+ if { [info exists comments($sec)] } {
+ puts $channel "$char [join $comments($sec) "\n$char "]\n"
+ }
+ puts $channel "\[$sec\]"
+ foreach key [lsort -dictionary [array names data [_globescape $sec]\000*]] {
+ ::set key [lindex [split $key \000] 1]
+ if {[info exists comments($sec\000$key)]} {
+ puts $channel "$char [join $comments($sec\000$key) "\n$char "]"
+ }
+ puts $channel "$key=$data($sec\000$key)"
+ }
+ puts $channel ""
+ }
+ ::close $channel
+ ::set channel [::open $file r+]
+ return
+}
+
+# internal command to read in a file
+# see open and revert for public commands
+
+proc ::ini::_loadfile {fh} {
+ variable ::ini::${fh}::data
+ variable ::ini::${fh}::comments
+ variable ::ini::${fh}::sections
+ variable ::ini::${fh}::channel
+ variable ::ini::${fh}::file
+ variable ::ini::${fh}::mode
+ variable commentchar
+
+ ::set cur {}
+ ::set com {}
+
+ ::set char $commentchar
+ seek $channel 0 start
+
+ foreach line [split [read $channel] "\n"] {
+ # bug 3612465 - allow and ignore leading and trailing whitespace.
+ ::set line [string trim $line]
+
+ if { [string match "$char*" $line] } {
+ lappend com [string trim [string range $line [string length $char] end]]
+ } elseif { [string match {\[*\]} $line] } {
+ ::set cur [string range $line 1 end-1]
+ if { $cur == "" } { continue }
+ ::set sections($cur) 1
+ if { $com != "" } {
+ ::set comments($cur) $com
+ ::set com {}
+ }
+ } elseif { [string match {*=*} $line] } {
+ ::set line [split $line =]
+ ::set key [string trim [lindex $line 0]]
+ if { $key == "" || $cur == "" } { continue }
+ ::set value [string trim [join [lrange $line 1 end] =]]
+ if { [regexp "^(\".*\")\s+${char}(.*)$" $value -> 1 2] } {
+ ::set value $1
+ lappend com $2
+ }
+ ::set data($cur\000$key) $value
+ if { $com != "" } {
+ ::set comments($cur\000$key) $com
+ ::set com {}
+ }
+ }
+ }
+ return
+}
+
+# internal command to escape glob special characters
+
+proc ::ini::_globescape {string} {
+ return [string map {* \\* ? \\? \\ \\\\ \[ \\\[ \] \\\]} $string]
+}
+
+# internal command to check if a section or key is nonexistant
+
+proc ::ini::_exists {fh sec args} {
+ variable ::ini::${fh}::sections
+ variable ::ini::${fh}::data
+
+ if { ![info exists sections($sec)] } {
+ return -code error \
+ -errorcode {INIFILE SECTION INVALID} \
+ "no such section \"$sec\""
+ }
+ if { [llength $args] > 0 } {
+ ::set key [lindex $args 0]
+ if { ![info exists data($sec\000$key)] } {
+ return -code error \
+ -errorcode {INIFILE KEY INVALID} \
+ "can't read key \"$key\""
+ }
+ }
+ return
+}
+
+# internal command to check validity of a handle
+
+if { [package vcompare [package provide Tcl] 8.4] < 0 } {
+ proc ::ini::_normalize {path} {
+ return $path
+ }
+ proc ::ini::_valid_ns {name} {
+ variable ::ini::${name}::data
+ if { ![info exists data] } {
+ return -code error \
+ -errorcode {INIFILE HANDLE INVALID} \
+ "$name is not an open INI file"
+ }
+ }
+} else {
+ proc ::ini::_normalize {path} {
+ file normalize $path
+ }
+ proc ::ini::_valid_ns {name} {
+ if { ![namespace exists ::ini::$name] } {
+ return -code error \
+ -errorcode {INIFILE HANDLE INVALID} \
+ "$name is not an open INI file"
+ }
+ }
+}
+
+# get and set the ini comment character
+
+proc ::ini::commentchar { {new {}} } {
+ variable commentchar
+ if {$new != ""} {
+ if {[string length $new] > 1} {
+ return -code error \
+ -errorcode {INIFILE COMMENT-CHAR INVALID} \
+ "comment char must be a single character"
+ }
+ ::set commentchar $new
+ }
+ return $commentchar
+}
+
+# return all section names
+
+proc ::ini::sections {fh} {
+ _valid_ns $fh
+ variable ::ini::${fh}::sections
+ return [array names sections]
+}
+
+# return boolean indicating existance of section or key in section
+
+proc ::ini::exists {fh sec {key {}}} {
+ _valid_ns $fh
+ variable ::ini::${fh}::sections
+ variable ::ini::${fh}::data
+
+ if { $key == "" } {
+ return [info exists sections($sec)]
+ }
+ return [info exists data($sec\000$key)]
+}
+
+# return all key names of section
+# error if section is nonexistant
+
+proc ::ini::keys {fh sec} {
+ _valid_ns $fh
+ _exists $fh $sec
+ variable ::ini::${fh}::data
+
+ ::set keys {}
+ foreach x [array names data [_globescape $sec]\000*] {
+ lappend keys [lindex [split $x \000] 1]
+ }
+ return $keys
+}
+
+# return all key value pairs of section
+# error if section is nonexistant
+
+proc ::ini::get {fh sec} {
+ _valid_ns $fh
+ _exists $fh $sec
+ variable ::ini::${fh}::data
+
+ ::set r {}
+ foreach x [array names data [_globescape $sec]\000*] {
+ lappend r [lindex [split $x \000] 1] $data($x)
+ }
+ return $r
+}
+
+# return the value of a key
+# return default value if key or section is nonexistant otherwise error
+
+proc ::ini::value {fh sec key {default {}}} {
+ _valid_ns $fh
+ variable ::ini::${fh}::data
+
+ if {$default != "" && ![info exists data($sec\000$key)]} {
+ return $default
+ }
+ _exists $fh $sec $key
+ return [::set data($sec\000$key)]
+}
+
+# set the value of a key
+# new section or key names are created
+
+proc ::ini::set {fh sec key value} {
+ _valid_ns $fh
+ variable ::ini::${fh}::sections
+ variable ::ini::${fh}::data
+
+ ::set sec [string trim $sec]
+ ::set key [string trim $key]
+ if { $sec == "" || $key == "" } {
+ return -code error \
+ -errorcode {INIFILE SYNTAX} \
+ "section or key may not be empty"
+ }
+ ::set data($sec\000$key) $value
+ ::set sections($sec) 1
+ return $value
+}
+
+# delete a key or an entire section
+# may delete nonexistant keys and sections
+
+proc ::ini::delete {fh sec {key {}}} {
+ _valid_ns $fh
+ variable ::ini::${fh}::sections
+ variable ::ini::${fh}::data
+
+ if { $key == "" } {
+ array unset data [_globescape $sec]\000*
+ array unset sections [_globescape $sec]
+ }
+ catch {unset data($sec\000$key)}
+}
+
+# read and set comments for sections and keys
+# may comment nonexistant sections and keys
+
+proc ::ini::comment {fh sec key args} {
+ _valid_ns $fh
+ variable ::ini::${fh}::comments
+
+ ::set r $sec
+ if { $key != "" } { append r \000$key }
+ if { [llength $args] == 0 } {
+ if { ![info exists comments($r)] } { return {} }
+ return $comments($r)
+ }
+ if { [llength $args] == 1 && [lindex $args 0] == "" } {
+ unset -nocomplain comments($r)
+ return {}
+ }
+ # take care of any embedded newlines
+ for {::set i 0} {$i < [llength $args]} {incr i} {
+ ::set args [eval [list lreplace $args $i $i] [split [lindex $args $i] \n]]
+ }
+ eval [list lappend comments($r)] $args
+}
+
+# return the physical filename for the handle
+
+proc ::ini::filename {fh} {
+ _valid_ns $fh
+ variable ::ini::${fh}::file
+ return $file
+}
+
+# reload the file from disk losing all changes since the last commit
+
+proc ::ini::revert {fh} {
+ _valid_ns $fh
+ namespace eval ::ini::$fh {
+ array set data {}
+ array set comments {}
+ array set sections {}
+ }
+ if { ![string match "w*" $mode] } {
+ _loadfile $fh
+ }
+}
diff --git a/tcllib/modules/inifile/inifile.pcx b/tcllib/modules/inifile/inifile.pcx
new file mode 100644
index 0000000..f74749e
--- /dev/null
+++ b/tcllib/modules/inifile/inifile.pcx
@@ -0,0 +1,89 @@
+# -*- tcl -*- inifile.pcx
+# Syntax of the commands provided by package inifile.
+
+# For use by TclDevKit's static syntax checker.
+# See http://www.activestate.com/solutions/tcl/
+# See http://aspn.activestate.com/ASPN/docs/Tcl_Dev_Kit/4.0/Checker.html#pcx_api
+# for the documentation describing the format of the code contained in this file
+#
+
+package require pcx
+pcx::register inifile
+pcx::tcldep 0.2.1 needs tcl 8.2
+
+namespace eval ::inifile {}
+
+#pcx::message FOO {... text ...} type
+#pcx::scan <VERSION> <NAME> <RULE>
+
+pcx::check 0.2.1 std ::ini::close \
+ {checkSimpleArgs 1 1 {
+ checkChannelID
+ }}
+pcx::check 0.2.1 std ::ini::comment \
+ {checkSimpleArgs 4 4 {
+ checkChannelID
+ checkWord
+ checkWord
+ checkWord
+ }}
+pcx::check 0.2.1 std ::ini::commit \
+ {checkSimpleArgs 1 1 {
+ checkChannelID
+ }}
+pcx::check 0.2.1 std ::ini::delete \
+ {checkSimpleArgs 2 3 {
+ checkChannelID
+ checkWord
+ checkWord
+ }}
+pcx::check 0.2.1 std ::ini::exists \
+ {checkSimpleArgs 1 1 {
+ checkChannelID
+ }}
+pcx::check 0.2.1 std ::ini::filename \
+ {checkSimpleArgs 1 1 {
+ checkChannelID
+ }}
+pcx::check 0.2.1 std ::ini::get \
+ {checkSimpleArgs 1 2 {
+ checkWord
+ checkWord
+ }}
+pcx::check 0.2.1 std ::ini::keys \
+ {checkSimpleArgs 2 2 {
+ checkChannelID
+ checkWord
+ }}
+# TODO: file open access mode
+pcx::check 0.2.1 std ::ini::open \
+ {checkSimpleArgs 1 2 {
+ checkWord
+ checkWord
+ }}
+pcx::check 0.2.1 std ::ini::revert \
+ {checkSimpleArgs 1 1 {
+ checkChannelID
+ }}
+pcx::check 0.2.1 std ::ini::sections \
+ {checkSimpleArgs 1 1 {
+ checkChannelID
+ }}
+pcx::check 0.2.1 std ::ini::set \
+ {checkSimpleArgs 4 4 {
+ checkChannelID
+ checkWord
+ checkWord
+ checkWord
+ }}
+pcx::check 0.2.1 std ::ini::value \
+ {checkSimpleArgs 3 4 {
+ checkChannelID
+ checkWord
+ checkWord
+ checkWord
+ }}
+
+# Initialization via pcx::init.
+# Use a ::inifile::init procedure for non-standard initialization.
+pcx::complete
diff --git a/tcllib/modules/inifile/inifile.test b/tcllib/modules/inifile/inifile.test
new file mode 100644
index 0000000..3aa9627
--- /dev/null
+++ b/tcllib/modules/inifile/inifile.test
@@ -0,0 +1,218 @@
+# -*- tcl -*-
+# Tests for module 'inifile'
+
+# -------------------------------------------------------------------------
+
+source [file join \
+ [file dirname [file dirname [file join [pwd] [info script]]]] \
+ devtools testutilities.tcl]
+
+testsNeedTcl 8.2
+testsNeedTcltest 1.0
+
+testing {
+ useLocal ini.tcl inifile
+}
+
+#---------------------------------------------------------------------
+
+set inifile [localPath ini.tcl]
+set testini [localPath test.ini]
+set sampini [localPath sample.ini]
+
+#---------------------------------------------------------------------
+
+test inifile-1.1 {ini::open} {
+ set res [ini::open $testini r]
+ ini::close $res
+ set res
+} {ini0}
+
+test inifile-1.2 {ini::sections} {
+ set hdl [ini::open $testini r]
+ set res [ini::sections $hdl]
+ ini::close $hdl
+ set res
+} {emptysection section1 \{test section2}
+
+test inifile-1.3 {ini::keys} {
+ set hdl [ini::open $testini r]
+ set res [ini::keys $hdl section1]
+ ini::close $hdl
+ set res
+} {testkey key}
+
+test inifile-1.4 {ini::keys} {
+ set hdl [ini::open $testini r]
+ set res [ini::keys $hdl \{test]
+ ini::close $hdl
+ set res
+} {\}key}
+
+test inifile-1.5 {ini::get} {
+ set hdl [ini::open $testini r]
+ set res [ini::get $hdl section1]
+ ini::close $hdl
+ set res
+} {testkey hi key value}
+
+test inifile-1.6 {ini::get} {
+ set hdl [ini::open $testini r]
+ set res [ini::get $hdl \{test]
+ ini::close $hdl
+ set res
+} {\}key {$blah}}
+
+test inifile-1.7 {ini::value} {
+ set hdl [ini::open $testini r]
+ set res [ini::value $hdl section1 key]
+ ini::close $hdl
+ set res
+} {value}
+
+test inifile-1.8 {ini::value} {
+ set hdl [ini::open $testini r]
+ set res [ini::value $hdl \{test \}key]
+ ini::close $hdl
+ set res
+} {$blah}
+
+test inifile-1.9 {ini::exists} {
+ set hdl [ini::open $testini r]
+ set res [ini::exists $hdl section1]
+ ini::close $hdl
+ set res
+} {1}
+
+test inifile-1.10 {ini::exists} {
+ set hdl [ini::open $testini r]
+ set res [ini::exists $hdl section]
+ ini::close $hdl
+ set res
+} {0}
+
+test inifile-1.11 {ini::exists} {
+ set hdl [ini::open $testini r]
+ set res [ini::exists $hdl section1 testkey]
+ ini::close $hdl
+ set res
+} {1}
+
+test inifile-1.12 {ini:::exists} {
+ set hdl [ini::open $testini r]
+ set res [ini::exists $hdl section1 blah]
+ ini::close $hdl
+ set res
+} {0}
+
+test inifile-1.13 {ini:::exists} {
+ set hdl [ini::open $testini r]
+ set res [ini::exists $hdl \{test]
+ ini::close $hdl
+ set res
+} {1}
+
+test inifile-1.14 {ini:::exists} {
+ set hdl [ini::open $testini r]
+ set res [ini::exists $hdl \{test \}key]
+ ini::close $hdl
+ set res
+} {1}
+
+#---------------------------------------------------------------------
+# Tests for bug #1281136 --
+set N 0
+foreach name {nexthandle commentchar} {
+ test inifile-2.$N {bug 1281136 - collision with global variable names} {
+ set script {list [catch {
+ array set ::%var {}
+ source %file
+ } err] $err}
+ regsub {%file} $script $inifile script
+ regsub {%var} $script $name script
+ interp create slave0
+ set r [slave0 eval $script]
+ interp delete slave0
+ set r
+ } {0 {}}
+ incr N
+}
+foreach name {data comments sections} {
+ test inifile-2.$N {bug 1281136 - collision with global variable names} {
+ set script {list [catch {
+ ::set ::%var 0
+ source %file
+ set res [ini::open %testini r]
+ ini::close $res
+ } err] $err}
+ foreach {s v} [list %file $inifile %var $name %testini $testini] {
+ regsub $s $script $v script
+ }
+ interp create slave0
+ set r [slave0 eval $script]
+ interp delete slave0
+ set r
+ } {0 {}}
+ incr N
+}
+
+#---------------------------------------------------------------------
+
+test inifile-3.0 {bug 3612465, leading & trailing spaces} {
+ set fh [ini::open $sampini]
+ set res [ini::sections $fh]
+ ini::close $fh
+ unset fh
+ set res
+} General
+
+test inifile-3.1 {bug 3612465, leading & trailing spaces} {
+ set fh [ini::open $sampini]
+ #set res [llength [ini::sections $fh]]
+ set res [lsort -dict [ini::keys $fh General]]
+ ini::close $fh
+ unset fh
+ set res
+} {key key2}
+
+#---------------------------------------------------------------------
+
+test inifile-4.0 {bug c4b8162da5 - ini::open} {
+ set res [ini::open $testini -encoding unicode r]
+ ini::close $res
+ set res
+} {ini16}
+
+# Test various error conditions.
+test inifile-4.1 {bug c4b8162da5 - ini::open - invalid encoding} {
+ catch {
+ ini::open $testini -encoding foo r
+ } res
+ set res
+} {unknown encoding "foo"}
+
+test inifile-4.2 {bug c4b8162da5 - ini::open - invalid option} {
+ catch {
+ ini::open $testini -bogus foo r
+ } res
+ set res
+} {Invalid option -bogus, expected -encoding}
+
+test inifile-4.3 {bug c4b8162da5 - ini::open - invalid mode} {
+ catch {
+ ini::open $testini x
+ } res
+ set res
+} {x is not a valid access mode}
+
+test inifile-4.4 {bug c4b8162da5 - ini::open - invalid mode} {
+ catch {
+ set res [ini::open $testini w-]
+ } res
+ set res
+} {w- is not a valid access mode}
+
+#---------------------------------------------------------------------
+# Clean up
+testsuiteCleanup
+return
diff --git a/tcllib/modules/inifile/pkgIndex.tcl b/tcllib/modules/inifile/pkgIndex.tcl
new file mode 100644
index 0000000..9aa3f98
--- /dev/null
+++ b/tcllib/modules/inifile/pkgIndex.tcl
@@ -0,0 +1,2 @@
+if { ![package vsatisfies [package provide Tcl] 8.2] } { return }
+package ifneeded inifile 0.3 [list source [file join $dir ini.tcl]]
diff --git a/tcllib/modules/inifile/sample.ini b/tcllib/modules/inifile/sample.ini
new file mode 100644
index 0000000..884ccae
--- /dev/null
+++ b/tcllib/modules/inifile/sample.ini
@@ -0,0 +1,5 @@
+[General]
+ key=value
+key2=value2
+
+ ; ....
diff --git a/tcllib/modules/inifile/test.ini b/tcllib/modules/inifile/test.ini
new file mode 100644
index 0000000..ac8eb56
--- /dev/null
+++ b/tcllib/modules/inifile/test.ini
@@ -0,0 +1,15 @@
+[emptysection]
+
+; a comment for section 1
+
+[section1]
+key=value
+testkey=hi
+
+[section2]
+; key comment
+key=othervalue
+
+
+[{test]
+}key = $blah