Mercurial > ~dholland > hg > swallowtail > index.cgi
annotate shelltools/query-pr/query.py @ 54:36d91dfe017f
use valid sql syntax, mostly from yetoo on freenode
author | David A. Holland |
---|---|
date | Sun, 10 Apr 2022 17:41:24 -0400 |
parents | c0be30249ffe |
children | 40f64a96481f |
rev | line source |
---|---|
7
c013fb703183
Empty placeholder scripts so the build will run.
David A. Holland
parents:
diff
changeset
|
1 #!@PYTHON@ |
33 | 2 |
48 | 3 import sys |
33 | 4 import argparse |
5 import psycopg2 | |
6 | |
7 program_version = "@VERSION@" | |
8 | |
9 ############################################################ | |
10 # settings | |
11 | |
12 outfile = sys.stdout | |
13 outmaterial = "headers" | |
14 outformat = "text" | |
15 | |
16 ############################################################ | |
52 | 17 # simple dump widget |
18 | |
19 class Dumper: | |
20 def __init__(self, f): | |
21 self.f = f | |
22 self.indentation = 0 | |
23 def indent(self): | |
24 self.indentation += 3 | |
25 def unindent(self): | |
26 self.indentation -= 3 | |
27 def write(self, msg): | |
28 self.f.write(" " * self.indentation + msg + "\n") | |
29 | |
30 ############################################################ | |
47 | 31 # database field access |
32 | |
33 # | |
34 # Fields of PRs that we might search are spread across a number of | |
35 # tables and require varying joins to get them. And, because of | |
36 # classication schemes, the set of fields isn't static and we can't | |
37 # just assemble a massive view with one column for each field. | |
38 # | |
39 # The QueryBuilder class knows how to arrange for all known fields to | |
40 # be present. | |
41 # | |
42 | |
43 class QueryBuilder: | |
50 | 44 # these fields are in the PRs table |
45 prtable_fields = [ | |
46 "id", "synopsis", "confidential", "state", "locked", | |
47 "timeout_date", "timeout_state", | |
48 "arrival_schemaversion", "arrival_date", "modified_date", | |
49 "closed_date", | |
50 "release", "environment" | |
51 ] | |
47 | 52 |
50 | 53 # these fields are aliases for others |
54 alias_fields = { | |
55 "number" : "id", | |
56 "date" : "arrival_date", | |
57 } | |
47 | 58 |
50 | 59 def __init__(self): |
60 self.present = {} | |
61 self.joined = {} | |
62 self.fromitems = [] | |
63 self.whereitems = [] | |
64 self.order = None | |
47 | 65 |
50 | 66 def setorder(self, order): |
67 self.order = order | |
47 | 68 |
50 | 69 # add to present{} and return the value for convenience (internal) |
70 def makepresent(self, field, name): | |
71 self.present[field] = name | |
72 return name | |
47 | 73 |
50 | 74 # add a join item (once only) (internal) |
75 def addjoin(self, table, as_ = None): | |
76 if as_ is not None: | |
77 key = table + "-" + as_ | |
78 val = table + " AS " + as_ | |
79 else: | |
80 key = table | |
81 val = table | |
82 if key not in self.joined: | |
83 self.joined[key] = True | |
84 self.fromitems.append(val) | |
47 | 85 |
50 | 86 # returns a sql expression for the field |
87 def getfield(self, field): | |
88 # already-fetched fields | |
89 if field in self.present: | |
90 return self.present[field] | |
47 | 91 |
50 | 92 # aliases for other fields |
93 if field in alias_fields: | |
94 return self.getfield(alias_fields[field]) | |
47 | 95 |
50 | 96 # simple fields directly in the PRs table |
97 if field in prtable_fields: | |
98 self.addjoin("PRs") | |
99 return self.makepresent(field, "PRs." + field) | |
47 | 100 |
50 | 101 # now it gets more interesting... |
102 if field == "closed": | |
103 self.addjoin("PRs") | |
104 self.addjoin("states") | |
105 self.addwhere("PRs.state = states.name") | |
106 return self.makepresent(field, "states.closed") | |
47 | 107 |
50 | 108 # XXX let's pick one set of names and use them everywhere |
109 # (e.g. change "posttime" in the schema to "message_date" | |
110 # or something) | |
111 if field == "comment_date" or field == "posttime": | |
112 self.addjoin("PRs") | |
113 self.addjoin("messages") | |
114 self.addwhere("PRs.id = messages.pr") | |
115 return self.makepresent(field, "messages.posttime") | |
47 | 116 |
50 | 117 if field == "comment" or field == "message" or field == "post": |
118 self.addjoin("PRs") | |
119 self.addjoin("messages") | |
120 self.addwhere("PRs.id = messages.pr") | |
121 return self.makepresent(field, "messages.body") | |
47 | 122 |
50 | 123 if field == "attachment": |
124 self.addjoin("PRs") | |
125 self.addjoin("messages") | |
126 self.addjoin("attachments") | |
127 self.addwhere("PRs.id = messages.pr") | |
128 self.addwhere("messages.id = attachments.msgid") | |
129 return self.makepresent(field, "attachments.body") | |
47 | 130 |
50 | 131 if field == "patch": |
132 self.addjoin("PRs") | |
133 self.addjoin("messages") | |
134 self.addjoin("attachments", "patches") | |
135 self.addwhere("PRs.id = messages.pr") | |
136 self.addwhere("messages.id = patches.msgid") | |
137 self.addwhere("patches.mimetype = " + | |
138 "'application/x-patch'") | |
139 return self.makepresent(field, "patches.body") | |
47 | 140 |
50 | 141 if field == "mimetype": |
142 subquery = "((SELECT mtmessages1.pr as pr, " + \ | |
143 "mtmessages1.mimetype as mimetype " + \ | |
144 "FROM messages as mtmessages1) " + \ | |
145 "UNION " + \ | |
146 "(SELECT mtmessages2.pr as pr, " + \ | |
147 "mtattach2.mimetype as mimetype " + \ | |
148 "FROM messages as mtmessages2, " + \ | |
149 " attachments as mtattach2 " + \ | |
150 "WHERE mtmessages2.id = mtattach2.msgid))" | |
151 self.addjoin("PRs") | |
152 self.addjoin(subquery, "mimetypes") | |
153 self.addwhere("PRs.id = mimetypes.pr") | |
154 return self.makepresent(field, "mimetypes.mimetype") | |
47 | 155 |
50 | 156 # XXX: need view userstrings |
157 # select (id, username as name) from users | |
158 # union select (id, realname as name) from users | |
159 # (allow searching emails? ugh) | |
160 if field == "originator" or field == "submitter": | |
161 self.addjoin("PRs") | |
162 self.addjoin("userstrings", "originators") | |
163 self.addwhere("PRs.originator = originators.id") | |
164 return self.makepresent(field, "originators.name") | |
47 | 165 |
50 | 166 if field == "reporter" or field == "respondent": |
167 self.addjoin("PRs") | |
168 self.addjoin("subscriptions") | |
169 self.addjoin("userstrings", "reporters") | |
170 self.addwhere("subscriptions.userid = reporters.id") | |
171 self.addwhere("subscriptions.reporter") | |
172 return self.makepresent(field, "reporters.name") | |
47 | 173 |
50 | 174 if field == "responsible": |
175 self.addjoin("PRs") | |
176 self.addjoin("subscriptions") | |
177 self.addjoin("userstrings", "responsibles") | |
178 self.addwhere("subscriptions.userid = responsibles.id") | |
179 self.addwhere("subscriptions.responsible") | |
180 return self.makepresent(field, "responsibles.name") | |
47 | 181 |
50 | 182 if field in hierclasses: |
183 col = field + "_data" | |
184 self.addjoin("PRs") | |
185 self.addjoin("hierclass_data", col) | |
186 self.addwhere("PRs.id = %s.pr" % col) | |
187 self.addwhere("%s.scheme = '%s'" % (col, field)) | |
188 return self.makepresent(field, "%s.value" % col) | |
47 | 189 |
50 | 190 if field in flatclasses: |
191 col = field + "_data" | |
192 self.addjoin("PRs") | |
193 self.addjoin("flatclass_data", col) | |
194 self.addwhere("PRs.id = %s.pr" % col) | |
195 self.addwhere("%s.scheme = '%s'" % (col, field)) | |
196 return self.makepresent(field, "%s.value" % col) | |
47 | 197 |
50 | 198 if field in textclasses: |
199 col = field + "_data" | |
200 self.addjoin("PRs") | |
201 self.addjoin("textclass_data", col) | |
202 self.addwhere("PRs.id = %s.pr" % col) | |
203 self.addwhere("%s.scheme = '%s'" % (col, field)) | |
204 return self.makepresent(field, "%s.value" % col) | |
47 | 205 |
50 | 206 if field in tagclasses: |
207 col = field + "_data" | |
208 self.addjoin("PRs") | |
209 self.addjoin("tagclass_data", col) | |
210 self.addwhere("PRs.id = %s.pr" % col) | |
211 self.addwhere("%s.scheme = '%s'" % (col, field)) | |
212 return self.makepresent(field, "%s.value" % col) | |
47 | 213 |
50 | 214 sys.stderr.write("Unknown field %s" % field) |
215 exit(1) | |
216 # end getfield | |
47 | 217 |
50 | 218 # emit sql |
219 def build(self, sels): | |
220 s = ", ".join(sels) | |
221 f = ", ".join(self.fromitems) | |
222 w = " and ".join(self.whereitems) | |
223 q = "SELECT %s\nFROM %s\nWHERE %s\n" % (s, f, w) | |
224 if self.order is not None: | |
225 q = q + "ORDER BY " + self.order + "\n" | |
226 return q | |
227 # endif | |
47 | 228 |
229 # end class QueryBuilder | |
230 | |
231 # XXX we need to add dynamically: | |
232 # hierclass_names.name to hierclasses[] | |
233 # flatclass_names.name to flatclasses[] | |
234 # textclass_names.name to textclasses[] | |
235 # tagclass_names.name to tagclasses[] | |
236 | |
237 ############################################################ | |
33 | 238 # database |
239 | |
240 dblink = None | |
241 | |
242 def opendb(): | |
50 | 243 global dblink |
33 | 244 |
50 | 245 host = "localhost" |
246 user = "swallowtail" | |
247 database = "swallowtail" | |
248 dblink = psycopg2.connect("host=%s user=%s dbname=%s" % | |
249 (host, user, database)) | |
33 | 250 # end opendb |
251 | |
252 def closedb(): | |
50 | 253 global dblink |
33 | 254 |
50 | 255 dblink.close() |
256 dblink = None | |
33 | 257 # end closedb |
258 | |
259 def querydb(qtext, args): | |
51 | 260 print("Executing this query:") |
261 print(qtext) | |
262 print("Args are:") | |
263 print(args) | |
33 | 264 |
50 | 265 cursor = dblink.cursor() |
266 cursor.execute(qtext, args) | |
267 result = cursor.fetchall() | |
268 cursor.close() | |
269 return result | |
33 | 270 # end querydb |
271 | |
272 ############################################################ | |
273 # query class for searches | |
47 | 274 # XXX: obsolete, remove |
33 | 275 |
276 class Query: | |
50 | 277 def __init__(self): |
278 self.selections = [] | |
279 self.tables = [] | |
280 self.constraints = [] | |
281 self.args = [] | |
282 prtables = ["PRs"] | |
283 prconstraints = [] | |
33 | 284 |
50 | 285 def select(self, s): |
286 self.selections.append(s) | |
33 | 287 |
50 | 288 def addtable(self, t): |
289 assert(t not in self.tables) | |
290 self.tables.append(t) | |
33 | 291 |
50 | 292 def constrain(self, expr): |
293 self.constraints.append(t) | |
33 | 294 |
50 | 295 def internval(self, val): |
296 num = len(self.args) | |
297 self.args[num] = val | |
298 return "$%d" % num | |
33 | 299 |
50 | 300 def textify(self): |
301 s = "SELECT %s\n" % ",".join(self.selections) | |
302 f = "FROM %s\n" % ",".join(self.tables) | |
303 w = "WHERE %s\n" % " AND ".join(self.constraints) | |
304 return s + f + w | |
33 | 305 # end class Query |
306 | |
307 def regexp_constraint(q, field, value): | |
50 | 308 cleanval = q.internval(value) |
309 if not isregexp(value): | |
310 return "%s = %s" % (field, cleanval) | |
311 else: | |
312 # XXX what's the right operator again? | |
313 return "%s ~= %s" % (field, cleanval) | |
33 | 314 # end regexp_constraint |
315 | |
316 def intrange_constraint(q, field, value): | |
50 | 317 (lower, upper) = args.number |
318 if lower is not None: | |
319 assert(typeof(lower) == int) | |
320 prq.constrain("%s >= %d" % (field, lower)) | |
321 if upper is not None: | |
322 assert(typeof(upper) == int) | |
323 prq.constrain("%s <= %d" % (field, upper)) | |
33 | 324 # end intrange_constraint |
325 | |
326 def daterange_constraint(q, field, value): | |
50 | 327 # XXX |
328 assert(0) | |
33 | 329 # end daterange_constraint |
330 | |
331 ############################################################ | |
332 | |
47 | 333 # this is old code that needs to be merged or deleted into the new stuff |
334 def oldstuff(): | |
335 | |
50 | 336 # If we're doing something other than a search, do it now |
337 if args.attach is not None: | |
338 get_attachment(args.attach) | |
339 exit(0) | |
340 if args.message is not None: | |
341 get_message(args.message) | |
342 exit(0) | |
33 | 343 |
50 | 344 if args.prs is not None and len(args.prs) > 0: |
345 show_prs(args.prs) | |
346 exit(0) | |
33 | 347 |
50 | 348 # |
349 # Collect up the search constraints | |
350 # | |
351 | |
352 # 1. Constraints on the PRs table | |
353 checkprtable = False | |
354 prq = Query() | |
355 prq.select("PRs.id as id") | |
356 prq.addtable("PRs") | |
357 if not args.closed: | |
358 checkprtable = True | |
359 prq.addtable("states") | |
360 prq.constrain("PRs.state = states.name") | |
361 prq.constrain("states.closed = FALSE") | |
362 if args.public: | |
363 checkprtable = True | |
364 prq.constrain("NOT PRs.confidential") | |
365 if args.number is not None: | |
366 checkprtable = True | |
367 intrange_constraint(prq, "PRs.id", args.number) | |
368 if args.synopsis is not None: | |
369 checkprtable = True | |
370 regexp_constraint(prq, "PRs.synopsis", args.synopsis) | |
371 if args.confidential is not None: | |
372 checkprtable = True | |
373 assert(typeof(args.confidential) == bool) | |
374 if args.confidential: | |
375 prq.constrain("PRs.confidential") | |
376 else: | |
377 prq.constrain("not PRs.confidential") | |
378 if args.state is not None: | |
379 checkprtable = True | |
380 regexp_constraint(prq, "PRs.state", args.state) | |
381 if args.locked is not None: | |
382 checkprtable = True | |
383 assert(typeof(args.locked) == bool) | |
384 if args.locked: | |
385 prq.constrain("PRs.locked") | |
386 else: | |
387 prq.constrain("not PRs.locked") | |
388 if args.arrival_schemaversion is not None: | |
389 checkprtable = True | |
390 intrange_constraint(prq, "PRs.arrival_schemaversion", | |
391 args.arrival_schemaversion) | |
392 if args.arrival_date is not None: | |
393 checkprtable = True | |
394 daterange_constraint(prq, "PRs.arrival_date", | |
395 args.arrival_date) | |
396 if args.closed_date is not None: | |
397 checkprtable = True | |
398 daterange_constraint(prq, "PRs.closed_date", | |
399 args.closed_date) | |
400 if args.last_modified is not None: | |
401 checkprtable = True | |
402 daterange_constraint(prq, "PRs.last_modified", | |
403 args.last_modified) | |
404 if args.release is not None: | |
405 checkprtable = True | |
406 regexp_constraint(prq, "PRs.release", args.release) | |
407 if args.environment is not None: | |
408 checkprtable = True | |
409 regexp_constraint(prq, "PRs.environment", args.environment) | |
33 | 410 |
50 | 411 if args.originator_name is not None or \ |
412 args.originator_email is not None: | |
413 prq.addtable("usermail as originator") | |
414 prq.constrain("PRs.originator = originator.id") | |
415 if args.originator_name is not None: | |
416 checkprtable = True | |
417 regexp_constraint(prq, "originator.realname", | |
418 args.originator_name) | |
419 if args.originator_email is not None: | |
420 checkprtable = True | |
421 regexp_constraint(prq, "originator.email", | |
422 args.originator_name) | |
423 if args.originator_id is not None: | |
424 checkprtable = True | |
425 intrange_constraint(prq, "PRs.originator", args.originator_id) | |
33 | 426 |
50 | 427 queries = [] |
428 if checkprtable: | |
429 queries.append(prq) | |
33 | 430 |
50 | 431 if args.responsible is not None: |
432 sq = Query() | |
433 sq.select("subscriptions.pr as id") | |
434 sq.addtable("subscriptions") | |
435 sq.addtable("users") | |
436 sq.constrain("subscriptions.userid = users.id") | |
437 regexp_constraint(sq, "users.realname", args.responsible) | |
438 sq.constrain("subscriptions.responsible") | |
439 queries.append(sq) | |
440 if args.respondent is not None: | |
441 sq = Query() | |
442 sq.select("subscriptions.pr as id") | |
443 sq.addtable("subscriptions") | |
444 sq.addtable("users as subscribed") | |
445 sq.constrain("subscriptions.userid = users.id") | |
446 regexp_constraint(sq, "users.realname", args.respondent) | |
447 sq.constrain("subscriptions.reporter") | |
448 queries.append(sq) | |
449 if args.subscribed is not None: | |
450 sq = Query() | |
451 sq.select("subscriptions.pr as id") | |
452 sq.addtable("subscriptions") | |
453 sq.addtable("users as subscribed") | |
454 sq.constrain("subscriptions.userid = users.id") | |
455 regexp_constraint(sq, "users.realname", args.subscribed) | |
456 queries.append(sq) | |
33 | 457 |
50 | 458 if args.messages is not None: |
459 mq = Query() | |
460 mq.select("messages.pr as id") | |
461 mq.addtable("messages") | |
462 regexp_constraint(sq, "messages.text", args.messages) | |
463 queries.append(mq) | |
33 | 464 |
50 | 465 if args.adminlog is not None: |
466 aq = Query() | |
467 aq.select("adminlog.pr as id") | |
468 aq.addtable("adminlog") | |
469 regexp_constraint(sq, "adminlog.change", args.adminlog) | |
470 regexp_constraint(sq, "adminlog.comment", args.adminlog) | |
471 assert(len(aq.constraints) == 2) | |
472 x = "%s OR %s" % (aq.constraints[0], aq.constraints[1]) | |
473 aq.constraints = [x] | |
474 queries.append(aq) | |
33 | 475 |
50 | 476 if args.anytext is not None: |
477 choke("--anytext isn't supported yet") | |
33 | 478 |
50 | 479 for scheme in classification_schemes: |
480 if args[scheme] is not None: | |
481 schemetype = classification_schemetypes[scheme] | |
482 tbl = "%sclass_data" % schemetype | |
483 cq = Query() | |
484 cq.select("scheme.pr as id") | |
485 cq.addtable("%s as scheme" % schemetype) | |
486 cq.constrain("scheme.scheme = '%s'" % scheme) | |
487 regexp_constraint(cq, "scheme.value", args[scheme]) | |
488 queries.append(cq) | |
489 # end loop | |
33 | 490 |
50 | 491 querytexts = [q.textify() for q in queries] |
492 return "INTERSECT\n".join(querytexts) | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
493 |
47 | 494 ############################################################ |
495 # printing | |
496 | |
497 class PrintText: | |
50 | 498 def __init__(self, output): |
499 self.lines = (output == "RAW" or output == "LIST") | |
500 def printheader(self, row): | |
501 # nothing | |
48 | 502 pass |
50 | 503 def printrow(self, row): |
504 # XXX | |
51 | 505 print(row) |
50 | 506 def printfooter(self, row): |
507 # nothing | |
48 | 508 pass |
47 | 509 # end class PrintText |
510 | |
511 class PrintCsv: | |
50 | 512 def __init__(self, output): |
513 # nothing | |
48 | 514 pass |
50 | 515 def printheader(self, row): |
516 # XXX | |
48 | 517 pass |
50 | 518 def printrow(self, row): |
519 # XXX | |
48 | 520 pass |
50 | 521 def printfooter(self, row): |
522 # nothing | |
48 | 523 pass |
47 | 524 # end class PrintCsv |
525 | |
526 class PrintXml: | |
50 | 527 def __init__(self, output): |
528 # nothing | |
48 | 529 pass |
50 | 530 def printheader(self, row): |
531 # XXX | |
48 | 532 pass |
50 | 533 def printrow(self, row): |
534 # XXX | |
48 | 535 pass |
50 | 536 def printfooter(self, row): |
537 # XXX | |
48 | 538 pass |
47 | 539 # end class PrintXml |
540 | |
541 class PrintJson: | |
50 | 542 def __init__(self, output): |
543 # nothing | |
48 | 544 pass |
50 | 545 def printheader(self, row): |
546 # XXX | |
48 | 547 pass |
50 | 548 def printrow(self, row): |
549 # XXX | |
48 | 550 pass |
50 | 551 def printfooter(self, row): |
552 # XXX | |
48 | 553 pass |
47 | 554 # end class PrintJson |
555 | |
556 class PrintRdf: | |
50 | 557 def __init__(self, output): |
558 # nothing | |
48 | 559 pass |
50 | 560 def printheader(self, row): |
561 # XXX | |
48 | 562 pass |
50 | 563 def printrow(self, row): |
564 # XXX | |
48 | 565 pass |
50 | 566 def printfooter(self, row): |
567 # XXX | |
48 | 568 pass |
47 | 569 # end class PrintRdf |
570 | |
571 class PrintRdflike: | |
50 | 572 def __init__(self, output): |
573 # nothing | |
48 | 574 pass |
50 | 575 def printheader(self, row): |
576 # XXX | |
48 | 577 pass |
50 | 578 def printrow(self, row): |
579 # XXX | |
48 | 580 pass |
50 | 581 def printfooter(self, row): |
582 # XXX | |
48 | 583 pass |
47 | 584 # end class PrintRdflike |
585 | |
586 def print_prs(ids): | |
50 | 587 if sel.outformat == "TEXT": |
588 mkprinter = PrintText | |
589 elif sel.outformat == "CSV": | |
590 mkprinter = PrintCsv | |
591 elif sel.outformat == "XML": | |
592 mkprinter = PrintXml | |
593 elif sel.outformat == "JSON": | |
594 mkprinter = PrintJson | |
595 elif sel.outformat == "RDF": | |
596 mkprinter = PrintRdf | |
597 elif sel.outformat == "RDFLIKE": | |
598 mkprinter = PrintRdflike | |
599 else: | |
600 assert(False) | |
47 | 601 |
50 | 602 # reset the printer |
603 printer = mkprinter(sel.output) | |
47 | 604 |
50 | 605 if sel.output == "RAW": |
606 printer.printheader(ids[0]) | |
607 for id in ids: | |
608 printer(id) | |
609 printer.printfooter(ids[0]) | |
610 return | |
611 elif sel.output == "LIST": | |
612 # XXX is there a clean way to do this passing the | |
613 # whole list of ids at once? | |
614 query = "SELECT id, synopsis\n" + \ | |
615 "FROM PRs\n" + \ | |
616 "WHERE id = $1" | |
617 elif sel.output == "HEADERS": | |
618 query = None # XXX | |
619 elif sel.output == "META": | |
620 query = None # XXX | |
621 elif sel.output == "FULL": | |
622 query = None # XXX | |
623 else: | |
624 assert(False) | |
47 | 625 |
50 | 626 first = True |
627 for id in ids: | |
628 results = querydb(query, [id]) | |
629 if first: | |
630 printer.printheader(results[0]) | |
631 first = False | |
632 for r in results: | |
633 printer.printrow(r) | |
634 printer.printfooter(results[0]) | |
47 | 635 # end print_prs |
636 | |
637 # XXX if in public mode we need to check if the PR is public | |
638 def print_message(pr, msgnum): | |
50 | 639 query = "SELECT users.username AS username,\n" + \ |
640 " users.realname AS realname,\n" + \ | |
641 " messages.id AS id, parent_id,\n" + \ | |
642 " posttime, mimetype, body\n" + \ | |
643 "FROM messages, users\n" + \ | |
644 "WHERE messages.who = users.id\n" + \ | |
645 " AND messages.pr = $1\n" + \ | |
646 " AND messages.number_in_pr = $2\n" | |
647 # Note that while pr is safe, msgnum came from the commandline | |
648 # and may not be. | |
649 results = querydb(query, [pr, msgnum]) | |
650 [result] = results | |
651 (username, realname, id, parent_id, posttime, mimetype, body) = result | |
652 # XXX honor mimetype | |
653 # XXX honor output format (e.g. html) | |
654 sys.stdout.write("From swallowtail@%s %s\n" % (organization,posttime)) | |
655 sys.stdout.write("From: %s (%s)\n" % (username, realname)) | |
656 sys.stdout.write("References: %s\n" % parent_id) | |
657 sys.stdout.write("Date: %s\n" % posttime) | |
658 sys.stdout.write("Content-Type: %s\n" % mimetype) | |
659 sys.stdout.write("\n") | |
660 sys.stdout.write(body) | |
47 | 661 # end print_message |
662 | |
663 # XXX if in public mode we need to check if the PR is public | |
664 def print_attachment(pr, attachnum): | |
50 | 665 query = "SELECT a.mimetype as mimetype, a.body as body\n" + \ |
666 "FROM messages, attachments as a\n" + \ | |
667 "WHERE messages.pr = $1\n" + \ | |
668 " AND messages.id = a.msgid\n" + \ | |
669 " AND a.number_in_pr = $2\n" | |
670 # Note that while pr is safe, attachnum came from the | |
671 # commandline and may not be. | |
672 results = querydb(query, [pr, msgnum]) | |
673 [result] = results | |
674 (mimetype, body) = result | |
675 # XXX honor mimetype | |
676 # XXX need an http output mode so we can send the mimetype! | |
677 sys.stdout.write(body) | |
47 | 678 # end print_attachment |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
679 |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
680 ############################################################ |
52 | 681 # AST for input query |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
682 |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
683 class Invocation: |
52 | 684 Q_TERM = 1 |
685 Q_SQL = 2 | |
50 | 686 class Query: |
687 def __init__(self, type): | |
688 self.type = type | |
52 | 689 def dump(self, d): |
690 if self.type == Invocation.Q_TERM: | |
691 d.write("query.term({})".format(self.term)) | |
692 else: | |
693 d.write("query.sql({})".format(self.sql)) | |
694 def mkterm(term): | |
695 self = Invocation.Query(Invocation.Q_TERM) | |
696 self.term = term | |
697 return self | |
698 def mksql(s): | |
699 self = Invocation.Query(Invocation.Q_SQL) | |
700 self.sql = s | |
701 return self | |
47 | 702 |
50 | 703 class Order: |
704 def __init__(self, field, rev = False): | |
705 self.field = field | |
706 self.rev = rev | |
52 | 707 def dump(self, d): |
708 d.write("order({}, {})".format(self.field, self.rev)) | |
709 def mkoldest(): | |
710 return Invocation.Order("number") | |
711 def mknewest(): | |
712 return Invocation.Order("number", True) | |
713 def mkstaleness(): | |
714 return Invocation.Order("modified_date", True) | |
715 def mkfield(field): | |
716 return Invocation.Order(field) | |
717 def mkrevfield(field): | |
718 return Invocation.Order(field, True) | |
47 | 719 |
50 | 720 class Search: |
721 def __init__(self, qs, openonly, publiconly, os): | |
722 self.queries = qs | |
723 self.openonly = openonly | |
724 self.publiconly = publiconly | |
725 self.orders = os | |
52 | 726 def dump(self, d): |
727 d.write("search({}, {})".format( | |
728 "open" if self.openonly else "closed", | |
729 "public" if self.publiconly else "privileged")) | |
730 d.indent() | |
731 d.write("queries") | |
732 d.indent() | |
733 for query in self.queries: | |
734 query.dump(d) | |
735 d.unindent() | |
736 d.write("orders") | |
737 d.indent() | |
738 for order in self.orders: | |
739 order.dump(d) | |
740 d.unindent() | |
741 d.unindent() | |
47 | 742 |
52 | 743 S_PR = 1 |
744 S_MESSAGE = 2 | |
745 S_ATTACHMENT = 3 | |
50 | 746 class Selection: |
747 def __init__(self, type): | |
748 self.type = type | |
52 | 749 def dump(self, d): |
750 if self.type == Invocation.S_PR: | |
751 d.write("selection.pr({}, {})".format( | |
752 self.output, self.outformat)) | |
753 elif self.type == Invocation.S_MESSAGE: | |
754 d.write("selection.message({})".format( | |
755 self.message)) | |
756 else: | |
757 d.write("selection.attachment({})".format( | |
758 self.attachment)) | |
759 def mkpr(output, outformat): | |
760 self = Invocation.Selection(Invocation.S_PR) | |
761 self.output = output | |
762 self.outformat = outformat | |
763 return self | |
764 def mkmessage(arg): | |
765 self = Invocation.Selection(Invocation.S_MESSAGE) | |
766 self.message = arg | |
767 return self | |
768 def mkattachment(arg): | |
769 self = Invocation.Selection(Invocation.S_ATTACHMENT) | |
770 self.attachment = arg | |
771 return self | |
47 | 772 |
52 | 773 OP_FIELDS = 1 |
774 OP_SHOW = 2 | |
775 OP_RANGE = 3 | |
776 OP_SEARCH = 4 | |
50 | 777 class Op: |
778 def __init__(self, type): | |
779 self.type = type | |
52 | 780 def dump(self, d): |
781 if self.type == Invocation.OP_FIELDS: | |
782 d.write("op.fields") | |
783 elif self.type == Invocation.OP_SHOW: | |
784 d.write("op.show({})".format(self.field)) | |
785 elif self.type == Invocation.OP_RANGE: | |
786 d.write("op.range({})".format(self.field)) | |
787 else: | |
788 d.write("op.search:") | |
789 d.indent() | |
790 self.search.dump(d) | |
791 for sel in self.sels: | |
792 sel.dump(d) | |
793 d.unindent() | |
794 def mkfields(): | |
795 return Invocation.Op(Invocation.OP_FIELDS) | |
796 def mkshow(field): | |
797 self = Invocation.Op(Invocation.OP_SHOW) | |
798 self.field = field | |
799 return self | |
800 def mkrange(field): | |
801 self = Invocation.Op(Invocation.OP_RANGE) | |
802 self.field = field | |
803 return self | |
804 def mksearch(s, sels): | |
805 self = Invocation.Op(Invocation.OP_SEARCH) | |
806 self.search = s | |
807 self.sels = sels | |
808 return self | |
47 | 809 |
50 | 810 def __init__(self, ops): |
811 self.ops = ops | |
52 | 812 def dump(self, d): |
813 d.write("invocation: {} ops".format(len(self.ops))) | |
814 d.indent() | |
815 for op in self.ops: | |
816 op.dump(d) | |
817 d.unindent() | |
47 | 818 # end class Invocation |
819 | |
820 ############################################################ | |
821 # run (eval the SQL and print the results) | |
822 | |
823 def run_sel(sel, ids): | |
50 | 824 if sel.type == S_PR: |
825 if ids == []: | |
826 sys.stderr.write("No PRs matched.\n") | |
827 exit(1) | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
828 |
50 | 829 print_prs(ids) |
830 elif sel.type == S_MESSAGE: | |
831 if len(ids) != 1: | |
832 sys.stderr.write("Cannot retrieve messages " + | |
833 "from multiple PRs.") | |
834 exit(1) | |
835 print_message(ids[0], sel.message) | |
836 elif sel.type == S_ATTACHMENT: | |
837 if len(ids) != 1: | |
838 sys.stderr.write("Cannot retrieve attachments " + | |
839 "from multiple PRs.") | |
840 exit(1) | |
841 print_message(ids[0], sel.attachment) | |
842 else: | |
843 assert(False) | |
47 | 844 |
845 def run_op(op): | |
50 | 846 if op.type == OP_FIELDS: |
847 list_fields() | |
848 elif op.type == OP_SHOW: | |
849 describe_field(op.field) | |
850 elif op.type == OP_RANGE: | |
851 print_field_range(op.field) | |
852 elif op.type == OP_SEARCH: | |
853 sql = op.search | |
854 args = op.args # XXX not there! | |
855 ids = querydb(op.search, args) | |
856 for s in op.sels: | |
857 run_sel(s, ids) | |
858 else: | |
859 assert(False) | |
47 | 860 |
861 def run(ast): | |
50 | 862 for op in ast.ops: |
863 run_op(op) | |
47 | 864 |
865 ############################################################ | |
866 # compile (convert the AST so the searches are pure SQL) | |
867 | |
868 # | |
869 # XXX this doesn't work, we need to keep the interned strings | |
870 # on return from compile_query. | |
871 # | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
872 |
48 | 873 def matches(s, rx): |
874 # XXX | |
875 return True | |
876 | |
47 | 877 def compile_query(q): |
50 | 878 if q.type == Q_QSTRING: |
879 # XXX should use a split that honors quotes | |
880 terms = q.string.split() | |
881 terms = [dotstring(t) for t in terms] | |
882 return compile_query(doand(terms)) | |
883 if q.type == Q_TSTRING: | |
884 qb = QueryBuilder() | |
885 s = q.string | |
886 if matches(s, "^[0-9]+$"): | |
887 f = qb.getfield("number") | |
888 # Note: s is user-supplied but clean to insert directly | |
889 qb.addwhere("%s = %s" % (f, s)) | |
890 elif matches(s, "^[0-9]+-[0-9]+$"): | |
891 f = qb.getfield("number") | |
892 ss = s.split("-") | |
893 # Note: ss[] is user-supplied but clean | |
894 qb.addwhere("%s >= %s" % (f, ss[0])) | |
895 qb.addwhere("%s <= %s" % (f, ss[1])) | |
896 elif matches(s, "^[0-9]+-$"): | |
897 f = qb.getfield("number") | |
898 ss = s.split("-") | |
899 # Note: ss[] is user-supplied but clean | |
900 qb.addwhere("%s >= %s" % (f, ss[0])) | |
901 elif matches(s, "^-[0-9]+$"): | |
902 f = qb.getfield("number") | |
903 ss = s.split("-") | |
904 # Note: ss[] is user-supplied but clean | |
905 qb.addwhere("%s <= %s" % (f, ss[1])) | |
906 elif matches(s, "^[^:]+:[^:]+$"): | |
907 # XXX honor quoted terms | |
908 # XXX = or LIKE? | |
909 ss = s.split(":") | |
910 # ss[0] is not clean but if it's crap it won't match | |
911 f = qb.getfield(ss[0]) | |
912 # ss[1] is not clean, so intern it for safety | |
913 s = qb.intern(ss[1]) | |
914 qb.addwhere("%s = %s" % (f, s)) | |
915 elif matches(s, "^-[^:]+:[^:]+$"): | |
916 # XXX honor quoted terms | |
917 # XXX <> or NOT LIKE? | |
918 ss = s.split(":") | |
919 # ss[0] is not clean but if it's crap it won't match | |
920 f = qb.getfield(ss[0]) | |
921 # ss[1] is not clean, so intern it for safety | |
922 s = qb.intern(ss[1]) | |
923 qb.addwhere("%s <> %s" % (f, s)) | |
924 elif matches(s, "^-"): | |
925 # XXX <> or NOT LIKE? | |
926 f = qb.getfield("alltext") | |
927 # s is not clean, so intern it for safety | |
928 s = qb.intern(s) | |
929 qb.addwhere("%s <> %s" % (f, s)) | |
930 else: | |
931 # XXX = or LIKE? | |
932 f = qb.getfield("alltext") | |
933 # s is not clean, so intern it for safety | |
934 s = qb.intern(s) | |
935 qb.addwhere("%s = %s" % (f, s)) | |
47 | 936 |
50 | 937 # XXX also does not handle: |
938 # | |
939 # field: with no string (supposed to use a default | |
940 # search string) | |
941 # | |
942 # generated search fields that parse dates: | |
943 # {arrived,closed,modified,etc.}-{before,after}:date | |
944 # | |
945 # stale:time | |
47 | 946 |
50 | 947 return qb.build("PRs.id") |
948 # end Q_TSTRING case | |
949 if q.type == Q_OR: | |
950 subqueries = ["(" + compile_query(sq) + ")" for sq in q.args] | |
951 return " UNION ".join(subqueries) | |
952 if q.type == Q_AND: | |
953 subqueries = ["(" + compile_query(sq) + ")" for sq in q.args] | |
954 return " INTERSECT ".join(subqueries) | |
955 if q.type == Q_SQL: | |
956 return q.sql | |
957 assert(False) | |
47 | 958 # end compile_query |
959 | |
960 def compile_order(qb, o): | |
50 | 961 str = qb.getfield(o.field) |
962 if o.rev: | |
963 str = str + " DESCENDING" | |
964 return str | |
47 | 965 |
966 def compile_search(s): | |
50 | 967 qb2 = QueryBuilder() |
47 | 968 |
50 | 969 # multiple query strings are treated as OR |
970 query = door(s.queries) | |
971 query = compile_query(q) | |
47 | 972 |
50 | 973 if s.openonly: |
974 qb2.addwhere("not %s" % qb.getfield("closed")) | |
975 if s.publiconly: | |
976 qb2.addwhere("not %s" % qb.getfield("confidential")) | |
47 | 977 |
50 | 978 orders = [compile_order(qb2, o) for o in s.orders] |
979 order = ", ".join(orders) | |
980 if order != "": | |
981 qb2.setorder(order) | |
47 | 982 |
50 | 983 if qb2.nonempty(): |
984 qb2.addjoin(query, "search") | |
985 qb2.addjoin("PRs") | |
986 qb2.addwhere("search = PRs.id") | |
987 query = qb2.build(["search"]) | |
47 | 988 |
50 | 989 return query |
47 | 990 # end compile_search |
991 | |
992 def compile_op(op): | |
50 | 993 if op.type == OP_SEARCH: |
994 op.search = compile_search(op.search) | |
995 return op | |
47 | 996 |
997 def compile(ast): | |
50 | 998 ast.ops = [compile_op(op) for op in ast.ops] |
47 | 999 |
1000 ############################################################ | |
1001 # arg handling | |
1002 | |
1003 # | |
52 | 1004 # I swear, all getopt interfaces suck. You have to write your own to |
1005 # not go mad. | |
47 | 1006 # |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1007 |
52 | 1008 # Usage. |
1009 def usage(): | |
1010 sys.stderr.write(""" | |
1011 query-pr: search for and retrieve problem reports | |
1012 usage: query-pr [options] [searchterms] Query the database. | |
1013 query-pr [options] --sql QUERY Execute QUERY as the search. | |
1014 query-pr [options] -s QUERY Same as --sql. | |
1015 query-pr --fields List database fields. | |
1016 query-pr --show FIELD Print info about database field. | |
1017 query-pr --range FIELD Print extant range for database field. | |
1018 query-pr --help / -h Print this message. | |
1019 query-pr --version / -v Print version and exit. | |
1020 options: | |
1021 --search-string STRING Forcibly treat STRING as a search term. | |
1022 --message NUM / -m NUM Print a message by its ID number. | |
1023 --attachment NUM / -a NUM Print an attachment by its ID number. | |
1024 --paranoid Deny unsafe settings. | |
1025 filter options: | |
1026 --open Exclude closed PRs. (default) | |
1027 --closed Include closed PRs. | |
1028 --public Exclude confidential PRs. | |
1029 --privileged Include confidential PRs. (default) | |
1030 sort options: | |
1031 --oldest Sort with oldest PRs first. (default) | |
1032 --newest Sort with newest PRs first. | |
1033 --staleness Sort by last modification time. | |
1034 --orderby FIELD Sort by specific field. | |
1035 output options: | |
1036 --raw / -r Print raw SQL output. | |
1037 --list / -l Print in list form. | |
1038 --headers Print headers only. | |
1039 --meta / --metadata Print all metadata. | |
1040 --full / -f Print entire PR. | |
1041 --text Print text. (default) | |
1042 --csv Print CSV. | |
1043 --xml Print XML. | |
1044 --json Print JSON. | |
1045 --rdf Print RDF. | |
1046 --rdflike Print RDF-like text. | |
1047 search terms: | |
1048 NUM Single PR by number. | |
1049 NUM-[NUM] Range of PRs by number. | |
1050 TEXT Search string | |
1051 FIELD:TEXT Search string for a particular field. | |
1052 FIELD: Use the field's default search string. | |
1053 derived fields: | |
1054 arrived-before:DATE Arrival date before DATE. | |
1055 arrived-after:DATE Arrival date after DATE. | |
1056 closed-before:DATE Close date before DATE. | |
1057 closed-after:DATE Close date after DATE, or none. | |
1058 last-modified-before:DATE Last modified before DATE. | |
1059 last-modified-after:DATE Last modified after DATE. | |
1060 stale:TIME Last modified at least TIME ago. | |
1061 Explicit SQL queries should return lists of PR numbers (only). | |
1062 """) | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1063 |
52 | 1064 # Widget to hold argv and allow peeling args off one at a time. |
1065 class ArgHolder: | |
1066 def __init__(self, argv): | |
1067 self.argc = len(argv) | |
1068 self.argv = argv | |
1069 self.pos = 1 | |
1070 def next(self): | |
1071 if self.pos >= self.argc: | |
1072 return None | |
1073 ret = self.argv[self.pos] | |
1074 self.pos += 1 | |
1075 return ret | |
1076 def getarg(self, opt): | |
1077 ret = self.next() | |
1078 if ret is None: | |
1079 msg = "Option {} requires an argument\n".format(opt) | |
1080 sys.stderr.write(msg) | |
1081 exit(1) | |
1082 return ret | |
1083 | |
1084 # Read the argument list and convert it into an Invocation. | |
1085 def getargs(argv): | |
1086 # Results | |
1087 ops = [] | |
1088 orders = [] | |
1089 queries = [] | |
1090 selections = [] | |
1091 output = "LIST" | |
1092 outformat = "TEXT" | |
1093 openonly = True | |
1094 publiconly = False | |
1095 paranoid = False | |
1096 nomoreoptions = False | |
1097 | |
1098 # | |
1099 # Functions for the options | |
1100 # | |
1101 | |
1102 def do_paranoid(): | |
1103 nonlocal paranoid, publiconly | |
1104 paranoid = True | |
1105 publiconly = True | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1106 |
52 | 1107 def do_help(): |
1108 usage() | |
1109 exit(0) | |
1110 def do_version(): | |
1111 msg = "query-pr {}\n".format(program_version) | |
1112 sys.stdout.write(msg) | |
1113 exit(0) | |
1114 def do_fields(): | |
1115 ops.append(Invocation.mkfields()) | |
1116 def do_show(field): | |
1117 ops.append(Invocation.mkshow(field)) | |
1118 def do_range(field): | |
1119 ops.append(Invocation.mkrange(field)) | |
1120 | |
1121 def do_search(term): | |
1122 queries.append(Invocation.mkterm(term)) | |
1123 def do_sql(text): | |
1124 assert(not paranoid) | |
1125 queries.append(Invocation.mksql(text)) | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1126 |
52 | 1127 def do_open(): |
1128 nonlocal openonly | |
1129 openonly = True | |
1130 def do_closed(): | |
1131 nonlocal openonly | |
1132 openonly = False | |
1133 def do_public(): | |
1134 nonlocal publiconly | |
1135 publiconly = True | |
1136 def do_privileged(): | |
1137 nonlocal publiconly | |
1138 assert(not paranoid) | |
1139 publiconly = False | |
1140 | |
1141 def do_oldest(): | |
1142 orders.append(Invocation.mkoldest()) | |
1143 def do_newest(): | |
1144 orders.append(Invocation.mknewest()) | |
1145 def do_staleness(): | |
1146 orders.append(Invocation.mkstaleness()) | |
1147 def do_orderby(field): | |
1148 orders.append(Invocation.mkfield(field)) | |
1149 def do_revorderby(field): | |
1150 orders.append(Invocation.mkrevfield(field)) | |
1151 | |
1152 def do_message(n): | |
1153 selections.append(Invocation.mkmessage(n)) | |
1154 def do_attachment(n): | |
1155 selections.append(Invocation.mkattachment(n)) | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1156 |
52 | 1157 def do_raw(): |
1158 nonlocal output | |
1159 output = "RAW" | |
1160 def do_list(): | |
1161 nonlocal output | |
1162 output = "LIST" | |
1163 def do_headers(): | |
1164 nonlocal output | |
1165 output = "HEADERS" | |
1166 def do_meta(): | |
1167 nonlocal output | |
1168 output = "META" | |
1169 def do_full(): | |
1170 nonlocal output | |
1171 output = "FULL" | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1172 |
52 | 1173 def do_text(): |
1174 nonlocal outformat | |
1175 outformat = "TEXT" | |
1176 def do_csv(): | |
1177 nonlocal outformat | |
1178 outformat = "CSV" | |
1179 def do_xml(): | |
1180 nonlocal outformat | |
1181 outformat = "XML" | |
1182 def do_json(): | |
1183 nonlocal outformat | |
1184 outformat = "JSON" | |
1185 def do_rdf(): | |
1186 nonlocal outformat | |
1187 outformat = "RDF" | |
1188 def do_rdflike(): | |
1189 nonlocal outformat | |
1190 outformat = "RDFLIKE" | |
1191 | |
1192 def do_unknown(opt): | |
1193 sys.stderr.write("Unknown option {}\n".format(opt)) | |
1194 exit(1) | |
1195 | |
1196 args = ArgHolder(argv) | |
1197 while True: | |
1198 arg = args.next() | |
1199 if arg is None: | |
1200 break | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1201 |
52 | 1202 if nomoreoptions or arg[0] != "-": |
1203 do_search(arg) | |
1204 elif arg == "--": | |
1205 nomoreoptions = True | |
1206 # Long options | |
1207 elif arg == "--attachment": | |
1208 do_attachment(args.getarg(arg)) | |
1209 elif arg.startswith("--attachment="): | |
1210 do_message(arg[13:]) | |
1211 elif arg == "--closed": | |
1212 do_closed() | |
1213 elif arg == "--csv": | |
1214 do_csv() | |
1215 elif arg == "--fields": | |
1216 do_fields() | |
1217 elif arg == "--full": | |
1218 do_full() | |
1219 elif arg == "--headers": | |
1220 do_headers() | |
1221 elif arg == "--help": | |
1222 do_help() | |
1223 elif arg == "--json": | |
1224 do_json() | |
1225 elif arg == "--list": | |
1226 do_list() | |
1227 elif arg == "--message": | |
1228 do_message(args.getarg(arg)) | |
1229 elif arg.startswith("--message="): | |
1230 do_message(arg[10:]) | |
1231 elif arg == "--meta": | |
1232 do_meta() | |
1233 elif arg == "--metadata": | |
1234 do_meta() | |
1235 elif arg == "--newest": | |
1236 do_newest() | |
1237 elif arg == "--oldest": | |
1238 do_oldest() | |
1239 elif arg == "--orderby": | |
1240 do_orderby(args.getarg(arg)) | |
1241 elif arg.startswith("--orderby="): | |
1242 do_orderby(arg[10:]) | |
1243 elif arg == "--open": | |
1244 do_open() | |
1245 elif arg == "--paranoid": | |
1246 do_paranoid() | |
1247 elif arg == "--public": | |
1248 do_public() | |
1249 elif arg == "--privileged" and not paranoid: | |
1250 do_privileged() | |
1251 elif arg == "--range": | |
1252 do_range(args.getarg(arg)) | |
1253 elif arg.startswith("--range="): | |
1254 do_range(arg[8:]) | |
1255 elif arg == "--raw": | |
1256 do_raw() | |
1257 elif arg == "--rdf": | |
1258 do_rdf() | |
1259 elif arg == "--rdflike": | |
1260 do_rdflike() | |
1261 elif arg == "--revorderby": | |
1262 do_revorderby(args.getarg(arg)) | |
1263 elif arg.startswith("--revorderby="): | |
1264 do_revorderby(arg[13:]) | |
1265 elif arg == "--search": | |
1266 do_search(args.getarg(arg)) | |
1267 elif arg.startswith("--search="): | |
1268 do_search(arg[9:]) | |
1269 elif arg == "--show": | |
1270 do_show(args.getarg(arg)) | |
1271 elif arg.startswith("--show="): | |
1272 do_show(arg[7:]) | |
1273 elif arg == "--sql" and not paranoid: | |
1274 do_sql(args.getarg(arg)) | |
1275 elif arg.startswith("--sql=") and not paranoid: | |
1276 do_sql(arg[7:]) | |
1277 elif arg == "--staleness": | |
1278 do_staleness() | |
1279 elif arg == "--text": | |
1280 do_text() | |
1281 elif arg == "--version": | |
1282 do_version() | |
1283 elif arg == "--xml": | |
1284 do_xml() | |
1285 elif arg.startswith("--"): | |
1286 do_unknown(arg) | |
1287 else: | |
1288 # short options | |
1289 i = 1 | |
1290 n = len(arg) | |
1291 while i < n: | |
1292 opt = arg[i] | |
1293 i += 1 | |
1294 def getarg(): | |
1295 nonlocal i | |
1296 if i < n: | |
1297 ret = arg[i:] | |
1298 else: | |
1299 ret = args.getarg("-" + opt) | |
1300 i = n | |
1301 return ret | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1302 |
52 | 1303 if opt == "a": |
1304 do_attachment(getarg()) | |
1305 elif opt == "f": | |
1306 do_full() | |
1307 elif opt == "h": | |
1308 do_help() | |
1309 elif opt == "l": | |
1310 do_list() | |
1311 elif opt == "m": | |
1312 do_message(getarg()) | |
1313 elif opt == "s" and not paranoid: | |
1314 do_sql(getarg()) | |
1315 elif opt == "r": | |
1316 do_raw() | |
1317 elif opt == "v": | |
1318 do_version() | |
1319 else: | |
1320 do_unknown("-" + opt) | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1321 |
52 | 1322 # Now convert what we got to a single thing. |
1323 if queries != []: | |
1324 if orders == []: | |
1325 orders = [Invocation.mkoldest()] | |
1326 if selections == []: | |
1327 selections = [Invocation.mkpr(output, outformat)] | |
1328 search = Invocation.Search(queries, openonly, publiconly, orders) | |
1329 ops.append(Invocation.mksearch(search, selections)) | |
1330 else: | |
1331 if orders != []: | |
1332 msg = "No queries given for requested orderings\n" | |
1333 sys.stderr.write(msg) | |
1334 exit(1) | |
1335 if selections != []: | |
1336 msg = "No queries given for requested selections\n" | |
1337 sys.stderr.write(msg) | |
1338 exit(1) | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1339 |
50 | 1340 return Invocation(ops) |
33 | 1341 # end getargs |
1342 | |
1343 ############################################################ | |
1344 # main | |
1345 | |
52 | 1346 todo = getargs(sys.argv) |
1347 #todo.dump(Dumper(sys.stdout)) | |
1348 | |
47 | 1349 opendb() |
1350 fetch_classifications() | |
1351 todo = compile(todo) | |
1352 run(todo) | |
1353 closedb() | |
1354 exit(0) | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1355 |