hare

[hare] The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

fmt.ha (2977B)


      1 use ascii;
      2 use encoding::utf8;
      3 use fmt;
      4 use io;
      5 use net::ip;
      6 use strconv;
      7 use strings;
      8 use strio;
      9 
     10 
     11 // Extract from RFC3986 ABNF
     12 // URI        = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
     13 // unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
     14 // sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
     15 // reg-name   = *( unreserved / pct-encoded / sub-delims )
     16 // host       = IP-literal / IPv4address / reg-name
     17 // pchar      = unreserved / pct-encoded / sub-delims / ":" / "@"
     18 // query      = *( pchar / "/" / "?" )
     19 // fragment   = *( pchar / "/" / "?" )
     20 
     21 def unres_host: str = "-._~!$&'()*+,;=";
     22 def unres_query_frag: str = "-._~!$&'()*+,;=:@/?";
     23 def unres_path: str = "-._~!$&'()*+,;=:@/";
     24 
     25 // Writes a formatted [[uri]] to an [[io::handle]]. Returns the number of bytes
     26 // written.
     27 export fn fmt(out: io::handle, u: *const uri) (size | io::error) = {
     28 	let n = 0z;
     29 	let slashes_w = false;
     30 	if (u.scheme != "") {
     31 		n += fmt::fprintf(out, "{}:", u.scheme)?;
     32 	};
     33 	if (len(u.userinfo) > 0) {
     34 		assert(!(u.host is str) || len(u.host as str) > 0);
     35 		n += fmt::fprintf(out, "//{}@", u.userinfo)?;
     36 		slashes_w = true;
     37 	};
     38 	match (u.host) {
     39 	case let host: str =>
     40 		// file scheme is allowed an empty host
     41 		if (len(host) > 0 || u.scheme == "file") {
     42 			if (!slashes_w) {
     43 				n += fmt::fprint(out, "//")?;
     44 			};
     45 			let unres = if(u.scheme == "file") {
     46 				yield unres_path;
     47 			} else {
     48 				yield unres_host;
     49 			};
     50 			n += percent_encode(out, host, unres)?;
     51 		};
     52 	case let addr: ip::addr =>
     53 		if (!slashes_w) {
     54 			n += fmt::fprint(out, "//")?;
     55 		};
     56 		n += fmtaddr(out, addr)?;
     57 	};
     58 	if (u.port != 0) {
     59 		n += fmt::fprintf(out, ":{}", u.port)?;
     60 	};
     61 	n += percent_encode(out, u.path, unres_path)?;
     62 	if (len(u.query) > 0) {
     63 		// Always percent-encoded, see parse and encodequery/decodequery
     64 		n += fmt::fprintf(out, "?{}", u.query)?;
     65 	};
     66 	if (len(u.fragment) > 0) {
     67 		n += fmt::fprint(out, "#")?;
     68 		n += percent_encode(out, u.fragment, unres_query_frag)?;
     69 	};
     70 
     71 	return n;
     72 };
     73 
     74 fn fmtaddr(out: io::handle, addr: ip::addr) (size | io::error) = {
     75 	let n = 0z;
     76 	match (addr) {
     77 	case let addr: ip::addr4 =>
     78 		n += ip::fmt(out, addr)?;
     79 	case let addr: ip::addr6 =>
     80 		n += fmt::fprintf(out, "[")?;
     81 		n += ip::fmt(out, addr)?;
     82 		n += fmt::fprintf(out, "]")?;
     83 	};
     84 	return n;
     85 };
     86 
     87 fn percent_encode(out: io::handle, src: str, allowed: str) (size | io::error) = {
     88 	let iter = strings::iter(src);
     89 	let n = 0z;
     90 	for (true) {
     91 		const r = match (strings::next(&iter)) {
     92 		case let r: rune =>
     93 			yield r;
     94 		case =>
     95 			break;
     96 		};
     97 		if (ascii::isalnum(r) || strings::contains(allowed, r)) {
     98 			n += fmt::fprint(out, r)?;
     99 		} else {
    100 			const en = utf8::encoderune(r);
    101 			for (let i = 0z; i < len(en); i += 1) {
    102 				n += fmt::fprintf(out, "%{:X}", en[i])?;
    103 			};
    104 		};
    105 	};
    106 	return n;
    107 };
    108 
    109 // Formats a [[uri]] into a string. The result must be freed by the caller.
    110 export fn string(u: *const uri) str = {
    111 	const st = strio::dynamic();
    112 	fmt(&st, u)!;
    113 	return strio::string(&st);
    114 };