hare

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

ext_stack.ha (4500B)


      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(p: *path, exts: str...) (str | error) = {
     11 	match (peek(p)) {
     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 = p.end + 1 + len(ext);
     17 		if (MAX < newend) return too_long;
     18 		p.buf[p.end] = '.';
     19 		p.buf[p.end+1..newend] = strings::toutf8(ext);
     20 		p.end = newend;
     21 	};
     22 	return string(p);
     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 path's buffer.
     27 // Leading dots will be ignored when looking for extensions, such that ".ssh"
     28 // isn't considered to have any extensions.
     29 export fn pop_ext(p: *path) (str | void) = {
     30 	const ext = split_ext(p);
     31 	p.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 path's buffer.
     37 // Leading dots will be ignored when looking for extensions, such that ".ssh"
     38 // isn't considered to have any extensions.
     39 export fn peek_ext(p: *path) (str | void) = split_ext(p).1;
     40 
     41 // helper function, returns (end of non-extension, extension string)
     42 fn split_ext(p: *path) (size, (str | void)) = match (peek(p)) {
     43 case void => return (p.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 (p.end, void);
     49 	case let i: size =>
     50 		return (p.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 path's
     58 // buffer.
     59 export fn pop_exts(p: *path) (str | void) = {
     60 	const ext = split_exts(p);
     61 	p.end = ext.0;
     62 	return ext.1;
     63 };
     64 
     65 // Examine all the extensions in a path. The result will not include the
     66 // leading '.', but will include separating dots. Leading dots will
     67 // be ignored when looking for extensions, such that ".ssh" isn't considered
     68 // to have any extensions. The result is borrowed from the path's buffer.
     69 export fn peek_exts(p: *path) (str | void) = split_exts(p).1;
     70 
     71 // helper function, returns (end of non-extension, extension string)
     72 fn split_exts(p: *path) (size, (str | void)) = match (peek(p)) {
     73 case void => return (p.end, void);
     74 case let s: str =>
     75 	const bs = strings::toutf8(s);
     76 	bs = bytes::ltrim(bs, '.');
     77 	match (bytes::index(bs, '.')) {
     78 	case void => return (p.end, void);
     79 	case let i: size =>
     80 		return (p.end - len(bs) + i, strings::fromutf8_unsafe(bs[i+1..]));
     81 	};
     82 };
     83 
     84 @test fn ext() void = {
     85 	// push_ext
     86 	let p = init()!;
     87 	assert(push_ext(&p, "bash") is cant_extend);
     88 	set(&p, sepstr)!;
     89 	assert(push_ext(&p, "bash") is cant_extend);
     90 	set(&p, "....")!;
     91 	assert(push_ext(&p, "bash") is cant_extend);
     92 	set(&p, "bashrc")!;
     93 	assert(push_ext(&p, "bash") as str == "bashrc.bash");
     94 	set(&p, ".bashrc")!;
     95 	assert(push_ext(&p, "bash") as str == ".bashrc.bash");
     96 
     97 	// pop_ext
     98 	set(&p)!;
     99 	assert(pop_ext(&p) is void);
    100 	set(&p, "..")!;
    101 	assert(pop_ext(&p) is void);
    102 	set(&p, sepstr)!;
    103 	assert(pop_ext(&p) is void);
    104 
    105 	set(&p, "index.html.tmpl")!;
    106 	assert(pop_ext(&p) as str == "tmpl");
    107 	assert(string(&p) == "index.html");
    108 	assert(pop_ext(&p) as str == "html");
    109 	assert(string(&p) == "index");
    110 	assert(pop_ext(&p) is void);
    111 	assert(string(&p) == "index");
    112 
    113 	set(&p, ".secret.tar.gz")!;
    114 	assert(pop_ext(&p) as str == "gz");
    115 	assert(string(&p) == ".secret.tar");
    116 	assert(pop_ext(&p) as str == "tar");
    117 	assert(string(&p) == ".secret");
    118 	assert(pop_ext(&p) is void);
    119 	assert(string(&p) == ".secret");
    120 
    121 	set(&p, "..ext")!;
    122 	assert(pop_ext(&p) is void);
    123 	assert(string(&p) == "..ext");
    124 
    125 	// pop_exts
    126 	set(&p, "index.html.tmpl")!;
    127 	assert(pop_exts(&p) as str == "html.tmpl");
    128 	assert(string(&p) == "index");
    129 	assert(pop_exts(&p) is void);
    130 	assert(string(&p) == "index");
    131 
    132 	set(&p, ".secret.tar.gz")!;
    133 	assert(pop_exts(&p) as str == "tar.gz");
    134 	assert(string(&p) == ".secret");
    135 	assert(pop_ext(&p) is void);
    136 	assert(string(&p) == ".secret");
    137 };