Compare commits
4 Commits
0362ae4515
...
6ff4624dd8
Author | SHA1 | Date | |
---|---|---|---|
6ff4624dd8 | |||
2033df5335 | |||
0bfa8f6914 | |||
34f1864d5f |
@ -1,3 +1,4 @@
|
||||
use io;
|
||||
use locale;
|
||||
use strings;
|
||||
|
||||
@ -18,7 +19,6 @@ export type file = struct {
|
||||
exec: str,
|
||||
path: str,
|
||||
terminal: bool,
|
||||
actions: []str,
|
||||
mime_type: []str,
|
||||
categories: []str,
|
||||
implements: []str,
|
||||
@ -28,127 +28,179 @@ export type file = struct {
|
||||
url: str,
|
||||
prefers_non_default_gpu: bool,
|
||||
single_main_window: bool,
|
||||
|
||||
actions: []action,
|
||||
};
|
||||
|
||||
export type action = struct {
|
||||
key: str,
|
||||
name: locale::string,
|
||||
icon: locale::string,
|
||||
exec: str,
|
||||
};
|
||||
|
||||
// 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 file = file { ... };
|
||||
let scanne = scan(input);
|
||||
defer finish(&scanne);
|
||||
|
||||
for (let entr => next_entry(&scanne)) {
|
||||
match(parse_handle_entry(entr)) {
|
||||
|
||||
for (let entr => next_entry(&scanne)?) {
|
||||
match(parse_handle_entry(&file, entr)) {
|
||||
case let err: error =>
|
||||
file_finish(&state.file);
|
||||
file_finish(&file);
|
||||
return err;
|
||||
case void => void;
|
||||
};
|
||||
};
|
||||
|
||||
return file;
|
||||
};
|
||||
|
||||
// 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);
|
||||
export fn file_finish(this: *file) void = {
|
||||
free(this.typ);
|
||||
free(this.version);
|
||||
locale_string_finish(this.name);
|
||||
locale_string_finish(this.generic_name);
|
||||
locale_string_finish(this.comment);
|
||||
locale_string_finish(this.icon);
|
||||
strings::freeall(this.only_show_in);
|
||||
strings::freeall(this.not_show_in);
|
||||
free(this.try_exec);
|
||||
free(this.exec);
|
||||
free(this.path);
|
||||
strings::freeall(this.mime_type);
|
||||
strings::freeall(this.categories);
|
||||
strings::freeall(this.implements);
|
||||
locale_strings_finish(this.keywords);
|
||||
free(this.startup_wm_class);
|
||||
free(this.url);
|
||||
|
||||
for (let actio .. this.actions) {
|
||||
free(actio.key);
|
||||
locale_string_finish(actio.name);
|
||||
locale_string_finish(actio.icon);
|
||||
free(actio.exec);
|
||||
};
|
||||
free(this.actions);
|
||||
};
|
||||
|
||||
type parse_state = struct {
|
||||
file: file,
|
||||
fn parse_handle_entry(this: *file, entr: entry) (void | error) = {
|
||||
const desktop_action_prefix = "Desktop Action ";
|
||||
if (entr.group == "Desktop Entry") {
|
||||
return parse_handle_desktop_entry_entry(this, entr);
|
||||
} else if (strings::hasprefix(entr.group, desktop_action_prefix)) {
|
||||
let key = strings::trimprefix(entr.group, desktop_action_prefix);
|
||||
return parse_handle_desktop_action_entry(this, entr, key);
|
||||
};
|
||||
};
|
||||
|
||||
fn parse_handle_entry(this: *parse_state, entr: entry) (void | error) = {
|
||||
if (entr.group != "Desktop Entry") return void;
|
||||
|
||||
fn parse_handle_desktop_action_entry(this: *file, entr: entry, key: str) (void | error) = {
|
||||
let actio = match(parse_find_action(this, key)) {
|
||||
case let actio: *action => yield actio;
|
||||
case void => return void;
|
||||
};
|
||||
if (entr.key == "Name") {
|
||||
parse_set_locale_string(
|
||||
&actio.name, entr.locale,
|
||||
parse_localestring(entr.value)?);
|
||||
} else if (entr.key == "Icon") {
|
||||
parse_set_locale_string(
|
||||
&actio.icon, entr.locale,
|
||||
parse_iconstring(entr.value)?);
|
||||
} else if (entr.key == "Exec") {
|
||||
if (parse_is_localized(entr)) return void;
|
||||
actio.exec = parse_string(entr.value)?;
|
||||
};
|
||||
};
|
||||
|
||||
fn parse_handle_desktop_entry_entry(this: *file, entr: entry) (void | error) = {
|
||||
if (entr.key == "Type") {
|
||||
if parse_is_localized(entr) return void;
|
||||
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;
|
||||
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)?)?;
|
||||
&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)?)?;
|
||||
&this.generic_name, entr.locale,
|
||||
parse_localestring(entr.value)?);
|
||||
} else if (entr.key == "NoDisplay") {
|
||||
if parse_is_localized(entr) return void;
|
||||
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)?)?;
|
||||
&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)?)?;
|
||||
&this.icon, entr.locale,
|
||||
parse_iconstring(entr.value)?);
|
||||
} else if (entr.key == "Hidden") {
|
||||
if parse_is_localized(entr) return void;
|
||||
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;
|
||||
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;
|
||||
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)?;
|
||||
if (parse_is_localized(entr)) return void;
|
||||
this.dbus_activatable = parse_boolean(entr.value)?;
|
||||
} else if (entr.key == "TryExec") {
|
||||
if parse_is_localized(entr) return void;
|
||||
if (parse_is_localized(entr)) return void;
|
||||
this.try_exec = parse_string(entr.value)?;
|
||||
} else if (entr.key == "Exec") {
|
||||
if (parse_is_localized(entr)) return void;
|
||||
this.exec = parse_string(entr.value)?;
|
||||
} else if (entr.key == "Path") {
|
||||
if parse_is_localized(entr) return void;
|
||||
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;
|
||||
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)?;
|
||||
if (parse_is_localized(entr)) return void;
|
||||
let strings = parse_strings(entr.value)?;
|
||||
defer free(strings);
|
||||
this.actions = alloc([], len(strings));
|
||||
for (let string .. strings) {
|
||||
append(this.actions, action {
|
||||
key = string,
|
||||
...
|
||||
});
|
||||
};
|
||||
} else if (entr.key == "MimeType") {
|
||||
if parse_is_localized(entr) return void;
|
||||
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;
|
||||
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;
|
||||
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)?)?;
|
||||
&this.keywords, entr.locale,
|
||||
parse_localestrings(entr.value)?);
|
||||
} else if (entr.key == "StartupWMClass") {
|
||||
if parse_is_localized(entr) return void;
|
||||
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;
|
||||
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)?;
|
||||
if (parse_is_localized(entr)) return void;
|
||||
this.prefers_non_default_gpu = parse_boolean(entr.value)?;
|
||||
} else if (entr.key == "SingleMainWindow") {
|
||||
if parse_is_localized(entr) return void;
|
||||
this.single_main_window = parse_bool(entr.value)?;
|
||||
if (parse_is_localized(entr)) return void;
|
||||
this.single_main_window = parse_boolean(entr.value)?;
|
||||
};
|
||||
};
|
||||
|
||||
@ -156,24 +208,30 @@ 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 = {
|
||||
fn parse_set_locale_string(dest: *locale::string, local: locale::locale, value: str) void = {
|
||||
for (let existing &.. *dest) {
|
||||
if (locale::equal(existing.0, locale)) {
|
||||
if (locale::equal(existing.0, local)) {
|
||||
existing.1 = value;
|
||||
return;
|
||||
};
|
||||
};
|
||||
append(*dest, (locale::dup(local), strings::dup(value)));
|
||||
append(dest, (locale::dup(local), strings::dup(value)));
|
||||
};
|
||||
|
||||
fn parse_set_locale_strings(dest *locale::strings, local: locale::locale, value: []str) void = {
|
||||
fn parse_set_locale_strings(dest: *locale::strings, local: locale::locale, value: []str) void = {
|
||||
for (let existing &.. *dest) {
|
||||
if (locale::equal(existing.0, locale)) {
|
||||
if (locale::equal(existing.0, local)) {
|
||||
existing.1 = value;
|
||||
return;
|
||||
};
|
||||
};
|
||||
append(*dest, (locale::dup(local), strings::dupall(value)));
|
||||
append(dest, (locale::dup(local), strings::dupall(value)));
|
||||
};
|
||||
|
||||
fn parse_find_action(this: *file, key: str) (*action | void) = {
|
||||
for (let actio &.. this.actions) {
|
||||
if (actio.key == key) return actio;
|
||||
};
|
||||
};
|
||||
|
||||
fn locale_string_finish(string: locale::string) void = {
|
||||
@ -185,5 +243,9 @@ fn locale_string_finish(string: locale::string) void = {
|
||||
};
|
||||
|
||||
fn locale_strings_finish(strings: locale::strings) void = {
|
||||
for (string .. strings) locale_string_finish(string);
|
||||
for (let pair .. strings) {
|
||||
locale::finish(pair.0);
|
||||
strings::freeall(pair.1);
|
||||
};
|
||||
free(strings);
|
||||
};
|
||||
|
101
format/desktop_entry/desktop_entry_test.ha
Normal file
101
format/desktop_entry/desktop_entry_test.ha
Normal file
@ -0,0 +1,101 @@
|
||||
use fmt;
|
||||
use io;
|
||||
use locale;
|
||||
use os;
|
||||
|
||||
@test fn parse() void = {
|
||||
let file = os::open("format/desktop_entry/test_data/foo_full.desktop")!;
|
||||
defer io::close(file)!;
|
||||
let file = parse(file)!;
|
||||
defer file_finish(&file);
|
||||
|
||||
assert(file.typ == "Application");
|
||||
assert(file.version == "1.0");
|
||||
assert(locale_string_equal(file.name, [
|
||||
(locale::c, "Foo Viewer"),
|
||||
]));
|
||||
assert(locale_string_equal(file.generic_name, [
|
||||
(locale::c, "Foo Viewer"),
|
||||
(locale::parse("xx_XX.UTF-8")!, "Sneep glorp"),
|
||||
]));
|
||||
assert(file.no_display == false);
|
||||
assert(locale_string_equal(file.comment, [
|
||||
(locale::c, "The best viewer for Foo objects available!"),
|
||||
]));
|
||||
assert(locale_string_equal(file.icon, [
|
||||
(locale::c, "fooview"),
|
||||
]));
|
||||
assert(file.hidden == false);
|
||||
assert(strings_equal(file.only_show_in, ["MATE", "KDE"]));
|
||||
assert(strings_equal(file.not_show_in, ["GNOME"]));
|
||||
assert(file.dbus_activatable == false);
|
||||
assert(file.try_exec == "fooview");
|
||||
assert(file.exec == "fooview %F");
|
||||
assert(file.path == "");
|
||||
assert(file.terminal == false);
|
||||
assert(strings_equal(file.mime_type, ["image/x-foo"]));
|
||||
assert(strings_equal(file.categories, ["Graphics", "Utility"]));
|
||||
assert(strings_equal(file.implements, [
|
||||
"com.example.Example",
|
||||
"com.example.OtherExample",
|
||||
]));
|
||||
assert(locale_strings_equal(file.keywords, [
|
||||
(locale::c, ["foo", "image", "view", "viewer"]),
|
||||
]));
|
||||
assert(file.startup_notify == false);
|
||||
assert(file.url == "");
|
||||
assert(file.prefers_non_default_gpu == false);
|
||||
assert(file.single_main_window == false);
|
||||
|
||||
for (let actio .. file.actions) switch (actio.key) {
|
||||
case "Gallery" =>
|
||||
assert(locale_string_equal(actio.name, [
|
||||
(locale::c, "Browse Gallery"),
|
||||
]));
|
||||
assert(locale_string_equal(actio.icon, []));
|
||||
assert(actio.exec == "fooview --gallery");
|
||||
case "Create" =>
|
||||
assert(locale_string_equal(actio.name, [
|
||||
(locale::c, "Create a new Foo!"),
|
||||
(locale::parse("en_US")!, "Create a new Foo!"),
|
||||
(locale::parse("xx_XX.UTF-8")!, "Zweep zoop flooble glorp"),
|
||||
]));
|
||||
assert(locale_string_equal(actio.icon, [
|
||||
(locale::c, "fooview-new"),
|
||||
]));
|
||||
assert(actio.exec == "fooview --create-new");
|
||||
case =>
|
||||
fmt::println("unexpected action", actio.key)!;
|
||||
abort("unexpected action");
|
||||
};
|
||||
};
|
||||
|
||||
fn strings_equal(a: []str, b: []str) bool = {
|
||||
if (len(a) != len(b)) return false;
|
||||
for (let index = 0z; index < len(a); index += 1) {
|
||||
if (a[index] != b[index]) return false;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
fn locale_string_equal(a: locale::string, b: locale::string) bool = {
|
||||
if (len(a) != len(b)) return false;
|
||||
for (let index = 0z; index < len(a); index += 1) {
|
||||
let a = a[index];
|
||||
let b = b[index];
|
||||
if (!locale::equal(a.0, b.0)) return false;
|
||||
if (a.1 != b.1 ) return false;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
fn locale_strings_equal(a: locale::strings, b: locale::strings) bool = {
|
||||
if (len(a) != len(b)) return false;
|
||||
for (let index = 0z; index < len(a); index += 1) {
|
||||
let a = a[index];
|
||||
let b = b[index];
|
||||
if (!locale::equal(a.0, b.0)) return false;
|
||||
if (!strings_equal(a.1, b.1)) return false;
|
||||
};
|
||||
return true;
|
||||
};
|
@ -1,4 +1,5 @@
|
||||
use fmt;
|
||||
use io;
|
||||
use locale;
|
||||
use strings;
|
||||
|
||||
@ -46,9 +47,9 @@ export fn entry_finish(entr: entry) void = {
|
||||
|
||||
// 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 blank => yield fmt::fprintln(output);
|
||||
case let lin: comment => yield fmt::fprintf(output, "#{}\n", lin: str);
|
||||
case let lin: group_header => yield fmt::fprintf(output, "[{}]\n", lin: str);
|
||||
case let lin: entry =>
|
||||
let wrote = fmt::print(lin.key)?;
|
||||
if (!locale::equal(locale::c, lin.locale)) {
|
||||
|
28
format/desktop_entry/test_data/foo_full.desktop
Normal file
28
format/desktop_entry/test_data/foo_full.desktop
Normal file
@ -0,0 +1,28 @@
|
||||
[Desktop Entry]
|
||||
Version=1.0
|
||||
Type=Application
|
||||
Name=Foo Viewer
|
||||
GenericName=Foo Viewer
|
||||
GenericName[xx_XX.UTF-8]=Sneep glorp
|
||||
Comment=The best viewer for Foo objects available!
|
||||
TryExec=fooview
|
||||
Exec=fooview %F
|
||||
Icon=fooview
|
||||
MimeType=image/x-foo;
|
||||
Actions=Gallery;Create;
|
||||
OnlyShowIn=MATE;KDE;
|
||||
NotShowIn=GNOME
|
||||
Categories=Graphics;Utility
|
||||
Implements=com.example.Example;com.example.OtherExample
|
||||
Keywords=foo;image;view;viewer
|
||||
|
||||
[Desktop Action Gallery]
|
||||
Exec=fooview --gallery
|
||||
Name=Browse Gallery
|
||||
|
||||
[Desktop Action Create]
|
||||
Exec=fooview --create-new
|
||||
Name=Create a new Foo!
|
||||
Name[en_US]=Create a new Foo!
|
||||
Name[xx_XX.UTF-8]=Zweep zoop flooble glorp
|
||||
Icon=fooview-new
|
Loading…
Reference in New Issue
Block a user