Implement querying of localized values of desktop files

This commit is contained in:
Sasha Koshka 2024-10-05 00:40:43 -04:00
parent 3e3c794a1e
commit 0b464d368c

View File

@ -1,59 +1,62 @@
use locale;
use strings;
// Represents a file of the generic key/value format used by desktop entries. // Represents a file of the generic key/value format used by desktop entries.
// Specification: §3 // Specification: §3
type file = struct { export type file = struct {
preceeding_lines: []line; preceeding_lines: []line,
groups: []group; groups: []group,
}; };
// A named group of key/value entries. // A named group of key/value entries.
// Specification: §3.2 // Specification: §3.2
type group = struct { export type group = struct {
name: str; name: str,
lines: []line; lines: []line,
} };
// A line in the file, which can be a comment (or a blank line), an entry, or // A line in the file, which can be a comment (or a blank line), an entry, or
// a localized entry. // a localized entry.
type line = (comment | entry | localized_entry); export type line = (comment | entry | localized_entry);
// A comment. // A comment.
// Specification: §3.1 // Specification: §3.1
type comment = str; export type comment = str;
// A key/value pair. // A key/value pair.
// Specification: §3.3 // Specification: §3.3
type entry = struct { export type entry = struct {
key: str; key: str,
value: value; value: value,
}; };
// A localized entry. // A localized entry.
// Specification: §5 // Specification: §5
type localized_entry struct { export type localized_entry = struct {
key: str; key: str,
value: value; value: value,
locale: locale::locale; locale: locale::locale,
}; };
// An entry value. Values that reference memory (such as [[str]]) are borrowed // 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 // 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. // 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 // 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. // 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)) { let grou = match (file_find_group(fil, group_name)) {
case index: size => yield &file.groups[index]; case let index: size => yield &fil.groups[index];
case void => return void; case void => return void;
}; };
let lin = match (group_find_entry(grou, key)) { let lin = match (group_find_entry(grou, key)) {
case index: size => yield &grou.lines[index]; case let index: size => yield grou.lines[index];
case void => return void; case void => return void;
}; };
match (lin) { match (lin) {
case entr: entry => return entr.value; case let entr: entry => return entr.value;
case => abort(); case => abort();
}; };
}; };
@ -65,67 +68,131 @@ export fn file_get_localized(
fil: *file, fil: *file,
group_name: str, group_name: str,
key: str, key: str,
local: locale, local: locale::locale,
) (value | void) { ) (value | void) = {
let grou = match (file_find_group(fil, group_name)) { let grou = match (file_find_group(fil, group_name)) {
case index: size => yield &file.groups[index]; case let index: size => yield &fil.groups[index];
case void => return void; case void => return void;
}; };
let lin = match (group_find_localized_entry(grou, key, local)) { let lin = match (group_find_localized_entry(grou, key, local)) {
case index: size => yield &grou.lines[index]; case let index: size => yield grou.lines[index];
case void => return void; case void =>
yield match(group_find_entry(grou, key)) {
case let index: size => yield index;
case void => return void;
};
}; };
match (lin) { match (lin) {
case entr: localized_entry => return entr.value; case let entr: localized_entry => return entr.value;
case entr: entry => return entr.value; case let entr: entry => return entr.value;
case => abort(); 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) = { fn file_find_group(fil: *file, group_name: str) (size | void) = {
let index = 0; let index = 0z;
for (let grou .. file.groups) { for (let grou .. fil.groups) {
if (strings::compare(group.name, group_name) == 0) { if (grou.name == group_name) {
return index; return index;
} };
index ++; index += 1;
} };
} };
fn group_find_entry (grou *group, key str): (size | void) { fn group_find_entry(grou: *group, key: str) (size | void) = {
let index = 0; let index = 0z;
for (let lin .. grou.lines) { for (let lin .. grou.lines) {
match (lin) { match (lin) {
case entr: entry => case let entr: entry =>
if (strings::compare(entry.key, entr) == 0) { if (entr.key == key) {
return index; return index;
} };
case => void ; case => void;
} };
index ++; 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. // Duplicates a [[value]]. Use [[value_finish]] to get rid of it.
export fn value_dup(valu: value): value = match (valu) { export fn value_dup(valu: value) value = match (valu) {
case let valu: str => yield strings.dup(valu); case let valu: str => yield strings::dup(valu);
case => yield valu; case => yield valu;
}; };
// Frees an [[entry]] previously duplicated with [[entry_dup]]. // 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 let valu: str => free(valu);
case => void; case => void;
}; };