This commit is contained in:
Anthony Scopatz 2017-02-24 22:39:36 -05:00
parent 834e45f3ae
commit f056fe01c3
4 changed files with 50 additions and 21 deletions

View file

@ -1792,6 +1792,9 @@ def test_comment_only():
def test_echo_slash_question():
check_xonsh_ast({}, '![echo /?]', False)
def test_bad_quotes():
with pytest.raises(SyntaxError):
check_xonsh_ast({}, '![echo """hello]', False)
def test_redirect():
assert check_xonsh_ast({}, '$[cat < input.txt]', False)

View file

@ -24,7 +24,7 @@ from xonsh.tools import (
is_string_seq, pathsep_to_seq, seq_to_pathsep, is_nonstring_seq_of_strings,
pathsep_to_upper_seq, seq_to_upper_pathsep, expandvars, is_int_as_str, is_slice_as_str,
ensure_timestamp, get_portions, is_balanced, subexpr_before_unbalanced,
swap_values, get_logical_line, replace_logical_line
swap_values, get_logical_line, replace_logical_line, check_quotes,
)
from xonsh.environ import Env
@ -208,6 +208,11 @@ def test_subproc_toks_hello_mom_second():
assert exp == obs
def test_subproc_toks_hello_bad_quotes():
with pytest.raises(SyntaxError):
obs = subproc_toks('echo """hello', lexer=LEXER, returnline=True)
def test_subproc_toks_comment():
exp = None
obs = subproc_toks('# I am a comment', lexer=LEXER, returnline=True)
@ -353,6 +358,17 @@ def test_replace_logical_line(src, idx, exp_line, exp_n):
assert exp == obs
@pytest.mark.parametrize('inp, exp', [
('f(1,10),x.y', True),
('"x"', True),
("'y'", True),
('b"x"', True),
("r'y'", True),
])
def test_check_quotes(inp, exp):
obs = check_quotes(inp)
assert exp is obs
@pytest.mark.parametrize('inp', [
'f(1,10),x.y',
])

View file

@ -19,6 +19,7 @@ from xonsh.platform import PYTHON_VERSION_INFO
from xonsh.tokenize import SearchPath, StringPrefix
from xonsh.lazyasd import LazyObject
from xonsh.parsers.context_check import check_contexts
from xonsh.tools import check_quotes
RE_SEARCHPATH = LazyObject(lambda: re.compile(SearchPath), globals(),
'RE_SEARCHPATH')
@ -2505,25 +2506,7 @@ class BaseParser(object):
"""
if not isinstance(p, ast.Str):
return p
s = p.s
if s.startswith('"""'):
ok = s.endswith('"""')
elif s.endswith('"""'):
ok = s.startswith('"""')
elif s.startswith("'''"):
ok = s.endswith("'''")
elif s.endswith("'''"):
ok = s.startswith("'''")
elif s.startswith('"'):
ok = s.endswith('"')
elif s.endswith('"'):
ok = s.startswith('"')
elif s.startswith("'"):
ok = s.endswith("'")
elif s.endswith("'"):
ok = s.startswith("'")
else:
ok = True
ok = check_quotes(p.s)
if not ok:
msg = ("Subprocess tokes must have matching begining and ending "
"quotes, if they start or end with quotes, got {0}")

View file

@ -319,6 +319,8 @@ def subproc_toks(line, mincol=-1, maxcol=None, lexer=None, returnline=False):
else:
tok.lexpos = len(line)
break
elif isinstance(tok.value, str) and not check_quotes(tok.value):
return
else:
if len(toks) > 0 and toks[-1].type in END_TOK_TYPES:
if _is_not_lparen_and_rparen(lparens, toks[-1]):
@ -346,6 +348,24 @@ def subproc_toks(line, mincol=-1, maxcol=None, lexer=None, returnline=False):
return rtn
def check_quotes(s):
"""Checks a string to make sure that if it starts with quotes, it also
ends with quotes.
"""
starts_as_str = RE_BEGIN_STRING.match(s) is not None
ends_as_str = s.endswith('"') or s.endswith("'")
if not starts_as_str and not ends_as_str:
ok = True
elif starts_as_str and not ends_as_str:
ok = False
elif not starts_as_str and ends_as_str:
ok = False
else:
m = RE_COMPLETE_STRING.match(s)
ok = m is not None
return ok
def get_logical_line(lines, idx):
"""Returns a single logical line (i.e. one without line continuations)
from a list of lines. This line should begin at index idx. This also
@ -1526,7 +1546,7 @@ def format_std_prepost(template, env=None):
return s
_RE_STRING_START = "[bBrRuU]*"
_RE_STRING_START = "[bBprRuU]*"
_RE_STRING_TRIPLE_DOUBLE = '"""'
_RE_STRING_TRIPLE_SINGLE = "'''"
_RE_STRING_DOUBLE = '"'
@ -1558,6 +1578,13 @@ match the contents of a string beginning with those quotes (not including the
terminating quotes)"""
@lazyobject
def RE_COMPLETE_STRING():
ptrn = ('^' + _RE_STRING_START + '(?P<quote>' + "|".join(_STRINGS) + ')' +
'.*?(?P=quote)$')
return re.compile(ptrn)
def check_for_partial_string(x):
"""Returns the starting index (inclusive), ending index (exclusive), and
starting quote string of the most recent Python string found in the input.