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 };