]> arthur.barton.de Git - bup.git/blob - lib/bup/shquote.py
Use absolute_import from the __future__ everywhere
[bup.git] / lib / bup / shquote.py
1
2 from __future__ import absolute_import
3 import re
4
5 q = "'"
6 qq = '"'
7
8
9 class QuoteError(Exception):
10     pass
11
12
13 def _quotesplit(line):
14     inquote = None
15     inescape = None
16     wordstart = 0
17     word = ''
18     for i in range(len(line)):
19         c = line[i]
20         if inescape:
21             if inquote == q and c != q:
22                 word += '\\'  # single-q backslashes can only quote single-q
23             word += c
24             inescape = False
25         elif c == '\\':
26             inescape = True
27         elif c == inquote:
28             inquote = None
29             # this is un-sh-like, but do it for sanity when autocompleting
30             yield (wordstart, word)
31             word = ''
32             wordstart = i+1
33         elif not inquote and not word and (c == q or c == qq):
34             # the 'not word' constraint on this is un-sh-like, but do it
35             # for sanity when autocompleting
36             inquote = c
37             wordstart = i
38         elif not inquote and c in [' ', '\n', '\r', '\t']:
39             if word:
40                 yield (wordstart, word)
41             word = ''
42             wordstart = i+1
43         else:
44             word += c
45     if word:
46         yield (wordstart, word)
47     if inquote or inescape or word:
48         raise QuoteError()
49
50
51 def quotesplit(line):
52     """Split 'line' into a list of offset,word tuples.
53
54     The words are produced after removing doublequotes, singlequotes, and
55     backslash escapes.
56
57     Note that this implementation isn't entirely sh-compatible.  It only
58     dequotes words that *start* with a quote character, that is, a string like
59        hello"world"
60     will not have its quotes removed, while a string like
61        hello "world"
62     will be turned into [(0, 'hello'), (6, 'world')] (ie. quotes removed).
63     """
64     l = []
65     try:
66         for i in _quotesplit(line):
67             l.append(i)
68     except QuoteError:
69         pass
70     return l
71
72
73 def unfinished_word(line):
74     """Returns the quotechar,word of any unfinished word at the end of 'line'.
75
76     You can use this to determine if 'line' is a completely parseable line
77     (ie. one that quotesplit() will finish successfully) or if you need
78     to read more bytes first.
79
80     Args:
81       line: an input string
82     Returns:
83       quotechar,word: the initial quote char (or None), and the partial word.
84     """
85     try:
86         for (wordstart,word) in _quotesplit(line):
87             pass
88     except QuoteError:
89         firstchar = line[wordstart]
90         if firstchar in [q, qq]:
91             return (firstchar, word)
92         else:
93             return (None, word)
94     else:
95         return (None, '')
96
97
98 def quotify(qtype, word, terminate):
99     """Return a string corresponding to given word, quoted using qtype.
100
101     The resulting string is dequotable using quotesplit() and can be
102     joined with other quoted strings by adding arbitrary whitespace
103     separators.
104
105     Args:
106       qtype: one of '', shquote.qq, or shquote.q
107       word: the string to quote.  May contain arbitrary characters.
108       terminate: include the trailing quote character, if any.
109     Returns:
110       The quoted string.
111     """
112     if qtype == qq:
113         return qq + word.replace(qq, '\\"') + (terminate and qq or '')
114     elif qtype == q:
115         return q + word.replace(q, "\\'") + (terminate and q or '')
116     else:
117         return re.sub(r'([\"\' \t\n\r])', r'\\\1', word)
118
119
120 def quotify_list(words):
121   """Return a minimally-quoted string produced by quoting each word.
122
123   This calculates the qtype for each word depending on whether the word
124   already includes singlequote characters, doublequote characters, both,
125   or neither.
126
127   Args:
128     words: the list of words to quote.
129   Returns:
130     The resulting string, with quoted words separated by ' '.
131   """
132   wordout = []
133   for word in words:
134     qtype = q
135     if word and not re.search(r'[\s\"\']', word):
136       qtype = ''
137     elif q in word and qq not in word:
138       qtype = qq
139     wordout.append(quotify(qtype, word, True))
140   return ' '.join(wordout)
141
142
143 def what_to_add(qtype, origword, newword, terminate):
144     """Return a qtype that is needed to finish a partial word.
145
146     For example, given an origword of '\"frog' and a newword of '\"frogston',
147     returns either:
148        terminate=False: 'ston'
149        terminate=True:  'ston\"'
150
151     This is useful when calculating tab completion strings for readline.
152
153     Args:
154       qtype: the type of quoting to use (ie. the first character of origword)
155       origword: the original word that needs completion.
156       newword: the word we want it to be after completion.  Must start with
157         origword.
158       terminate: true if we should add the actual quote character at the end.
159     Returns:
160       The string to append to origword to produce (quoted) newword.
161     """
162     if not newword.startswith(origword):
163         return ''
164     else:
165         qold = quotify(qtype, origword, terminate=False)
166         return quotify(qtype, newword, terminate=terminate)[len(qold):]