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:
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