diff --git a/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/!TOML Reader and Writer.md b/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/!TOML Reader and Writer.md new file mode 100644 index 00000000..24e4806b --- /dev/null +++ b/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/!TOML Reader and Writer.md @@ -0,0 +1,12 @@ +# TOML Reader and Writer +This is a class designed to read and write TOML configuration files. + +## Notes: +- Some features of TOML (such as inline tables, lists, backslash escape) are not implemented yet. +- Only double quotes is supported for string values. + +## Supported TOML value types +- Strings. +- Intagers. +- Floatingpoints. +- Booleans. diff --git a/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/.MDRoot b/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/.MDRoot new file mode 100644 index 00000000..1e79c680 --- /dev/null +++ b/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/.MDRoot @@ -0,0 +1 @@ +This file indicates that this subdirectory should be considered as a root page in the markdown version of the documentation, see the docgen notes for more details. \ No newline at end of file diff --git a/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml/Methods/dump.nvgt b/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml/Methods/dump.nvgt new file mode 100644 index 00000000..5798cd99 --- /dev/null +++ b/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml/Methods/dump.nvgt @@ -0,0 +1,28 @@ +/** + Dump all loaded data into a string. + `string toml::dump(bool indent = true, bool empty_sections = true);` + ## Arguments: + - `bool indent = true`: If this is set to true, all keys in every section will be proceeded with a tab character in the output. This indent number will increase if you have nested subsections, e.g. `game.server.name` will have 2 tabs on its key, `name`. + - `bool empty_sections = true`: Should the data include empty sections? Default is usually ok. + ## Returns: + `string`: The entire TOML data as a string. +*/ + +// Example: +#include "toml.nvgt" +void main() { + toml t; + // Lets set all the possible types of supported values. + t.set("", "main_key", "Hello, world!"); + t.set("", "int_key", 23); + t.set("", "float_key", 10.5); + t.set("", "boolean_key", false); + t.set("", "boolean_key2", true); + // Lets completely set another section. + t.set("owner", "name", "Jon"); + t.set("owner", "age", 20); + // Subsections? + t.set("owner.contact", "telegram", "bla_bla_bla"); + t.set("owner.contact", "facebook", "bla_bla_bla_book"); + alert("Info", "\n\n" + t.dump()); +} diff --git a/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml/Methods/get.md b/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml/Methods/get.md new file mode 100644 index 00000000..bb45ef24 --- /dev/null +++ b/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml/Methods/get.md @@ -0,0 +1,12 @@ +# get +Fetch a value from the TOML data given a section and key. + +`var@ toml::get(string section, string key = "", var@ def = null);` + +## Arguments: +- `string section`: The section to get the value from (if any). E.g. game.server +- `string key = ""`: The key to get. +- `var@ def = null`: The default value to facilitate handling by having an alternative value in case the original value ends up in failure. + +## Returns: +`var@`: The value at the particular key if found, the default value otherwise. diff --git a/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml/Methods/get_keys.md b/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml/Methods/get_keys.md new file mode 100644 index 00000000..fc8ee96d --- /dev/null +++ b/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml/Methods/get_keys.md @@ -0,0 +1,11 @@ +# get_keys +List all key names in a given section. + +`string[] toml::get_keys(string name, bool all = false);` + +## Arguments: +- `string name`: The name of the section. Can be blank if you want. +- `bool all = false`: Should the function fetch all keys of all sections? Note that this currently does not work if you have multiple keys with the same name. + +## Returns: +`string[]`: An array containing all the keys. An empty array means that the section is either blank or does not exist. diff --git a/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml/Methods/initialize_section.nvgt b/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml/Methods/initialize_section.nvgt new file mode 100644 index 00000000..e2f3a0a6 --- /dev/null +++ b/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml/Methods/initialize_section.nvgt @@ -0,0 +1,24 @@ +/** + Returns a TOML section with the given name, creating if necessary. + `toml_section@ toml::initialize_section(string name = "", bool create = false);` + ## Arguments: + - `string name = ""`: The name of the section to retrieve. An empty string always means the main sectionless. + - `bool create = false`: Should this section be created if it does not exist? + ## Returns: + `toml_section@`: A handle to the `toml_section` class on success, null otherwise. + ## Remarks: + This method is useful to modify a specific section or create if necessary so you do not have to verify. +*/ + +// Example: +#include "toml.nvgt" +void main() { + toml t; + t.set("", "main_key", "Hello"); + alert("Info", string(t.get("", "main_key", "Default value (woops?)"))); + toml_section@ s = t.initialize_section(""); + alert("Same as above box", string(s.get("main_key", "Default value"))); + @s = t.initialize_section("test", true); // Create new. + s.set("testkey", "World"); + alert("Info", t.dump()); +} diff --git a/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml/Methods/load.md b/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml/Methods/load.md new file mode 100644 index 00000000..836494a5 --- /dev/null +++ b/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml/Methods/load.md @@ -0,0 +1,14 @@ +# load +Load a TOML file / stream. + +1. `bool toml::load(string filename);` +2. `bool toml::load(datastream@ f);` + +## Arguments (1): +- `string filename`: The name of the TOML file to load. + +## Arguments (2): +- `datastream@ f`: A stream (i.e the file object) that is ready to be read. + +## Returns: +`bool`: `true` if the TOML data was successfully loaded, `false` otherwise. diff --git a/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml/Methods/loads.md b/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml/Methods/loads.md new file mode 100644 index 00000000..624209bd --- /dev/null +++ b/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml/Methods/loads.md @@ -0,0 +1,10 @@ +# loads +This function loads TOML data stored as a string, doing it this way insures that TOML data can come from any source, e.g. dynamic string. + +`bool toml::loads(string str);` + +## Arguments: +- `string str`: The TOML data to load (as a string). + +## Returns: +`bool`: `true` if the data was successfully loaded, `false` otherwise. diff --git a/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml/Methods/remove.md b/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml/Methods/remove.md new file mode 100644 index 00000000..d78f7dc2 --- /dev/null +++ b/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml/Methods/remove.md @@ -0,0 +1,14 @@ +# remove +Removes a key or a section. + +`bool toml::remove(string section, string key = "");` + +## Arguments: +- `string section`: The section to remove or to look up the key from. +- `string key = ""`: The key to remove. If this is set to blank, the entire section (if any) will be removed. + +## Returns: +`bool`: `true` if the key or the section was successfully removed, `false` otherwise. + +## Remarks: +If the `key` argument is empty, the function will remove the entire section. Otherwise, it will remove a given key if exists. diff --git a/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml/Methods/set.md b/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml/Methods/set.md new file mode 100644 index 00000000..7bb35c76 --- /dev/null +++ b/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml/Methods/set.md @@ -0,0 +1,15 @@ +# set +Set a value in the TOML data given a section name, a key and a value. + +`bool toml::set(string section, string key = "", any@ value = null);` + +## Arguments: +- `string section`: The section to put this key/value pair in (leave blank to add at the top of the file without a section). +- `string key = ""`: The key to set. +- `any@ value = null`: The value to set, see main TOML Reader introduction for supported types. Unsupported type of value always returns `false`. + +## Returns: +`bool`: `true` if the value was successfully written, `false` otherwise. + +## Remarks: +If you specified a section that does not exist, it will automatically be created. diff --git a/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml_section/!toml_section.md b/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml_section/!toml_section.md new file mode 100644 index 00000000..86c805b0 --- /dev/null +++ b/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml_section/!toml_section.md @@ -0,0 +1,11 @@ +# toml_section +This class is integrated in `toml` class serving as a TOML section. + +`toml_section(string name, dictionary data = dictionary());` + +## Arguments: +- `string name`: The name of the section. Empty for the main top section. +- `dictionary data = dictionary()`: The data to associate with this section. + +## Remarks: +You don't usually use this class unless you are doing with each TOML section. diff --git a/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml_section/Methods/dump.md b/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml_section/Methods/dump.md new file mode 100644 index 00000000..0e137e46 --- /dev/null +++ b/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml_section/Methods/dump.md @@ -0,0 +1,13 @@ +# dump +Dump all loaded data into a string. + +`string toml_section::dump(bool head, int indent_size = 0, int indent_head_size = 0, bool empty_sections = true);` + +## Arguments: +- `bool head`: Should the data be prefixed with the section's name if the section name is not empty? For example, if the section is named test, the first line of the data will be `[test]`. +- `int indent_size = 0`: Indentation size. 0 is no indentation. +- `int indent_head_size = 0`: Indentation size for the header. This is the same with `indent_size` argument, but this is for the header itself if the `head` argument will be `true`. +- `bool empty_sections = true`: Should the data include empty sections? Default is usually ok. If this is used from the main TOML class, this value is retrieved from the main `dump` method of the TOML class. + +## Returns: +`string`: The entire TOML data of this section as a string. diff --git a/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml_section/Methods/get.md b/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml_section/Methods/get.md new file mode 100644 index 00000000..e2f387b2 --- /dev/null +++ b/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml_section/Methods/get.md @@ -0,0 +1,11 @@ +# get +Fetch a value from the TOML data given a key. + +`var@ toml_section::get(string key, var@ def = null);` + +## Arguments: +- `string key`: The key to get. +- `var@ def = null`: The default value if the key could not be retrieved. + +## Returns: +`var@`: The value at the particular key if found, the default value otherwise. diff --git a/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml_section/Methods/remove.md b/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml_section/Methods/remove.md new file mode 100644 index 00000000..41f2e1ee --- /dev/null +++ b/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml_section/Methods/remove.md @@ -0,0 +1,10 @@ +# remove +Removes a key. + +`bool toml_section::remove(string key);` + +## Arguments: +- `string key`: The key to remove. + +## Returns: +`bool`: `true` if the key was successfully removed, `false` otherwise. diff --git a/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml_section/Methods/set.md b/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml_section/Methods/set.md new file mode 100644 index 00000000..76cc73cd --- /dev/null +++ b/doc/src/references/include/TOML Reader and Writer (toml.nvgt)/classes/toml_section/Methods/set.md @@ -0,0 +1,11 @@ +# set +Set a value in the TOML data given a key and a value. + +`bool toml_section::set(string key, any@ value);` + +## Arguments: +- `string key`: The key to set. +- `any@ value`: The value to set. Supported types are the same with the ones in the main introduction documentation. + +## Returns: +`bool`: `true` if the value was successfully written, `false` otherwise. diff --git a/release/include/toml.nvgt b/release/include/toml.nvgt new file mode 100644 index 00000000..a7405606 --- /dev/null +++ b/release/include/toml.nvgt @@ -0,0 +1,223 @@ +/* toml.nvgt - TOML reader and writer + * + * Copyright (c) 2025-2026 [Harry Min Khant](https://github.com/harrymkt), under the same license as the NVGT: + * + * NVGT - NonVisual Gaming Toolkit + * Copyright (c) 2022-2025 Sam Tupy + * https://nvgt.gg + * This software is provided "as-is", without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. + * Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: + * 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. +*/ +class toml { + toml_section@[] sections(0); + toml() { + this.initialize_section(create = true); + } + bool load(string filename) { + file f; + if (!f.open(filename, "r")) return false; + bool result = this.load(f); + f.close(); + return result; + } + bool load(datastream@ f) { + if (@f != null && f.active) return this.loads(f.read()); + return false; + } + bool loads(string str) { + this.parse(str); + return this.sections.length() > 0; + } + toml_section@ initialize_section(string name = "", bool create = false) { + if (name == "" && this.sections.length() < 1 && create) { + this.sections.insert_last(toml_section(name)); + return @this.sections[0]; + } else if (name == "" && this.sections.length() > 0) return @this.sections[0]; + else if (name != "") { + int index = this.section_index[name]; + if (index < 0 && create) { + toml_section@ t = toml_section(name); + this.sections.insert_last(t); + return @t; + } + return (index > -1 ? @this.sections[index] : null); + } + return null; + } + void parse(string d) { + this.sections.resize(0); + toml_section@ t = this.initialize_section("", true); // Create the main section. + d.replace_this("\r\n", "\n"); + string[] lines = d.split("\n"); + if (lines.length() < 1) return; + for (uint i = 0; i < lines.length(); i++) { + string l = lines[i].trim_whitespace(); + if (l.starts_with("#")) continue; // Skip commented lines. + string key = l, value; // Initializes the key to the l (main value), since keys such as [section_name] would not contain assigned value. + int ei = l.find("="); + if (ei > -1) { + key = l.substr(0, ei).trim_whitespace(); // If the separator is found, trims the key, taking the content left of the separator. + value = l.substr(ei + 1).trim_whitespace(); // The value, the right content after the separator. + } + int comment = value.find("#"); + if (comment > -1) { + int q = value.rfind('"', comment); + if (q > -1) value = value.slice(0, q + 1); // Trims the right side comment out of a quoted string. + else { + q = value.find("#"); + if (q > -1) value = value.slice(0, q); + } + } + value.trim_whitespace_this(); // Make sure the whitespace trim verification is done. + string oneval = value.split(" ")[0]; // For values that cannot contain spaces, i.e comment with numbers, e.g., key = 24 #comment. + if (key == "") continue; // Empty line. + if (key.starts_with("[") && key.ends_with("]")) { // Section. + string name = ""; + if (key.length() > 2) name = key.slice(1, key.length() - 1); // For security, only trims the brackets when the key has more than 2 characters to avoid trimming empty sections, i.e, []. + @t = this.initialize_section(name, true); // Initializes a section, creating if necessary as the boolean parameter is set to true + continue; + } + if (value.starts_with('"') && value.ends_with('"')) // A string + t.set(key, value.slice(1, value.length() - 1)); + else if (oneval.find(".") > -1) { // Possibly floatingpoint number. + double f = parse_double(oneval); + t.set(key, f); + } else if (oneval.is_digits()) // Int / uint / these kinds. + t.set(key, parse_int(oneval)); + else if (value.lower() == "true" || value.lower() == "false") // Boolean + t.set(key, ((value.lower() == "true" ? true : false))); + else if (value.starts_with("[") && value.ends_with("]")) { // Array, nonfunctional at this time. + string[] va = value.split(","); + if (value.find('"') > -1) va = value.split('",'); + if (va.length() < 1) continue; + t.set(key, va); + } + } + } + string[] get_keys(string name, bool all = false) { + if (this.sections.length() < 1) return {}; + string[] k; + for (uint a = 0; a < this.sections.length(); a++) { + if ((!all && this.sections[a].name == name) || (all)) k.extend(this.sections[a].keys); + } + return {}; + } + string dump(bool indent = true, bool empty_sections = true) { + if (this.sections.length() < 1) return ""; + string final = ""; + for (uint a = 0; a < this.sections.length(); a++) { + int indentsize = (indent ? 1 : 0); + toml_section@ s = @this.sections[a]; + if (indent) { + string[] parse = (s.name).split("."); + if (parse.length() > 0) indentsize = parse.length(); + } + final += this.sections[a].dump(true, indentsize, indentsize - 1, empty_sections); + } + return final; + } + var@ get(string section, string key = "", var@ def = null) { + toml_section@ s = this.initialize_section(section); + if (@s == null) return def; + return s.get(key, def); + } + bool set(string section, string key = "", any@ value = null) { + toml_section@ t = this.initialize_section(section, true); + return @t != null && key != "" && t.set(key, value); + } + int get_section_index(string section) property { + if (this.sections.length() < 1) return -1; + for (uint a = 0; a < this.sections.length(); a++) { + if (this.sections[a].name == section) return a; + } + return -1; + } + bool remove(string section, string key = "") { + toml_section@ t = this.initialize_section(section); + if (@t == null) return false; + if (key != "") return t.remove(key); + int x = this.section_index[t.name]; + if (x > -1) { + @this.sections[x] = null; + this.sections.remove_at(x); + } + return this.section_index[t.name] < 0; + } +} +class toml_section { + dictionary data; + string name; + private string[] knames; // To maintain key order. + toml_section(string name, dictionary data = dictionary()) { + this.name = name; + this.data = data; + } + private bool add(string key, any@ v) { + if (@v is null) return false; + bool r = true; + string vstr; + double vdouble; + int vint; + bool vbool; + if (v.retrieve(vstr)) this.data.set(key, vstr); + else if (v.retrieve(vdouble)) this.data.set(key, vdouble); + else if (v.retrieve(vint)) this.data.set(key, vint); + else if (v.retrieve(vbool)) this.data.set(key, vbool); + else r = false; + return r; + } + bool set(string key, any@ value) { + if (!this.add(key, value)) return false; + if (this.knames.find(key) < 0) this.knames.insert_last(key); + return true; + } + string[] get_keys() property { + return this.knames; + } + var@ get(string key, var@ def = null) { + if (!this.data.exists(key)) return def; + string vstr; + double vdouble; + int vint; + bool vbool; + if (this.data.get(key, vstr)) return vstr; + else if (this.data.get(key, vdouble)) return vdouble; + else if (this.data.get(key, vint)) return vint; + else if (this.data.get(key, vbool)) return vbool; + else return def; + } + bool remove(string key) { + int f = this.knames.find(key); + if (f > -1) this.knames.remove_at(f); + if (!this.data.exists(key)) return false; + return this.data.delete(key); + } + string dump(bool head, int indent_size = 0, int indent_head_size = 0, bool empty_sections = true) { + if (this.keys.length() < 1 && !empty_sections) return ""; + string final = ""; + string indent, indenthead; + int ind = indent_size, indhead = indent_head_size; + while(ind > 0) { + indent += "\t"; + ind--; + } + while(indhead > 0) { + indenthead += "\t"; + indhead--; + } + if (head && this.name != "") final += "%0[%1]\n".format(indenthead, this.name); + string[] k = this.keys; + for (uint a = 0; a < k.length(); a++) { + var@ v = this.get(k[a]); + if (@v is null) continue; + string[] vals = {string(v)}; + if (v.is_string) vals = {'"', v, '"'}; + else if (v.is_boolean) vals = {(v == true ? "true" : "false")}; + final += (this.name != "" && head ? indent : "") + k[a] + " = " + join(vals, "") + "\n"; + } + return final; + } +} \ No newline at end of file diff --git a/test/data/test.toml b/test/data/test.toml new file mode 100644 index 00000000..60d34416 --- /dev/null +++ b/test/data/test.toml @@ -0,0 +1,5 @@ +description = "A library to interact with TOML data" +radio = true +[author] + name = "Harry Min Khant" + homepage = "https://harrymkt.github.io" \ No newline at end of file diff --git a/test/quick/TOML_Test.nvgt b/test/quick/TOML_Test.nvgt new file mode 100644 index 00000000..5d710afb --- /dev/null +++ b/test/quick/TOML_Test.nvgt @@ -0,0 +1,11 @@ +#include "toml.nvgt" +toml t; +void main() { + alert("Loaded", t.load("../data/test.toml")); + alert("Author", t.get("author", "name", "") + " <" + t.get("author", "homepage", "") + ">"); + for (uint a = 0; a < t.sections.length(); a++) + alert(t.sections[a].name, t.sections[a].dump(true, 1, 0)); + alert("Info removal", t.remove("author")); + alert("Info removal", t.remove("test.hahaha")); + alert("All dump copied", clipboard_set_raw_text(t.dump())); +} \ No newline at end of file