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 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 localized entry. | ||||
| export type line = (blank | comment | entry | localized_entry); | ||||
| @ -27,195 +13,39 @@ export type blank = void; | ||||
| // Specification: §3.1 | ||||
| export type comment = str; | ||||
| 
 | ||||
| // A group header. | ||||
| // Specification: §3.2 | ||||
| export type group_header = str; | ||||
| 
 | ||||
| // A key/value pair. | ||||
| // Specification: §3.3 | ||||
| export type entry = struct { | ||||
| 	key:   str, | ||||
| 	value: str, | ||||
| }; | ||||
| export type entry = (str, str); | ||||
| 
 | ||||
| // A localized key/value pair. | ||||
| // Specification: §5 | ||||
| export type localized_entry = struct { | ||||
| 	key:    str, | ||||
| 	value:  str, | ||||
| 	locale: locale::locale, | ||||
| export type localized_entry = (str, str, locale::locale); | ||||
| 
 | ||||
| // Duplicates an [[entry]]. Use [[entry_finish]] to get rid of it. | ||||
| 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, | ||||
| // or the group exists but the key isn't in it, it returns void. | ||||
| export fn file_get(fil: *file, group_name: str, key: str) (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_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 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; | ||||
| 	}; | ||||
| // Duplicates a [[localized_entry]]. Use [[localized_entry_finish]] to get rid | ||||
| // of it. | ||||
| export fn localized_entry_dup(entr: localized_entry) localized_entry = ( | ||||
| 	strings::dup(entr.0), | ||||
| 	strings::dup(entr.1), | ||||
| 	locale::dup(entr.2)); | ||||
| 
 | ||||
| // Frees memory associated with an [[localized_entry]]. | ||||
| export fn localized_entry_finish(entr: localized_entry) void = { | ||||
| 	free(entr.0); | ||||
| 	free(entr.1); | ||||
| 	locale::finish(entr.2); | ||||
| }; | ||||
|  | ||||
| @ -1,5 +1,11 @@ | ||||
| // All errors defined in this module. | ||||
| export type error = !( | ||||
| 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 | | ||||
| @ -20,8 +26,10 @@ 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 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); | ||||
| }; | ||||
|  | ||||
| @ -6,83 +6,69 @@ use locale; | ||||
| use memio; | ||||
| use strings; | ||||
| 
 | ||||
| // Parses a [[file]]. The result must be freed using [[file_finish]]. | ||||
| export fn file_parse(in: io::stream) !(file | error | io::error | utf8::invalid) = { | ||||
| 	let scanner = bufio::newscanner(&in); | ||||
| 	defer bufio::finish(&scanner); | ||||
| export type scanner = struct { | ||||
| 	scanner:         bufio::scanner, | ||||
| 	entry:           entry, | ||||
| 	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) { | ||||
| 		let text = match (bufio::scan_line(&scanner)) { | ||||
| 		case let text: const str => | ||||
| 			yield text; | ||||
| 		case let err: io::EOF => | ||||
| 			break; | ||||
| 		case let err: (io::error | utf8::invalid) => | ||||
| 			file_finish(&fil); | ||||
| 			return err; | ||||
| 		}; | ||||
| 	if (text == "") { | ||||
| 		// blank line | ||||
| 		return blank; | ||||
| 		 | ||||
| 		if (text == "") { | ||||
| 			// blank line | ||||
| 			file_append_line(&fil, blank); | ||||
| 			 | ||||
| 		} else if (strings::hasprefix(text, '#')) { | ||||
| 			// comment | ||||
| 			let text = strings::dup(strings::ltrim(text, '#')); | ||||
| 			file_append_line(&fil, text: comment); | ||||
| 			 | ||||
| 		} else if (strings::hasprefix(text, '[')) { | ||||
| 			// group header | ||||
| 			let name = strings::dup(parse_group_header(text)?); | ||||
| 			file_append_group(&fil, name); | ||||
| 			 | ||||
| 		} else { | ||||
| 			// key/value pair | ||||
| 			let (key, valu, local) = match (parse_entry(text)) { | ||||
| 			case let result: (str, str, (locale::locale | void)) => | ||||
| 				yield result; | ||||
| 			case let err: error => | ||||
| 				file_finish(&fil); | ||||
| 				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, | ||||
| 				}; | ||||
| 			}); | ||||
| 	} else if (strings::hasprefix(text, '#')) { | ||||
| 		// comment | ||||
| 		return strings::ltrim(text, '#'): comment; | ||||
| 		 | ||||
| 	} else if (strings::hasprefix(text, '[')) { | ||||
| 		// group header | ||||
| 		return parse_group_header(text)?; | ||||
| 		 | ||||
| 	} else { | ||||
| 		// key/value pair | ||||
| 		let (key, valu, local) = parse_entry(text)?; | ||||
| 
 | ||||
| 		match (local) { | ||||
| 		case let local: locale::locale => | ||||
| 			localized_entry_finish(this.localized_entry); | ||||
| 			this.localized_entry = (key, valu, local); | ||||
| 			return this.localized_entry; | ||||
| 		case void => | ||||
| 			entry_finish(this.entry); | ||||
| 			this.entry = (key, valu); | ||||
| 			return this.entry; | ||||
| 		}; | ||||
| 	}; | ||||
| 	return fil; | ||||
| 	 | ||||
| }; | ||||
| 
 | ||||
| fn file_append_group(fil: *file, name: str) void = { | ||||
| 	append(fil.groups, group { | ||||
| 		name = name, | ||||
| 		... | ||||
| 	}); | ||||
| }; | ||||
| 
 | ||||
| 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); | ||||
| 	}; | ||||
| // Frees resources associated with a [[scanner]]. | ||||
| export fn finish(this: *scanner) void = { | ||||
| 	bufio::finish(&this.scanner); | ||||
| 	entry_finish(this.entry); | ||||
| }; | ||||
| 
 | ||||
| // 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, ']')) { | ||||
| 		return invalid_group_header; | ||||
| 	}; | ||||
| @ -91,7 +77,7 @@ fn parse_group_header(text: str) (str | error) = { | ||||
| 	if (strings::contains(text, '[', ']')) { | ||||
| 		return invalid_group_header; | ||||
| 	}; | ||||
| 	return text; | ||||
| 	return text: group_header; | ||||
| }; | ||||
| 
 | ||||
| // memory must be freed by the caller | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user