Skip to content

Commit

Permalink
Merge pull request #17 from fonttools/windows-path-args
Browse files Browse the repository at this point in the history
use Jython's cmdline2list on Windows instead of POSIX shlex.split to parse_args
  • Loading branch information
anthrotype authored Feb 9, 2022
2 parents 716b52c + c650a99 commit 338d34a
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 4 deletions.
61 changes: 58 additions & 3 deletions src/python/ttfautohint/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,57 @@ def stdout_or_output_path_type(s):
return s


def parse_args(args=None):
def _windows_cmdline2list(cmdline):
"""Build an argv list from a Microsoft shell style cmdline str.
The reverse of subprocess.list2cmdline that follows the same MS C runtime rules.
Borrowed from Jython source code:
https://github.com/jython/jython/blob/50729e6/Lib/subprocess.py#L668-L722
"""
whitespace = ' \t'
# count of preceding '\'
bs_count = 0
in_quotes = False
arg = []
argv = []

for ch in cmdline:
if ch in whitespace and not in_quotes:
if arg:
# finalize arg and reset
argv.append(''.join(arg))
arg = []
bs_count = 0
elif ch == '\\':
arg.append(ch)
bs_count += 1
elif ch == '"':
if not bs_count % 2:
# Even number of '\' followed by a '"'. Place one
# '\' for every pair and treat '"' as a delimiter
if bs_count:
del arg[-(bs_count / 2):]
in_quotes = not in_quotes
else:
# Odd number of '\' followed by a '"'. Place one '\'
# for every pair and treat '"' as an escape sequence
# by the remaining '\'
del arg[-(bs_count / 2 + 1):]
arg.append(ch)
bs_count = 0
else:
# regular char
arg.append(ch)
bs_count = 0

# A single trailing '"' delimiter yields an empty arg
if arg or in_quotes:
argv.append(''.join(arg))

return argv


def parse_args(args=None, splitfunc=None):
"""Parse command line arguments and return a dictionary of options
for ttfautohint.ttfautohint function.
Expand All @@ -295,8 +345,13 @@ def parse_args(args=None):
else:
capture_sys_exit = True
if isinstance(args, basestring):
import shlex
args = shlex.split(args)
if splitfunc is None:
if sys.platform == "win32":
splitfunc = _windows_cmdline2list
else:
import shlex
splitfunc = shlex.split
args = splitfunc(args)

parser = argparse.ArgumentParser(
prog="ttfautohint",
Expand Down
17 changes: 16 additions & 1 deletion tests/test_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from ttfautohint.options import (
validate_options, format_varargs, strong_stem_width,
stdin_or_input_path_type, stdout_or_output_path_type, parse_args,
stem_width_mode, StemWidthMode
stem_width_mode, StemWidthMode, _windows_cmdline2list
)


Expand Down Expand Up @@ -529,3 +529,18 @@ def test_source_date_epoch_invalid(self, monkeypatch):
def test_show_ttfa_info_unsupported(self):
with pytest.raises(NotImplementedError):
parse_args("-T")

def test_parse_args_custom_splitfunc(self):
# https://github.com/fonttools/ttfautohint-py/issues/2
s = '"build folder\\unhinted\\Test_Lt.ttf" "output folder\\hinted\\Test_Lt.ttf"'
args = parse_args(s, splitfunc=_windows_cmdline2list)
assert args["in_file"] == "build folder\\unhinted\\Test_Lt.ttf"
assert args["out_file"] == "output folder\\hinted\\Test_Lt.ttf"

@pytest.mark.skipif(sys.platform != "win32", reason="only for windows")
def test_parse_args_windows_paths(self):
# https://github.com/fonttools/ttfautohint-py/issues/2
s = '"build folder\\unhinted\\Test_Lt.ttf" "output folder\\hinted\\Test_Lt.ttf"'
args = parse_args(s)
assert args["in_file"] == "build folder\\unhinted\\Test_Lt.ttf"
assert args["out_file"] == "output folder\\hinted\\Test_Lt.ttf"

0 comments on commit 338d34a

Please sign in to comment.