/* --------------------------------------------------------------- */ /* Get or set a value in an .ini file */ /* --------------------------------------------------------------- */ /* */ /* Copyright (c) Mike Cowlishaw, 2006-2013. All rights reserved. */ /* Parts Copyright (c) IBM, 2006-2010. */ /* */ /* Permission to use, copy, modify, and distribute this software */ /* for any non-commercial purpose without fee is hereby granted, */ /* provided that the above copyright notice and this permission */ /* notice appear in all copies, and that notice and the date of */ /* any modifications be added to the software. */ /* */ /* This software is provided "as is". No warranties, whether */ /* express, implied, or statutory, including, but not limited to, */ /* implied warranties of merchantability and fitness for a */ /* particular purpose apply to this software. The author shall */ /* not, in any circumstances, be liable for special, incidental, */ /* or consequential damages, for any reason whatsoever. */ /* */ /* --------------------------------------------------------------- */ /* Arguments: */ /* */ /* Arg1 is the .ini file specification (suggest fully qualified) */ /* Arg2 is the group name for the key (e.g., 'Settings') */ /* Arg3 is the key for the value (e.g., 'Users') */ /* Arg4, if present, is a value to set */ /* */ /* returns the value of the key (before any set) unless error */ /* */ /* This is a subset of sysini function, enough to retrieve and */ /* set simple keys. Group and key names are case-insensite, */ /* and are stored with first-use case. */ /* */ /* A value of '' deletes a key (undocumented sysini feature); it */ /* is not an error if the key did not exist in this case */ /* */ /* 'ERROR:' is returned if any error (e.g., key does not exist or */ /* cannot be set). If the get got 'ERROR:' but the set succeeded */ /* it is assumed this is first-create, and '' is returned. */ /* Duplicate group or keyname in group is also an error if a value */ /* is being set. */ /* */ /* Uses exists.rex */ /* --------------------------------------------------------------- */ -- Shared reading is permitted -- Use of this to change values in a multi-threaded or multi-tasking -- application shall protect all reads/writes with a mutex verbose=1 signal on syntax signal on novalue parse arg file, group ., key ., newvalue -- say '>ini>' file':'group':'key':'newvalue set=arg(4, 'e') -- 1 if value provided if \exists(file) then do if \set then do -- file not found for Get -- if verbose then say 'ini file not found:' file group key newvalue return 'ERROR:' end call charout file, '' if result then do -- failed to write call lineout file -- close if verbose then say 'ini file failed to write:' file group key newvalue return 'ERROR:' end call lineout file -- close end /* File format might be something like this: ; semicolon as first non-blank is a comment line [History] Created=20060319_145516 [Preferences] UserStyle=0 UserNoBar=0 UserCompact=1 UserNoLast=0 */ ugroup=translate(group) ukey=translate(key) -- read the file, set up linked-list pointers, and note line of key -- if exists, also line of key's [group] if exists call stream file, 'c', 'open read shared' groupline=0 -- line # of key's [group] keyline=0 -- line # of key keyvalue='' -- current value of key ingroup=0 -- 1 while we are parsing keys in 'our' group num=0 link.=0 -- linked list from link.0 dups=0 -- 1 if duplicates spotted do while lines(file)>0 link.num=num+1 -- linked list 'pointer' num=num+1 line.num=linein(file) parse var line.num word1 . if word1='' then iterate -- blank if left(word1, 1)=';' then iterate -- comment -- say '-->' num':' line.num parse var line.num pre '[' gname . ']' . -- ignore junk on end if pre='' & gname\='' then do -- have group line ingroup=0 if translate(gname)=ugroup then do if groupline\=0 then dups=1 -- duplicate group groupline=num ingroup=1 end iterate end if \ingroup then iterate -- uninteresting line/group -- could be 'our' key=value parse var line.num kname'='kvalue if translate(kname)\=ukey then iterate -- not us if keyline\=0 then dups=1 -- duplicate key keyline=num keyname=kname keyvalue=kvalue if \set then leave -- have all we need -- NB dups only checked if set end call stream file, 'c', 'close' call lineout file if dups then do -- had duplicate if verbose then say 'ini had duplicate:' file group key newvalue return 'ERROR:' end -- say 'Read='num 'keyline='keyline if \set then do -- retrieving only if keyline=0 then do -- not found -- if verbose then say 'ini key not found:' file group key return 'ERROR:' end return keyvalue end -- set=1; a change is requested newvalue=strip(newvalue) if newvalue='' then do -- deleting if keyline=0 then return keyvalue -- didn't exist, so no change -- drop through to delete end else if keyvalue==newvalue then -- no change, so no write needed return keyvalue -- .. if keyline\=0 then do -- easy update if newvalue='' then do -- delete m=keyline-1 link.m=link.keyline -- close up end else do -- modify line.keyline=keyname'='newvalue end end else do -- new key if groupline=0 then do -- new group, too if num>0 then do -- add blank line link.num=num+1 num=num+1 line.num=' ' end link.num=num+1 num=num+1 line.num='['group']' groupline=num end -- add new group -- add key line after group line num=num+1 line.num=key'='newvalue next=link.groupline link.groupline=num -- chain it in link.num=next end -- rewrite the file; there's no generic swap/rename so just have to -- hope no crash while this is done... -- say '-----' file ' -----' p=lastpos('\', file) if p=0 then p=lastpos('/', file) if p=0 then path='' else path=left(file, p) tempfile=path'ini.tmp' call sysfiledelete tempfile rc=result if rc\=0 & rc\=2 then do -- 2 = not found (expected) if verbose then say 'ini error deleting:' tempfile '[rc='rc']' end n=link.0 bad=0 do while n>0 -- say n':' line.n call lineout tempfile, line.n bad=bad|result n=link.n end call stream tempfile, 'c', 'flush' call lineout tempfile if bad then do -- uh-uh if verbose then say 'ini error writing:' tempfile group key newvalue return 'ERROR:' end -- was OK -- delete and rename also ensures capitalization is correct, for ooRexx 4.00 -- [allow retry on the delete, sometimes get rc=5 Busy] do i=1 to 3 rc=sysfiledelete(file) if rc=0 then leave if rc\=5 | i=3 then do if verbose then say 'ini error deleting:' file '[rc='rc']' return 'ERROR:' end if verbose then say 'ini retry deleting:' file call syssleep 0.1 end name=filespec('n', file) address cmd 'rename "'tempfile'" "'name'"' if rc\=0 then do if verbose then say 'ini error renaming:' tempfile 'to' name return 'ERROR:' end return keyvalue /* --------------------------------------------------------------- */ /* Error handlers */ /* --------------------------------------------------------------- */ syntax: say 'Rexx error' rc 'on line' sigl':' space(sourceline(sigl)) say errortext(rc) exit -1 novalue: say 'Variable' condition('D') 'raised NOVALUE at line' sigl':' say ' *-*' space(sourceline(sigl)) exit -1