diff --git a/tools/util.py b/tools/util.py index 659d9584b4..442aeb1437 100644 --- a/tools/util.py +++ b/tools/util.py @@ -39,6 +39,26 @@ def run_output(args, quiet=False, cwd=None, env=None, merge_env={}): return subprocess.check_output(args, cwd=cwd, env=env, shell=shell) +def shell_quote_win(arg): + if re.search(r'[\x00-\x20"^%~!@&?*<>|()=]', arg): + # Double all " quote characters. + arg = arg.replace('"', '""') + # Wrap the entire string in " quotes. + arg = '"' + arg + '"' + # Double any N backslashes that are immediately followed by a " quote. + arg = re.sub(r'(\\+)(?=")', r'\1\1', arg) + return arg + + +def shell_quote(arg): + if os.name == "nt": + return shell_quote_win(arg) + else: + # Python 2 has posix shell quoting built in, albeit in a weird place. + from pipes import quote + return quote(arg) + + def remove_and_symlink(target, name, target_is_dir=False): try: # On Windows, directory symlink can only be removed with rmdir(). diff --git a/tools/util_test.py b/tools/util_test.py index 308951bc29..e40d0aed0d 100644 --- a/tools/util_test.py +++ b/tools/util_test.py @@ -1,5 +1,5 @@ # Copyright 2018 the Deno authors. All rights reserved. MIT license. -from util import pattern_match, parse_exit_code +from util import pattern_match, parse_exit_code, shell_quote_win def pattern_match_test(): @@ -34,9 +34,20 @@ def parse_exit_code_test(): assert 0 == parse_exit_code('hello_world') +def shell_quote_win_test(): + print "Testing util.shell_quote_win()..." + assert 'simple' == shell_quote_win('simple') + assert 'roof/\\isoprojection' == shell_quote_win('roof/\\isoprojection') + assert '"with space"' == shell_quote_win('with space') + assert '"embedded""quote"' == shell_quote_win('embedded"quote') + assert '"a""b""""c\\d\\\\""e\\\\\\\\"' == shell_quote_win( + 'a"b""c\\d\\"e\\\\') + + def util_test(): pattern_match_test() parse_exit_code_test() + shell_quote_win_test() if __name__ == '__main__':