hare

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

commit f6040114919ba1a952994e58baf2ea65254ec101
parent 326da86c47437fa35796bf23e887b30b1101c371
Author: Drew DeVault <sir@cmpwn.com>
Date:   Fri, 15 Apr 2022 11:42:52 +0200

shlex: add quote, quotestr

Signed-off-by: Drew DeVault <sir@cmpwn.com>

Diffstat:
Mscripts/gen-stdlib | 1+
Mshlex/+test.ha | 14++++++++++++++
Ashlex/escape.ha | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mstdlib.mk | 2++
4 files changed, 86 insertions(+), 0 deletions(-)

diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib @@ -1073,6 +1073,7 @@ gensrcs_strconv() { gensrcs_shlex() { gen_srcs shlex \ + escape.ha \ split.ha \ $* } diff --git a/shlex/+test.ha b/shlex/+test.ha @@ -60,3 +60,17 @@ use strings; assert(split("'dangling single quote") is syntaxerr); assert(split(`unterminated\ backslash \`) is syntaxerr); }; + +@test fn quote() void = { + const bare = quotestr(`hello`); + defer free(bare); + assert(bare == `hello`); + + const spaces = quotestr(`hello world`); + defer free(spaces); + assert(spaces == `"hello world"`); + + const quotes = quotestr(`'hello' "world"`); + defer free(quotes); + assert(quotes == `"'hello' "'"'"world"'"'""`); +}; diff --git a/shlex/escape.ha b/shlex/escape.ha @@ -0,0 +1,69 @@ +use ascii; +use encoding::utf8; +use io; +use strings; +use strio; + +fn is_safe(s: str) bool = { + const iter = strings::iter(s); + for (true) { + const rn = match (strings::next(&iter)) { + case let rn: rune => + yield rn; + case void => + break; + }; + + if (!ascii::isprint(rn) || ascii::isspace(rn)) { + return false; + }; + + switch (rn) { + case '@', '%', '+', '=', ':', ',', '.', '/', '-' => + return false; + case => + yield; + }; + }; + return true; +}; + +// Quotes a shell string and writes it to the provided I/O handle. +export fn quote(sink: io::handle, s: str) (void | io::error) = { + if (len(s) == 0) { + io::write(sink, strings::toutf8(`''`))?; + return; + }; + if (is_safe(s)) { + io::write(sink, strings::toutf8(s))?; + return; + }; + + io::write(sink, ['"'])?; + + const iter = strings::iter(s); + for (true) { + const rn = match (strings::next(&iter)) { + case let rn: rune => + yield rn; + case void => + break; + }; + + if (rn == '"') { + io::write(sink, strings::toutf8(`"'"'"`))?; + } else { + io::write(sink, utf8::encoderune(rn))?; + }; + }; + + io::write(sink, ['"'])?; +}; + +// Quotes a shell string and returns a new string. The caller must free the +// return value. +export fn quotestr(s: str) str = { + const sink = strio::dynamic(); + quote(&sink, s)!; + return strio::string(&sink); +}; diff --git a/stdlib.mk b/stdlib.mk @@ -1674,6 +1674,7 @@ $(HARECACHE)/regex/regex-any.ssa: $(stdlib_regex_any_srcs) $(stdlib_rt) $(stdlib # shlex (+any) stdlib_shlex_any_srcs= \ + $(STDLIB)/shlex/escape.ha \ $(STDLIB)/shlex/split.ha $(HARECACHE)/shlex/shlex-any.ssa: $(stdlib_shlex_any_srcs) $(stdlib_rt) $(stdlib_bufio_$(PLATFORM)) $(stdlib_io_$(PLATFORM)) $(stdlib_strings_$(PLATFORM)) $(stdlib_strio_$(PLATFORM)) @@ -3685,6 +3686,7 @@ $(TESTCACHE)/regex/regex-any.ssa: $(testlib_regex_any_srcs) $(testlib_rt) $(test # shlex (+any) testlib_shlex_any_srcs= \ + $(STDLIB)/shlex/escape.ha \ $(STDLIB)/shlex/split.ha \ $(STDLIB)/shlex/+test.ha