Compare commits
3 Commits
24ee999173
...
f401ad26df
Author | SHA1 | Date | |
---|---|---|---|
f401ad26df | |||
02cf7ffb05 | |||
30926c1f10 |
@ -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";
|
||||
|
@ -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;
|
||||
};
|
||||
};
|
||||
|
145
format/desktop_entry/scan_test.ha
Normal file
145
format/desktop_entry/scan_test.ha
Normal 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,
|
||||
};
|
26
format/desktop_entry/test_data/foo.desktop
Normal file
26
format/desktop_entry/test_data/foo.desktop
Normal 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
|
Loading…
Reference in New Issue
Block a user