A little function to read all variables from ini file

Post your working scripts, libraries and tools for AHK v1.1 and older
Varacolaci
Posts: 16
Joined: 25 Jun 2015, 00:29

A little function to read all variables from ini file

19 Apr 2022, 22:10

Hello this is a function I have been using for years now.
It is very simple, it reads and ini file and it create variables with it in the format of
SECTION_key := value

so for example, if you have an ini like this

Code: Select all

[SYSTEM]
name=Applecation Name
version=1.0.0.4
lang=english

[OPTIONS]
rootdir=C:\PathToApp
The function will create the variables:

SYSTEM_name
SYSTEM_version
SYSTEM_lang
OPTIONS_rootdir

and save the ini values in each of them

This is the function:
Just read the comments for usage and instructions

Code: Select all

; Created by 		Cristófano Varacolaci
; For 			 	ObsessedDesigns Studios™, Inc.
; Version 			1.2.00.00
; Build 			16:30 2022.04.19
; ==================================================================================================================================
; Function:       variablesFromIni
; Description     read all variables in an ini and store in variables
; Usage:          variablesFromIni(_SourcePath [, _ValueDelim = "=", _VarPrefixDelim = "_"])
; Parameters:
;  _SourcePath    	-  path to the ini file ["path\file.ini"]
;  _ValueDelim      -  This is the delimitator used for key 'delim' value function
;  _VarPrefixDelim 	-  This specifies the separator between section name and key name.
; 						All section names and key names are merged into single name.
; Return values:  
;     Variables from the ini, named after SECTION Delimiter KEY
; Change history:
;     1.2.00.00/2022.04.19
;     Updated to use SubStr() ad regExMatch() instead of deprecated functions
; Remarks:
; Change history:
;     1.1.00.00/2022.03.30
;     Added _ValueDelim
;     corrected an error, now it is working again
; Remarks:
; Change history:
;     1.1.00.00/2021.12.05
;     Added _ValueDelim
; Remarks:

variablesFromIni(_SourcePath, _ValueDelim = "=", _VarPrefixDelim = "_")
{
    Global
    if !FileExist(_SourcePath){
        MsgBox, 16, % "Error", % "The file " . _SourcePath . " does not esxist"
    } else {        
        Local FileContent, CurrentPrefix, CurrentVarName, CurrentVarContent, DelimPos
        FileRead, FileContent, %_SourcePath%
        If ErrorLevel = 0
        {
            Loop, Parse, FileContent, `n, `r%A_Tab%%A_Space%
            {
                If A_LoopField Is Not Space
                {
                    If (SubStr(A_LoopField, 1, 1) = "[")
                    {
                        RegExMatch(A_LoopField, "\[(.*)\]", ini_Section)
                        CurrentPrefix := ini_Section1
                    }
                    Else
                    {
                        DelimPos := InStr(A_LoopField, _ValueDelim)
                        CurrentVarName := SubStr(A_LoopField, 1, DelimPos - 1)
                        CurrentVarContent := SubStr(A_LoopField, DelimPos + 1)
                        %CurrentPrefix%%_VarPrefixDelim%%CurrentVarName% = %CurrentVarContent%
                        ;---- [Uncomment for variable name and value display]
                        ;MsgBox, , Title, %CurrentPrefix%%_VarPrefixDelim%%CurrentVarName% = %CurrentVarContent%
                    }
                    
                }
            }
        }
    }
}
Edit: Edited to change my global variable CONFIGURATION_FILE to function local _SourcePath
Last edited by Varacolaci on 22 Apr 2022, 13:19, edited 1 time in total.
Kisang Kim
Posts: 12
Joined: 31 Jul 2019, 02:37

Re: A little function to read all variables from ini file

20 Apr 2022, 22:51

wonderful !

I changed one line
if !FileExist(CONFIGURATION_FILE){ to
if !FileExist(_SourcePath){
User avatar
Xeo786
Posts: 760
Joined: 09 Nov 2015, 02:43
Location: Karachi, Pakistan

Re: A little function to read all variables from ini file

21 Apr 2022, 03:58

How about loading INI into array and unload 2d array into ini
viewtopic.php?f=6&t=91436
"When there is no gravity, there is absolute vacuum and light travel with no time" -Game changer theory
Varacolaci
Posts: 16
Joined: 25 Jun 2015, 00:29

Re: A little function to read all variables from ini file

22 Apr 2022, 13:00

Kisang Kim wrote:
20 Apr 2022, 22:51
wonderful !

I changed one line
if !FileExist(CONFIGURATION_FILE){ to
if !FileExist(_SourcePath){
Oh in fact you are right, my mistake
User avatar
Delta Pythagorean
Posts: 628
Joined: 13 Feb 2017, 13:44
Location: Somewhere in the US
Contact:

Re: A little function to read all variables from ini file

23 Apr 2022, 16:00

I have my own version that does not require to be global variables, parses to objects, and allows for great customization.

Code: Select all

/*
	name ------ : ini.ahk
	description : Parse INI configuration text or dump an object as INI configuration text.
	author ---- : TopHatCat
	version --- : v1.0.0
	date ------ : 22-01-2022  [DD-MM-YYYY]
*/

class IniConf
{
	static allow_read_file := true            ; PARSE()                Whether to allow reading a given file path or to only accept strings.
	static allow_multi_line_values := false   ; PARSE()                Whether to allow multi-line parsed values.
	static allow_bool_translation := true     ; PARSE()                Whether to allow boolean translation ("true" to true, "no" to false, etc).
	static allow_null_translation := true     ; PARSE()                Whether to allow translating "null" to null character.
	static allow_empty_translation := true    ; PARSE()                Whether to allow empty values to exist.
	static allow_escape_translation := false  ; PARSE()                Whether to allow translating backslash escapes. Saves on performance to have off.
	static allow_trailing_whitespace := true  ; PARSE()                Whether to allow trailing whitespace.
	static comment_flag := "`;"               ; PARSE() & STRINGIFY()  Comment flag. By default, the comment character is a semi-colon (;).
	static null_character := Chr(0)           ; PARSE()                The NULL character to use. By default it's ASCII 0.
	static allow_dunder_comments := true      ; STRINGIFY()            Whether to allow comments when parsing in the form of "__comment__".
	static escape_values :=                   ; PARSE()                The custom escape values to translate. To add your own, push a new 2 value array.
	(Ltrim Join Comments
		[
			["\n", "`n"],                     ; Newline
			["\r", "`r"],                     ; Carriage return
			["\t", "`t"],                     ; (Horizonal) Tab
			["\a", "`a"],                     ; Alert (bell)
			["\v", "`v"],                     ; Vetical tab
			["\f", "`f"],                     ; Formfeed
			["\b", "`b"],                     ; Backspace
			["\0", IniConf.null_character],   ; Null character
			["\\", "\"],                      ; Normal backslash
			["\""", """"],                    ; Quote
			["\`;", "`;"],                    ; Semi-colon
			["\#", "#"],                      ; Pound
			["\:", ":"],                      ; Colon
			["\=", "="]                       ; Equals
		]
	)

	/*
		@name parse
		@brief Parse an INI configuation string into a object.
		@param {string} str - The text to parse. If ALLOW_READ_FILE is true, this can be a file path.
		@return {object} The parsed object.
	*/
	parse(str)
	{
		if (IniConf.allow_read_file)
		{
			if (FileExist(str))
			{
				FileRead, str, % str
			}
		}

		result := {}
		split_lines := StrSplit(str, "`n", "`r")
		last_section := ""
		skip_line := false

		for index, line in split_lines
		{
			if (skip_line)
			{
				skip_line := false
				continue
			}

			; Replace comments.
			line := RegExReplace(line, "im)((?:[\t ]+|)\Q" . IniConf.comment_flag . "\E.*)$")

			; Ignore blanks
			if (line == "")
			{
				continue
			}

			split_keyval := StrSplit(line, "=")

			; If it's a valid section, create a sub object to the result.
			if (RegExMatch(line, "iO)\[([a-z_0-9]+)\]", match_obj))
			{
				result[match_obj[1]] := {}
				last_section := match_obj[1]
				continue
			}
			else if (split_keyval.Count() > 0)
			{
				; If we've split more than needed, just combine the rest of the array.
				if (split_keyval.Count() > 2)
				{
					loop, % split_keyval.Count()
					{
						split_keyval[2] .= split_keyval[A_Index + 2]
					}
				}

				; If it's not a valid key-value pair, throw a syntax error.
				if (split_keyval[1] == "")
				{
					throw Exception(Format("[line {1}] SynatxError: Invalid key-value pair. "
						. "Maybe you used a colon instead of an equals sign?", index), -1, line)
				}

				; If the settings of the class allows for multi-line values and
				; if the current line has the "continuation" character, add the next line to this.
				if ((IniConf.allow_multi_line_values) && (line ~= "\s+?\\$"))
				{
					split_keyval[2] .= split_lines[index + 1]
					skip_line := true
				}
				else
				{
					switch (split_keyval[2])
					{
						case "true", "yes":
							(IniConf.allow_bool_translation ? split_keyval[2] := true)
						case "false", "no":
							(IniConf.allow_bool_translation ? split_keyval[2] := false)
						case "null", "undefined":
							(IniConf.allow_null_translation ? split_keyval[2] := IniConf.null_character)
						case "":
						{
							if (!IniConf.allow_empty_translation)
							{
								throw Exception(Format("[line {1}] ValueError: Empty value. "
									. "Did you accidently delete the value?", index), -1, line)
							}
							split_keyval[2] := ""
						}
						default:
						{
							if (IniConf.allow_escape_translation)
							{
								for i, val in IniConf.escape_values
								{
									split_keyval[2] := StrReplace(split_keyval[2], val[1], val[2])
								}
							}
						}
					}
					result[last_section, Trim(split_keyval[1])] := IniConf.allow_trailing_whitespace ? LTrim(split_keyval[2]) : Trim(split_keyval[2])
				}
			}
		}
		return result
	}

	/*
		@name stringify
		@brief Dump an object into a standard
		@param {string} str - The text to parse.
		@return {object} The parsed object.
	*/
	stringify(obj, replacer := false, spacer := "`n")
	{
		; Since this function parses an object into a string,
		; and the object is already a string, we just return the value.
		if (!IsObject(obj))
		{
			return obj
		}

		result := ""

		for section_name, entry in obj
		{
			result .= Format("[{1}]`n", section_name)

			for key, value in entry
			{
				; Don't allow objects as results in the config file.
				if (IsObject(value))
				{
					throw Exception("TypeError: Expected string, integer, boolean, or null value. "
						. "Make sure to only use a single nested object.", -1, value)
				}

				if (IsFunc(replacer))
				{
					value := replacer.Call(key, value)
				}

				if (IniConf.allow_dunder_comments)
				{
					if (key = "__comment__")
					{
						if (IsObject(value))
						{
							for index, comment in value
							{
								result .= Format("{1} {2}`n", IniConf.comment_flag, comment)
							}
						}
						else
						{
							result .= "# " . value . "`n"
						}
						continue
					}
				}

				if (IniConf.allow_escape_translation)
				{
					for i, val in IniConf.escape_values
					{
						value := StrReplace(value, val[2], val[1])
					}
				}

				result .= Format("{1} = {2}`n", key, value)
			}

			; When we move to a new object, we can add a "spacer" between sections.
			result .= spacer
		}
		return RTrim(result, "`n")
	}
}

An example usage would be:

Code: Select all

/*
	; @file:  config.ini ========
	[program]
	name = IniConf
	version = 1.0.0
	is_config_allowed = true
*/

config := IniConf.parse(A_ScriptDir . "/config.ini")

MsgBox % "program.name = " . program.name

[AHK]......: v2.0.12 | 64-bit
[OS].......: Windows 11 | 23H2 (OS Build: 22621.3296)
[GITHUB]...: github.com/DelPyth
[PAYPAL]...: paypal.me/DelPyth
[DISCORD]..: tophatcat

Varacolaci
Posts: 16
Joined: 25 Jun 2015, 00:29

Re: A little function to read all variables from ini file

27 Apr 2022, 09:43

Delta Pythagorean wrote:
23 Apr 2022, 16:00
I have my own version that does not require to be global variables, parses to objects, and allows for great customization.

Code: Select all

/*
	name ------ : ini.ahk
	description : Parse INI configuration text or dump an object as INI configuration text.
	author ---- : TopHatCat
	version --- : v1.0.0
	date ------ : 22-01-2022  [DD-MM-YYYY]
*/

class IniConf
{
	static allow_read_file := true            ; PARSE()                Whether to allow reading a given file path or to only accept strings.
	static allow_multi_line_values := false   ; PARSE()                Whether to allow multi-line parsed values.
	static allow_bool_translation := true     ; PARSE()                Whether to allow boolean translation ("true" to true, "no" to false, etc).
	static allow_null_translation := true     ; PARSE()                Whether to allow translating "null" to null character.
	static allow_empty_translation := true    ; PARSE()                Whether to allow empty values to exist.
	static allow_escape_translation := false  ; PARSE()                Whether to allow translating backslash escapes. Saves on performance to have off.
	static allow_trailing_whitespace := true  ; PARSE()                Whether to allow trailing whitespace.
	static comment_flag := "`;"               ; PARSE() & STRINGIFY()  Comment flag. By default, the comment character is a semi-colon (;).
	static null_character := Chr(0)           ; PARSE()                The NULL character to use. By default it's ASCII 0.
	static allow_dunder_comments := true      ; STRINGIFY()            Whether to allow comments when parsing in the form of "__comment__".
	static escape_values :=                   ; PARSE()                The custom escape values to translate. To add your own, push a new 2 value array.
	(Ltrim Join Comments
		[
			["\n", "`n"],                     ; Newline
			["\r", "`r"],                     ; Carriage return
			["\t", "`t"],                     ; (Horizonal) Tab
			["\a", "`a"],                     ; Alert (bell)
			["\v", "`v"],                     ; Vetical tab
			["\f", "`f"],                     ; Formfeed
			["\b", "`b"],                     ; Backspace
			["\0", IniConf.null_character],   ; Null character
			["\\", "\"],                      ; Normal backslash
			["\""", """"],                    ; Quote
			["\`;", "`;"],                    ; Semi-colon
			["\#", "#"],                      ; Pound
			["\:", ":"],                      ; Colon
			["\=", "="]                       ; Equals
		]
	)

	/*
		@name parse
		@brief Parse an INI configuation string into a object.
		@param {string} str - The text to parse. If ALLOW_READ_FILE is true, this can be a file path.
		@return {object} The parsed object.
	*/
	parse(str)
	{
		if (IniConf.allow_read_file)
		{
			if (FileExist(str))
			{
				FileRead, str, % str
			}
		}

		result := {}
		split_lines := StrSplit(str, "`n", "`r")
		last_section := ""
		skip_line := false

		for index, line in split_lines
		{
			if (skip_line)
			{
				skip_line := false
				continue
			}

			; Replace comments.
			line := RegExReplace(line, "im)((?:[\t ]+|)\Q" . IniConf.comment_flag . "\E.*)$")

			; Ignore blanks
			if (line == "")
			{
				continue
			}

			split_keyval := StrSplit(line, "=")

			; If it's a valid section, create a sub object to the result.
			if (RegExMatch(line, "iO)\[([a-z_0-9]+)\]", match_obj))
			{
				result[match_obj[1]] := {}
				last_section := match_obj[1]
				continue
			}
			else if (split_keyval.Count() > 0)
			{
				; If we've split more than needed, just combine the rest of the array.
				if (split_keyval.Count() > 2)
				{
					loop, % split_keyval.Count()
					{
						split_keyval[2] .= split_keyval[A_Index + 2]
					}
				}

				; If it's not a valid key-value pair, throw a syntax error.
				if (split_keyval[1] == "")
				{
					throw Exception(Format("[line {1}] SynatxError: Invalid key-value pair. "
						. "Maybe you used a colon instead of an equals sign?", index), -1, line)
				}

				; If the settings of the class allows for multi-line values and
				; if the current line has the "continuation" character, add the next line to this.
				if ((IniConf.allow_multi_line_values) && (line ~= "\s+?\\$"))
				{
					split_keyval[2] .= split_lines[index + 1]
					skip_line := true
				}
				else
				{
					switch (split_keyval[2])
					{
						case "true", "yes":
							(IniConf.allow_bool_translation ? split_keyval[2] := true)
						case "false", "no":
							(IniConf.allow_bool_translation ? split_keyval[2] := false)
						case "null", "undefined":
							(IniConf.allow_null_translation ? split_keyval[2] := IniConf.null_character)
						case "":
						{
							if (!IniConf.allow_empty_translation)
							{
								throw Exception(Format("[line {1}] ValueError: Empty value. "
									. "Did you accidently delete the value?", index), -1, line)
							}
							split_keyval[2] := ""
						}
						default:
						{
							if (IniConf.allow_escape_translation)
							{
								for i, val in IniConf.escape_values
								{
									split_keyval[2] := StrReplace(split_keyval[2], val[1], val[2])
								}
							}
						}
					}
					result[last_section, Trim(split_keyval[1])] := IniConf.allow_trailing_whitespace ? LTrim(split_keyval[2]) : Trim(split_keyval[2])
				}
			}
		}
		return result
	}

	/*
		@name stringify
		@brief Dump an object into a standard
		@param {string} str - The text to parse.
		@return {object} The parsed object.
	*/
	stringify(obj, replacer := false, spacer := "`n")
	{
		; Since this function parses an object into a string,
		; and the object is already a string, we just return the value.
		if (!IsObject(obj))
		{
			return obj
		}

		result := ""

		for section_name, entry in obj
		{
			result .= Format("[{1}]`n", section_name)

			for key, value in entry
			{
				; Don't allow objects as results in the config file.
				if (IsObject(value))
				{
					throw Exception("TypeError: Expected string, integer, boolean, or null value. "
						. "Make sure to only use a single nested object.", -1, value)
				}

				if (IsFunc(replacer))
				{
					value := replacer.Call(key, value)
				}

				if (IniConf.allow_dunder_comments)
				{
					if (key = "__comment__")
					{
						if (IsObject(value))
						{
							for index, comment in value
							{
								result .= Format("{1} {2}`n", IniConf.comment_flag, comment)
							}
						}
						else
						{
							result .= "# " . value . "`n"
						}
						continue
					}
				}

				if (IniConf.allow_escape_translation)
				{
					for i, val in IniConf.escape_values
					{
						value := StrReplace(value, val[2], val[1])
					}
				}

				result .= Format("{1} = {2}`n", key, value)
			}

			; When we move to a new object, we can add a "spacer" between sections.
			result .= spacer
		}
		return RTrim(result, "`n")
	}
}

An example usage would be:

Code: Select all

/*
	; @file:  config.ini ========
	[program]
	name = IniConf
	version = 1.0.0
	is_config_allowed = true
*/

config := IniConf.parse(A_ScriptDir . "/config.ini")

MsgBox % "program.name = " . program.name
I used to have a function that also created object but many times when working with GUI, you cant use the object, it has to be a variable in the %Variable% format so it adds a line before where you have variable = object.key which tends to make everything more confusin, so I just opted to use variables from the begining.

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: No registered users and 88 guests