2 from __future__ import absolute_import
5 from bup.compat import bytes_from_byte
11 class QuoteError(Exception):
15 def _quotesplit(line):
20 for i in range(len(line)):
21 c = bytes_from_byte(line[i])
23 if inquote == q and c != q:
24 word += b'\\' # single-q backslashes can only quote single-q
31 # this is un-sh-like, but do it for sanity when autocompleting
32 yield (wordstart, word)
35 elif not inquote and not word and (c == q or c == qq):
36 # the 'not word' constraint on this is un-sh-like, but do it
37 # for sanity when autocompleting
40 elif not inquote and c in [b' ', b'\n', b'\r', b'\t']:
42 yield (wordstart, word)
48 yield (wordstart, word)
49 if inquote or inescape or word:
54 """Split 'line' into a list of offset,word tuples.
56 The words are produced after removing doublequotes, singlequotes, and
59 Note that this implementation isn't entirely sh-compatible. It only
60 dequotes words that *start* with a quote character, that is, bytes like
62 will not have its quotes removed, while bytes like
64 will be turned into [(0, 'hello'), (6, 'world')] (ie. quotes removed).
68 for i in _quotesplit(line):
75 def unfinished_word(line):
76 """Returns the quotechar,word of any unfinished word at the end of 'line'.
78 You can use this to determine if 'line' is a completely parseable line
79 (ie. one that quotesplit() will finish successfully) or if you need
80 to read more bytes first.
85 quotechar,word: the initial quote char (or None), and the partial word.
88 for (wordstart,word) in _quotesplit(line):
91 firstchar = bytes_from_byte(line[wordstart])
92 if firstchar in [q, qq]:
93 return (firstchar, word)
99 def quotify(qtype, word, terminate):
100 """Return a bytes corresponding to given word, quoted using qtype.
102 The resulting bytes are dequotable using quotesplit() and can be
103 joined with other quoted bytes by adding arbitrary whitespace
107 qtype: one of '', shquote.qq, or shquote.q
108 word: the bytes to quote. May contain arbitrary characters.
109 terminate: include the trailing quote character, if any.
114 return qq + word.replace(qq, b'\\"') + (terminate and qq or b'')
116 return q + word.replace(q, b"\\'") + (terminate and q or b'')
118 return re.sub(br'([\"\' \t\n\r])', br'\\\1', word)
121 def quotify_list(words):
122 """Return minimally-quoted bytes produced by quoting each word.
124 This calculates the qtype for each word depending on whether the word
125 already includes singlequote characters, doublequote characters, both,
129 words: the list of words to quote.
131 The resulting bytes, with quoted words separated by ' '.
136 if word and not re.search(br'[\s\"\']', word):
138 elif q in word and qq not in word:
140 wordout.append(quotify(qtype, word, True))
141 return b' '.join(wordout)
144 def what_to_add(qtype, origword, newword, terminate):
145 """Return a qtype that is needed to finish a partial word.
147 For example, given an origword of '\"frog' and a newword of '\"frogston',
149 terminate=False: 'ston'
150 terminate=True: 'ston\"'
152 This is useful when calculating tab completions for readline.
155 qtype: the type of quoting to use (ie. the first character of origword)
156 origword: the original word that needs completion.
157 newword: the word we want it to be after completion. Must start with
159 terminate: true if we should add the actual quote character at the end.
161 The bytes to append to origword to produce (quoted) newword.
163 if not newword.startswith(origword):
166 qold = quotify(qtype, origword, terminate=False)
167 return quotify(qtype, newword, terminate=terminate)[len(qold):]