http://scons.tigris.org/issues/show_bug.cgi?id=2345
[scons.git] / src / engine / SCons / compat / _scons_shlex.py
1 # -*- coding: iso-8859-1 -*-
2 """A lexical analyzer class for simple shell-like syntaxes."""
3
4 # Module and documentation by Eric S. Raymond, 21 Dec 1998
5 # Input stacking and error message cleanup added by ESR, March 2000
6 # push_source() and pop_source() made explicit by ESR, January 2001.
7 # Posix compliance, split(), string arguments, and
8 # iterator interface by Gustavo Niemeyer, April 2003.
9
10 import imp
11 import os.path
12 import sys
13 #from collections import deque
14
15 class deque:
16     def __init__(self):
17         self.data = []
18     def __len__(self):
19         return len(self.data)
20     def appendleft(self, item):
21         self.data.insert(0, item)
22     def popleft(self):
23         return self.data.pop(0)
24
25 try:
26     basestring
27 except NameError:
28     import types
29     def is_basestring(s):
30         return isinstance(s, str)
31 else:
32     def is_basestring(s):
33         return isinstance(s, basestring)
34
35 # Use the "imp" module to protect the imports below from fixers.
36 try:
37     _cStringIO = imp.load_module('cStringIO', *imp.find_module('cStringIO'))
38 except ImportError:
39     _StringIO = imp.load_module('StringIO', *imp.find_module('StringIO'))
40     StringIO = _StringIO.StringIO
41     del _StringIO
42 else:
43     StringIO = _cStringIO.StringIO
44     del _cStringIO
45
46 __all__ = ["shlex", "split"]
47
48 class shlex:
49     "A lexical analyzer class for simple shell-like syntaxes."
50     def __init__(self, instream=None, infile=None, posix=False):
51         if is_basestring(instream):
52             instream = StringIO(instream)
53         if instream is not None:
54             self.instream = instream
55             self.infile = infile
56         else:
57             self.instream = sys.stdin
58             self.infile = None
59         self.posix = posix
60         if posix:
61             self.eof = None
62         else:
63             self.eof = ''
64         self.commenters = '#'
65         self.wordchars = ('abcdfeghijklmnopqrstuvwxyz'
66                           'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_')
67         if self.posix:
68             self.wordchars = self.wordchars + ('ßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ'
69                                'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞ')
70         self.whitespace = ' \t\r\n'
71         self.whitespace_split = False
72         self.quotes = '\'"'
73         self.escape = '\\'
74         self.escapedquotes = '"'
75         self.state = ' '
76         self.pushback = deque()
77         self.lineno = 1
78         self.debug = 0
79         self.token = ''
80         self.filestack = deque()
81         self.source = None
82         if self.debug:
83             print 'shlex: reading from %s, line %d' \
84                   % (self.instream, self.lineno)
85
86     def push_token(self, tok):
87         "Push a token onto the stack popped by the get_token method"
88         if self.debug >= 1:
89             print "shlex: pushing token " + repr(tok)
90         self.pushback.appendleft(tok)
91
92     def push_source(self, newstream, newfile=None):
93         "Push an input source onto the lexer's input source stack."
94         if is_basestring(newstream):
95             newstream = StringIO(newstream)
96         self.filestack.appendleft((self.infile, self.instream, self.lineno))
97         self.infile = newfile
98         self.instream = newstream
99         self.lineno = 1
100         if self.debug:
101             if newfile is not None:
102                 print 'shlex: pushing to file %s' % (self.infile,)
103             else:
104                 print 'shlex: pushing to stream %s' % (self.instream,)
105
106     def pop_source(self):
107         "Pop the input source stack."
108         self.instream.close()
109         (self.infile, self.instream, self.lineno) = self.filestack.popleft()
110         if self.debug:
111             print 'shlex: popping to %s, line %d' \
112                   % (self.instream, self.lineno)
113         self.state = ' '
114
115     def get_token(self):
116         "Get a token from the input stream (or from stack if it's nonempty)"
117         if self.pushback:
118             tok = self.pushback.popleft()
119             if self.debug >= 1:
120                 print "shlex: popping token " + repr(tok)
121             return tok
122         # No pushback.  Get a token.
123         raw = self.read_token()
124         # Handle inclusions
125         if self.source is not None:
126             while raw == self.source:
127                 spec = self.sourcehook(self.read_token())
128                 if spec:
129                     (newfile, newstream) = spec
130                     self.push_source(newstream, newfile)
131                 raw = self.get_token()
132         # Maybe we got EOF instead?
133         while raw == self.eof:
134             if not self.filestack:
135                 return self.eof
136             else:
137                 self.pop_source()
138                 raw = self.get_token()
139         # Neither inclusion nor EOF
140         if self.debug >= 1:
141             if raw != self.eof:
142                 print "shlex: token=" + repr(raw)
143             else:
144                 print "shlex: token=EOF"
145         return raw
146
147     def read_token(self):
148         quoted = False
149         escapedstate = ' '
150         while True:
151             nextchar = self.instream.read(1)
152             if nextchar == '\n':
153                 self.lineno = self.lineno + 1
154             if self.debug >= 3:
155                 print "shlex: in state", repr(self.state), \
156                       "I see character:", repr(nextchar)
157             if self.state is None:
158                 self.token = ''        # past end of file
159                 break
160             elif self.state == ' ':
161                 if not nextchar:
162                     self.state = None  # end of file
163                     break
164                 elif nextchar in self.whitespace:
165                     if self.debug >= 2:
166                         print "shlex: I see whitespace in whitespace state"
167                     if self.token or (self.posix and quoted):
168                         break   # emit current token
169                     else:
170                         continue
171                 elif nextchar in self.commenters:
172                     self.instream.readline()
173                     self.lineno = self.lineno + 1
174                 elif self.posix and nextchar in self.escape:
175                     escapedstate = 'a'
176                     self.state = nextchar
177                 elif nextchar in self.wordchars:
178                     self.token = nextchar
179                     self.state = 'a'
180                 elif nextchar in self.quotes:
181                     if not self.posix:
182                         self.token = nextchar
183                     self.state = nextchar
184                 elif self.whitespace_split:
185                     self.token = nextchar
186                     self.state = 'a'
187                 else:
188                     self.token = nextchar
189                     if self.token or (self.posix and quoted):
190                         break   # emit current token
191                     else:
192                         continue
193             elif self.state in self.quotes:
194                 quoted = True
195                 if not nextchar:      # end of file
196                     if self.debug >= 2:
197                         print "shlex: I see EOF in quotes state"
198                     # XXX what error should be raised here?
199                     raise ValueError("No closing quotation")
200                 if nextchar == self.state:
201                     if not self.posix:
202                         self.token = self.token + nextchar
203                         self.state = ' '
204                         break
205                     else:
206                         self.state = 'a'
207                 elif self.posix and nextchar in self.escape and \
208                      self.state in self.escapedquotes:
209                     escapedstate = self.state
210                     self.state = nextchar
211                 else:
212                     self.token = self.token + nextchar
213             elif self.state in self.escape:
214                 if not nextchar:      # end of file
215                     if self.debug >= 2:
216                         print "shlex: I see EOF in escape state"
217                     # XXX what error should be raised here?
218                     raise ValueError("No escaped character")
219                 # In posix shells, only the quote itself or the escape
220                 # character may be escaped within quotes.
221                 if escapedstate in self.quotes and \
222                    nextchar != self.state and nextchar != escapedstate:
223                     self.token = self.token + self.state
224                 self.token = self.token + nextchar
225                 self.state = escapedstate
226             elif self.state == 'a':
227                 if not nextchar:
228                     self.state = None   # end of file
229                     break
230                 elif nextchar in self.whitespace:
231                     if self.debug >= 2:
232                         print "shlex: I see whitespace in word state"
233                     self.state = ' '
234                     if self.token or (self.posix and quoted):
235                         break   # emit current token
236                     else:
237                         continue
238                 elif nextchar in self.commenters:
239                     self.instream.readline()
240                     self.lineno = self.lineno + 1
241                     if self.posix:
242                         self.state = ' '
243                         if self.token or (self.posix and quoted):
244                             break   # emit current token
245                         else:
246                             continue
247                 elif self.posix and nextchar in self.quotes:
248                     self.state = nextchar
249                 elif self.posix and nextchar in self.escape:
250                     escapedstate = 'a'
251                     self.state = nextchar
252                 elif nextchar in self.wordchars or nextchar in self.quotes \
253                     or self.whitespace_split:
254                     self.token = self.token + nextchar
255                 else:
256                     self.pushback.appendleft(nextchar)
257                     if self.debug >= 2:
258                         print "shlex: I see punctuation in word state"
259                     self.state = ' '
260                     if self.token:
261                         break   # emit current token
262                     else:
263                         continue
264         result = self.token
265         self.token = ''
266         if self.posix and not quoted and result == '':
267             result = None
268         if self.debug > 1:
269             if result:
270                 print "shlex: raw token=" + repr(result)
271             else:
272                 print "shlex: raw token=EOF"
273         return result
274
275     def sourcehook(self, newfile):
276         "Hook called on a filename to be sourced."
277         if newfile[0] == '"':
278             newfile = newfile[1:-1]
279         # This implements cpp-like semantics for relative-path inclusion.
280         if is_basestring(self.infile) and not os.path.isabs(newfile):
281             newfile = os.path.join(os.path.dirname(self.infile), newfile)
282         return (newfile, open(newfile, "r"))
283
284     def error_leader(self, infile=None, lineno=None):
285         "Emit a C-compiler-like, Emacs-friendly error-message leader."
286         if infile is None:
287             infile = self.infile
288         if lineno is None:
289             lineno = self.lineno
290         return "\"%s\", line %d: " % (infile, lineno)
291
292     def __iter__(self):
293         return self
294
295     def __next__(self):
296         token = self.get_token()
297         if token == self.eof:
298             raise StopIteration
299         return token
300
301 def split(s, comments=False):
302     lex = shlex(s, posix=True)
303     lex.whitespace_split = True
304     if not comments:
305         lex.commenters = ''
306     #return list(lex)
307     result = []
308     while True:
309         token = lex.get_token()
310         if token == lex.eof:
311             break
312         result.append(token)
313     return result
314
315 if __name__ == '__main__':
316     if len(sys.argv) == 1:
317         lexer = shlex()
318     else:
319         file = sys.argv[1]
320         lexer = shlex(open(file), file)
321     while True:
322         tt = lexer.get_token()
323         if tt:
324             print "Token: " + repr(tt)
325         else:
326             break
327
328 # Local Variables:
329 # tab-width:4
330 # indent-tabs-mode:nil
331 # End:
332 # vim: set expandtab tabstop=4 shiftwidth=4: