hare

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

ext_stack.ha (4619B)


      1 // SPDX-License-Identifier: MPL-2.0
      2 // (c) Hare authors <https://harelang.org>
      3 
      4 use bytes;
      5 use strings;
      6 
      7 // Add extensions onto the end of the final path segment. The separating '.'
      8 // will be inserted for you. If the final path segment consists entirely of dots
      9 // or the path is root, this function will return [[cant_extend]].
     10 export fn push_ext(buf: *buffer, exts: str...) (str | error) = {
     11 	match (peek(buf)) {
     12 	case void => return cant_extend;
     13 	case let s: str => if (strings::ltrim(s, '.') == "") return cant_extend;
     14 	};
     15 	for (let ext .. exts) {
     16 		const newend = buf.end + 1 + len(ext);
     17 		if (MAX < newend) return too_long;
     18 		buf.buf[buf.end] = '.';
     19 		buf.buf[buf.end+1..newend] = strings::toutf8(ext);
     20 		buf.end = newend;
     21 	};
     22 	return string(buf);
     23 };
     24 
     25 // Remove and return the final extension in a path. The result will not
     26 // include the leading '.'. The result is borrowed from the buffer. Leading dots
     27 // will be ignored when looking for extensions, such that ".ssh" isn't
     28 // considered to have any extensions.
     29 export fn pop_ext(buf: *buffer) (str | void) = {
     30 	const ext = split_ext(buf);
     31 	buf.end = ext.0;
     32 	return ext.1;
     33 };
     34 
     35 // Examine the final extension in a path. The result will not
     36 // include the leading '.'. The result is borrowed from the buffer. Leading dots
     37 // will be ignored when looking for extensions, such that ".ssh" isn't
     38 // considered to have any extensions.
     39 export fn peek_ext(buf: *buffer) (str | void) = split_ext(buf).1;
     40 
     41 // helper function, returns (end of non-extension, extension string)
     42 fn split_ext(buf: *buffer) (size, (str | void)) = match (peek(buf)) {
     43 case void => return (buf.end, void);
     44 case let s: str =>
     45 	const bs = strings::toutf8(s);
     46 	bs = bytes::ltrim(bs, '.');
     47 	match (bytes::rindex(bs, '.')) {
     48 	case void => return (buf.end, void);
     49 	case let i: size =>
     50 		return (buf.end - len(bs) + i, strings::fromutf8_unsafe(bs[i+1..]));
     51 	};
     52 };
     53 
     54 // Remove and return all the extensions in a path. The result will not
     55 // include the leading '.', but will include separating dots. Leading dots
     56 // will be ignored when looking for extensions, such that ".ssh" isn't
     57 // considered to have any extensions. The result is borrowed from the buffer.
     58 export fn pop_exts(buf: *buffer) (str | void) = {
     59 	const ext = split_exts(buf);
     60 	buf.end = ext.0;
     61 	return ext.1;
     62 };
     63 
     64 // Examine all the extensions in a path. The result will not include the
     65 // leading '.', but will include separating dots. Leading dots will
     66 // be ignored when looking for extensions, such that ".ssh" isn't considered
     67 // to have any extensions. The result is borrowed from the buffer.
     68 export fn peek_exts(buf: *buffer) (str | void) = split_exts(buf).1;
     69 
     70 // helper function, returns (end of non-extension, extension string)
     71 fn split_exts(buf: *buffer) (size, (str | void)) = match (peek(buf)) {
     72 case void => return (buf.end, void);
     73 case let s: str =>
     74 	const bs = strings::toutf8(s);
     75 	bs = bytes::ltrim(bs, '.');
     76 	match (bytes::index(bs, '.')) {
     77 	case void => return (buf.end, void);
     78 	case let i: size =>
     79 		return (buf.end - len(bs) + i, strings::fromutf8_unsafe(bs[i+1..]));
     80 	};
     81 };
     82 
     83 @test fn ext() void = {
     84 	// push_ext
     85 	let buf = init()!;
     86 	assert(push_ext(&buf, "bash") is cant_extend);
     87 	set(&buf, sepstr)!;
     88 	assert(push_ext(&buf, "bash") is cant_extend);
     89 	set(&buf, "....")!;
     90 	assert(push_ext(&buf, "bash") is cant_extend);
     91 	set(&buf, "bashrc")!;
     92 	assert(push_ext(&buf, "bash") as str == "bashrc.bash");
     93 	set(&buf, ".bashrc")!;
     94 	assert(push_ext(&buf, "bash") as str == ".bashrc.bash");
     95 
     96 	// pop_ext
     97 	set(&buf)!;
     98 	assert(pop_ext(&buf) is void);
     99 	set(&buf, "..")!;
    100 	assert(pop_ext(&buf) is void);
    101 	set(&buf, sepstr)!;
    102 	assert(pop_ext(&buf) is void);
    103 
    104 	set(&buf, "index.html.tmpl")!;
    105 	assert(pop_ext(&buf) as str == "tmpl");
    106 	assert(string(&buf) == "index.html");
    107 	assert(pop_ext(&buf) as str == "html");
    108 	assert(string(&buf) == "index");
    109 	assert(pop_ext(&buf) is void);
    110 	assert(string(&buf) == "index");
    111 
    112 	set(&buf, ".secret.tar.gz")!;
    113 	assert(pop_ext(&buf) as str == "gz");
    114 	assert(string(&buf) == ".secret.tar");
    115 	assert(pop_ext(&buf) as str == "tar");
    116 	assert(string(&buf) == ".secret");
    117 	assert(pop_ext(&buf) is void);
    118 	assert(string(&buf) == ".secret");
    119 
    120 	set(&buf, "..ext")!;
    121 	assert(pop_ext(&buf) is void);
    122 	assert(string(&buf) == "..ext");
    123 
    124 	// pop_exts
    125 	set(&buf, "index.html.tmpl")!;
    126 	assert(pop_exts(&buf) as str == "html.tmpl");
    127 	assert(string(&buf) == "index");
    128 	assert(pop_exts(&buf) is void);
    129 	assert(string(&buf) == "index");
    130 
    131 	set(&buf, ".secret.tar.gz")!;
    132 	assert(pop_exts(&buf) as str == "tar.gz");
    133 	assert(string(&buf) == ".secret");
    134 	assert(pop_ext(&buf) is void);
    135 	assert(string(&buf) == ".secret");
    136 };