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