Make desktop_entry module more similar to ini module
This commit is contained in:
parent
8521320a7c
commit
ca44e2d750
@ -1,20 +1,6 @@
|
|||||||
use locale;
|
use locale;
|
||||||
use strings;
|
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 line in the file, which can be a comment (or a blank line), an entry, or
|
||||||
// a localized entry.
|
// a localized entry.
|
||||||
export type line = (blank | comment | entry | localized_entry);
|
export type line = (blank | comment | entry | localized_entry);
|
||||||
@ -27,195 +13,39 @@ export type blank = void;
|
|||||||
// Specification: §3.1
|
// Specification: §3.1
|
||||||
export type comment = str;
|
export type comment = str;
|
||||||
|
|
||||||
|
// A group header.
|
||||||
|
// Specification: §3.2
|
||||||
|
export type group_header = str;
|
||||||
|
|
||||||
// A key/value pair.
|
// A key/value pair.
|
||||||
// Specification: §3.3
|
// Specification: §3.3
|
||||||
export type entry = struct {
|
export type entry = (str, str);
|
||||||
key: str,
|
|
||||||
value: str,
|
|
||||||
};
|
|
||||||
|
|
||||||
// A localized key/value pair.
|
// A localized key/value pair.
|
||||||
// Specification: §5
|
// Specification: §5
|
||||||
export type localized_entry = struct {
|
export type localized_entry = (str, str, locale::locale);
|
||||||
key: str,
|
|
||||||
value: str,
|
// Duplicates an [[entry]]. Use [[entry_finish]] to get rid of it.
|
||||||
locale: locale::locale,
|
export fn entry_dup(entr: entry) entry = (
|
||||||
|
strings::dup(entr.0),
|
||||||
|
strings::dup(entr.1));
|
||||||
|
|
||||||
|
// Frees memory associated with an [[entry]].
|
||||||
|
export fn entry_finish(entr: entry) void = {
|
||||||
|
free(entr.0);
|
||||||
|
free(entr.1);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Gets a non-localized [[value]] from a [[file]]. If the group does not exist,
|
// Duplicates a [[localized_entry]]. Use [[localized_entry_finish]] to get rid
|
||||||
// or the group exists but the key isn't in it, it returns void.
|
// of it.
|
||||||
export fn file_get(fil: *file, group_name: str, key: str) (str | void) = {
|
export fn localized_entry_dup(entr: localized_entry) localized_entry = (
|
||||||
let grou = match (file_find_group(fil, group_name)) {
|
strings::dup(entr.0),
|
||||||
case let index: size => yield fil.groups[index];
|
strings::dup(entr.1),
|
||||||
case void => return void;
|
locale::dup(entr.2));
|
||||||
};
|
|
||||||
let lin = match (group_find_entry(grou, key)) {
|
// Frees memory associated with an [[localized_entry]].
|
||||||
case let index: size => yield grou.lines[index];
|
export fn localized_entry_finish(entr: localized_entry) void = {
|
||||||
case void => return void;
|
free(entr.0);
|
||||||
};
|
free(entr.1);
|
||||||
match (lin) {
|
locale::finish(entr.2);
|
||||||
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;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
// All errors defined in this module.
|
use encoding::utf8;
|
||||||
export type error = !(
|
use io;
|
||||||
|
|
||||||
|
// Any error that may occur during parsing.
|
||||||
|
export type error = !(syntaxerr | io::error | utf8::invalid);
|
||||||
|
|
||||||
|
// All syntax errors defined in this module.
|
||||||
|
export type syntaxerr = !(
|
||||||
invalid_group_header |
|
invalid_group_header |
|
||||||
invalid_entry |
|
invalid_entry |
|
||||||
invalid_ascii |
|
invalid_ascii |
|
||||||
@ -20,8 +26,10 @@ export type invalid_escape = !void;
|
|||||||
|
|
||||||
// Converts a desktop entry [[error]] into a user-friendly string.
|
// Converts a desktop entry [[error]] into a user-friendly string.
|
||||||
export fn strerror(err: error) str = match (err) {
|
export fn strerror(err: error) str = match (err) {
|
||||||
case invalid_group_header => yield "invalid group header";
|
case invalid_group_header => yield "invalid group header";
|
||||||
case invalid_entry => yield "invalid entry";
|
case invalid_entry => yield "invalid entry";
|
||||||
case invalid_ascii => yield "invalid ascii";
|
case invalid_ascii => yield "invalid ascii";
|
||||||
case invalid_escape => yield "invalid escape";
|
case invalid_escape => yield "invalid escape";
|
||||||
|
case let err: io::error => yield io::strerror(err);
|
||||||
|
case let err: utf8::invalid => yield utf8::strerror(err);
|
||||||
};
|
};
|
||||||
|
@ -6,83 +6,69 @@ use locale;
|
|||||||
use memio;
|
use memio;
|
||||||
use strings;
|
use strings;
|
||||||
|
|
||||||
// Parses a [[file]]. The result must be freed using [[file_finish]].
|
export type scanner = struct {
|
||||||
export fn file_parse(in: io::stream) !(file | error | io::error | utf8::invalid) = {
|
scanner: bufio::scanner,
|
||||||
let scanner = bufio::newscanner(&in);
|
entry: entry,
|
||||||
defer bufio::finish(&scanner);
|
localized_entry: localized_entry,
|
||||||
|
};
|
||||||
|
|
||||||
let fil = file { ... };
|
// Creates a desktop entry file scanner. Use [[next]] to read lines. The caller
|
||||||
|
// must call [[finish]] once they're done with this object.
|
||||||
|
export fn scan(input: io::handle) scanner = scanner {
|
||||||
|
scanner = bufio::newscanner(input),
|
||||||
|
...
|
||||||
|
};
|
||||||
|
|
||||||
|
// Returns the next entry from a desktop entry file. The return value is
|
||||||
|
// borrowed from the [[scanner]] use [[line_dup]] to retain a copy.
|
||||||
|
export fn next(this: *scanner) (line | io::EOF | error) = {
|
||||||
|
let text = match (bufio::scan_line(&this.scanner)) {
|
||||||
|
case let text: const str =>
|
||||||
|
yield text;
|
||||||
|
case let err: (io::error | utf8::invalid) =>
|
||||||
|
return err;
|
||||||
|
case io::EOF =>
|
||||||
|
return io::EOF;
|
||||||
|
};
|
||||||
|
|
||||||
for (true) {
|
if (text == "") {
|
||||||
let text = match (bufio::scan_line(&scanner)) {
|
// blank line
|
||||||
case let text: const str =>
|
return blank;
|
||||||
yield text;
|
|
||||||
case let err: io::EOF =>
|
|
||||||
break;
|
|
||||||
case let err: (io::error | utf8::invalid) =>
|
|
||||||
file_finish(&fil);
|
|
||||||
return err;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (text == "") {
|
} else if (strings::hasprefix(text, '#')) {
|
||||||
// blank line
|
// comment
|
||||||
file_append_line(&fil, blank);
|
return strings::ltrim(text, '#'): comment;
|
||||||
|
|
||||||
} else if (strings::hasprefix(text, '#')) {
|
} else if (strings::hasprefix(text, '[')) {
|
||||||
// comment
|
// group header
|
||||||
let text = strings::dup(strings::ltrim(text, '#'));
|
return parse_group_header(text)?;
|
||||||
file_append_line(&fil, text: comment);
|
|
||||||
|
} else {
|
||||||
} else if (strings::hasprefix(text, '[')) {
|
// key/value pair
|
||||||
// group header
|
let (key, valu, local) = parse_entry(text)?;
|
||||||
let name = strings::dup(parse_group_header(text)?);
|
|
||||||
file_append_group(&fil, name);
|
match (local) {
|
||||||
|
case let local: locale::locale =>
|
||||||
} else {
|
localized_entry_finish(this.localized_entry);
|
||||||
// key/value pair
|
this.localized_entry = (key, valu, local);
|
||||||
let (key, valu, local) = match (parse_entry(text)) {
|
return this.localized_entry;
|
||||||
case let result: (str, str, (locale::locale | void)) =>
|
case void =>
|
||||||
yield result;
|
entry_finish(this.entry);
|
||||||
case let err: error =>
|
this.entry = (key, valu);
|
||||||
file_finish(&fil);
|
return this.entry;
|
||||||
return err;
|
|
||||||
};
|
|
||||||
|
|
||||||
file_append_line(&fil, match (local) {
|
|
||||||
case let local: locale::locale =>
|
|
||||||
yield localized_entry {
|
|
||||||
key = key,
|
|
||||||
value = valu,
|
|
||||||
locale = local,
|
|
||||||
};
|
|
||||||
case void =>
|
|
||||||
yield entry {
|
|
||||||
key = key,
|
|
||||||
value = valu,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
return fil;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fn file_append_group(fil: *file, name: str) void = {
|
// Frees resources associated with a [[scanner]].
|
||||||
append(fil.groups, group {
|
export fn finish(this: *scanner) void = {
|
||||||
name = name,
|
bufio::finish(&this.scanner);
|
||||||
...
|
entry_finish(this.entry);
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
fn file_append_line(fil: *file, lin: line) void = {
|
|
||||||
if (len(fil.groups) > 0) {
|
|
||||||
append(fil.groups[len(fil.groups) - 1].lines, lin);
|
|
||||||
} else {
|
|
||||||
append(fil.preceeding_lines, lin);
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// memory is borrowed from the input
|
// memory is borrowed from the input
|
||||||
fn parse_group_header(text: str) (str | error) = {
|
fn parse_group_header(text: str) (group_header | error) = {
|
||||||
if (!strings::hasprefix(text, '[') || !strings::hassuffix(text, ']')) {
|
if (!strings::hasprefix(text, '[') || !strings::hassuffix(text, ']')) {
|
||||||
return invalid_group_header;
|
return invalid_group_header;
|
||||||
};
|
};
|
||||||
@ -91,7 +77,7 @@ fn parse_group_header(text: str) (str | error) = {
|
|||||||
if (strings::contains(text, '[', ']')) {
|
if (strings::contains(text, '[', ']')) {
|
||||||
return invalid_group_header;
|
return invalid_group_header;
|
||||||
};
|
};
|
||||||
return text;
|
return text: group_header;
|
||||||
};
|
};
|
||||||
|
|
||||||
// memory must be freed by the caller
|
// memory must be freed by the caller
|
||||||
|
Loading…
Reference in New Issue
Block a user