From 0b464d368cc39fecf3a92b65d1d6a50043b380b1 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sat, 5 Oct 2024 00:40:43 -0400 Subject: [PATCH] Implement querying of localized values of desktop files --- format/desktop_entry/desktop_entry.ha | 187 +++++++++++++++++--------- 1 file changed, 127 insertions(+), 60 deletions(-) diff --git a/format/desktop_entry/desktop_entry.ha b/format/desktop_entry/desktop_entry.ha index 3b210d1..1ca1478 100644 --- a/format/desktop_entry/desktop_entry.ha +++ b/format/desktop_entry/desktop_entry.ha @@ -1,59 +1,62 @@ +use locale; +use strings; + // Represents a file of the generic key/value format used by desktop entries. // Specification: §3 -type file = struct { - preceeding_lines: []line; - groups: []group; +export type file = struct { + preceeding_lines: []line, + groups: []group, }; // A named group of key/value entries. // Specification: §3.2 -type group = struct { - name: str; - lines: []line; -} +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. -type line = (comment | entry | localized_entry); +export type line = (comment | entry | localized_entry); // A comment. // Specification: §3.1 -type comment = str; +export type comment = str; // A key/value pair. // Specification: §3.3 -type entry = struct { - key: str; - value: value; +export type entry = struct { + key: str, + value: value, }; // A localized entry. // Specification: §5 -type localized_entry struct { - key: str; - value: value; - locale: locale::locale; +export type localized_entry = struct { + key: str, + value: value, + locale: locale::locale, }; // An entry value. Values that reference memory (such as [[str]]) are borrowed // from the [[file]]. These may be free'd or overwritten when other functions in // this module are called, so [[value_dup]] is required to extend its lifetime. -export type value = (str, bool, f32); +export type value = (str | bool | f32); // 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) (value | void) { +export fn file_get(fil: *file, group_name: str, key: str) (value | void) = { let grou = match (file_find_group(fil, group_name)) { - case index: size => yield &file.groups[index]; - case void => return void; + case let index: size => yield &fil.groups[index]; + case void => return void; }; let lin = match (group_find_entry(grou, key)) { - case index: size => yield &grou.lines[index]; - case void => return void; + case let index: size => yield grou.lines[index]; + case void => return void; }; match (lin) { - case entr: entry => return entr.value; - case => abort(); + case let entr: entry => return entr.value; + case => abort(); }; }; @@ -65,67 +68,131 @@ export fn file_get_localized( fil: *file, group_name: str, key: str, - local: locale, -) (value | void) { + local: locale::locale, +) (value | void) = { let grou = match (file_find_group(fil, group_name)) { - case index: size => yield &file.groups[index]; - case void => return void; + case let index: size => yield &fil.groups[index]; + case void => return void; }; let lin = match (group_find_localized_entry(grou, key, local)) { - case index: size => yield &grou.lines[index]; - case void => return void; + 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 entr: localized_entry => return entr.value; - case entr: entry => return entr.value; - case => abort(); + case let entr: localized_entry => return entr.value; + case let entr: entry => return entr.value; + case => abort(); }; }; -export fn file_add +//export fn file_add -export fn file_add_localized +//export fn file_add_localized -export fn file_remove +//export fn file_remove -export fn file_remove_localized +//export fn file_remove_localized -export fn file_encode +//export fn file_encode -export fn file_finish +//export fn file_finish -fn file_find_group (fil *file, group_name str): (size | void) = { - let index = 0; - for (let grou .. file.groups) { - if (strings::compare(group.name, group_name) == 0) { +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 ++; - } -} + }; + index += 1; + }; +}; -fn group_find_entry (grou *group, key str): (size | void) { - let index = 0; +fn group_find_entry(grou: *group, key: str) (size | void) = { + let index = 0z; for (let lin .. grou.lines) { match (lin) { - case entr: entry => - if (strings::compare(entry.key, entr) == 0) { + case let entr: entry => + if (entr.key == key) { return index; - } - case => void ; - } - 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; + }; +}; // Duplicates a [[value]]. Use [[value_finish]] to get rid of it. -export fn value_dup(valu: value): value = match (valu) { -case let valu: str => yield strings.dup(valu); +export fn value_dup(valu: value) value = match (valu) { +case let valu: str => yield strings::dup(valu); case => yield valu; }; // Frees an [[entry]] previously duplicated with [[entry_dup]]. -export fn value_finish(valu: value): void = match (valu) { +export fn value_finish(valu: value) void = match (valu) { case let valu: str => free(valu); case => void; };