hare

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

fmt.ha (3120B)


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