hare

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

fmt.ha (3132B)


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