savedefault


JOB - 2016-07-19 08:13:11

Purpose: Utility package to store & retrieve user settings. The package requires inifile and fileutil from tcllib.

# -----------------------------------------------------------------------------
# savedefault.tcl ---
# -----------------------------------------------------------------------------
# (c) 2016, Johann Oberdorfer - Engineering Support | CAD | Software
#     johann.oberdorfer [at] gmail.com
#     www.johann-oberdorfer.eu
# -----------------------------------------------------------------------------
# This source file is distributed under the BSD 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 BSD License for more details.
# -----------------------------------------------------------------------------
# Purpose:
#  Package to store & retrieve user settings.
#
# -----------------------------------------------------------------------------

package provide savedefault 1.3

namespace eval ::savedefault {

        # public interface
        namespace export \
                savedefault \
                readsettings \
                savesettings

        variable ini
        variable ini_defaults

        # ini sections declarations
        
        array set ini {
                DEFAULT_SECTION "Settings"
                COMMENTS        "Comments"
                INI_FILE ""
        }

        proc GetTempDirectory {} {
                # return the temporary directory

                set tempDir [fileutil::tempdir]
                set subDir ".tcl"
                set cpath [file join $tempDir $subDir]

                if { ![file isdirectory $cpath] &&
                         [catch {file mkdir $cpath}] != 0 } {

                        # no write access ?!..., so we use standard:
                        return $tempDir
                }

                return $cpath
        }

        proc savedefault {fname ini_list} {
                #
                # this function needs to be called the 1st time to set up defaults
                # arguments:
                #    fname - ini file, where to store the settings
                #    ini_list - a dictionary list with the default ini declarations
                #
                variable ini
                variable ini_defaults

                # file declaration
                set ini(INI_FILE) [file join [GetTempDirectory] $fname]

                # default settings declaration
                set ini_defaults {}

                foreach item $ini_list {
                        set name [lindex $item 0]
                        set value [lindex $item 1]
                        lappend ini_defaults [list $name $value]
                }
        }

        proc savesettings {ini_list} {
                #
                # save settings to file
                # ini_list - dictionary list containing {name value} sub-lists
                #            for each setting declaration
                #
                variable ini
                variable ini_defaults

                # file must at least be there / erase previous file using "w" option
                set buff [open $ini(INI_FILE) "w"]
                close $buff
                
                set istream [::ini::open $ini(INI_FILE)]
                
                # comments:
                set section $ini(COMMENTS)
                ::ini::set $istream $section "DATE" \
                                [clock format [clock seconds] -format "%Y-%m-%d-%H:%M"]
                ::ini::comment $istream $section "DATE" \
                                "This file was generated by: savedefault, User: $::tcl_platform(user)" \
                                "*** DO NOT MODIFY ***"
                
                set section $ini(DEFAULT_SECTION)
                foreach item $ini_list {
                        ::ini::set $istream $section [lindex $item 0] [lindex $item 1]
                }
                
                ::ini::commit $istream
                ::ini::close $istream

                return
        }

        
        proc readsettings {ini_array} {
                #
                # read settings from the ini file
                #
                # ini_array - array handling all values retrieved from the configuration file,
                #             or otherwise use the given default values (ini_defaults)
                #
                # Hint: When reading in the option values from the configuration file,
                # each option is validated against the corresponding default option
                # if the name is not matching, it is not taken into account
                #
                upvar $ini_array arr
                variable ini
                variable ini_defaults
                
                array set arr {}
                
                if {![file exists $ini(INI_FILE)] ||
                        ![file readable $ini(INI_FILE)] } {
                        
                        foreach item $ini_defaults {
                                set name [lindex $item 0]
                                set val  [lindex $item 1]
                                set arr($name) $val
                        }
                        return
                }
                
                set istream  [::ini::open $ini(INI_FILE)]
                set sections [::ini::sections $istream]
                set cnt 0

                foreach s $sections {
                        if {[string trim $s] == $ini(DEFAULT_SECTION)} {
                                
                                # retrieve all available key values ...
                                #   (returns a list of all they key names in the
                                #    section and file specified)
                                set keyval_lst [::ini::keys $istream $s]
                                
                                foreach item $ini_defaults {
                                        set sec_name [string trim [lindex $item 0]]
                                        
                                        # puts "--> $keyval_lst :: $sec_name :: [lsearch $keyval_lst $sec_name] <--"
                                        if {[set idx [lsearch $keyval_lst $sec_name]] != -1} {
                                                
                                                set keyval    [lindex $keyval_lst $idx]
                                                set sec_value [::ini::value $istream $s $keyval]
                                                #  puts "==> $keyval :: $sec_value"
                                                
                                                if {$sec_value == ""} {
                                                        # set default value, just in case the value is empty (?!...)
                                                        set arr($keyval) [lindex $item end]
                                                } else {
                                                        set arr($keyval) $sec_value
                                                }
                                                incr cnt
                                        }
                                }
                        }
                }

                ::ini::close $istream
                
                # one more comparison:
                if {$cnt != [llength $ini_defaults]} {
                        array set arr {}

                        foreach item $ini_defaults {
                                set name [lindex $item 0]
                                set arr($name) [lindex $item 1]
                        }
                }
                
                return
        }
}

Demo Code:

# -----------------------------------------------------------------------------
# savedefault_test.tcl ---
# -----------------------------------------------------------------------------

# where to find required tcllib packages:
set dir [file dirname [info script]]

lappend auto_path [file join $dir "."]
lappend auto_path [file join $dir "../tcllib"]

package require fileutil
package require inifile
package require savedefault


# test starts here ...

package require Tk
catch {console show}


# initializing default settings
                
set ini_defaults {
        {"geometry" "950x570+140+170"}
        {"fontsize" 10}
        {"paneorient" "vertical"}
        {"test" ""}
        {"test1" ""}
        {"test2" ""}
}


::savedefault::savedefault \
        "SaveDefault_Test.ini" \
        $ini_defaults

# overwrite existing array names (if any)
#    - with the values retrieved from the configuration file
#    - otherwise use the given default values (ini_defaults)

array set this_array {}

::savedefault::readsettings this_array
parray this_array


puts "------------------------"

set ini_list {}
lappend ini_list [list "geometry" "800x500"]
lappend ini_list [list "fontsize" 12]
lappend ini_list [list "paneorient" horizontal]
lappend ini_list [list "test" test]
lappend ini_list [list "test1" 123]
lappend ini_list [list "test2" XYZ]

::savedefault::savesettings $ini_list

::savedefault::readsettings this_array
parray this_array