|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2011 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the syntax check for Python 2. |
|
8 """ |
|
9 |
|
10 import sys |
|
11 import re |
|
12 import traceback |
|
13 from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF32 |
|
14 |
|
15 coding_regexps = [ |
|
16 (2, re.compile(r'''coding[:=]\s*([-\w_.]+)''')), |
|
17 (1, re.compile(r'''<\?xml.*\bencoding\s*=\s*['"]([-\w_.]+)['"]\?>''')), |
|
18 ] |
|
19 |
|
20 def get_coding(text): |
|
21 """ |
|
22 Function to get the coding of a text. |
|
23 |
|
24 @param text text to inspect (string) |
|
25 @return coding string |
|
26 """ |
|
27 lines = text.splitlines() |
|
28 for coding in coding_regexps: |
|
29 coding_re = coding[1] |
|
30 head = lines[:coding[0]] |
|
31 for l in head: |
|
32 m = coding_re.search(l) |
|
33 if m: |
|
34 return m.group(1).lower() |
|
35 return None |
|
36 |
|
37 def decode(text): |
|
38 """ |
|
39 Function to decode a text. |
|
40 |
|
41 @param text text to decode (string) |
|
42 @return decoded text and encoding |
|
43 """ |
|
44 try: |
|
45 if text.startswith(BOM_UTF8): |
|
46 # UTF-8 with BOM |
|
47 return unicode(text[len(BOM_UTF8):], 'utf-8'), 'utf-8-bom' |
|
48 elif text.startswith(BOM_UTF16): |
|
49 # UTF-16 with BOM |
|
50 return unicode(text[len(BOM_UTF16):], 'utf-16'), 'utf-16' |
|
51 elif text.startswith(BOM_UTF32): |
|
52 # UTF-32 with BOM |
|
53 return unicode(text[len(BOM_UTF32):], 'utf-32'), 'utf-32' |
|
54 coding = get_coding(text) |
|
55 if coding: |
|
56 return unicode(text, coding), coding |
|
57 except (UnicodeError, LookupError): |
|
58 pass |
|
59 |
|
60 # Assume UTF-8 |
|
61 try: |
|
62 return unicode(text, 'utf-8'), 'utf-8-guessed' |
|
63 except (UnicodeError, LookupError): |
|
64 pass |
|
65 |
|
66 # Assume Latin-1 (behaviour before 3.7.1) |
|
67 return unicode(text, "latin-1"), 'latin-1-guessed' |
|
68 |
|
69 def compile(file): |
|
70 """ |
|
71 Function to compile one Python source file to Python bytecode. |
|
72 |
|
73 @param file source filename (string) |
|
74 @return A tuple indicating status (1 = an error was found), the |
|
75 filename, the linenumber, the code string and the error message |
|
76 (boolean, string, string, string, string). The values are only |
|
77 valid, if the status equals 1. |
|
78 """ |
|
79 import __builtin__ |
|
80 try: |
|
81 f = open(file) |
|
82 codestring, encoding = decode(f.read()) |
|
83 f.close() |
|
84 except IOError, msg: |
|
85 return (1, file, "1", "", "I/O Error: %s" % unicode(msg)) |
|
86 |
|
87 if type(codestring) == type(u""): |
|
88 codestring = codestring.encode('utf-8') |
|
89 codestring = codestring.replace("\r\n","\n") |
|
90 codestring = codestring.replace("\r","\n") |
|
91 |
|
92 if codestring and codestring[-1] != '\n': |
|
93 codestring = codestring + '\n' |
|
94 |
|
95 try: |
|
96 if type(file) == type(u""): |
|
97 file = file.encode('utf-8') |
|
98 |
|
99 if file.endswith('.ptl'): |
|
100 try: |
|
101 import quixote.ptl_compile |
|
102 except ImportError: |
|
103 return (0, None, None, None, None) |
|
104 template = quixote.ptl_compile.Template(codestring, file) |
|
105 template.compile() |
|
106 codeobject = template.code |
|
107 else: |
|
108 codeobject = __builtin__.compile(codestring, file, 'exec') |
|
109 except SyntaxError, detail: |
|
110 lines = traceback.format_exception_only(SyntaxError, detail) |
|
111 match = re.match('\s*File "(.+)", line (\d+)', |
|
112 lines[0].replace('<string>', '%s' % file)) |
|
113 if match is not None: |
|
114 fn, line = match.group(1, 2) |
|
115 if lines[1].startswith('SyntaxError:'): |
|
116 code = "" |
|
117 error = re.match('SyntaxError: (.+)', lines[1]).group(1) |
|
118 else: |
|
119 code = re.match('(.+)', lines[1]).group(1) |
|
120 error = "" |
|
121 for seLine in lines[2:]: |
|
122 if seLine.startswith('SyntaxError:'): |
|
123 error = re.match('SyntaxError: (.+)', seLine).group(1) |
|
124 else: |
|
125 fn = detail.filename |
|
126 line = detail.lineno and detail.lineno or 1 |
|
127 code = "" |
|
128 error = detail.msg |
|
129 return (1, fn, line, code, error) |
|
130 except ValueError, detail: |
|
131 try: |
|
132 fn = detail.filename |
|
133 line = detail.lineno |
|
134 error = detail.msg |
|
135 except AttributeError: |
|
136 fn = file |
|
137 line = 1 |
|
138 error = unicode(detail) |
|
139 code = "" |
|
140 return (1, fn, line, code, error) |
|
141 except StandardError, detail: |
|
142 try: |
|
143 fn = detail.filename |
|
144 line = detail.lineno |
|
145 code = "" |
|
146 error = detail.msg |
|
147 return (1, fn, line, code, error) |
|
148 except: # this catchall is intentional |
|
149 pass |
|
150 |
|
151 return (0, None, None, None, None) |
|
152 |
|
153 if __name__ == "__main__": |
|
154 if len(sys.argv) != 2: |
|
155 print "ERROR" |
|
156 print "" |
|
157 print "" |
|
158 print "" |
|
159 print "No file name given." |
|
160 else: |
|
161 filename = sys.argv[1] |
|
162 res, fname, line, code, error = compile(filename) |
|
163 |
|
164 if res: |
|
165 print "ERROR" |
|
166 else: |
|
167 print "NO_ERROR" |
|
168 print fname |
|
169 print line |
|
170 print code |
|
171 print error |
|
172 |
|
173 sys.exit(0) |
|
174 |
|
175 # |
|
176 # eflag: FileType = Python2 |