INI Class Topic is solved

Get help with using AutoHotkey (v2 or newer) and its commands and hotkeys
AHK_user
Posts: 515
Joined: 04 Dec 2015, 14:52
Location: Belgium

INI Class

Post by AHK_user » 04 Mar 2023, 06:32

I tried to create an Ini class to read write even easier from an Ini file.

Now I can read write by using INI[Section,Key].
Personally I would prefer to use properties like the syntax INI.Section.Key

Is this possible?

Code: Select all

class Ini {
	__New(FileName){
		this.FileName := FileName
	}
    __Item[Section, Key, Default:=""] {
        get {
			return IniRead(this.Filename, Section, Key, Default)
		}
        set {
			return IniWrite(Value, this.Filename, Section, Key)
		}
    }
}

Ini1 := Ini(RegExReplace(A_ScriptName, "(.*)\.(.*)", "$11.ini"))
Ini1.GetOwnPropDesc(Name)
ini1["pos","W"] := "100"

MsgBox(ini1["pos","W"])

teadrinker
Posts: 4326
Joined: 29 Mar 2015, 09:41
Contact:

Re: INI Class  Topic is solved

Post by teadrinker » 04 Mar 2023, 18:28

PoC:

Code: Select all

MyIni := Ini('test.ini')
MyIni.section.key := 'value'
MsgBox MyIni.section.key
MsgBox MyIni.section.key2['default value']
MyIni.section.key2 := 'value2'
MsgBox MyIni.section.key2['default value']

class Ini {
    __New(iniFilePath) => this.__path__ := iniFilePath
    __Get(name, *)     => Ini.Section(this.__path__, name)

    class Section {
        __New(iniFilePath, sectionName) {
            this.DefineProp('__path__', { get: (*) => iniFilePath })
            this.DefineProp('__name__', { get: (*) => sectionName })
        }
        __Get(keyName, default) => IniRead(this.__path__, this.__name__, keyName, default.Has(1) ? default[1] : '')
        __Set(keyName,_, value) => IniWrite(value, this.__path__, this.__name__, keyName)
    }
}

AHK_user
Posts: 515
Joined: 04 Dec 2015, 14:52
Location: Belgium

Re: INI Class

Post by AHK_user » 06 Mar 2023, 13:18

@teadrinker : This was exactly what I was looking for, Thanks a lot. :dance:

It is really beautifully written. :bravo:
Maybe a nomination to put in the V2 documentation?

I will probably also add some extra methods to load and save objects to the ini file, in case speed is of the essence and we do not want to read and write it in.

AHK_user
Posts: 515
Joined: 04 Dec 2015, 14:52
Location: Belgium

Re: INI Class

Post by AHK_user » 06 Mar 2023, 15:31

Here is my last version, that has some extra interesting features:
- Added ReadObject and WriteObject method to read and write objects (section or ini) (in case you want to read/Write all the variables at once)
- Accepts now maps and arrays as variables
- Allows `n and `r to be in variables

For the maps, arrays and `n and `r, this class shoud be used to read and write it.

Feel free to give some advice, improvements or corrections to make it cleaner. I love to learn.
It looks and works already great, and I was missing some functionalities.

(I know Regex would be shorter to write, but choose to use SubStr as it is normally faster.)

Code: Select all

#Requires AutoHotkey v2.0-beta.3
#SingleInstance force

MyIni := Ini()

;Example writing a map
mTest := Map("first",55,"Second",66)
MyIni.Map.Test := mTest

;Example Writing an double object to an ini file
oTest := Object()
oTest.Pos := Object()
oTest.Pos.x := atest
oTest.Pos.y := 2
path := 'test.ini'
oResult := MyIni.WriteObject(oTest)

; Writing an object as a section
MyIni.Pos2.WriteObject(oTest.pos)

;Reading an object as a section
oPos := MyIni.Pos.ReadObject()
;MsgBox(oPos.x)

; Example reading and writing in the file
MyIni.section.key := 'value'
MsgBox(MyIni.section.key)
MsgBox(MyIni.section.key2['default value'])
MyIni.section.key2 := 'value2'
MsgBox(MyIni.section2.key2['default value'])

class Ini {
    __New(FileName:="") => this.__path__ := (FileName="") ? RegExReplace(A_ScriptName, "(.*\.)(.*)", "$1ini") : FileName
    __Get(sectionName, *) => Ini.Section(this.__path__, sectionName)

    class Section {
        __New(FileName, sectionName) {
            this.DefineProp('__path__', { get: (*) => FileName })
            this.DefineProp('__name__', { get: (*) => sectionName })
        }
        __Get(keyName, default) => this.StrToAhk(IniRead(this.__path__, this.__name__, keyName, default.Has(1) ? default[1] : ''))
        __Set(keyName,_, value) => IniWrite(this.AhkToStr(value), this.__path__, this.__name__, keyName)

        ; Read the section as an object
        ReadObject(){
            oResult := Object()
            For VariableLine in StrSplit(IniRead(this.__path__,this.__name__), "`n", "`r"){
                FoundPos := InStr(VariableLine, "=")
                KeyName := SubStr(VariableLine,1,FoundPos-1)
                oResult.%KeyName% := this.StrToAhk(SubStr(VariableLine,FoundPos+1))
            }
            return oResult
        }

        ; Write an object as a Section
        WriteObject(oSection){
            if !IsObject(oSection){
                throw ValueError("Object was required", -1, oSection)
            }
            For keyName, value in oSection.OwnProps(){
                IniWrite(this.AhkToStr(value), this.__path__, this.__name__, keyName)
            }
        }

        StrToAhk(var){
            var := StrReplace(StrReplace(var, "``r","`r"), "``n","`n")
            if (SubStr(var,1,6)="Array:"){
                return StrSplit(SubStr(var,7),"¢")
            } else if (SubStr(var,1,4)="Map:"){
                aTemp := StrSplit(SubStr(var,5),"¢")
                mVar := Map()
                for Value in aTemp{
                    aValue := StrSplit(Value,"¤")
                    mVar[aValue[1]] := aValue[2]
                }
                Return mVar
            }
            Return var
        }
        AhkToStr(var){

            result := ""
            if (Type(Var)="Array"){
                for Value in Var{
                    result .= (result="") ? "Array:" Value : "¢" Value
                }
            } else if (Type(Var)="Map"){
                for key, Value in Var{
                    result .= (result="") ? "Map:" key "¤" Value : "¢" key "¤" Value
                }
            } else{
                result := var
            }
            result := StrReplace(StrReplace(result, "`r","``r"), "`n","``n")
            Return result
        }
    }

    ; Write a double Object in the ini file
    WriteObject(oIni){
        if !IsObject(oIni){
            throw ValueError("Object was required", -1, oIni)
        }
        For SectionName, oSection in oIni.OwnProps()
        {
            if !IsObject(oSection){
                throw ValueError("Object of Objects was required", -1, oIni)
            }
            For keyName, value in oSection.OwnProps(){
                IniWrite(this.Section.AhkToStr(value), this.__path__, SectionName, keyName)
            }
        }
    }

    ; Read the ini file as a double Object
    ReadObject(){
        oResult := Object()
        For SectionName in StrSplit(IniRead(this.__path__), "`n", "`r"){
            oResult.%SectionName% := Object()
            For VariableLine in StrSplit(IniRead(this.__path__,SectionName), "`n", "`r"){
                FoundPos := InStr(VariableLine, "=")
                KeyName := SubStr(VariableLine,1,FoundPos-1)
                oResult.%SectionName%.%KeyName% := this.Section.StrToAhk(SubStr(VariableLine,FoundPos+1))
            }
        }
        return oResult
    }
}

teadrinker
Posts: 4326
Joined: 29 Mar 2015, 09:41
Contact:

Re: INI Class

Post by teadrinker » 06 Mar 2023, 16:28

@AHK_user
Interesting :wave:

AHK_user
Posts: 515
Joined: 04 Dec 2015, 14:52
Location: Belgium

Re: INI Class

Post by AHK_user » 07 Mar 2023, 06:26

teadrinker wrote:
06 Mar 2023, 16:28
@AHK_user
Interesting :wave:
@teadrinker Thanks, I actually have a potentional better script to save objects to a string with ahk v2 code
But I still need to figure out how to read the string back into autohotkey. :facepalm:
If you have any Idea, it would be very usefull. :think:

The code was posted here, but failed to get attention:
[V2] Object to ahk string and string to object


AHK_user
Posts: 515
Joined: 04 Dec 2015, 14:52
Location: Belgium

Re: INI Class

Post by AHK_user » 07 Mar 2023, 12:04

teadrinker wrote:
07 Mar 2023, 07:02
I only know ObjDump / ObjLoad - Object Backup.
I know, but this needs to be updated to V2, and the syntax is a little to abstract for me :crazy:

iPhilip
Posts: 817
Joined: 02 Oct 2013, 12:21

Re: INI Class

Post by iPhilip » 07 Mar 2023, 12:43

@teadrinker, Thank you for sharing that proof-of-concept class. I was wondering if one could achieve the same without creating a new instance each time a value is read from or written to the file. I am specifically thinking of this line:
teadrinker wrote:
04 Mar 2023, 18:28

Code: Select all

    __Get(name, *)     => Ini.Section(this.__path__, name)
After some testing, I came up with the following solution that leverages AutoHotkey's ability to add methods to primitive values, e.g. strings.

Code: Select all

class Ini {
   __New(Filename) {
      DefineProp := {}.DefineProp.Bind(''.base)
      DefineProp('__Get', {Call: (Section, Key, Default) => IniRead(Filename, Section, Key, Default.Has(1) ? Default[1] : UnSet)})
      DefineProp('__Set', {Call: (Section, Key, _, Value) => IniWrite(Value, Filename, Section, Key)})
   }
   __Get(Section, *) => Section
}
Note that the above throws an OSError if the default value is not specified and the key, section, or file is not found.
AHK_user wrote:
06 Mar 2023, 13:18
Maybe a nomination to put in the V2 documentation?
Unfortunately, I don't see a way to provide a complete set of Ini functions using this approach. An alternate way is to have a class as follows:

Code: Select all

class Ini {
   __New(Filename) => this.Filename := Filename
   Read(Section?, Key?, Default?) => IniRead(this.Filename, Section ?? UnSet, Key ?? UnSet, Default ?? UnSet)
   Write(Value, Section, Key?) => IniWrite(Value, this.Filename, Section, Key ?? UnSet)
   Delete(Section, Key?) => IniDelete(this.Filename, Section, Key ?? UnSet)
}
Unfortunately, it doesn't use the property-like syntax you mentioned in your original post.

Edit: Simplified class.
Last edited by iPhilip on 22 Mar 2023, 18:23, edited 3 times in total.
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)

teadrinker
Posts: 4326
Joined: 29 Mar 2015, 09:41
Contact:

Re: INI Class

Post by teadrinker » 07 Mar 2023, 13:24

@iPhilip
Wow, great, thanks!
iPhilip wrote: I came up with the following solution that leverages AutoHotkey's ability to add methods to primitive values
I've never used this before, interesting solution!

teadrinker
Posts: 4326
Joined: 29 Mar 2015, 09:41
Contact:

Re: INI Class

Post by teadrinker » 07 Mar 2023, 19:41

@iPhilip
But you need to take into account that in this case sections cannot be called "read" and "write".

iPhilip
Posts: 817
Joined: 02 Oct 2013, 12:21

Re: INI Class

Post by iPhilip » 07 Mar 2023, 22:51

teadrinker wrote:
07 Mar 2023, 19:41
@iPhilip
But you need to take into account that in this case sections cannot be called "read" and "write".
@teadrinker, Thank you for noticing that. I edited my post to simplify the class and remove that restriction. Still, both your and my class have the limitation that the sections cannot be called "__New" or "__Get" but that seems like a reasonable compromise to me. Would you agree?
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)

teadrinker
Posts: 4326
Joined: 29 Mar 2015, 09:41
Contact:

Re: INI Class

Post by teadrinker » 08 Mar 2023, 08:18

Yep, that's why I used names like __path__. :)

ntepa
Posts: 427
Joined: 19 Oct 2022, 20:52

Re: INI Class

Post by ntepa » 21 Mar 2023, 02:08

It's possible to have section names like "__New" or "__Get" if you define both get and call properties:

Code: Select all

MyIni := Ini('test.ini')

MyIni.section.key := 'value'
MsgBox MyIni.section.key
MsgBox MyIni.section.key2['default value']
MyIni.section.key2 := 'value2'
MsgBox MyIni.section.key2['default value']

MyIni.__New.__New := 'asdf'
MyIni.__Get.__Get := 'test'
MyIni.section.key := 'value'
MyIni.Path.test := 'hello'

MsgBox MyIni.__New.__New
MsgBox MyIni.__Get.__Set['default']
MyIni.__Get.__Set := 'hello world'
MsgBox MyIni.__Get.__Set['default']

class Ini {
    __New => Ini.Section(this.Path(), '__New')
    __New(iniFilePath) {
        this.DefineProp('Path', {
            Call:(*)=>iniFilePath,
            Get:(*)=>Ini.Section(iniFilePath, 'Path')
        })
    }
    __Get => Ini.Section(this.Path(), '__Get')
    __Get(name, *) => Ini.Section(this.Path(), name)

    class Section {
        __New[default:=''] {
            get => IniRead(this.Path(), this.Name(), '__New', default)
            set => IniWrite(value, this.Path(), this.Name(), '__New')
        }
        __New(iniFilePath, sectionName) {
            this.DefineProp('Path', {
                Call:(*)=>iniFilePath,
                Get:(this, default:='')=>IniRead(iniFilePath, this.Name(), 'Path', default)
            })
            this.DefineProp('Name', {
                Call:(*)=>sectionName,
                Get:(this, default:='')=>IniRead(iniFilePath, this.Name(), 'Name', default)
            })
        }
        __Get[default:=''] {
            get => IniRead(this.Path(), this.Name(), '__Get', default)
            set => IniWrite(value, this.Path(), this.Name(), '__Get')
        }
        __Set[default:=''] {
            get => IniRead(this.Path(), this.Name(), '__Set', default)
            set => IniWrite(value, this.Path(), this.Name(), '__Set')
        }
        __Get(keyName, default) => IniRead(this.Path(), this.Name(), keyName, default.Has(1) ? default[1] : '')
        __Set(keyName,_, value) => IniWrite(value, this.Path(), this.Name(), keyName)
    }
}

teadrinker
Posts: 4326
Joined: 29 Mar 2015, 09:41
Contact:

Re: INI Class

Post by teadrinker » 21 Mar 2023, 13:25

Interesting! :)

iPhilip
Posts: 817
Joined: 02 Oct 2013, 12:21

Re: INI Class

Post by iPhilip » 22 Mar 2023, 18:23

It's relatively easy to do the same with my approach:

Code: Select all

class Ini {
   static __New() {
      this.Prototype.DefineProp('__New',  {Get: (*) => '__New'})
      this.Prototype.DefineProp('__Get',  {Get: (*) => '__Get'})
   }
   __New(Filename) {
      DefineProp := {}.DefineProp.Bind(''.base)
      DefineProp('__Get', {Call: (Section, Key, Default) => IniRead(Filename, Section, Key, Default.Has(1) ? Default[1] : UnSet)})
      DefineProp('__Set', {Call: (Section, Key, _, Value) => IniWrite(Value, Filename, Section, Key)})
   }
   __Get(Section, *) => Section
}

MyIni := Ini('test.ini')
Section := '__New'
MyIni.%Section%.key := 'value'
MsgBox MyIni.%Section%.key
Section := '__Get'
MsgBox MyIni.%Section%.key2['default value']
MyIni.%Section%.key2 := 'value2'
MsgBox MyIni.%Section%.key2
MsgBox
FileDelete 'test.ini'
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)

teadrinker
Posts: 4326
Joined: 29 Mar 2015, 09:41
Contact:

Re: INI Class

Post by teadrinker » 22 Mar 2023, 19:00

@iPhilip Cool!
However, your approach has one peculiarity (I don't know if this can be called a disadvantage). You are changing the properties of strings in the global scope:

Code: Select all

Ini('test.ini')
MsgBox ''.__Get('', ['Hello!'])
MsgBox 'any'.any['Hello!']

class Ini {
    static __New() {
        this.Prototype.DefineProp('__New', { Get: (*) => '__New' })
        this.Prototype.DefineProp('__Get', { Get: (*) => '__Get' })
    }
    __New(Filename) {
        DefineProp := {}.DefineProp.Bind(''.base)
        DefineProp('__Get', { Call: (Section, Key, Default) => IniRead(Filename, Section, Key, Default.Has(1) ? Default[1] : UnSet) })
        DefineProp('__Set', { Call: (Section, Key, _, Value) => IniWrite(Value, Filename, Section, Key) })
    }
    __Get(Section, *) => Section
}

iPhilip
Posts: 817
Joined: 02 Oct 2013, 12:21

Re: INI Class

Post by iPhilip » 22 Mar 2023, 19:53

teadrinker wrote:
22 Mar 2023, 19:00
... your approach has one peculiarity (I don't know if this can be called a disadvantage). You are changing the properties of strings in the global scope:

Code: Select all

Ini('test.ini')
MsgBox ''.__Get('', ['Hello!'])
That's correct. If you can live with that peculiarity, here is a version of the class that covers the following Ini* functions:

  • Value := IniRead(Filename, Section, Key [, Default])
  • IniWrite Value, Filename, Section, Key
  • IniDelete Filename, Section [, Key]

Code: Select all

class Ini {
   static __New() {
      this.Prototype.DefineProp('__New', {Get: (*) => '__New'})
      this.Prototype.DefineProp('__Get', {Get: (*) => '__Get'})
   }
   __New(Filename) {
      DefineProp := {}.DefineProp.Bind(''.base)
      DefineProp('__Get',  {Call: (Section, Key, Default) => IniRead(Filename, Section, Key, Default.Has(1) ? Default[1] : UnSet)})
      DefineProp('__Set',  {Call: (Section, Key, _, Value) => IniWrite(Value, Filename, Section, Key)})
      DefineProp('__Call', {Call: (Section, Method, Key) => Method = 'Delete' ? IniDelete(Filename, Section, Key.Has(1) ? Key[1] : UnSet) : ''})
   }
   __Get(Section, *) => Section
}

MyIni := Ini('test.ini')
Section := '__New'
MyIni.%Section%.key := 'value'
MsgBox MyIni.%Section%.key
MyIni.%Section%.Delete('key')
MsgBox 'Deleted "key" key'
MyIni.%Section%.Delete()
MsgBox 'Deleted "' Section '" section'
Section := '__Get'
MsgBox MyIni.%Section%.key2['default value']
MyIni.%Section%.key2 := 'value2'
MsgBox MyIni.%Section%.key2
MyIni.%Section%.Delete()
MsgBox 'Deleted "' Section '" section'
FileDelete 'test.ini'
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)

teadrinker
Posts: 4326
Joined: 29 Mar 2015, 09:41
Contact:

Re: INI Class

Post by teadrinker » 22 Mar 2023, 20:06

Thanks!
iPhilip wrote: If you can live with that peculiarity
Just found another one:

Code: Select all

Ini('test.ini')
'section'.key := 'value'
This can be considered as a "feauture". :)

iPhilip
Posts: 817
Joined: 02 Oct 2013, 12:21

Re: INI Class

Post by iPhilip » 23 Mar 2023, 02:25

There is a fundamental flaw in my class. :( It doesn't work for multiple instances as demonstrated by the following test:

Code: Select all

MyIni1 := Ini('test1.ini')
MyIni2 := Ini('test2.ini')
MyIni1.Section.key := 'value'
MsgBox '1. ' (FileExist('test1.ini') && IniRead('test1.ini', 'Section', 'key'))  ; => 1.
   . '`n2. ' (FileExist('test2.ini') && IniRead('test2.ini', 'Section', 'key'))  ; => 2. value

class Ini {
   static __New() {
      this.Prototype.DefineProp('__New', {Get: (*) => '__New'})
      this.Prototype.DefineProp('__Get', {Get: (*) => '__Get'})
   }
   __New(Filename) {
      DefineProp := {}.DefineProp.Bind(''.base)
      DefineProp('__Get',  {Call: (Section, Key, Default) => IniRead(Filename, Section, Key, Default.Has(1) ? Default[1] : UnSet)})
      DefineProp('__Set',  {Call: (Section, Key, _, Value) => IniWrite(Value, Filename, Section, Key)})
      DefineProp('__Call', {Call: (Section, Method, Key) => Method = 'Delete' ? IniDelete(Filename, Section, Key.Has(1) ? Key[1] : UnSet) : ''})
   }
   __Get(Section, *) => Section
}
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)

Post Reply

Return to “Ask for Help (v2)”