-
Notifications
You must be signed in to change notification settings - Fork 1
/
atdtool
executable file
·228 lines (186 loc) · 7.32 KB
/
atdtool
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
#!/usr/bin/python
''' atdtool
Command-line client for After the Deadline:
http://www.afterthedeadline.com
Based on AtD module by Miguel Ventura:
https://bitbucket.org/miguelventura/after_the_deadline/wiki/Home
'''
from optparse import OptionParser
import re
import sys
import httplib
import urllib
import base64
from xml.etree import ElementTree
PROGRAM_NAME = "atdtool"
PROGRAM_VERSION = "1.3"
def checkDocument(cfg, fd):
'''Invoke checkDocument service and return a list of errors.'''
server = re.sub(r'^https?://', '', cfg.server)
if cfg.atdlang != '':
server = cfg.atdlang + '.' + server
if cfg.server.startswith('https'):
service = httplib.HTTPSConnection(server, cfg.port)
else:
service = httplib.HTTPConnection(server, cfg.port)
headers = {"Content-Type":"application/x-www-form-urlencoded"}
if cfg.username:
headers["Authorization"] = "Basic %s" % (base64.b64encode("%s:%s" % (cfg.username, cfg.password)))
params = { 'key': cfg.key, 'data': fd.read()}
if cfg.lang != '':
params['lang'] = cfg.lang
service.request(method='POST',
url='/checkDocument',
body=urllib.urlencode(params),
headers=headers)
response = service.getresponse()
if response.status != httplib.OK:
service.close()
raise Exception('Unexpected response code from AtD server %s: %d' % (cfg.server, response.status))
et = ElementTree.fromstring(response.read())
service.close()
errs = et.findall('message')
if len(errs) > 0:
raise Exception('Server returned an error: %s' % errs[0].text)
return [ Error(e) for e in et.findall('error') ]
class Error:
'''Error objects.'''
def __init__(self, e):
self.string = e.find('string').text
self.description = e.find('description').text
self.precontext = e.find('precontext').text
self.type = e.find('type').text
self.url = ''
if not e.find('url') is None:
self.url = e.find('url').text
self.suggestions = []
if not e.find('suggestions') is None:
self.suggestions = [ o.text for o in e.find('suggestions').findall('option') ]
class FileWords:
'''Parser class, keeps line and column position.'''
def __init__(self, fd):
fd.seek(0)
self.re = re.compile('([^a-z0-9A-Z_-])')
self.skipre = re.compile('[^a-z0-9A-Z_-]+')
self.text = self.re.split(fd.read())
self.len = len(self.text)
self.reset()
def reset(self):
'''Goes to start of file.'''
self.i = 0
self.line = 1
self.col = 0
self.eof = False
def next(self):
'''Goes to next token.'''
if self.eof:
return
self.col = self.col + len(self.text[self.i])
self.i = self.i + 1
if self.i >= self.len:
self.eof = True
return
if self.text[self.i] == '\n':
self.line = self.line + 1
self.col = 0
def skipnw(self):
'''Skips non-word tokens.'''
while self.skipre.match(self.text[self.i]) or self.text[self.i] == '':
self.next()
def checkpos(self, words0):
'''Checks if words0 is in current position.'''
words = tuple(self.re.split(words0))
text = self.text
t = []
j = 0
w = ''
while len(t) < len(words):
if self.i + j == self.len:
self.eof = True
return False, ''
t.append(text[self.i+j])
w = w + text[self.i+j]
if self.i + j + 1 < self.len and text[self.i+j+1] == '.':
t.append(t.pop() + text[self.i+j+2])
w = w + '.' + text[self.i+j+2]
j = j + 1
return tuple(t) == words, w
def goto(self, prec, words):
'''Goes to words preceded by prec; returns False and stays at eof if not found.'''
found = False
w = ''
if prec:
target = prec
else:
target = words
while not self.eof and not found:
found, w = self.checkpos(target)
if not found:
self.next()
elif prec:
self.next()
self.skipnw()
found, w = self.checkpos(words)
if found:
self.words = w
return True
return False
def find(self, prec, words):
'''Tries to find words preceded by prec from current position, then from start of file.'''
found = self.goto(prec, words)
if not found:
self.reset()
found = self.goto(prec, words)
return found
def showerrs(filename, fd, errs):
'''Shows the errors found, in the context of the file.'''
t = FileWords(fd)
for e in errs:
exactstr = ''
if not t.find(e.precontext, e.string):
exactstr = ' (?)'
print('%s:%d:%d:%s %s "%s"' % (filename, t.line, t.col, exactstr, e.description, t.words if hasattr(t, 'words') else ''))
if len(e.suggestions) > 0:
print(' suggestions: %s' % ', '.join(e.suggestions))
def main():
parser = OptionParser(usage='Usage: %prog <file>',
version="%prog "+PROGRAM_VERSION,
description='''\
atdtool submits the given file to the After the Deadline language checking
service at http://www.afterthedeadline.com/ and presents the results in
the same format as gcc, making integration with other tools (vi, emacs, etc.)
straightforward.
''')
parser.add_option('-s', '--server', dest='server', help='Select AtD server', default='service.afterthedeadline.com')
parser.add_option('-P', '--port', dest='port', help='Server port (default: 80)', default='80')
parser.add_option('-l', '--atdlang', dest='atdlang', help='Select language server [fr/de/pt/es], used for official AtD server', default='')
parser.add_option('-L', '--lang', dest='lang', help='Language parameter, used for other servers', default='')
parser.add_option('-e', '--error', dest='error', help='Exit with error if any mistakes are found', default=False)
parser.add_option('-k', '--key', dest='key', help='Key to use', default='')
parser.add_option('-u', '--username', dest='username', help='Username (if required by server)', default='')
parser.add_option('-p', '--password', dest='password', help='Password (if required by server)', default='')
(cfg, args) = parser.parse_args()
if len(args) == 0:
parser.error('expecting file argument')
if cfg.server != 'service.afterthedeadline.com' and cfg.atdlang != '':
parser.error('The atdlang option selects an official AtD server, there is no reason to specify both.')
if (cfg.password or cfg.username) and not (cfg.password and cfg.username):
parser.error("Username and password both have to be set (or none).")
founderr = False
for filename in args:
fd = None
try:
fd = open(filename)
except IOError:
print('%s: No such file or directory' % filename)
if not fd:
continue
errs = checkDocument(cfg, fd)
founderr = founderr or len(errs) > 0
fd.seek(0)
showerrs(filename, fd, errs)
fd.close()
if cfg.error and founderr:
sys.exit(1)
if __name__ == '__main__':
main()