Compare commits
	
		
			10 Commits
		
	
	
		
			0b464d368c
			...
			b42b1b1dd2
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| b42b1b1dd2 | |||
| cabc7c1e19 | |||
| b90f0c5378 | |||
| ca44e2d750 | |||
| 8521320a7c | |||
| 19e6aa5259 | |||
| e8af5d01d7 | |||
| 3e72688127 | |||
| 427d30c255 | |||
| baea275b66 | 
@ -1,198 +1,44 @@
 | 
				
			|||||||
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 = (comment | entry | localized_entry);
 | 
					export type line = (blank | comment | group_header | entry);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// A blank line.
 | 
				
			||||||
 | 
					// Specification: §3.1
 | 
				
			||||||
 | 
					export type blank = void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// A comment.
 | 
					// A comment.
 | 
				
			||||||
// Specification: §3.1
 | 
					// Specification: §3.1
 | 
				
			||||||
export type comment = str;
 | 
					export type comment = str;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// A key/value pair.
 | 
					// A group header.
 | 
				
			||||||
// Specification: §3.3
 | 
					// Specification: §3.2
 | 
				
			||||||
export type entry = struct {
 | 
					export type group_header = str;
 | 
				
			||||||
	key:   str,
 | 
					 | 
				
			||||||
	value: value,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// A localized entry.
 | 
					// An entry in a desktop file. Entries without an explicitly stated locale are
 | 
				
			||||||
// Specification: §5
 | 
					// assigned [[locale::c]].
 | 
				
			||||||
export type localized_entry = struct {
 | 
					// Specification: §3.3, §5
 | 
				
			||||||
 | 
					export type entry = struct {
 | 
				
			||||||
 | 
						group:  str,
 | 
				
			||||||
	key:    str,
 | 
						key:    str,
 | 
				
			||||||
	value:  value,
 | 
						value:  str,
 | 
				
			||||||
	locale: locale::locale,
 | 
						locale: locale::locale,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// An entry value. Values that reference memory (such as [[str]]) are borrowed
 | 
					// Duplicates an [[entry]]. Use [[entry_finish]] to get rid of it.
 | 
				
			||||||
// from the [[file]]. These may be free'd or overwritten when other functions in
 | 
					export fn entry_dup(entr: entry) entry = entry {
 | 
				
			||||||
// this module are called, so [[value_dup]] is required to extend its lifetime.
 | 
						group  = strings::dup(entr.group),
 | 
				
			||||||
export type value = (str | bool | f32);
 | 
						key    = strings::dup(entr.key),
 | 
				
			||||||
 | 
						value  = strings::dup(entr.value),
 | 
				
			||||||
// Gets a non-localized value from a file. If the group does not exist, or the
 | 
						locale = locale::dup(entr.locale),
 | 
				
			||||||
// group exists but the key isn't in it, it returns void.
 | 
					 | 
				
			||||||
export fn file_get(fil: *file, group_name: str, key: str) (value | 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_entry(grou, key)) {
 | 
					 | 
				
			||||||
	case let index: size => yield grou.lines[index];
 | 
					 | 
				
			||||||
	case void            => return void;
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
	match (lin) {
 | 
					 | 
				
			||||||
	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
 | 
					// Frees memory associated with an [[entry]].
 | 
				
			||||||
// exists but the key isnt in it, it returns void. If the key is in the group
 | 
					export fn entry_finish(entr: entry) void = {
 | 
				
			||||||
// but is not localized to the specified locale, the non-localized value is
 | 
						free(entr.group);
 | 
				
			||||||
// returned.
 | 
						free(entr.key);
 | 
				
			||||||
export fn file_get_localized(
 | 
						free(entr.value);
 | 
				
			||||||
	fil:        *file,
 | 
						locale::finish(entr.locale);
 | 
				
			||||||
	group_name: str,
 | 
					 | 
				
			||||||
	key:        str,
 | 
					 | 
				
			||||||
	local:      locale::locale,
 | 
					 | 
				
			||||||
) (value | 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();
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//export fn file_add
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//export fn file_add_localized
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//export fn file_remove
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//export fn file_remove_localized
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//export fn file_encode
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//export fn file_finish
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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;
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Duplicates a [[value]]. Use [[value_finish]] to get rid of it.
 | 
					 | 
				
			||||||
export fn value_dup(valu: value) value = match (valu) {
 | 
					 | 
				
			||||||
case let valu: str => yield strings::dup(valu);
 | 
					 | 
				
			||||||
case               => yield valu;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Frees an [[entry]] previously duplicated with [[entry_dup]].
 | 
					 | 
				
			||||||
export fn value_finish(valu: value) void = match (valu) {
 | 
					 | 
				
			||||||
case let valu: str => free(valu);
 | 
					 | 
				
			||||||
case               => void;
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										35
									
								
								format/desktop_entry/error.ha
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								format/desktop_entry/error.ha
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					use encoding::utf8;
 | 
				
			||||||
 | 
					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_entry |
 | 
				
			||||||
 | 
						invalid_ascii |
 | 
				
			||||||
 | 
						invalid_escape);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Returned when a malformed group header is encountered while parsing.
 | 
				
			||||||
 | 
					export type invalid_group_header = !void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Returned when a malformed entry is encountered while parsing.
 | 
				
			||||||
 | 
					export type invalid_entry = !void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Returned when ASCII text was expected while parsing, but something else was
 | 
				
			||||||
 | 
					// given.
 | 
				
			||||||
 | 
					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.
 | 
				
			||||||
 | 
					export fn strerror(err: error) str = match (err) {
 | 
				
			||||||
 | 
					case invalid_group_header   => yield "invalid group header";
 | 
				
			||||||
 | 
					case invalid_entry          => yield "invalid entry";
 | 
				
			||||||
 | 
					case invalid_ascii          => yield "invalid ascii";
 | 
				
			||||||
 | 
					case invalid_escape         => yield "invalid escape";
 | 
				
			||||||
 | 
					case let err: io::error     => yield io::strerror(err);
 | 
				
			||||||
 | 
					case let err: utf8::invalid => yield utf8::strerror(err);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										122
									
								
								format/desktop_entry/scan.ha
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								format/desktop_entry/scan.ha
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,122 @@
 | 
				
			|||||||
 | 
					use bufio;
 | 
				
			||||||
 | 
					use encoding::utf8;
 | 
				
			||||||
 | 
					use errors;
 | 
				
			||||||
 | 
					use io;
 | 
				
			||||||
 | 
					use locale;
 | 
				
			||||||
 | 
					use memio;
 | 
				
			||||||
 | 
					use strings;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type scanner = struct {
 | 
				
			||||||
 | 
						scanner: bufio::scanner,
 | 
				
			||||||
 | 
						group:   str,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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 line from a desktop entry file. The return value is
 | 
				
			||||||
 | 
					// borrowed from the [[scanner]]. Use [[line_dup]] to retain a copy. If all you
 | 
				
			||||||
 | 
					// want is the file's data, use next_entry.
 | 
				
			||||||
 | 
					// FIXME: there is no line_dup
 | 
				
			||||||
 | 
					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;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						if (text == "") {
 | 
				
			||||||
 | 
							// blank line
 | 
				
			||||||
 | 
							return blank;
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
						} else if (strings::hasprefix(text, '#')) {
 | 
				
			||||||
 | 
							// comment
 | 
				
			||||||
 | 
							return strings::ltrim(text, '#'): comment;
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
						} else if (strings::hasprefix(text, '[')) {
 | 
				
			||||||
 | 
							// group header
 | 
				
			||||||
 | 
							let header = parse_group_header(text)?;
 | 
				
			||||||
 | 
							free(this.group);
 | 
				
			||||||
 | 
							this.group = header: str;
 | 
				
			||||||
 | 
							return header;
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							// key/value pair
 | 
				
			||||||
 | 
							let entry = parse_entry(text)?;
 | 
				
			||||||
 | 
							entry.group = this.group;
 | 
				
			||||||
 | 
							return entry;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Returns the next entry from the desktop file, skipping over blank lines,
 | 
				
			||||||
 | 
					// comments, group headers, etc. The return value is borrowed from the
 | 
				
			||||||
 | 
					// [[scanner]]. Use [[entry_dup]] to retain a copy.
 | 
				
			||||||
 | 
					export fn next_entry(this: *scanner) (entry | io::EOF | error) = {
 | 
				
			||||||
 | 
						for (true) match(next(this)?) {
 | 
				
			||||||
 | 
						case let entr: entry => return entr;
 | 
				
			||||||
 | 
						case io::EOF         => return io::EOF;
 | 
				
			||||||
 | 
						case                 => void;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Frees resources associated with a [[scanner]].
 | 
				
			||||||
 | 
					export fn finish(this: *scanner) void = {
 | 
				
			||||||
 | 
						bufio::finish(&this.scanner);
 | 
				
			||||||
 | 
						free(this.group);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// memory is borrowed from the input
 | 
				
			||||||
 | 
					fn parse_group_header(text: str) (group_header | error) = {
 | 
				
			||||||
 | 
						if (!strings::hasprefix(text, '[') || !strings::hassuffix(text, ']')) {
 | 
				
			||||||
 | 
							return invalid_group_header;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						text = strings::rtrim(strings::ltrim(text, '['), ']');
 | 
				
			||||||
 | 
						if (strings::contains(text, '[', ']')) {
 | 
				
			||||||
 | 
							return invalid_group_header;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						return text: group_header;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// memory is borrowed from the input
 | 
				
			||||||
 | 
					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 != "") {
 | 
				
			||||||
 | 
							yield locale::c;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							local_string = strings::rtrim(local_string, ']');
 | 
				
			||||||
 | 
							if (!validate_entry_locale(local_string)) return invalid_entry;
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							yield match(locale::parse(local_string)) {
 | 
				
			||||||
 | 
							case let local: locale::locale => yield local;
 | 
				
			||||||
 | 
							case locale::invalid           => return invalid_entry;
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return entry {
 | 
				
			||||||
 | 
							key    = key,
 | 
				
			||||||
 | 
							value  = valu,
 | 
				
			||||||
 | 
							locale = local,
 | 
				
			||||||
 | 
							...
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn validate_entry_key(key: str) bool = {
 | 
				
			||||||
 | 
						return strings::contains(key, '[', ']', '=');
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn validate_entry_locale(key: str) bool = {
 | 
				
			||||||
 | 
						return strings::contains(key, '[', ']', '=');
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -52,29 +52,29 @@ fn get_locale(var: str) locale =
 | 
				
			|||||||
		};
 | 
							};
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn get_locale_no_fallback(var: str) (locale | errors::invalid) =
 | 
					fn get_locale_no_fallback(var: str) (locale | invalid) =
 | 
				
			||||||
	match (get_env_locale(var)) {
 | 
						match (get_env_locale(var)) {
 | 
				
			||||||
	case let local: locale => yield local;
 | 
						case let local: locale => yield local;
 | 
				
			||||||
	case                   => yield (get_locale_conf_entry(var));
 | 
						case                   => yield (get_locale_conf_entry(var));
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn get_env_locale(var: str) (locale | errors::invalid) =
 | 
					fn get_env_locale(var: str) (locale | invalid) =
 | 
				
			||||||
	match (os::getenv(var)) {
 | 
						match (os::getenv(var)) {
 | 
				
			||||||
	case let env: str => return parse(env);
 | 
						case let env: str => return parse(env);
 | 
				
			||||||
	case              => return errors::invalid;
 | 
						case              => return invalid;
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let locale_conf: []str = [];
 | 
					let locale_conf: []str = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@fini fn locale_conf() void = strings::freeall(locale_conf);
 | 
					@fini fn locale_conf() void = strings::freeall(locale_conf);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn get_locale_conf_entry(var: str) (locale | errors::invalid) = {
 | 
					fn get_locale_conf_entry(var: str) (locale | invalid) = {
 | 
				
			||||||
	get_locale_conf();
 | 
						get_locale_conf();
 | 
				
			||||||
	for (let entry .. locale_conf) {
 | 
						for (let entry .. locale_conf) {
 | 
				
			||||||
		let (key, value) = strings::cut(entry, "=");
 | 
							let (key, value) = strings::cut(entry, "=");
 | 
				
			||||||
		if (key == var) return parse(value);
 | 
							if (key == var) return parse(value);
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	return errors::invalid;
 | 
						return invalid;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn get_locale_conf() []str = {
 | 
					fn get_locale_conf() []str = {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,3 @@
 | 
				
			|||||||
use errors;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Returns the locale to use for character classification and case conversion.
 | 
					// Returns the locale to use for character classification and case conversion.
 | 
				
			||||||
// The memory is statically allocated and must not be free'd. It may be
 | 
					// The memory is statically allocated and must not be free'd. It may be
 | 
				
			||||||
// overwritten later, so use [[dup]] to extend its lifetime.
 | 
					// overwritten later, so use [[dup]] to extend its lifetime.
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										7
									
								
								locale/error.ha
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								locale/error.ha
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					// All errors defined in this module.
 | 
				
			||||||
 | 
					export type error = !invalid;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Returned when a malformed locale is encountered.
 | 
				
			||||||
 | 
					export type invalid = !void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export fn strerror(err: error) str = "invalid locale";
 | 
				
			||||||
@ -21,16 +21,16 @@ export def c = locale {
 | 
				
			|||||||
//   lang_COUNTRY.ENCODING@MODIFIER
 | 
					//   lang_COUNTRY.ENCODING@MODIFIER
 | 
				
			||||||
// 
 | 
					// 
 | 
				
			||||||
// Where _COUNTRY, .ENCODING, and @MODIFIER may be omitted. The function
 | 
					// Where _COUNTRY, .ENCODING, and @MODIFIER may be omitted. The function
 | 
				
			||||||
// returns a [[locale]], or [[errors::invalid]] if the input cannot be parsed.
 | 
					// returns a [[locale]], or [[invalid]] if the input cannot be parsed. All
 | 
				
			||||||
// All memory is borrowed from the input, so [[finish]] should not be used to
 | 
					// memory is borrowed from the input, so [[finish]] should not be used to free
 | 
				
			||||||
// free it.
 | 
					// it.
 | 
				
			||||||
export fn parse(in: str) (locale | errors::invalid) = {
 | 
					export fn parse(in: str) (locale | invalid) = {
 | 
				
			||||||
	let (in, modifier) = strings::rcut(in, "@");
 | 
						let (in, modifier) = strings::rcut(in, "@");
 | 
				
			||||||
	if (strings::compare(in, "") == 0) return void: errors::invalid;
 | 
						if (strings::compare(in, "") == 0) return void: invalid;
 | 
				
			||||||
	let (in, encoding) = strings::rcut(in, ".");
 | 
						let (in, encoding) = strings::rcut(in, ".");
 | 
				
			||||||
	if (strings::compare(in, "") == 0) return void: errors::invalid;
 | 
						if (strings::compare(in, "") == 0) return void: invalid;
 | 
				
			||||||
	let (in, country) = strings::rcut(in, "_");
 | 
						let (in, country) = strings::rcut(in, "_");
 | 
				
			||||||
	if (strings::compare(in, "") == 0) return void: errors::invalid;
 | 
						if (strings::compare(in, "") == 0) return void: invalid;
 | 
				
			||||||
	return locale {
 | 
						return locale {
 | 
				
			||||||
		lang     = in,
 | 
							lang     = in,
 | 
				
			||||||
		country  = country,
 | 
							country  = country,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,4 @@
 | 
				
			|||||||
use fmt;
 | 
					use fmt;
 | 
				
			||||||
use errors;
 | 
					 | 
				
			||||||
use strings;
 | 
					use strings;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@test fn parse_full() void = {
 | 
					@test fn parse_full() void = {
 | 
				
			||||||
@ -64,8 +63,8 @@ use strings;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@test fn parse_error() void = {
 | 
					@test fn parse_error() void = {
 | 
				
			||||||
	let local = match(parse("_COUNTRY.ENCODING@MODIFIER")) {
 | 
						let local = match(parse("_COUNTRY.ENCODING@MODIFIER")) {
 | 
				
			||||||
	case errors::invalid => void;
 | 
						case invalid => void;
 | 
				
			||||||
	case                 => abort("error");
 | 
						case         => abort("no error");
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user