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