Move format::desktop_entry to format::xdg::ini

This commit is contained in:
2024-10-22 18:00:11 -04:00
parent 9ede109998
commit 4861950176
9 changed files with 1 additions and 1 deletions

8
format/xdg/ini/README Normal file
View File

@@ -0,0 +1,8 @@
The ini module implements the basic INI-like format used by the XDG Desktop
Entry Specification as described in
(https://specifications.freedesktop.org/desktop-entry-spec/latest). Since other
specifications make use of the basic desktop entry format (but with different
group names, entry requirements, etc.), this module only implements the
generalized format. This module will attempt to accept malformed files for the
purposes of retaining their underlying representation, and being able to
reproduce that representation when writing updated information to the files.

60
format/xdg/ini/error.ha Normal file
View File

@@ -0,0 +1,60 @@
use encoding::utf8;
use io;
// Any error that may occur during parsing.
export type error = !(syntaxerr | io::error | utf8::invalid);
// All syntax errors defined in this module.
export type syntaxerr = !(
invalid_group_header |
invalid_entry |
invalid_ascii |
invalid_escape |
invalid_boolean |
invalid_numeric |
invalid_integer |
expected_single);
// Returned when a malformed group header is encountered while parsing.
export type invalid_group_header = !void;
// Returned when a malformed entry is encountered while parsing.
export type invalid_entry = !void;
// Returned when ASCII text was expected while parsing, but something else was
// given.
export type invalid_ascii = !void;
// Returned when an invalid escape sequence was enctountered while parsing.
export type invalid_escape = !void;
// Returned when a boolean value was expected while parsing, but something else
// was given.
export type invalid_boolean = !void;
// Returned when a numeric value was expected while parsing, but something else
// was given.
export type invalid_numeric = !void;
// Returned when a numeric value was expected while parsing, but something else
// was given.
export type invalid_integer = !void;
// Returned when a singular value was expected while parsing, but multiple
// values were given.
export type expected_single = !void;
// Returns a user-friendly representation of [[error]]. The result may be
// statically allocated.
export fn strerror(err: error) str = match (err) {
case invalid_group_header => yield "invalid group header";
case invalid_entry => yield "invalid entry";
case invalid_ascii => yield "invalid ascii";
case invalid_escape => yield "invalid escape";
case invalid_boolean => yield "invalid boolean";
case invalid_numeric => yield "invalid numeric";
case invalid_integer => yield "invalid integer";
case expected_single => yield "expected single";
case let err: io::error => yield io::strerror(err);
case let err: utf8::invalid => yield utf8::strerror(err);
};

62
format/xdg/ini/line.ha Normal file
View File

@@ -0,0 +1,62 @@
use fmt;
use io;
use locale;
use strings;
// A line in the file, which can be a comment (or a blank line), an entry, or
// a localized entry.
export type line = (blank | comment | group_header | entry);
// A blank line.
// Specification: §3.1
export type blank = void;
// A comment.
// Specification: §3.1
export type comment = str;
// A group header.
// Specification: §3.2
export type group_header = str;
// An entry in a desktop file. Entries without an explicitly stated locale are
// assigned [[locale::c]].
// Specification: §3.3, §5
export type entry = struct {
group: str,
key: str,
value: str,
locale: locale::locale,
};
// Duplicates an [[entry]]. Use [[entry_finish]] to get rid of it.
export fn entry_dup(entr: entry) entry = entry {
group = strings::dup(entr.group),
key = strings::dup(entr.key),
value = strings::dup(entr.value),
locale = locale::dup(entr.locale),
};
// Frees memory associated with an [[entry]].
export fn entry_finish(entr: entry) void = {
free(entr.group);
free(entr.key);
free(entr.value);
locale::finish(entr.locale);
};
// Formats a [[line]] and writes it to an [[io::handle]].
export fn fprint(output: io::handle, lin: line) (size | io::error) = match (lin) {
case blank => yield fmt::fprintln(output);
case let lin: comment => yield fmt::fprintf(output, "#{}\n", lin: str);
case let lin: group_header => yield fmt::fprintf(output, "[{}]\n", lin: str);
case let lin: entry =>
let wrote = fmt::print(lin.key)?;
if (!locale::equal(locale::c, lin.locale)) {
let localestr = locale::format(lin.locale);
defer free(localestr);
wrote += fmt::printf("[{}]", localestr)?;
};
wrote += fmt::printf("={}\n", lin.value)?;
yield wrote;
};

133
format/xdg/ini/scan.ha Normal file
View File

@@ -0,0 +1,133 @@
use ascii;
use bufio;
use encoding::utf8;
use errors;
use io;
use locale;
use memio;
use strings;
export type scanner = struct {
scanner: bufio::scanner,
group: str,
};
// Creates a desktop entry file scanner. Use [[next]] to read lines. The caller
// must call [[finish]] once they're done with this object.
export fn scan(input: io::handle) scanner = scanner {
scanner = bufio::newscanner(input),
...
};
// Returns the next line from a desktop entry file. The return value is
// borrowed from the [[scanner]]. Use [[strings::dup]], [[entry_dup]], etc. to
// retain a copy. If all you want is the file's data, use [[next_entry]].
export fn next(this: *scanner) (line | io::EOF | error) = {
let text = match (bufio::scan_line(&this.scanner)?) {
case let text: const str => yield text;
case io::EOF => return io::EOF;
};
if (text == "") {
// blank line
return blank;
} else if (strings::hasprefix(text, '#')) {
// comment
return strings::ltrim(text, '#'): comment;
} else if (strings::hasprefix(text, '[')) {
// group header
let header = parse_group_header(text)?;
free(this.group);
this.group = strings::dup(header: str);
return header;
} else {
// key/value pair
let entry = parse_entry(text)?;
entry.group = this.group;
return entry;
};
};
// Returns the next entry from the desktop file, skipping over blank lines,
// comments, group headers, etc. The return value is borrowed from the
// [[scanner]]. Use [[entry_dup]] to retain a copy.
export fn next_entry(this: *scanner) (entry | io::EOF | error) = {
for (true) match(next(this)?) {
case let entr: entry => return entr;
case io::EOF => return io::EOF;
case => void;
};
};
// Frees resources associated with a [[scanner]].
export fn finish(this: *scanner) void = {
bufio::finish(&this.scanner);
free(this.group);
};
// memory is borrowed from the input
fn parse_group_header(text: str) (group_header | error) = {
if (!strings::hasprefix(text, '[') || !strings::hassuffix(text, ']')) {
return invalid_group_header;
};
text = strings::rtrim(strings::ltrim(text, '['), ']');
if (strings::contains(text, '[', ']')) {
return invalid_group_header;
};
return text: group_header;
};
// memory is borrowed from the input
fn parse_entry(line: str) (entry | error) = {
if (!strings::contains(line, '=')) return invalid_entry;
let (key, valu) = strings::cut(line, "=");
key = strings::ltrim(strings::rtrim(key));
let (key, local_string) = strings::cut(key, "[");
let local = if (local_string == "") {
yield locale::c;
} else {
local_string = strings::rtrim(local_string, ']');
validate_entry_locale(local_string)?;
yield match(locale::parse(local_string)) {
case let local: locale::locale => yield local;
case locale::invalid => return invalid_entry;
};
};
local.encoding = "";
validate_entry_key(key)?;
return entry {
key = key,
value = valu,
locale = local,
...
};
};
fn validate_entry_key(key: str) (void | error) = {
for (let byte .. strings::toutf8(key)) {
const ok =
(byte >= 'A' && byte <= 'Z') ||
(byte >= 'a' && byte <= 'z') ||
(byte >= '0' && byte <= '9') ||
(byte == '-');
if (!ok) return invalid_entry;
};
};
fn validate_entry_locale(locale: str) (void | error) = {
for (let byte .. strings::toutf8(locale)) {
const bad =
(byte == '[') ||
(byte == ']') ||
(byte == '=') ||
!ascii::isprint(byte: rune);
if (bad) return invalid_entry;
};
};

146
format/xdg/ini/scan_test.ha Normal file
View File

@@ -0,0 +1,146 @@
use fmt;
use io;
use locale;
use os;
@test fn next() void = {
let h_de = "Desktop Entry";
let h_dag = "Desktop Action Gallery";
let h_dac = "Desktop Action Create";
let correct: []line = [
"This is a comment": comment,
blank,
h_de: group_header,
entry_new(h_de, "Version", "1.0", locale::c),
entry_new(h_de, "Type", "Application", locale::c),
entry_new(h_de, "Name", "Foo Viewer", locale::c),
entry_new(h_de, "Comment", "The best viewer for Foo objects available!", locale::c),
entry_new(h_de, "TryExec", "fooview", locale::c),
entry_new(h_de, "Exec", "fooview %F", locale::c),
blank,
entry_new(h_de, "Icon", "fooview", locale::c),
entry_new(h_de, "MimeType", "image/x-foo;", locale::c),
entry_new(h_de, "Actions", "Gallery;Create;", locale::c),
blank,
h_dag: group_header,
entry_new(h_dag, "Exec", "fooview --gallery", locale::c),
entry_new(h_dag, "Name", "Browse Gallery", locale::c),
blank,
h_dac: group_header,
entry_new(h_dac, "Exec", "fooview --create-new", locale::c),
entry_new(h_dac, "Name", "Create a new Foo!", locale::c),
entry_new(
h_dac, "Name", "Create a new Foo!",
locale::parse("en_US")!),
entry_new(
h_dac, "Name", "Zweep zoop flooble glorp",
locale::parse("xx_XX.UTF-8")!),
"Another comment": comment,
entry_new(h_dac, "Icon", "fooview-new", locale::c),
];
let file = os::open("format/desktop_entry/test_data/foo.desktop")!;
defer io::close(file)!;
let scanne = scan(file);
defer finish(&scanne);
for (let correct .. correct) match(next(&scanne)!) {
case io::EOF =>
abort("encountered EOF early");
case blank =>
assert(correct is blank);
case let ln: comment =>
assert(ln == correct as comment);
case let ln: group_header =>
assert(ln == correct as group_header);
case let ln: entry =>
assert(entry_equal(ln, correct as entry));
};
assert(next(&scanne) is io::EOF);
};
@test fn next_entry() void = {
let h_de = "Desktop Entry";
let h_dag = "Desktop Action Gallery";
let h_dac = "Desktop Action Create";
let correct: []entry = [
entry_new(h_de, "Version", "1.0", locale::c),
entry_new(h_de, "Type", "Application", locale::c),
entry_new(h_de, "Name", "Foo Viewer", locale::c),
entry_new(h_de, "Comment", "The best viewer for Foo objects available!", locale::c),
entry_new(h_de, "TryExec", "fooview", locale::c),
entry_new(h_de, "Exec", "fooview %F", locale::c),
entry_new(h_de, "Icon", "fooview", locale::c),
entry_new(h_de, "MimeType", "image/x-foo;", locale::c),
entry_new(h_de, "Actions", "Gallery;Create;", locale::c),
entry_new(h_dag, "Exec", "fooview --gallery", locale::c),
entry_new(h_dag, "Name", "Browse Gallery", locale::c),
entry_new(h_dac, "Exec", "fooview --create-new", locale::c),
entry_new(h_dac, "Name", "Create a new Foo!", locale::c),
entry_new(
h_dac, "Name", "Create a new Foo!",
locale::parse("en_US")!),
entry_new(
h_dac, "Name", "Zweep zoop flooble glorp",
locale::parse("xx_XX.UTF-8")!),
entry_new(h_dac, "Icon", "fooview-new", locale::c),
];
let file = os::open("format/desktop_entry/test_data/foo.desktop")!;
defer io::close(file)!;
let scanne = scan(file);
defer finish(&scanne);
for (let correct .. correct) {
assert(entry_equal(next_entry(&scanne)! as entry, correct));
};
assert(next(&scanne) is io::EOF);
};
@test fn parse_entry() void = {
assert(entry_equal(parse_entry("hello=world")!, entry {
key = "hello",
value = "world",
locale = locale::c,
...
}));
assert(entry_equal(parse_entry("hello[sr_YU.UTF-8@Latn]=world")!, entry {
key = "hello",
value = "world",
locale = locale::parse("sr_YU.UTF-8@Latn")!,
...
}));
};
@test fn validate_entry_key() void = {
// §3.3 Only the characters A-Za-z0-9- may be used in key names.
assert(validate_entry_key("SomeName") is void);
assert(validate_entry_key("some-name") is void);
assert(validate_entry_key("1234567890some-0987654321naMe") is void);
assert(validate_entry_key("^&(*)[") is invalid_entry);
assert(validate_entry_key("]") is invalid_entry);
assert(validate_entry_key("&*%^") is invalid_entry);
assert(validate_entry_key("asj=hd@#*()") is invalid_entry);
};
@test fn validate_entry_locale() void = {
assert(validate_entry_locale("sr_YU.UTF-8@Latn") is void);
assert(validate_entry_locale("abcd[") is invalid_entry);
assert(validate_entry_locale("ab]cd") is invalid_entry);
assert(validate_entry_locale("a=bcd") is invalid_entry);
};
fn entry_equal(a: entry, b: entry) bool =
a.group == b.group &&
a.key == b.key &&
a.value == b.value &&
locale::equal(a.locale, b.locale);
fn entry_new(group: str, key: str, value: str, local: locale::locale) entry = entry {
group = group,
key = key,
value = value,
locale = local,
};

View File

@@ -0,0 +1,25 @@
#This is a comment
[Desktop Entry]
Version=1.0
Type=Application
Name=Foo Viewer
Comment=The best viewer for Foo objects available!
TryExec=fooview
Exec=fooview %F
Icon=fooview
MimeType=image/x-foo;
Actions=Gallery;Create;
[Desktop Action Gallery]
Exec=fooview --gallery
Name=Browse Gallery
[Desktop Action Create]
Exec=fooview --create-new
Name=Create a new Foo!
Name[en_US]=Create a new Foo!
Name[xx_XX.UTF-8]=Zweep zoop flooble glorp
#Another comment
Icon=fooview-new

View File

@@ -0,0 +1,28 @@
[Desktop Entry]
Version=1.0
Type=Application
Name=Foo Viewer
GenericName=Foo Viewer
GenericName[xx_XX.UTF-8]=Sneep glorp
Comment=The best viewer for Foo objects available!
TryExec=fooview
Exec=fooview %F
Icon=fooview
MimeType=image/x-foo;
Actions=Gallery;Create;
OnlyShowIn=MATE;KDE;
NotShowIn=GNOME
Categories=Graphics;Utility
Implements=com.example.Example;com.example.OtherExample
Keywords=foo;image;view;viewer
[Desktop Action Gallery]
Exec=fooview --gallery
Name=Browse Gallery
[Desktop Action Create]
Exec=fooview --create-new
Name=Create a new Foo!
Name[en_US]=Create a new Foo!
Name[xx_XX.UTF-8]=Zweep zoop flooble glorp
Icon=fooview-new

207
format/xdg/ini/value.ha Normal file
View File

@@ -0,0 +1,207 @@
use ascii;
use strconv;
use strings;
// Parses a string value. It may contain all ASCII characters except for control
// characters. The memory must be freed by the caller.
// Specification: §4
export fn parse_string(in: str) (str | error) = {
for (let char .. strings::toutf8(in)) {
if (!ascii::valid(char: rune)) return invalid_ascii;
};
return parse_localestring(in);
};
// Parses a localestring value. It is user displayable, and is encoded in UTF-8.
// The memory must be freed by the caller.
// Specification: §4
export fn parse_localestring(in: str) (str | error) = {
let escaper = escape_string(in);
let string = match(string_escaper_next(&escaper)?) {
case let string: str => yield string;
case done => return "";
};
if (len(escaper) > 0) {
free(string);
return expected_single;
};
return string;
};
// Parses an iconstring value. It is the name of an icon; it may be an absolute
// path, or a symbolic name for an icon located using the algorithm described in
// the Icon Theme Specification. Such values are not user-displayable, and are
// encoded in UTF-8. The memory must be freed by the caller.
// Specification: §4
export fn parse_iconstring(in: str) (str | error) = parse_localestring(in);
// Parses a boolean value. It must either be the string "true" or "false".
// Specification: §4
export fn parse_boolean(in: str) (bool | error) = {
if (strings::contains(in, ";")) return expected_single;
if (in == "true" ) return true;
if (in == "false") return false;
return invalid_boolean;
};
// Parses a numeric value. It must be a valid floating point number as
// recognized by the %f specifier for scanf in the C locale.
// Specification: §4
export fn parse_numeric(in: str) (f32 | error) = match (strconv::stof32(in)) {
case let float: f32 => yield float;
case =>
if (strings::contains(in, ";")) return expected_single;
yield invalid_numeric;
};
// Parses an integer value. While there is no documentation on this data type,
// it is referred to by the Icon Theme Specification, §4, tables 2-3.
export fn parse_integer(in: str) (int | error) = match(strconv::stoi(in)) {
case let integer: int => yield integer;
case =>
if (strings::contains(in, ";")) return expected_single;
yield invalid_integer;
};
// Parses multiple string values. See [[parse_string]] for more information. The
// memory must be freed by the caller.
// Specification: §4
export fn parse_strings(in: str) ([]str | error) = {
for (let char .. strings::toutf8(in)) {
if (!ascii::valid(char: rune)) return invalid_ascii;
};
return parse_localestrings(in);
};
// Parses multiple localestring values. See [[parse_localestring]] for more
// information. The memory must be freed by the caller.
// Specification: §4
export fn parse_localestrings(in: str) ([]str | error) = {
let escaper = escape_string(in);
let result: []str = alloc([], 0);
for (let in => string_escaper_next(&escaper)) {
match (in) {
case let string: str =>
append(result, string);
case let err: error =>
strings::freeall(result);
return err;
};
};
return result;
};
// Parses multiple iconstring values. See [[parse_iconstring]] for more
// information. The memory must be freed by the caller.
// Specification: §4
export fn parse_iconstrings(in: str) ([]str | error) = parse_localestrings(in);
// Parses multiple boolean values. See [[parse_boolean]] for more information.
// The memory must be freed by the caller.
// Specification: §4
export fn parse_booleans(in: str) ([]bool | error) = {
let splitte = split(in);
let result: []bool = alloc([], 0);
for (let in => splitter_next(&splitte)) {
match (parse_boolean(in)) {
case let boolean: bool =>
append(result, boolean);
case let err: error =>
free(result);
return err;
};
};
return result;
};
// Parses multiple numeric values. See [[parse_numeric]] for more information.
// The memory must be freed by the caller.
// Specification: §4
export fn parse_numerics(in: str) ([]f32 | error) = {
let splitte = split(in);
let result: []f32 = alloc([], 0);
for (let in => splitter_next(&splitte)) {
match (parse_numeric(in)) {
case let number: f32 =>
append(result, number);
case let err: error =>
free(result);
return err;
};
};
return result;
};
// Parses multiple integer values. See [[parse_integer]] for more information.
// The memory must be freed by the caller.
export fn parse_integers(in: str) ([]int | error) = {
let splitte = split(in);
let result: []int = alloc([], 0);
for (let in => splitter_next(&splitte)) {
match (parse_integer(in)) {
case let number: int =>
append(result, number);
case let err: error =>
free(result);
return err;
};
};
return result;
};
type string_escaper = []u8;
fn escape_string(in: str) string_escaper = strings::toutf8(in): string_escaper;
fn string_escaper_next(this: *string_escaper) (str | done | error) = {
if (len(*this) < 1) return done;
let result: []u8 = alloc([], 0);
let end = 0z;
let saw_escape = false;
for (let byte .. *this) {
end += 1;
if (saw_escape) {
switch (byte) {
case ';' => append(result, ';');
case 's' => append(result, ' ');
case 'n' => append(result, '\n');
case 't' => append(result, '\t');
case 'r' => append(result, '\r');
case '\\' => append(result, '\\');
case =>
free(result);
return invalid_escape;
};
saw_escape = false;
} else {
switch (byte) {
case ';' => break;
case '\\' => saw_escape = true;
case => append(result, byte);
};
};
};
*this = (*this)[end..];
return strings::fromutf8_unsafe(result);
};
type splitter = []u8;
fn split(in: str) splitter = strings::toutf8(in): splitter;
fn splitter_next(this: *splitter) (str | done) = {
if (len(*this) < 1) return done;
let end = 0z;
let knife = 0z;
for (let byte .. *this) {
knife += 1;
if (byte == ';') {
break;
} else {
end += 1;
};
};
let result = this[..end];
*this = (*this)[knife..];
return strings::fromutf8_unsafe(result);
};

View File

@@ -0,0 +1,232 @@
use fmt;
use math;
use strings;
@test fn parse_string() void = {
assert(parse_string("hello")! == "hello");
assert(parse_string("hel\\s\\n\\t\\r\\\\\\;lo")! == "hel \n\t\r\\;lo");
assert(parse_string("hello☠") is invalid_ascii);
assert(parse_string("hello;world") is expected_single);
assert(parse_string("hello\\d") is invalid_escape);
};
@test fn parse_localestring() void = {
assert(parse_localestring("hello")! == "hello");
assert(parse_localestring("hel\\s\\n\\t\\r\\\\\\;lo")! == "hel \n\t\r\\;lo");
assert(parse_localestring("hello☠")! == "hello☠");
assert(parse_localestring("hello;world") is expected_single);
assert(parse_localestring("hello\\d") is invalid_escape);
};
@test fn parse_iconstring() void = {
assert(parse_iconstring("hello")! == "hello");
assert(parse_iconstring("hel\\s\\n\\t\\r\\\\\\;lo")! == "hel \n\t\r\\;lo");
assert(parse_iconstring("hello☠")! == "hello☠");
assert(parse_iconstring("hello;world") is expected_single);
assert(parse_iconstring("hello\\d") is invalid_escape);
};
@test fn parse_boolean() void = {
assert(parse_boolean("true")! == true);
assert(parse_boolean("false")! == false);
assert(parse_boolean("hello") is invalid_boolean);
assert(parse_boolean("ttrue") is invalid_boolean);
assert(parse_boolean("falsee") is invalid_boolean);
assert(parse_boolean("") is invalid_boolean);
assert(parse_boolean("1") is invalid_boolean);
assert(parse_boolean("0") is invalid_boolean);
assert(parse_boolean("true;false") is expected_single);
};
@test fn parse_numeric() void = {
assert(parse_numeric("9")! == 9.0f32);
assert(parse_numeric("9.0")! == 9.0f32);
assert(parse_numeric("34.93")! == 34.93f32);
assert(parse_numeric("-100.895")! == -100.895f32);
assert(math::isnan(parse_numeric("NaN")!));
assert(parse_numeric("Infinity")! == math::INF);
assert(parse_numeric("hello") is invalid_numeric);
assert(parse_numeric("--") is invalid_numeric);
assert(parse_numeric("....") is invalid_numeric);
assert(parse_numeric("234;7.4") is expected_single);
};
@test fn parse_integer() void = {
assert(parse_integer("9")! == 9);
assert(parse_integer("2348")! == 2348);
assert(parse_integer("-324")! == -324);
assert(parse_integer("324.9") is invalid_integer);
assert(parse_integer("324.0") is invalid_integer);
};
@test fn parse_strings() void = {
let correct: []str = [
"b\r\tird",
"wa;ter",
"",
"riv\ner",
"nuh uh",
];
let got = parse_strings("b\\r\\tird;wa\\;ter;;riv\\ner;nuh uh")!;
defer strings::freeall(got);
for (let index = 0z; index < len(correct); index += 1) {
assert(index < len(got), "ran out");
assert(compare_strings(correct[index], got[index]));
};
assert(len(got) == len(correct), "not done");
assert(parse_strings("hello☠;world") is invalid_ascii);
assert(parse_strings("hello\\d;world") is invalid_escape);
};
@test fn parse_localestrings() void = {
let correct: []str = [
"b\r\tir☠d",
"wa;ter",
"",
"ri☠v\ner",
"nuh uh",
];
let got = parse_localestrings("b\\r\\tir☠d;wa\\;ter;;ri☠v\\ner;nuh uh")!;
defer strings::freeall(got);
for (let index = 0z; index < len(correct); index += 1) {
assert(index < len(got), "ran out");
assert(compare_strings(correct[index], got[index]));
};
assert(len(got) == len(correct), "not done");
assert(parse_strings("hello\\d;world") is invalid_escape);
};
@test fn parse_booleans() void = {
let correct: []bool = [
true,
true,
false,
true,
false,
true,
];
let got = parse_booleans("true;true;false;true;false;true")!;
defer free(got);
for (let index = 0z; index < len(correct); index += 1) {
assert(index < len(got), "ran out");
let correct = correct[index];
let got = got[index];
fmt::printf("[{}]\t[{}]\n", correct, got)!;
assert(correct == got);
};
assert(len(got) == len(correct), "not done");
assert(parse_booleans("hello;world") is invalid_boolean);
assert(parse_booleans("true;;") is invalid_boolean);
assert(parse_booleans(";false;") is invalid_boolean);
};
@test fn parse_numerics() void = {
let correct: []f64 = [
5.0f32,
34.9f32,
29.0f32,
32498.23784f32,
];
let got = parse_numerics("5;34.9;29;32498.23784;")!;
defer free(got);
for (let index = 0z; index < len(correct); index += 1) {
assert(index < len(got), "ran out");
let correct = correct[index];
let got = got[index];
fmt::printf("[{}]\t[{}]\n", correct, got)!;
assert(correct == got);
};
assert(len(got) == len(correct), "not done");
assert(parse_numerics("hello;world") is invalid_numeric);
assert(parse_numerics("5;;") is invalid_numeric);
assert(parse_numerics(";5;") is invalid_numeric);
};
@test fn parse_integers() void = {
let correct: []int = [
5,
34,
-29,
32498,
];
let got = parse_integers("5;34;-29;32498")!;
defer free(got);
for (let index = 0z; index < len(correct); index += 1) {
assert(index < len(got), "ran out");
let correct = correct[index];
let got = got[index];
fmt::printf("[{}]\t[{}]\n", correct, got)!;
assert(correct == got);
};
assert(len(got) == len(correct), "not done");
assert(parse_integers("hello;world") is invalid_integer);
assert(parse_integers("5;;") is invalid_integer);
assert(parse_integers(";5;") is invalid_integer);
};
@test fn string_escaper_next_a() void = {
let escaper = escape_string("bird;water;;river;");
let correct: []str = [
"bird",
"water",
"",
"river",
];
for (let correct .. correct) {
let got = string_escaper_next(&escaper) as str;
assert(compare_strings(correct, got));
};
assert(string_escaper_next(&escaper) is done, "not done");
};
@test fn string_escaper_next_b() void = {
let escaper = escape_string("b\\r\\tird;wa\\;ter;;riv\\ner;nuh uh");
let correct: []str = [
"b\r\tird",
"wa;ter",
"",
"riv\ner",
"nuh uh",
];
for (let correct .. correct) {
let got = string_escaper_next(&escaper) as str;
assert(compare_strings(correct, got));
};
assert(string_escaper_next(&escaper) is done, "not done");
};
@test fn splitter_next_a() void = {
let splitte = split("bird;water;;river;");
let correct: []str = [
"bird",
"water",
"",
"river",
];
for (let correct .. correct) {
let got = splitter_next(&splitte) as str;
assert(compare_strings(correct, got));
};
assert(splitter_next(&splitte) is done, "not done");
};
@test fn splitter_next_b() void = {
let splitte = split("bird;water;;river");
let correct: []str = [
"bird",
"water",
"",
"river",
];
for (let correct .. correct) {
let got = splitter_next(&splitte) as str;
fmt::printf("[{}]\t[{}]\n", correct, got)!;
assert(compare_strings(correct, got));
};
assert(splitter_next(&splitte) is done, "not done");
};
fn compare_strings(a: str, b: str) bool = {
fmt::printf("[{}]\t[{}]\n", a, b)!;
return a == b;
};