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 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 let header = parse_group_header(text)?; free(this.group); this.group = 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)); if (!validate_entry_key(key)) return invalid_entry; let (key, local_string) = strings::cut(key, "["); let local = if (local_string != "") { yield locale::c; } else { local_string = strings::rtrim(local_string, ']'); if (!validate_entry_locale(local_string)) return invalid_entry; yield match(locale::parse(local_string)) { case let local: locale::locale => yield local; case locale::invalid => return invalid_entry; }; }; return entry { key = key, value = valu, locale = local, ... }; }; fn validate_entry_key(key: str) bool = { return strings::contains(key, '[', ']', '='); }; fn validate_entry_locale(key: str) bool = { return strings::contains(key, '[', ']', '='); };