use bufio; use encoding::utf8; use errors; use io; use locale; use memio; use strings; export type scanner = struct { scanner: bufio::scanner, entry: entry, localized_entry: localized_entry, }; // 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 entry from a desktop entry file. The return value is // borrowed from the [[scanner]] use [[line_dup]] to retain a copy. 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; }; if (text == "") { // blank line return blank; } else if (strings::hasprefix(text, '#')) { // comment return strings::ltrim(text, '#'): comment; } else if (strings::hasprefix(text, '[')) { // group header return parse_group_header(text)?; } else { // key/value pair let (key, valu, local) = parse_entry(text)?; match (local) { case let local: locale::locale => localized_entry_finish(this.localized_entry); this.localized_entry = (key, valu, local); return this.localized_entry; case void => entry_finish(this.entry); this.entry = (key, valu); return this.entry; }; }; }; // Frees resources associated with a [[scanner]]. export fn finish(this: *scanner) void = { bufio::finish(&this.scanner); entry_finish(this.entry); }; // 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 must be freed by the caller fn parse_entry(line: str) ((str, str, (locale::locale | void)) | error) = { if (!strings::contains(line, '=')) return invalid_entry; let (key, valu) = strings::cut(line, "="); key = strings::ltrim(strings::rtrim(key)); if (!validate_entry_key(key)) return invalid_entry; let local: (locale::locale | void) = void; let (key, local_string) = strings::cut(key, "["); if (local_string != "") { local_string = strings::rtrim(local_string, ']'); if (!validate_entry_locale(local_string)) return invalid_entry; local = match(locale::parse(local_string)) { case let local: locale::locale => yield local; case locale::invalid => return invalid_entry; }; }; return (strings::dup(key), strings::dup(valu), local); }; fn validate_entry_key(key: str) bool = { return strings::contains(key, '[', ']', '='); }; fn validate_entry_locale(key: str) bool = { return strings::contains(key, '[', ']', '='); };