Function: IniRead()

Post your working scripts, libraries and tools
User avatar
megnatar
Posts: 83
Joined: 27 Oct 2014, 20:49
GitHub: Megnatar
Location: The Netherlands

Function: IniRead()

07 Mar 2019, 09:18

This function can replace the IniRead command native to Autohotkey.
It will read all the variables stored in a ini and makes them known to the calling script by creating them under fly.
Useful because it can save many lines in a script.

Might be useful to others...

Code: Select all

ReadIni(InputFile) {                                                ; InputFile should be full path or var containing path to the ini. ReadIni("z:\IniFile.ini")
    local TmpVar                                                    ; Set TmpVar local to the function. All variables created by references name
                                                                    ; will be global to the calling script.
    Loop, parse, % FileOpen(InputFile, 0).read(), `n, `r            ; Use FileOpen() and parse the fileObject directly in to the loop using read() method.
    {                                                               ; A_LoopField will contain the 'current' line in the ini file.
    
        if (((InStr(A_LoopField, "[")) = 1 ? 1) || ((InStr(A_LoopField, "`;")) = 1 ? 1) || !A_LoopField) ; Comments and Sections Should start on the first
            Continue                                                                                     ; position on each line found in the ini file!
            
        TmpVar := InStr(SubStr(A_LoopField, 1, InStr(A_LoopField, "=")-1), ";" ; Assign variable name to TmpVar. The name will be referenced below.
        %TmpVar% := SubStr(A_LoopField, InStr(A_LoopField, "=")+1)             ; Assign the value for the variable name, referenced in %TmpVar%.
    }                                                                          ;  If TmpVar contains string GuiX, then the script will read '%TmpVar% := ...'
}                                                                              ;  as it is written 'GuiX := ...'

; by Megnatar.
Last edited by megnatar on 07 Mar 2019, 13:57, edited 5 times in total.
swagfag
Posts: 2848
Joined: 11 Jan 2017, 17:59

Re: Function: IniRead()

07 Mar 2019, 10:02

ignoring the fact that dumping vars into the global(or whatever scope, for that matter) scope probably isnt the way to go about this, how are these variables ure creating made accessible outside the function?
or are u suggesting people write their scripts inside the function, after the loop?
User avatar
megnatar
Posts: 83
Joined: 27 Oct 2014, 20:49
GitHub: Megnatar
Location: The Netherlands

Re: Function: IniRead()

07 Mar 2019, 10:54

because of the variable reference - %TempVar% - that is used in the loop.

And because of this:
Functions Local
•A dynamic variable reference may resolve to an existing global variable if no local variable exists by that name.

Here test it!

Code: Select all

/* 
[ScriptGui]
; comment should be here
SomeObject=MyObj[]
Gui_X=387
Gui_Y=144
Gui_W=160
Gui_H=28
[ScriptVars]
Tabnr=1
ClientApp=Q:\FileIsHere\ThisFile.exe
Folder=Q:\Some\Path\To\Here

*/
#NoEnv

ReadIni(A_ScriptFullPath)
MsgBox % ""
. "Gui_X = " Gui_X "`n"
. "Gui_Y = " Gui_Y "`n"
. "Gui_W = " Gui_W "`n"
. "Gui_H = " Gui_H "`n"
. "Tabnr = " Tabnr "`n"
. "ClientApp = " ClientApp "`n"
. "Folder = " Folder "`n"
. "SomeObject = " SomeObject "`n"
ExitApp

ReadIni(InputFile) {
    local TmpVar                                                   

    Loop, parse, % FileOpen(InputFile, 0).read(), `n, `r "/*"
    {
        if (((InStr(A_LoopField, "[")) = 1 ? 1) || ((InStr(A_LoopField, "`;")) = 1 ? 1) || !A_LoopField)
            Continue
        If (A_Index = 13)
            Break
            
        TmpVar := SubStr(A_LoopField, 1, InStr(A_LoopField, "=")-1)
        %TmpVar% := SubStr(A_LoopField, InStr(A_LoopField, "=")+1) 
    }                                                              
}
Last edited by megnatar on 07 Mar 2019, 13:13, edited 3 times in total.
just me
Posts: 6464
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: Function: IniRead()

07 Mar 2019, 11:15

INI-files are usually divided into sections for a reason.

What will your function do with

Code: Select all

[Edit1]
X=10
Y=10
W=100
H=20
[ListView1]
X=10
Y=40
W=300
H=100
?

And what happens with comment lines?
swagfag
Posts: 2848
Joined: 11 Jan 2017, 17:59

Re: Function: IniRead()

07 Mar 2019, 11:19

A dynamic variable reference may resolve to an existing global variable if no local variable exists by that name.
everyday we stray further from god's light
User avatar
megnatar
Posts: 83
Joined: 27 Oct 2014, 20:49
GitHub: Megnatar
Location: The Netherlands

Re: Function: IniRead()

07 Mar 2019, 12:14

just me wrote:
07 Mar 2019, 11:15
INI-files are usually divided into sections for a reason.

What will your function do with

Code: Select all

[Edit1]
X=10
Y=10
W=100
H=20
[ListView1]
X=10
Y=40
W=300
H=100
?

And what happens with comment lines?
Yeah it's true, the function can only be successful when the file is read from path. I could filter that though, let it read the contant from var.
Sections will be ignored, it imports the whole ini. Sections wont exist as variables..

Comments?
Good point. They will probably be stored as part of, or as a variable depending on where the comment is placed.
I will fix that!
toralf
Posts: 607
Joined: 27 Apr 2014, 21:08
Location: Germany

Re: Function: IniRead()

07 Mar 2019, 12:33

You are checking for a section by looking for a [. But the [ could be the value of a var. it would be better if you check if [ is the first character of that line. And maybe also check if there is a closing ].
ciao
toralf
User avatar
megnatar
Posts: 83
Joined: 27 Oct 2014, 20:49
GitHub: Megnatar
Location: The Netherlands

Re: Function: IniRead()

07 Mar 2019, 13:14

toralf wrote:
07 Mar 2019, 12:33
You are checking for a section by looking for a [. But the [ could be the value of a var. it would be better if you check if [ is the first character of that line. And maybe also check if there is a closing ].
Fixed!
Thanx for the good comment!
You have any more "what if's" available?
just me
Posts: 6464
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: Function: IniRead()

07 Mar 2019, 13:22

If you look a bit closer at my example you see that the keys X, Y, W, and H will be read twice with partially different values. AFAICS, only the second value will be available after the function call.
User avatar
megnatar
Posts: 83
Joined: 27 Oct 2014, 20:49
GitHub: Megnatar
Location: The Netherlands

Re: Function: IniRead()

19 Mar 2019, 12:30

just me wrote:
07 Mar 2019, 13:22
If you look a bit closer at my example you see that the keys X, Y, W, and H will be read twice with partially different values. AFAICS, only the second value will be available after the function call.

Yes i see that local variables are currently not supported. Although, the only situation I can think of when only loading one section. Is when a other function ore class uses IniRead. I don't know about others but I rarely read ini's from there. But for the sake of compatibility I will make it so that a array is returned by IniRead when loading a single section. When I have time.....
nou
Posts: 25
Joined: 24 Feb 2019, 21:21

Re: Function: IniRead()

20 Mar 2019, 13:22

What about this code? Run it, and then call the variables by doing: section.key

note: For some reason, when I make an associative array in the function using

Code: Select all

%sectionList%[key] := value
I'm able to find the variable outside the function. So if my ini file was like:

Code: Select all

[Edit1]
X=10 ; comments on side? 
Y=10
W=100
H=20	; randomly random comments. 
[ListView1]
X=10
Y=40
; more pseudo comments? 
W=300
H=100




M = 34





L= 85
I can find the value OUTSIDE the function by just doing this:

Code: Select all

iniRead("test.ini")
MsgBox % Listview1.W
Here's the code to the function I use

Code: Select all

; Function here: 
IniRead(filename)
{
; ************************* Read File *************************

	iniFile := FileOpen(filename, "r")
	ini := iniFile.read()
	ini.close()

; ********************* String Replace ************************

	; gives every comment it's own new line
	ini := StrReplace(ini, ";", "`r`n;")

	; removes all lines that starts with ; 
	ini := RegExReplace(ini, ";.*")

	; replaces all (`r`n) with (`n)
	ini := StrReplace(ini, "`r`n", "`n")

	; removes all doublespacings
	outCount := 1
	While (outCount != 0)
		ini := StrReplace(ini, "`n`n", "`n", OutCount)

; *************************************************************

	; splits by the `n
	arrayIni := StrSplit(ini, "`n")

	; makes an array to keep track of our Sections
	SectionList := []

; ********************* Getting the Info **********************

	; enumerate array + variables
	for key, value in arrayIni
		{
			; Regex search for starting "[" and ending "]"
			RegExMatch(trim(value), "^\[.*?]$", checkSection)
			if (trim(value) = checkSection)
			{
				; remove the first and last characters, the brackets. 
				section := substr(checkSection, 2, -1)

				; adds this into our list of Sections
				SectionList.push(section)

				; if found, create a new array with that as an array name. 
				%section% := {}
			}
			else 
				{
					; splits the line by the "=" sign. 
					newKey := StrSplit(value, "=")

					; stores the left + right values into our array, respectively.
					%section%[TRIM(newKey.1)] := TRIM(newKey.2)
				}

		}
; *************************************************************

	;~ ; check our arrays
	;~ 		str := ""

	;~ ; check our array
	;~ 	for index, Section in SectionList
	;~ 	{
	;~ 		; checks the name of the section
	;~ 		str .= Index ": " Section "`n"

	;~ 		; checks each section's key + value
	;~ 		for key, value in %Section%
	;~ 			str .= key ": " value "`n"

	;~ 		str .= "`n"
	;~ 	}

	;~ 	MsgBox % str

	return SectionList
}
Last edited by nou on 13 Jun 2019, 01:18, edited 1 time in total.
lblb
Posts: 129
Joined: 30 Sep 2013, 11:31

Re: Function: IniRead()

21 Mar 2019, 01:25

Reminds me of the conceptually similar globalsFromIni() from the old forum that creates global variables:

https://autohotkey.com/board/topic/25515-globalsfromini-creates-globals-from-an-ini-file/
User avatar
megnatar
Posts: 83
Joined: 27 Oct 2014, 20:49
GitHub: Megnatar
Location: The Netherlands

Re: Function: IniRead()

22 Mar 2019, 10:35

nou wrote:
20 Mar 2019, 13:22
What about this code? Run it, and then call the variables by doing: section.key

note: For some reason, when I make an associative array in the function using

Code: Select all

%sectionList%[key] := value
I'm able to find the variable outside the function. So
............
That's a good idea, to reference the section as 'attached variable'. why didn't I think of that. Honestly I was not to fond of the idea of returning a array. After all, the whole idea was to quickly load some variables from a ini. I will change it in due time...
nou
Posts: 25
Joined: 24 Feb 2019, 21:21

Re: Function: IniRead()

22 Mar 2019, 11:58

XD Don't think of it as "returning it as an array". think of it as a "Section.key" variable, as oppose to a "sectionkey" variable. You know, a variable with an extra period in between. Otherwise, without any separator between the section and key, you might run into issues when you do something like this:

Code: Select all

[Setting]
s1=25

[Settings]
1=3
freakkk
Posts: 14
Joined: 21 Sep 2014, 20:14

Re: Function: IniRead()

26 Mar 2019, 01:46

Here is a simple ini file class I used to use before I started to upgrade my data storage to json:

Code: Select all

  ;-- read values of ini file into obj
  oIni := new IniFileObj( A_ScriptDir "\MyIniFile1.ini" )

  ;-- If file doesn't exist, create sections and add keys to it
  If !FileExist( A_ScriptDir "\MyIniFile1.ini" ){
    oIni.section1 := {}
    oIni.section1.key1 := "section 1, key 1"
    oIni.section1.key2 := "section 1, key 2"
    oIni.section2 := {}
    oIni.section2.key1 := "section 2, key 1"
    oIni.section2.key2 := "section 2, key 2"
    oIni.Save()    ; save to file
  }

  ; MsgBox, % oIni.section1.key1
  MsgBox, % Obj2String( oIni )   ;- display all values

  ;-- Create ini file obj, set to save settings on exit
  oIni := new IniFileObj( A_ScriptDir "\MyIniFile2.ini", true )
  If !FileExist(A_ScriptDir "\MyIniFile2.ini")
    oIni.section3 := { key1: "this is section three, key one.." }

ExitApp


class IniFileObj {
  __New( filePath, saveOnExit:=false ){
    this.filePath := filePath
    while thisLine := StrSplit( FileOpen(filePath,"r").Read(), "`n", "`r" )[ A_Index ]
      If RegExMatch(thisLine,"((\[(?<Section>.*)\])|(?<Key>\w+)=(?<Value>.*)$)",_)
        _Key ? this[thisSection].Insert(_key,_value) : "", _Section ? (thisSection:=_Section, this[thisSection]:=[] )
    saveOnExit ? OnExit( ObjBindMethod(this, "Save") )      
  }
  Save(){
    For section, objSectionKeys in this
      For key, value in objSectionKeys
        IniWrite, % value, % this.filePath, % section, % key
  }
}

Obj2String(o,ind="",indChar="     ")  {
  For i, v in o
    l .= (l ? "`r`n" : "") ind i " - " v, l .= IsObject(v) ? "`r`n" %A_ThisFunc%(v, ind indChar, indChar) : ""
  return l
}
toralf
Posts: 607
Joined: 27 Apr 2014, 21:08
Location: Germany

Re: Function: IniRead()

26 May 2019, 15:04

Dear freakkk,

Thanks for posting the class.

If I understand correctly the class can’t have a section called filepath, is that correct?

Do you also have a Version that supports default values? E.g. by providing an default {section:{key:value}} object for the __New() method.

Is the a reason you use iniwrite on save() instead of filewrite or fileappend?
ciao
toralf
toralf
Posts: 607
Joined: 27 Apr 2014, 21:08
Location: Germany

Re: Function: IniRead()

26 May 2019, 15:43

Three more questions
Does the while loop read the file completely each iteration? That seams to be an overdose of doing almost everything on one line
You regex does not handle comments in the ini, is that correct?
And the while loop will stop on empty lines...

I do not want to criticize you. I appreciate what you have created and that you share it. I consider to use it myself. But before I want to understand it’s limits or get into a discussion on how these could be overcome.
ciao
toralf
toralf
Posts: 607
Joined: 27 Apr 2014, 21:08
Location: Germany

Re: Function: IniRead()

27 May 2019, 06:46

Hi there,

This would be my mod of the class.
Basically, I extended it with handling default values, changed the While to a For Loop and modified the RegEx to handle comments correctly. Thus, I hope it is more robust against human interaction with the ini file.

Code: Select all

  IniFileName := A_ScriptDir "\MyIniFile1.ini"
  If !FileExist( IniFileName ){
    Content = 
(
[Section1]
Key1=Section 1, Key 1
Key2=Section 1, Key 2
;comment with [Section Name]
;[Commented Section]
  ;  comment with [Commented Section Name]
  ;  [Commented Section indented]
   [Section2]
Key1=Section 2, Key 1
Key2=Section 2, Key 2
Key3=Text with = as a test
;Key4=a comment
  ;  Key5=a comment indented
  ;  comment = with equal sign
Key6 =  Spaces around =
  Key7=Key indented
)
  FileAppend, %Content%, %IniFileName%
  }

  DefaultValues := {Section1:{Key2: "Value exists in INI"
                            , Key4: "Value from Default"}
                 , "Section_test":{Key1: A_ScriptDir
                                 , 4: A_USerName }}

  oIni := new IniFileObj( IniFileName, DefaultValues )
  MsgBox, % Obj2String( oIni )   ;- display all values

  oIni := new IniFileObj( A_ScriptDir "\MyIniFile2.ini", ,true )
  If !FileExist(A_ScriptDir "\MyIniFile2.ini")
    oIni.section3 := { key1: "this is section three, key one.." }
  MsgBox, % Obj2String( oIni )   ;- display all values
  ExitApp
  
class IniFileObj {
  __New( filePath, DefaultValues := "", saveOnExit:=false ){
    For Section, KeysAndValues in DefaultValues {
      this[Section]:=[]
      For key, value in KeysAndValues
        this[Section].Insert(key,value)
    }
    this.filePath := filePath
    For i, thisLine in StrSplit( FileOpen(filePath,"r").Read(), "`n", "`r" ) 
      If RegExMatch(thisLine,"^\s*(?<!;)((\[(?<Section>.*?)\])|(?<Key>\w+)\s*=(?<Value>.*)$)",_)
        _Key ? this[thisSection].Insert(_key,_value) : "", _Section ? (thisSection:=_Section, !this.Haskey(thisSection) ? this[thisSection]:=[]) 
    saveOnExit ? OnExit( ObjBindMethod(this, "Save") ) 
  }
  Save(){
    For Section, KeysAndValues in this
      For key, value in KeysAndValues
        IniWrite, % value, % this.filePath, % Section, % key
  }
}

Obj2String(o,ind="",indChar="     ")  {
  For i, v in o
    l .= (l ? "`r`n" : "") ind i " - " v, l .= IsObject(v) ? "`r`n" %A_ThisFunc%(v, ind indChar, indChar) : ""
  return l
}
ciao
toralf
Adventurer
Posts: 1
Joined: 18 Apr 2019, 06:24

Re: Function: IniRead()

17 Jul 2019, 17:42

toralf wrote:
27 May 2019, 06:46
Hi there,

This would be my mod of the class.
Basically, I extended it with handling default values, changed the While to a For Loop and modified the RegEx to handle comments correctly. Thus, I hope it is more robust against human interaction with the ini file.

Code: Select all

  IniFileName := A_ScriptDir "\MyIniFile1.ini"
  If !FileExist( IniFileName ){
    Content = 
(
[Section1]
Key1=Section 1, Key 1
Key2=Section 1, Key 2
;comment with [Section Name]
;[Commented Section]
  ;  comment with [Commented Section Name]
  ;  [Commented Section indented]
   [Section2]
Key1=Section 2, Key 1
Key2=Section 2, Key 2
Key3=Text with = as a test
;Key4=a comment
  ;  Key5=a comment indented
  ;  comment = with equal sign
Key6 =  Spaces around =
  Key7=Key indented
)
  FileAppend, %Content%, %IniFileName%
  }

  DefaultValues := {Section1:{Key2: "Value exists in INI"
                            , Key4: "Value from Default"}
                 , "Section_test":{Key1: A_ScriptDir
                                 , 4: A_USerName }}

  oIni := new IniFileObj( IniFileName, DefaultValues )
  MsgBox, % Obj2String( oIni )   ;- display all values

  oIni := new IniFileObj( A_ScriptDir "\MyIniFile2.ini", ,true )
  If !FileExist(A_ScriptDir "\MyIniFile2.ini")
    oIni.section3 := { key1: "this is section three, key one.." }
  MsgBox, % Obj2String( oIni )   ;- display all values
  ExitApp
  
class IniFileObj {
  __New( filePath, DefaultValues := "", saveOnExit:=false ){
    For Section, KeysAndValues in DefaultValues {
      this[Section]:=[]
      For key, value in KeysAndValues
        this[Section].Insert(key,value)
    }
    this.filePath := filePath
    For i, thisLine in StrSplit( FileOpen(filePath,"r").Read(), "`n", "`r" ) 
      If RegExMatch(thisLine,"^\s*(?<!;)((\[(?<Section>.*?)\])|(?<Key>\w+)\s*=(?<Value>.*)$)",_)
        _Key ? this[thisSection].Insert(_key,_value) : "", _Section ? (thisSection:=_Section, !this.Haskey(thisSection) ? this[thisSection]:=[]) 
    saveOnExit ? OnExit( ObjBindMethod(this, "Save") ) 
  }
  Save(){
    For Section, KeysAndValues in this
      For key, value in KeysAndValues
        IniWrite, % value, % this.filePath, % Section, % key
  }
}

Obj2String(o,ind="",indChar="     ")  {
  For i, v in o
    l .= (l ? "`r`n" : "") ind i " - " v, l .= IsObject(v) ? "`r`n" %A_ThisFunc%(v, ind indChar, indChar) : ""
  return l
}
Spaces in the key name break it, e.g. 'key1' works but 'key 1' doesn't.

Cap'n Odin on Discord showed me how to fix this.

Code: Select all

;Code to replace:
If RegExMatch(thisLine,"^\s*(?<!;)((\[(?<Section>.*?)\])|(?<Key>\w+)\s*=(?<Value>.*)$)",_)

;Option #1 - This allows letters, numbers, and spaces to match:
If RegExMatch(thisLine,"^\s*(?<!;)((\[(?<Section>.*?)\])|(?<Key>(\w| )+?)\s*=(?<Value>.*)$)",_)

;Option #2 - This allows anything that isn't the equal sign to match:
If RegExMatch(thisLine,"^\s*(?<!;)((\[(?<Section>.*?)\])|(?<Key>.+?)\s*=(?<Value>.*)$)",_)

Return to “Scripts and Functions”

Who is online

Users browsing this forum: SpeedMaster and 41 guests