Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

class_EasyIni: Native syntax - Ini.Section.Key := val - Formatting retained


  • Please log in to reply
39 replies to this topic
Verdlin
  • Members
  • 256 posts
  • Last active: Apr 29 2016 06:46 PM
  • Joined: 21 Dec 2012

Download

 

A big problem I have had will all other Ini libraries (including the old version of EasyIni) is that their syntax is too cumbersome. The point of this library is to make it as easy as possible to R/W inis; furthermore, I wanted to make the class itself easy to read and accessible to other developers. EasyIni is currently 389 LOC.

An unexpected positive consequence of this class is that it performs exponentially faster than IniWrite operations. I posted an example below where class_EasyIni is 1162% faster than IniWrite.

 

My design approach was, "How can I make this as easy as possible?" The result is, you can interface with ini's using class_EasyIni objects with great ease. Thanks to custom objects, this class allows you to use the familiar, native, Object syntax.

 

To create an ini object,

vIni := class_EasyIni("MyIni.ini")

To interface with the object,

for section, aKeysAndVals in vIni
   for key, val in aKeysInVals
      vIni[Section][key] := 1

vIni[VariableWithSectionName][VariableWithKeyName] := val ; this is typically used when you are looping through sections and keys
vIni.NameOfSection.NameOfKey := val ; This syntax is the easiest, but you must know the literal section name and key name to do this.
   ; It is particularly useful for handling user-defined ini variables in your applications with relative ease.
vIni.NameOfSection[VariableWithKeyName] := val ; This is when you know the literal section name
vIni[VariableWithSectionName].key := val ; This is when you know the literal key name

This file is std lib compliant. Of course, you should make sure the file name is class_EasyIni.ahk in order for this to work.

 

Character limitations are mostly obvious:

  • You cannot use newlines in section names.
  • For any alphanumeric section, (i.e. "[A]") only one case is allowed. So if you tried to add section "[a]" AddSection() would fail. This is demonstrated in my 1500 sections example below.
  • During my tests, I noticed that SOH (chr(1)) and A_Space (chr(32)) were written to the same section [SOH]. I am not sure why this is the case, but I don't imagine this will be a problem for most people, if any.
  • I also noticed that quite a few odd character values were getting grouped together into the same sections. Whether this is a problem for anyone is doubtful, but I suspect it is possible for those who store BBCode in inis to have problems. Regardless, this classes is just a fancy custom object. If there are certain problems with section names, this is likely a problem with AHK itself and not the class.
  • You cannot have a section or key that starts with the text, EasyIni_ReservedFor_. I can't imagine this being a big problem.
  • "=" are not supported in keynames.
  • Do whatever you want with values wink.png

A couple of notes about ini data:

  • Keys without values are supported
  • Although it is not recommended if you plan on using your inis with other application that access the same ini, "]" in section names are supported.
  • EasyIni stores ini data in a custom object. Much credit for this custom object goes to Lexikos and Rbrtryn for their work with ordered arrays. For more information, see OrderedArray. Thanks to the pre-existing work with OrderedArrays, I was able to create an object that, without much trouble, enabled me to maintain the format of the ini file. If any comment or newline is removed from your file, or if any sections or keys are re-ordered in your file as a result of using class_EasyIni, then this is a bug and you should report it to me!
  • This rewrite of the library has increased the overall speed, as well. In fact, the Save() function is now as 3629% faster than the old EasyIni class!!

 

Below is a test where I add every possible character value (returned from chr()) to an ini. Writing to memory took < 1 second. Writing to a file took 17seconds (3629% faster than the old class).

Spoiler

 

This test shows what characters are readable by class_EasyIni. As I mentioned before, certain odd characters get grouped together into the same sections. If you care to know more about this, run the previous example first, and then run this example.

Spoiler

 

Adding 1500 sections, keys, and values takes < 1 sec total to write to memory and to an ini. This example shows how sections and keys are case-sensitive. It will produce errors for sections a-z because sections A-Z already existed in the ini.

Spoiler

 

Examples:

 

Creating an ini, adding and modifying sections, keys, and values, and saving.

Spoiler

 

Loading an ini from a string, and maintaining all formatting (including comments and newline) in an in file

Spoiler

 

IniWrite vs EasyIni:

Spoiler

 

I do appreciate any comments/input/criticism.


Scripts are written and tested using AHK_H 64w (unless otherwise specified).

CFlyout. EasyIni. Dynamic Label Execution (No Reload). Word Lookup.


evilOlive
  • Members
  • 8 posts
  • Last active: Oct 29 2013 09:19 PM
  • Joined: 09 Dec 2012

What can I say?  This is *exactly* what I needed, and it's as if you sensed I would need it this week, so you wrote it by last weekend.

 

Thank you for that.  You deserve a cookie.

 

This thing works precisely as advertised and is *fantastic*

 

Thank you so much for this! 

 

I also have made extensive use of berban's iniread() which is also very, very fast for loading all the data from an ini into variables and such.  Great tool.  This MIGHT supersede it but I haven't decided yet... it does some things automatically that I find super convenient.  In particular, it very quickly creates individual variables for each section and key pair in the ini and prepends the key name with the section name and an underscore, then spits out a delimited list of those variables.  

 

I have an ini with about 850 sections, each section has the same 25 keys or so.  The section names are crc32 file hashes, the keys are file attributes.  So when I grab the hash of a file, I can instantly access data about that file's attributes without a bunch of inireads from the file.

 

Anyway, I just love this.  Great work, and thanks again m8!



Verdlin
  • Members
  • 256 posts
  • Last active: Apr 29 2016 06:46 PM
  • Joined: 21 Dec 2012

Thank you, evilOlive! I'm glad to hear this was useful, and that you have no encountered any initial problems.

 

I just took a look an Berban's iniread(). I agree that one of the best features is the automatically variable creation. I guess whether or not people want to use that method would be a matter of preference.

 

I have an ini with about 850 sections, each section has the same 25 keys or so.  The section names are crc32 file hashes, the keys are file attributes.  So when I grab the hash of a file, I can instantly access data about that file's attributes without a bunch of inireads from the file.

 

Great! The design to load the ini into memory was very intentional. I specifically wanted to avoid multiple ini reads.

 

Cheers!


Scripts are written and tested using AHK_H 64w (unless otherwise specified).

CFlyout. EasyIni. Dynamic Label Execution (No Reload). Word Lookup.


evilOlive
  • Members
  • 8 posts
  • Last active: Oct 29 2013 09:19 PM
  • Joined: 09 Dec 2012

What I find most helpful about your methodology here is that it opens the file into memory and lets me make all my changes to it (which are common, and in some cases, a change is made to every section) inside memory rather than through a zillion file WRITES which is what I most wanted to avoid. Writing to memory is *so* much faster than writing to even an SSD, and lots of writes to my SSD is no good, so again, thank you a lot. Very nice job, and no, no problems yet, though it took me a second to learn what I needed to understand about how this is used differently from a function with embedded multiple functions like myFunction_SubFunction(). The use of a class and object model is 1) excellent 2) the right way to go and 3) totally new to me. So it's been a decent little learner.

 

I wrote up the following as a cheat sheet for how to do various things... maybe someone else will find it handy, and please correct me if I'm saying anything wrong here, this is just how I understood things and wrote it out for myself.

 

Spoiler


Verdlin
  • Members
  • 256 posts
  • Last active: Apr 29 2016 06:46 PM
  • Joined: 21 Dec 2012

RE: File writes: Ah! Good point. What was foremost in my mind as what methods to avoid was many reads, but writes are even more important as reading to memory if faster than writing to SSD/HDD.

 

Maybe I am misunderstanding you, but it looks like you are saying that the use of underscore in function names, such as "EasyIni_*" denotes some form of function embedding. To clarify, the user of the underscore in "EasyIni_*" is specifically so that you can put this file in your user library (see http://www.autohotke...nctions.htm#lib). Regardless of whether I misunderstood you or not, I should've clarified this in my original post.

 

Nice job on the cheat sheet! I was thinking I should add some better explanation, but then you posted that. Honestly, it's probably better than what I would've wrote, lol.


Scripts are written and tested using AHK_H 64w (unless otherwise specified).

CFlyout. EasyIni. Dynamic Label Execution (No Reload). Word Lookup.


evilOlive
  • Members
  • 8 posts
  • Last active: Oct 29 2013 09:19 PM
  • Joined: 09 Dec 2012

Well feel free to cut and paste to the OP or a PDF manual or whatever you might like.  Glad to help.  

 

 

What I was saying was actually just that there's a difference between using functions and using a class.  I hadn't used classes before, only the more traditional style of functions.  Anyway, overall, I've just been really thrilled with it.  Super helpful.  I Couldn't believe no one else had noticed or said anything yet.

 

Lots of file writes to an SSD is also very bad for the longevity of your SSD, so in this case, it's a really good thing.  Thanks again!



evilOlive
  • Members
  • 8 posts
  • Last active: Oct 29 2013 09:19 PM
  • Joined: 09 Dec 2012

Ok, little issue.  It seems that BBcode (stuff in brackets) within a value will trip up EasyINI.GetSections. 



Swannie
  • Members
  • 9 posts
  • Last active: Jun 04 2013 07:43 AM
  • Joined: 06 Dec 2011

Hi,I can`t download as Github does not work with Internet Explorer 7/8 anymore. Possible to put all your scripts on this forum ?

Thnx



Verdlin
  • Members
  • 256 posts
  • Last active: Apr 29 2016 06:46 PM
  • Joined: 21 Dec 2012

Swannie, I changed the download link to point to the actual file. After I did this, I was able to download it with Internet Explorer.

 

Sorry to hear that evilOlive. Would you mind posting a simple example of what your problem is so that I can replicate it with certainty?


Scripts are written and tested using AHK_H 64w (unless otherwise specified).

CFlyout. EasyIni. Dynamic Label Execution (No Reload). Word Lookup.


Verdlin
  • Members
  • 256 posts
  • Last active: Apr 29 2016 06:46 PM
  • Joined: 21 Dec 2012

Updates:

  • Utilizing meta-functions (finally)
  • GetSectionsArray() and GetKeysArray() for looping
  • Load from a string i.e.: new EasyIni(sFileName, sIniVar). Good for temporary inis
  • Fixing false-positive bug in AddKey() and AddKeyAndVal()

Example of loading from string:

Spoiler

Scripts are written and tested using AHK_H 64w (unless otherwise specified).

CFlyout. EasyIni. Dynamic Label Execution (No Reload). Word Lookup.


Cattleya
  • Members
  • 90 posts
  • Last active: Sep 13 2013 05:11 AM
  • Joined: 28 Sep 2011

Thank Verdlin, your function is very easy to use, hope you will update it if it have some bug.

I remember that the first time I use EasyIni, EasyIni makes me lost all data in my long pre-created, no sorted INI file. Is my INI need to be sorted before use with EasyIni for safety use ?

Here is my INI file:

[Launcher]

AdditionalParameters=
AllowMultipleInstances=false
CleanRegistryOnExit=false
ClearBrowserJunkFileAndRegistry=false
RemoveGoogleUpdaterPlugin=false
RunLocallyForUSB=false
SetDefaultBrowserPermanent=false
SetDefaultBrowserTemporary=false
SkipAddonCompatibilityCheck=true
ShowLauncherMenuIcon=false
DisablePluginContainer=false
DisableCrashReporter=true
CloseBrowserOnExit=false
CustomEnvironmentPath=C:\Windows\system32\Macromed\Flash\
CompeletelyDisableSystemPlugin=false
DisableSplashScreen=false
LoadSettingsRegistryOnStart=false
MakePortableAppsFullyStealth=false
ProfilePath=
RegisterAHKPermanent=false
RegisterAHKTemporary=false
AutoCorrectPath=false
BrowserClass=ahk_class MozillaWindowClass
BrowserEXEFile=palemoon.exe
BrowserTitle=Pale Moon
CheckTimer=50000

Is my problem because of my INI don't have space between Key and Value or my INI not sorted by name before ?



Verdlin
  • Members
  • 256 posts
  • Last active: Apr 29 2016 06:46 PM
  • Joined: 21 Dec 2012

Hi Cattleya,

 

Thank Verdlin, your function is very easy to use, hope you will update it if it have some bug.

 

You're welcome! Glad to hear it is serving it's purpose.

 

Sorry to see you have had trouble. I am not entirely certain what your problem is, but it seems it has to do with sorting -- is that correct?

 

A couple of notes about that (which I should probably post at the top):

  • EasyIni stores ini data in AHK_L associative arrays. The thing about these arrays is that the keys are sorted alphabetically. In the Save() function of EasyIni, it actually deletes the ini file, and then recreates it using the aforementioned array. This means three things:
  1. Sections in the Ini will be in alphabetical order
  2. Keys in the Ini will be in alphabetical order
  3. Comments anywhere in the Ini will be deleted

Now you said,

I remember that the first time I use EasyIni, EasyIni makes me lost all data in my long pre-created, no sorted INI file.

so the question is, what data did you lose? Was it comments only? or did sections, keys, and/or values get lost, too? What might be helpful is if you posted a before and after ini file. The before file would be one you created by hand, and the after file would be that same file after you load it up with EasyIni and call the Save() function.

 

Hope this helps,


Scripts are written and tested using AHK_H 64w (unless otherwise specified).

CFlyout. EasyIni. Dynamic Label Execution (No Reload). Word Lookup.


Relayer
  • Members
  • 122 posts
  • Last active: Jun 22 2015 09:21 PM
  • Joined: 24 Nov 2008

I took the liberty to rewrite the class and called it "EasierIni" not to compete with the script above, but to learn and solve the problem of losing the original format of the ini file.  It is more stripped down and simple.
 
It loads an ini file into an object array.  After that, all manipulations of the data can be done using the object notation supplied natively in _L.  Upon saving, the original ini file is written to with the AHK ini commands so that all formatting and comments are preserved.
 
Access notation is:
handle.section.key.value

 

examples:

handle.section.Insert(key, value)

handle.Insert(section, "")

handle.Remove(section)

handle.section.key := value
 
Because manipulation is through native tools, the supporting functions are few.  Messy renaming for sections and keys have a function:
handle.section.renameSection(section, newname)
handle.section.renameKey(section, key, newname)

 

The original ini is not altered until you call handle.Save()
 
There is also a function to create new sections and keys, but that can be done easily with the object manipulation built into _L.

Test it out on data you can afford to lose... there may be bugs.

Class EasierIni
{
	__New(file = "")
	{
		this.iniVariables := Object()
		if (file = "")
			file := ( A_ScriptDir . "\" . SubStr(A_ScriptName, 1, InStr(A_ScriptName, ".")) . "ini" )
		if !(FileExist(file))
		{
			Msgbox, 3, Create file?, %file% does not exist.  Create it?
			ifMsgbox, No
				Return False
			ifMsgbox, Cancel
				Return False
			FileAppend, , %file%
		}
		this.iniVariables.file := file
		
		FileRead, tmp, %file%
		EIdata := ""
		tmp := RegExReplace(tmp, "[\r\n]+", "`n") ;remove all blank lines
		Loop, Parse, tmp, `n
		{
			if (InStr(A_LoopField, ";"))
				Continue
			EIdata .= ((EIdata = "") ? ("") : ("`n")) . A_LoopField
		}

		this.iniStructure := Object()
		
		Loop, Parse, EIdata, `n
		{
			
			if (start := InStr(A_LoopField, "["))
			{
				keyNames := ""
				section := SubStr(A_LoopField, start + 1, InStr(A_LoopField, "]") - (start + 1))
				this[section] := Object()
			}
			else if (start := InStr(A_LoopField, "="))
			{
				key := SubStr(A_LoopField, 1, start - 1)
				value := SubStr(A_LoopField, start + 1)
				this[section].Insert(key, value)
				keyNames .= ((keyNames = "") ? ("") : (",")) . key
			}
			this.iniStructure.Insert(section, keyNames)
		}
	}
	
	New(section, key = "", value = "")
	{
		flag := False
		if !(this.HasKey(section))
		{
			this.Insert(section, "")
			this[section] := Object()
			flag := True
		}
		if (key != "")
		{
			this[section].Insert(key, value)
			flag := True
		}
		Return flag
	}
	
	renameSection(section, name)
	{
		if (this.HasKey(section))
		{
			tmp := this[section].Clone()
			this.Remove(section)
			this.Insert(name, "")
			this[name] := tmp
			Return True
		}
		else
			Return False
	}
	
	renameKey(section, key, name)
	{
		if (this.HasKey(section)) and (this[section].HasKey(key))
		{
			tmp := this[section][key]
			this[section].Remove(key)
			this[section].Insert(name, tmp)
			Return True
		}
		else
			Return False
	}

	Save()
	{
		file := this.iniVariables.file
		
		sectionList := ""
		for key, dummy in this.iniStructure
			sectionList .= ((sectionList = "") ? ("") : (",")) . key

		for section, sectionKeys in this
		{
			if (section = "iniVariables") or (section = "iniStructure")
				Continue
			if section not in %sectionList%
			{
				FileAppend, % "[" . section . "]`n", %file%
				this.iniStructure[section] := Object()
				keyNames := ""
				for key, value in sectionKeys
				{
					IniWrite, %value%, %file%, %section%, %key%
					keyNames .= ((keyNames = "") ? ("") : (",")) . key
				}
				this.iniStructure.Insert(section, keyNames)
			}
			else
			{
				tmp := ""
				Loop, Parse, sectionList, `,
					if (A_LoopField = section)
						Continue
					else
						tmp .= ((tmp = "") ? ("") : (",")) . A_LoopField
				sectionList := tmp

				keyList := this.iniStructure[section]
				for key, value in sectionKeys
				{
					if key in %keyList%
					{
						tmp := ""
						Loop, Parse, keyList, `,
							if (A_LoopField = key)
								Continue
							else
								tmp .= ((tmp = "") ? ("") : (",")) . A_LoopField
						keyList := tmp
					}
					IniWrite, %value%, %file%, %section%, %key%
				}
				Loop, Parse, keyList, `,, %A_Space%
				{
					IniDelete, %file%, %section%, %A_LoopField%
					this.iniStructure[section].Remove(A_LoopField)
				}
			}
		}
		Loop, Parse, sectionList, `,, %A_Space%
		{
			IniDelete, %file%, %A_LoopField%
			this.iniStructure.Remove(A_LoopField)
		}
	}
}


Verdlin
  • Members
  • 256 posts
  • Last active: Apr 29 2016 06:46 PM
  • Joined: 21 Dec 2012

I took the liberty to rewrite the class and called it "EasierIni" not to compete with the script above, but to learn and solve the problem of losing the original format of the ini file.  It is more stripped down and simple.
 
It loads an ini file into an object array.  After that, all manipulations of the data can be done using the object notation supplied natively in _L.  Upon saving, the original ini file is written to with the AHK ini commands so that all formatting and comments are preserved.

There is also a function to create new sections and keys, but that can be done easily with the object manipulation built into _L.

 

Wow! Great work. I have begun to realize this formatting is pretty important to users, so it has become important to me to need to support it. I think your syntax is much better -- I had no idea that you use use this more directly (i.e. this := object()) ::shock

 

Unless you object, I'd like to adapt your script to my class to achieve an "easier" ( ;) ) EasyIni class. I'm not exactly sure how much of the original script I would keep, but there are some good concepts you've brought my attention to which I want to use.


Scripts are written and tested using AHK_H 64w (unless otherwise specified).

CFlyout. EasyIni. Dynamic Label Execution (No Reload). Word Lookup.


Relayer
  • Members
  • 122 posts
  • Last active: Jun 22 2015 09:21 PM
  • Joined: 24 Nov 2008

Be my guest... that's the purpose of this forum!!!  Help and be helped!  You inspired me, I shared some of the concepts I use, and I'll probably learn something from your improved version!  I love it.

 

Relayer