Compare commits

...

3 Commits

4 changed files with 198 additions and 11 deletions

View File

@ -24,7 +24,8 @@ export type invalid_ascii = !void;
// Returned when an invalid escape sequence was enctountered while parsing.
export type invalid_escape = !void;
// Converts a desktop entry [[error]] into a user-friendly string.
// Returns a user-friendly representation of [[error]]. The result may be
// statically allocated.
export fn strerror(err: error) str = match (err) {
case invalid_group_header => yield "invalid group header";
case invalid_entry => yield "invalid entry";

View File

@ -1,3 +1,4 @@
use ascii;
use bufio;
use encoding::utf8;
use errors;
@ -30,7 +31,7 @@ export fn next(this: *scanner) (line | io::EOF | error) = {
case io::EOF =>
return io::EOF;
};
if (text == "") {
// blank line
return blank;
@ -43,7 +44,7 @@ export fn next(this: *scanner) (line | io::EOF | error) = {
// group header
let header = parse_group_header(text)?;
free(this.group);
this.group = header: str;
this.group = strings::dup(header: str);
return header;
} else {
@ -89,21 +90,21 @@ fn parse_entry(line: str) (entry | error) = {
if (!strings::contains(line, '=')) return invalid_entry;
let (key, valu) = strings::cut(line, "=");
key = strings::ltrim(strings::rtrim(key));
if (!validate_entry_key(key)) return invalid_entry;
let (key, local_string) = strings::cut(key, "[");
let local = if (local_string != "") {
let local = if (local_string == "") {
yield locale::c;
} else {
local_string = strings::rtrim(local_string, ']');
if (!validate_entry_locale(local_string)) return invalid_entry;
validate_entry_locale(local_string)?;
yield match(locale::parse(local_string)) {
case let local: locale::locale => yield local;
case locale::invalid => return invalid_entry;
};
};
validate_entry_key(key)?;
return entry {
key = key,
value = valu,
@ -112,10 +113,24 @@ fn parse_entry(line: str) (entry | error) = {
};
};
fn validate_entry_key(key: str) bool = {
return strings::contains(key, '[', ']', '=');
fn validate_entry_key(key: str) (void | error) = {
for (let byte .. strings::toutf8(key)) {
const ok =
(byte >= 'A' && byte <= 'Z') ||
(byte >= 'a' && byte <= 'z') ||
(byte >= '0' && byte <= '9') ||
(byte == '-');
if (!ok) return invalid_entry;
};
};
fn validate_entry_locale(key: str) bool = {
return strings::contains(key, '[', ']', '=');
fn validate_entry_locale(locale: str) (void | error) = {
for (let byte .. strings::toutf8(locale)) {
const bad =
(byte == '[') ||
(byte == ']') ||
(byte == '=') ||
!ascii::isprint(byte: rune);
if (bad) return invalid_entry;
};
};

View File

@ -0,0 +1,145 @@
use fmt;
use io;
use locale;
use os;
@test fn next() void = {
let h_de = "Desktop Entry";
let h_dag = "Desktop Action Gallery";
let h_dac = "Desktop Action Create";
let correct: []line = [
"This is a comment": comment,
h_de: group_header,
entry_new(h_de, "Version", "1.0", locale::c),
entry_new(h_de, "Type", "Application", locale::c),
entry_new(h_de, "Name", "Foo Viewer", locale::c),
entry_new(h_de, "Comment", "The best viewer for Foo objects available!", locale::c),
entry_new(h_de, "TryExec", "fooview", locale::c),
entry_new(h_de, "Exec", "fooview %F", locale::c),
blank,
entry_new(h_de, "Icon", "fooview", locale::c),
entry_new(h_de, "MimeType", "image/x-foo;", locale::c),
entry_new(h_de, "Actions", "Gallery;Create;", locale::c),
blank,
h_dag: group_header,
entry_new(h_dag, "Exec", "fooview --gallery", locale::c),
entry_new(h_dag, "Name", "Browse Gallery", locale::c),
blank,
h_dac: group_header,
entry_new(h_dac, "Exec", "fooview --create-new", locale::c),
entry_new(h_dac, "Name", "Create a new Foo!", locale::c),
entry_new(
h_dac, "Name", "Create a new Foo!",
locale::parse("en_US")!),
entry_new(
h_dac, "Name", "Zweep zoop flooble glorp",
locale::parse("xx_XX.UTF-8")!),
"Another comment": comment,
entry_new(h_dac, "Icon", "fooview-new", locale::c),
];
let file = os::open("format/desktop_entry/test_data/foo.desktop")!;
defer io::close(file)!;
let scanne = scan(file);
defer finish(&scanne);
for (let correct .. correct) match(next(&scanne)!) {
case io::EOF =>
abort("encountered EOF early");
case blank =>
assert(correct is blank);
case let ln: comment =>
assert(ln == correct as comment);
case let ln: group_header =>
assert(ln == correct as group_header);
case let ln: entry =>
assert(entry_equal(ln, correct as entry));
};
assert(next(&scanne) is io::EOF);
};
@test fn next_entry() void = {
let h_de = "Desktop Entry";
let h_dag = "Desktop Action Gallery";
let h_dac = "Desktop Action Create";
let correct: []entry = [
entry_new(h_de, "Version", "1.0", locale::c),
entry_new(h_de, "Type", "Application", locale::c),
entry_new(h_de, "Name", "Foo Viewer", locale::c),
entry_new(h_de, "Comment", "The best viewer for Foo objects available!", locale::c),
entry_new(h_de, "TryExec", "fooview", locale::c),
entry_new(h_de, "Exec", "fooview %F", locale::c),
entry_new(h_de, "Icon", "fooview", locale::c),
entry_new(h_de, "MimeType", "image/x-foo;", locale::c),
entry_new(h_de, "Actions", "Gallery;Create;", locale::c),
entry_new(h_dag, "Exec", "fooview --gallery", locale::c),
entry_new(h_dag, "Name", "Browse Gallery", locale::c),
entry_new(h_dac, "Exec", "fooview --create-new", locale::c),
entry_new(h_dac, "Name", "Create a new Foo!", locale::c),
entry_new(
h_dac, "Name", "Create a new Foo!",
locale::parse("en_US")!),
entry_new(
h_dac, "Name", "Zweep zoop flooble glorp",
locale::parse("xx_XX.UTF-8")!),
entry_new(h_dac, "Icon", "fooview-new", locale::c),
];
let file = os::open("format/desktop_entry/test_data/foo.desktop")!;
defer io::close(file)!;
let scanne = scan(file);
defer finish(&scanne);
for (let correct .. correct) {
assert(entry_equal(next_entry(&scanne)! as entry, correct));
};
assert(next(&scanne) is io::EOF);
};
@test fn parse_entry() void = {
assert(entry_equal(parse_entry("hello=world")!, entry {
key = "hello",
value = "world",
locale = locale::c,
...
}));
assert(entry_equal(parse_entry("hello[sr_YU.UTF-8@Latn]=world")!, entry {
key = "hello",
value = "world",
locale = locale::parse("sr_YU.UTF-8@Latn")!,
...
}));
};
@test fn validate_entry_key() void = {
// §3.3 Only the characters A-Za-z0-9- may be used in key names.
assert(validate_entry_key("SomeName") is void);
assert(validate_entry_key("some-name") is void);
assert(validate_entry_key("1234567890some-0987654321naMe") is void);
assert(validate_entry_key("^&(*)[") is invalid_entry);
assert(validate_entry_key("]") is invalid_entry);
assert(validate_entry_key("&*%^") is invalid_entry);
assert(validate_entry_key("asj=hd@#*()") is invalid_entry);
};
@test fn validate_entry_locale() void = {
assert(validate_entry_locale("sr_YU.UTF-8@Latn") is void);
assert(validate_entry_locale("abcd[") is invalid_entry);
assert(validate_entry_locale("ab]cd") is invalid_entry);
assert(validate_entry_locale("a=bcd") is invalid_entry);
};
fn entry_equal(a: entry, b: entry) bool =
a.group == b.group &&
a.key == b.key &&
a.value == b.value &&
locale::equal(a.locale, b.locale);
fn entry_new(group: str, key: str, value: str, local: locale::locale) entry = entry {
group = group,
key = key,
value = value,
locale = local,
};

View File

@ -0,0 +1,26 @@
#This is a comment
[Desktop Entry]
Version=1.0
Type=Application
Name=Foo Viewer
Comment=The best viewer for Foo objects available!
TryExec=fooview
Exec=fooview %F
Icon=fooview
MimeType=image/x-foo;
Actions=Gallery;Create;
Name[en_US]=foo.desktop
[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!
#Another comment
Icon=fooview-new