hare-xdg/format/desktop_entry/scan.ha

122 lines
3.1 KiB
Hare
Raw Normal View History

2024-10-12 20:12:43 -06:00
use bufio;
2024-10-12 21:34:57 -06:00
use encoding::utf8;
2024-10-12 21:43:07 -06:00
use errors;
2024-10-12 20:12:43 -06:00
use io;
2024-10-12 21:34:57 -06:00
use locale;
2024-10-12 20:12:43 -06:00
use memio;
2024-10-12 21:34:57 -06:00
use strings;
2024-10-12 20:12:43 -06:00
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),
...
};
2024-10-12 20:12:43 -06:00
2024-10-19 14:23:13 -06:00
// 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 let err: (io::error | utf8::invalid) =>
return err;
case io::EOF =>
return io::EOF;
};
2024-10-12 20:12:43 -06:00
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 = header: str;
return header;
2024-10-12 20:12:43 -06:00
} else {
// key/value pair
let entry = parse_entry(text)?;
entry.group = this.group;
return entry;
2024-10-12 20:12:43 -06:00
};
2024-10-12 21:43:07 -06:00
};
2024-10-19 14:23:13 -06:00
// 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);
2024-10-12 20:12:43 -06:00
};
// memory is borrowed from the input
fn parse_group_header(text: str) (group_header | error) = {
2024-10-12 21:34:57 -06:00
if (!strings::hasprefix(text, '[') || !strings::hassuffix(text, ']')) {
return invalid_group_header;
2024-10-12 20:12:43 -06:00
};
text = strings::rtrim(strings::ltrim(text, '['), ']');
2024-10-12 21:34:57 -06:00
if (strings::contains(text, '[', ']')) {
return invalid_group_header;
2024-10-12 20:12:43 -06:00
};
return text: group_header;
2024-10-12 20:12:43 -06:00
};
// memory is borrowed from the input
fn parse_entry(line: str) (entry | error) = {
2024-10-12 21:34:57 -06:00
if (!strings::contains(line, '=')) return invalid_entry;
let (key, valu) = strings::cut(line, "=");
2024-10-12 20:12:43 -06:00
key = strings::ltrim(strings::rtrim(key));
2024-10-12 21:34:57 -06:00
if (!validate_entry_key(key)) return invalid_entry;
2024-10-12 20:12:43 -06:00
2024-10-12 21:34:57 -06:00
let (key, local_string) = strings::cut(key, "[");
let local = if (local_string != "") {
yield locale::c;
} else {
2024-10-12 21:43:07 -06:00
local_string = strings::rtrim(local_string, ']');
2024-10-12 21:34:57 -06:00
if (!validate_entry_locale(local_string)) return invalid_entry;
2024-10-12 20:12:43 -06:00
yield match(locale::parse(local_string)) {
2024-10-12 21:34:57 -06:00
case let local: locale::locale => yield local;
2024-10-12 21:51:52 -06:00
case locale::invalid => return invalid_entry;
2024-10-12 20:12:43 -06:00
};
};
return entry {
key = key,
value = valu,
locale = local,
...
};
2024-10-12 20:12:43 -06:00
};
fn validate_entry_key(key: str) bool = {
return strings::contains(key, '[', ']', '=');
};
fn validate_entry_locale(key: str) bool = {
return strings::contains(key, '[', ']', '=');
};