escape.ha (1411B)
1 // SPDX-License-Identifier: MPL-2.0 2 // (c) Hare authors <https://harelang.org> 3 4 use ascii; 5 use encoding::utf8; 6 use io; 7 use memio; 8 use strings; 9 10 fn is_safe(s: str) bool = { 11 const iter = strings::iter(s); 12 for (true) { 13 const rn = match (strings::next(&iter)) { 14 case let rn: rune => 15 yield rn; 16 case void => 17 break; 18 }; 19 20 21 switch (rn) { 22 case '@', '%', '+', '=', ':', ',', '.', '/', '-' => 23 void; 24 case => 25 if (!ascii::isalnum(rn) || ascii::isspace(rn)) { 26 return false; 27 }; 28 }; 29 }; 30 return true; 31 }; 32 33 // Quotes a shell string and writes it to the provided I/O handle. 34 export fn quote(sink: io::handle, s: str) (size | io::error) = { 35 if (len(s) == 0) { 36 return io::writeall(sink, strings::toutf8(`''`))?; 37 }; 38 if (is_safe(s)) { 39 return io::writeall(sink, strings::toutf8(s))?; 40 }; 41 42 let z = io::writeall(sink, ['\''])?; 43 44 const iter = strings::iter(s); 45 for (true) { 46 const rn = match (strings::next(&iter)) { 47 case let rn: rune => 48 yield rn; 49 case void => 50 break; 51 }; 52 53 if (rn == '\'') { 54 z += io::writeall(sink, strings::toutf8(`'"'"'`))?; 55 } else { 56 z += io::writeall(sink, utf8::encoderune(rn))?; 57 }; 58 }; 59 60 z += io::writeall(sink, ['\''])?; 61 return z; 62 }; 63 64 // Quotes a shell string and returns a new string. The caller must free the 65 // return value. 66 export fn quotestr(s: str) str = { 67 const sink = memio::dynamic(); 68 quote(&sink, s)!; 69 return memio::string(&sink)!; 70 };