use locale; use strings; // Represents a file of the generic key/value format used by desktop entries. // Specification: §3 export type file = struct { preceeding_lines: []line, groups: []group, }; // A named group of key/value entries. // Specification: §3.2 export type group = struct { name: str, lines: []line, }; // 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 | entry | localized_entry); // A blank line. // Specification: §3.1 export type blank = void; // A comment. // Specification: §3.1 export type comment = str; // A key/value pair. // Specification: §3.3 export type entry = struct { key: str, value: str, }; // A localized key/value pair. // Specification: §5 export type localized_entry = struct { key: str, value: str, locale: locale::locale, }; // Gets a non-localized [[value]] from a [[file]]. If the group does not exist, // or the group exists but the key isn't in it, it returns void. export fn file_get(fil: *file, group_name: str, key: str) (str | void) = { let grou = match (file_find_group(fil, group_name)) { case let index: size => yield fil.groups[index]; case void => return void; }; let lin = match (group_find_entry(grou, key)) { case let index: size => yield grou.lines[index]; case void => return void; }; match (lin) { case let entr: entry => return entr.value; case => abort(); }; }; // Gets a localized [[value]] from a [[file]]. If the group does not exist, or // the group exists but the key isnt in it, it returns void. If the key is in // the group but is not localized to the specified [[locale::locale]], the // non-localized value is returned. Refer to the specification for the exact way // which the locale is matched. // Specification: §5 export fn file_get_localized( fil: *file, group_name: str, key: str, local: locale::locale, ) (str | void) = { let grou = match (file_find_group(fil, group_name)) { case let index: size => yield fil.groups[index]; case void => return void; }; let lin = match (group_find_localized_entry(grou, key, local)) { case let index: size => yield grou.lines[index]; case void => yield match(group_find_entry(grou, key)) { case let index: size => yield index; case void => return void; }; }; match (lin) { case let entr: localized_entry => return entr.value; case let entr: entry => return entr.value; case => abort(); }; }; // TODO //export fn file_add //export fn file_add_localized //export fn file_remove //export fn file_remove_localized //export fn file_encode // Frees memory associated with a [[file]]. export fn file_finish(fil: *file) void = { for (let lin .. fil.preceeding_lines) { line_finish(lin); }; free(fil.preceeding_lines); for (let grou .. fil.groups) { group_finish(grou); }; free(fil.groups); }; // Frees memory associated with a [[line]]. export fn line_finish(lin: line) void = match (lin) { case let lin: comment => free(lin); case let lin: entry => free(lin.key); free(lin.value); case let lin: localized_entry => free(lin.key); free(lin.value); locale::finish(lin.locale); case => void; }; // Frees memory associated with a [[group]]. export fn group_finish(grou: group) void = { for (let lin .. grou.lines) { line_finish(lin); }; free(grou.lines); }; fn file_find_group(fil: *file, group_name: str) (size | void) = { let index = 0z; for (let grou .. fil.groups) { if (grou.name == group_name) { return index; }; index += 1; }; }; fn group_find_entry(grou: group, key: str) (size | void) = { let index = 0z; for (let lin .. grou.lines) { match (lin) { case let entr: entry => if (entr.key == key) { return index; }; case => void; }; index += 1; }; }; fn group_find_localized_entry(grou: group, key: str, local: locale::locale) (size | void) = { // The matching is done as follows. If LC_MESSAGES is of the form // lang_COUNTRY.ENCODING@MODIFIER, then it will match a key of the form // lang_COUNTRY@MODIFIER. If such a key does not exist, it will attempt // to match lang_COUNTRY followed by lang@MODIFIER. Then, a match // against lang by itself will be attempted. Finally, if no matching // key is found the required key without a locale specified is used. // The encoding from the LC_MESSAGES value is ignored when matching. // // If LC_MESSAGES does not have a MODIFIER field, then no key with a // modifier will be matched. Similarly, if LC_MESSAGES does not have a // COUNTRY field, then no key with a country specified will be matched. // If LC_MESSAGES just has a lang field, then it will do a straight // match to a key with a similar value. let lang_country_modifier = local; lang_country_modifier.encoding = ""; match(group_find_localized_entry_exact(grou, key, lang_country_modifier)) { case let index: size => return index; case => void; }; let lang_country = lang_country_modifier; lang_country.modifier = ""; match(group_find_localized_entry_exact(grou, key, lang_country)) { case let index: size => return index; case => void; }; let lang_modifier = lang_country_modifier; lang_modifier.country = ""; match(group_find_localized_entry_exact(grou, key, lang_modifier)) { case let index: size => return index; case => void; }; let lang = lang_modifier; lang.modifier = ""; match(group_find_localized_entry_exact(grou, key, lang)) { case let index: size => return index; case => void; }; return void; }; fn group_find_localized_entry_exact(grou: group, key: str, local: locale::locale) (size | void) = { let index = 0z; for (let lin .. grou.lines) { match (lin) { case let entr: localized_entry => if (entr.key == key && locale::equal(entr.locale, local)) { return index; }; case => void; }; index += 1; }; };