|
|
@ -108,13 +108,42 @@ class StringView: |
|
|
|
|
|
|
|
# Parser |
|
|
|
|
|
|
|
# map from opening quotes to closing quotes |
|
|
|
_quotes = { |
|
|
|
'"': '"', |
|
|
|
"'": "'", |
|
|
|
"«": "»", |
|
|
|
"‘": "’", |
|
|
|
"‚": "‛", |
|
|
|
"“": "”", |
|
|
|
"„": "‟", |
|
|
|
"‹": "›", |
|
|
|
"⹂": "⹂", |
|
|
|
"「": "」", |
|
|
|
"『": "』", |
|
|
|
"〝": "〞", |
|
|
|
"﹁": "﹂", |
|
|
|
"﹃": "﹄", |
|
|
|
""": """, |
|
|
|
"'": "'", |
|
|
|
"「": "」", |
|
|
|
"«": "»", |
|
|
|
"‹": "›", |
|
|
|
"《": "》", |
|
|
|
"〈": "〉", |
|
|
|
} |
|
|
|
_all_quotes = set(_quotes.keys()) | set(_quotes.values()) |
|
|
|
|
|
|
|
def quoted_word(view): |
|
|
|
current = view.current |
|
|
|
|
|
|
|
if current is None: |
|
|
|
return None |
|
|
|
|
|
|
|
is_quoted = current == '"' |
|
|
|
close_quote = _quotes.get(current) |
|
|
|
is_quoted = bool(close_quote) |
|
|
|
if is_quoted: |
|
|
|
open_quote = current |
|
|
|
result = [] if is_quoted else [current] |
|
|
|
|
|
|
|
while not view.eof: |
|
|
@ -122,7 +151,7 @@ def quoted_word(view): |
|
|
|
if not current: |
|
|
|
if is_quoted: |
|
|
|
# unexpected EOF |
|
|
|
raise BadArgument('Expected closing "') |
|
|
|
raise BadArgument('Expected closing {}.'.format(close_quote)) |
|
|
|
return ''.join(result) |
|
|
|
|
|
|
|
# currently we accept strings in the format of "hello world" |
|
|
@ -133,32 +162,32 @@ def quoted_word(view): |
|
|
|
# string ends with \ and no character after it |
|
|
|
if is_quoted: |
|
|
|
# if we're quoted then we're expecting a closing quote |
|
|
|
raise BadArgument('Expected closing "') |
|
|
|
raise BadArgument('Expected closing {}.'.format(close_quote)) |
|
|
|
# if we aren't then we just let it through |
|
|
|
return ''.join(result) |
|
|
|
|
|
|
|
if next_char == '"': |
|
|
|
if next_char in ((open_quote, close_quote) if is_quoted else _all_quotes): |
|
|
|
# escaped quote |
|
|
|
result.append('"') |
|
|
|
result.append(next_char) |
|
|
|
else: |
|
|
|
# different escape character, ignore it |
|
|
|
view.undo() |
|
|
|
result.append(current) |
|
|
|
continue |
|
|
|
|
|
|
|
if not is_quoted and current in _all_quotes: |
|
|
|
# we aren't quoted |
|
|
|
raise BadArgument('Unexpected quote mark in non-quoted string') |
|
|
|
|
|
|
|
# closing quote |
|
|
|
if current == '"': |
|
|
|
if is_quoted and current == close_quote: |
|
|
|
next_char = view.get() |
|
|
|
valid_eof = not next_char or next_char.isspace() |
|
|
|
if is_quoted: |
|
|
|
if not valid_eof: |
|
|
|
raise BadArgument('Expected space after closing quotation') |
|
|
|
if not valid_eof: |
|
|
|
raise BadArgument('Expected space after closing quotation') |
|
|
|
|
|
|
|
# we're quoted so it's okay |
|
|
|
return ''.join(result) |
|
|
|
else: |
|
|
|
# we aren't quoted |
|
|
|
raise BadArgument('Unexpected quote mark in non-quoted string') |
|
|
|
# we're quoted so it's okay |
|
|
|
return ''.join(result) |
|
|
|
|
|
|
|
if current.isspace() and not is_quoted: |
|
|
|
# end of word found |
|
|
|