Compare commits
No commits in common. "0362ae4515803382e1ae24ab6c5e0f3bd18d3fdb" and "6af621213af253dc0d6531056226245387c2136b" have entirely different histories.
0362ae4515
...
6af621213a
@ -1,189 +0,0 @@
|
|||||||
use locale;
|
|
||||||
use strings;
|
|
||||||
|
|
||||||
// The information from a desktop entry file.
|
|
||||||
export type file = struct {
|
|
||||||
typ: str,
|
|
||||||
version: str,
|
|
||||||
name: locale::string,
|
|
||||||
generic_name: locale::string,
|
|
||||||
no_display: bool,
|
|
||||||
comment: locale::string,
|
|
||||||
icon: locale::string,
|
|
||||||
hidden: bool,
|
|
||||||
only_show_in: []str,
|
|
||||||
not_show_in: []str,
|
|
||||||
dbus_activatable: bool,
|
|
||||||
try_exec: str,
|
|
||||||
exec: str,
|
|
||||||
path: str,
|
|
||||||
terminal: bool,
|
|
||||||
actions: []str,
|
|
||||||
mime_type: []str,
|
|
||||||
categories: []str,
|
|
||||||
implements: []str,
|
|
||||||
keywords: locale::strings,
|
|
||||||
startup_notify: bool,
|
|
||||||
startup_wm_class: str,
|
|
||||||
url: str,
|
|
||||||
prefers_non_default_gpu: bool,
|
|
||||||
single_main_window: bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Parses a desktop entry file. Use [[file_finish]] to get rid of it.
|
|
||||||
export fn parse(input: io::handle) (file | error) = {
|
|
||||||
let state = parse_state { ... };
|
|
||||||
let scanne = scan(input);
|
|
||||||
defer finish(&scanne);
|
|
||||||
|
|
||||||
for (let entr => next_entry(&scanne)) {
|
|
||||||
match(parse_handle_entry(entr)) {
|
|
||||||
case let err: error =>
|
|
||||||
file_finish(&state.file);
|
|
||||||
return err;
|
|
||||||
case void => void;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Frees resources associated with a [[file]].
|
|
||||||
export fn file_finish(file: &file) void = {
|
|
||||||
free(file.typ);
|
|
||||||
free(file.version);
|
|
||||||
locale_string_finish(file.name);
|
|
||||||
locale_string_finish(file.generic_name);
|
|
||||||
locale_string_finish(file.comment);
|
|
||||||
locale_string_finish(file.icon);
|
|
||||||
strings::freeall(file.only_show_in);
|
|
||||||
strings::freeall(file.not_show_in);
|
|
||||||
free(file.try_exec);
|
|
||||||
free(file.exec);
|
|
||||||
free(file.path);
|
|
||||||
strings::freeall(file.actions);
|
|
||||||
strings::freeall(file.mime_type);
|
|
||||||
strings::freeall(file.categories);
|
|
||||||
strings::freeall(file.implements);
|
|
||||||
locale_strings_finish(file.keywords);
|
|
||||||
free(file.startup_wm_class);
|
|
||||||
free(file.url);
|
|
||||||
};
|
|
||||||
|
|
||||||
type parse_state = struct {
|
|
||||||
file: file,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn parse_handle_entry(this: *parse_state, entr: entry) (void | error) = {
|
|
||||||
if (entr.group != "Desktop Entry") return void;
|
|
||||||
|
|
||||||
if (entr.key == "Type") {
|
|
||||||
if parse_is_localized(entr) return void;
|
|
||||||
this.typ = parse_string(entr.value)?;
|
|
||||||
} else if (entr.key == "Version") {
|
|
||||||
if parse_is_localized(entr) return void;
|
|
||||||
this.version = parse_string(entr.value)?;
|
|
||||||
} else if (entr.key == "Name") {
|
|
||||||
parse_set_locale_string(
|
|
||||||
*this.name, entr.locale,
|
|
||||||
parse_localestring(entr.value)?)?;
|
|
||||||
} else if (entr.key == "GenericName") {
|
|
||||||
parse_set_locale_string(
|
|
||||||
*this.generic_name, entr.locale,
|
|
||||||
parse_localestring(entr.value)?)?;
|
|
||||||
} else if (entr.key == "NoDisplay") {
|
|
||||||
if parse_is_localized(entr) return void;
|
|
||||||
this.no_display = parse_boolean(entr.value)?;
|
|
||||||
} else if (entr.key == "Comment") {
|
|
||||||
parse_set_locale_string(
|
|
||||||
*this.comment, entr.locale,
|
|
||||||
parse_localestring(entr.value)?)?;
|
|
||||||
} else if (entr.key == "Icon") {
|
|
||||||
parse_set_locale_string(
|
|
||||||
*this.icon, entr.locale,
|
|
||||||
parse_iconstring(entr.value)?)?;
|
|
||||||
} else if (entr.key == "Hidden") {
|
|
||||||
if parse_is_localized(entr) return void;
|
|
||||||
this.hidden = parse_boolean(entr.value)?;
|
|
||||||
} else if (entr.key == "OnlyShowIn") {
|
|
||||||
if parse_is_localized(entr) return void;
|
|
||||||
this.only_show_in = parse_strings(entr.value)?;
|
|
||||||
} else if (entr.key == "NotShowIn") {
|
|
||||||
if parse_is_localized(entr) return void;
|
|
||||||
this.not_show_in = parse_strings(entr.value)?;
|
|
||||||
} else if (entr.key == "DBusActivatable") {
|
|
||||||
if parse_is_localized(entr) return void;
|
|
||||||
this.dbus_activatable = parse_bool(entr.value)?;
|
|
||||||
} else if (entr.key == "TryExec") {
|
|
||||||
if parse_is_localized(entr) return void;
|
|
||||||
this.try_exec = parse_string(entr.value)?;
|
|
||||||
} else if (entr.key == "Path") {
|
|
||||||
if parse_is_localized(entr) return void;
|
|
||||||
this.path = parse_string(entr.value)?;
|
|
||||||
} else if (entr.key == "Terminal") {
|
|
||||||
if parse_is_localized(entr) return void;
|
|
||||||
this.terminal = parse_boolean(entr.value)?;
|
|
||||||
} else if (entr.key == "Actions") {
|
|
||||||
if parse_is_localized(entr) return void;
|
|
||||||
this.actions = parse_strings(entr.value)?;
|
|
||||||
} else if (entr.key == "MimeType") {
|
|
||||||
if parse_is_localized(entr) return void;
|
|
||||||
this.mime_type = parse_strings(entr.value)?;
|
|
||||||
} else if (entr.key == "Categories") {
|
|
||||||
if parse_is_localized(entr) return void;
|
|
||||||
this.categories = parse_strings(entr.value)?;
|
|
||||||
} else if (entr.key == "Implements") {
|
|
||||||
if parse_is_localized(entr) return void;
|
|
||||||
this.implements = parse_strings(entr.value)?;
|
|
||||||
} else if (entr.key == "Keywords") {
|
|
||||||
parse_set_locale_strings(
|
|
||||||
*this.keywords, entr.locale,
|
|
||||||
parse_localestring(entr.value)?)?;
|
|
||||||
} else if (entr.key == "StartupWMClass") {
|
|
||||||
if parse_is_localized(entr) return void;
|
|
||||||
this.startup_wm_class = parse_string(entr.value)?;
|
|
||||||
} else if (entr.key == "URL") {
|
|
||||||
if parse_is_localized(entr) return void;
|
|
||||||
this.url = parse_string(entr.value)?;
|
|
||||||
} else if (entr.key == "PrefersNonDefaultGPU") {
|
|
||||||
if parse_is_localized(entr) return void;
|
|
||||||
this.prefers_non_default_gpu = parse_bool(entr.value)?;
|
|
||||||
} else if (entr.key == "SingleMainWindow") {
|
|
||||||
if parse_is_localized(entr) return void;
|
|
||||||
this.single_main_window = parse_bool(entr.value)?;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
fn parse_is_localized(entr: entry) bool = {
|
|
||||||
return !locale::equal(entr.locale, locale::c);
|
|
||||||
};
|
|
||||||
|
|
||||||
fn parse_set_locale_string(dest *locale::string, local: locale::locale, value: str) void = {
|
|
||||||
for (let existing &.. *dest) {
|
|
||||||
if (locale::equal(existing.0, locale)) {
|
|
||||||
existing.1 = value;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
append(*dest, (locale::dup(local), strings::dup(value)));
|
|
||||||
};
|
|
||||||
|
|
||||||
fn parse_set_locale_strings(dest *locale::strings, local: locale::locale, value: []str) void = {
|
|
||||||
for (let existing &.. *dest) {
|
|
||||||
if (locale::equal(existing.0, locale)) {
|
|
||||||
existing.1 = value;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
append(*dest, (locale::dup(local), strings::dupall(value)));
|
|
||||||
};
|
|
||||||
|
|
||||||
fn locale_string_finish(string: locale::string) void = {
|
|
||||||
for (let pair .. string) {
|
|
||||||
locale::finish(pair.0);
|
|
||||||
free(pair.1);
|
|
||||||
};
|
|
||||||
free(string);
|
|
||||||
};
|
|
||||||
|
|
||||||
fn locale_strings_finish(strings: locale::strings) void = {
|
|
||||||
for (string .. strings) locale_string_finish(string);
|
|
||||||
};
|
|
@ -1,4 +1,3 @@
|
|||||||
use fmt;
|
|
||||||
use locale;
|
use locale;
|
||||||
use strings;
|
use strings;
|
||||||
|
|
||||||
@ -43,19 +42,3 @@ export fn entry_finish(entr: entry) void = {
|
|||||||
free(entr.value);
|
free(entr.value);
|
||||||
locale::finish(entr.locale);
|
locale::finish(entr.locale);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Formats a [[line]] and writes it to an [[io::handle]].
|
|
||||||
export fn fprint(output: io::handle, lin: line) (size | io::error) = match (lin) {
|
|
||||||
case blank => yield fmt::fprintln(output, lin);
|
|
||||||
case let lin: comment => yield fmt::fprintf(output, "#{}\n", lin: string);
|
|
||||||
case let lin: group_header => yield fmt::fprintf(output, "[{}]\n", lin: string);
|
|
||||||
case let lin: entry =>
|
|
||||||
let wrote = fmt::print(lin.key)?;
|
|
||||||
if (!locale::equal(locale::c, lin.locale)) {
|
|
||||||
let localestr = locale::format(lin.locale);
|
|
||||||
defer free(localestr);
|
|
||||||
wrote += fmt::printf("[{}]", localestr)?;
|
|
||||||
};
|
|
||||||
wrote += fmt::printf("={}\n", lin.value)?;
|
|
||||||
yield wrote;
|
|
||||||
};
|
|
||||||
|
111
locale/string.ha
111
locale/string.ha
@ -1,120 +1,59 @@
|
|||||||
// A string localized into multiple locales.
|
// A string localized into multiple locales.
|
||||||
export type string = [](locale, str);
|
export type string = [](locale, str);
|
||||||
|
|
||||||
// -------------------------------------------------------------------------- //
|
|
||||||
// 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.
|
|
||||||
// -------------------------------------------------------------------------- //
|
|
||||||
|
|
||||||
// Selects the most appropriate localized version of a [[string]] given a
|
// Selects the most appropriate localized version of a [[string]] given a
|
||||||
// [[locale]]. The matching algorithm used is the one specified by the
|
// [[locale]]. The matching algorithm used is the one specified by the
|
||||||
// XDG Desktop Entry Specification, §5. If no suitable version is found, an
|
// XDG Desktop Entry Specification, §5. Memory is borrowed from the input.
|
||||||
// exact match with the C locale will be attempted. If there are none, void will
|
|
||||||
// be returned. Memory is borrowed from the input.
|
|
||||||
export fn string_resolve(strin: string, local: locale) (str | void) = {
|
export fn string_resolve(strin: string, local: locale) (str | void) = {
|
||||||
// lang_COUNTRY@MODIFIER
|
// 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;
|
let lang_country_modifier = local;
|
||||||
lang_country_modifier.encoding = "";
|
lang_country_modifier.encoding = "";
|
||||||
match (string_resolve_exact(strin, lang_country_modifier)) {
|
match (string_resolve_exact(strin, lang_country_modifier)) {
|
||||||
case let result: str => return result;
|
case let index: size => return index;
|
||||||
case => void;
|
case => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
// lang_COUNTRY
|
|
||||||
let lang_country = local;
|
let lang_country = local;
|
||||||
lang_country.modifier = "";
|
lang_country.modifier = "";
|
||||||
match (string_resolve_exact(strin, lang_country)) {
|
match (string_resolve_exact(strin, lang_country)) {
|
||||||
case let result: str => return result;
|
case let index: size => return index;
|
||||||
case => void;
|
case => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
// lang@MODIFIER
|
|
||||||
let lang_modifier = lang_country_modifier;
|
let lang_modifier = lang_country_modifier;
|
||||||
lang_modifier.country = "";
|
lang_modifier.country = "";
|
||||||
match (string_resolve_exact(strin, lang_modifier)) {
|
match (string_resolve_exact(strin, lang_modifier)) {
|
||||||
case let result: str => return result;
|
case let index: size => return index;
|
||||||
case => void;
|
case => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
// lang
|
|
||||||
let lang = lang_modifier;
|
let lang = lang_modifier;
|
||||||
lang.modifier = "";
|
lang.modifier = "";
|
||||||
match (string_resolve_exact(strin, lang)) {
|
match (string_resolve_exact(strin, lang)) {
|
||||||
case let result: str => return result;
|
case let index: size => return index;
|
||||||
case => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
// fallback to c locale
|
|
||||||
match (string_resolve_exact(strin, c)) {
|
|
||||||
case let result: str => return result;
|
|
||||||
case => void;
|
case => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
return void;
|
return void;
|
||||||
};
|
};
|
||||||
|
|
||||||
fn string_resolve_exact(strin: string, local: locale) (str | void) = {
|
fn string_resolve_exact(strin: string, local: locale) (size | void) = {
|
||||||
for (let pair .. strin) if(equal(pair.0, local)) return pair.1;
|
let index = 0z;
|
||||||
};
|
for (let pair .. strn) {
|
||||||
|
if(equal(pair.1, local)) return index;
|
||||||
// A list of strings localized into multiple locales.
|
index += 1;
|
||||||
export type strings = [](locale, []str);
|
};
|
||||||
|
|
||||||
// Selects the most appropriate localized version of a [[string]] given a
|
|
||||||
// [[locale]]. See the documentation for [[string_resolve]] for more
|
|
||||||
// information.
|
|
||||||
export fn strings_resolve(strins: strings, local: locale) ([]str | void) = {
|
|
||||||
// lang_COUNTRY@MODIFIER
|
|
||||||
let lang_country_modifier = local;
|
|
||||||
lang_country_modifier.encoding = "";
|
|
||||||
match (strings_resolve_exact(strins, lang_country_modifier)) {
|
|
||||||
case let result: []str => return result;
|
|
||||||
case => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
// lang_COUNTRY
|
|
||||||
let lang_country = local;
|
|
||||||
lang_country.modifier = "";
|
|
||||||
match (strings_resolve_exact(strins, lang_country)) {
|
|
||||||
case let result: []str => return result;
|
|
||||||
case => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
// lang@MODIFIER
|
|
||||||
let lang_modifier = lang_country_modifier;
|
|
||||||
lang_modifier.country = "";
|
|
||||||
match (strings_resolve_exact(strins, lang_modifier)) {
|
|
||||||
case let result: []str => return result;
|
|
||||||
case => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
// lang
|
|
||||||
let lang = lang_modifier;
|
|
||||||
lang.modifier = "";
|
|
||||||
match (strings_resolve_exact(strins, lang)) {
|
|
||||||
case let result: []str => return result;
|
|
||||||
case => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
// fallback to c locale
|
|
||||||
match (strings_resolve_exact(strins, c)) {
|
|
||||||
case let result: []str => return result;
|
|
||||||
case => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
return void;
|
|
||||||
};
|
|
||||||
|
|
||||||
fn strings_resolve_exact(strins: strings, local: locale) ([]str | void) = {
|
|
||||||
for (let pair .. strins) if(equal(pair.0, local)) return pair.1;
|
|
||||||
};
|
};
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
@test fn string_resolve() void = {
|
|
||||||
assert(string_resolve([
|
|
||||||
(c, "c"),
|
|
||||||
(parse("xx_XX")!, "xx_XX"),
|
|
||||||
], c) as str == "c");
|
|
||||||
assert(string_resolve([
|
|
||||||
(c, "c"),
|
|
||||||
(parse("xx_XX")!, "xx_XX"),
|
|
||||||
], parse("xx_XX.UTF-8")!) as str == "xx_XX");
|
|
||||||
assert(string_resolve([
|
|
||||||
(c, "c"),
|
|
||||||
(parse("xx_XX")!, "xx_XX"),
|
|
||||||
], parse("yy_YY.UTF-8")!) as str == "c");
|
|
||||||
assert(string_resolve([
|
|
||||||
(parse("xx_XX")!, "xx_XX"),
|
|
||||||
(parse("zz_ZZ")!, "zz_ZZ"),
|
|
||||||
], parse("yy_YY")!) is void);
|
|
||||||
};
|
|
Loading…
Reference in New Issue
Block a user