Mercurial > ~dholland > hg > swallowtail > index.cgi
annotate shelltools/query-pr/query.py @ 47:bcd1d06838fd
more better stuff
author | David A. Holland |
---|---|
date | Wed, 13 Aug 2014 03:03:30 -0400 (2014-08-13) |
parents | 73e6dac29391 |
children | 3d5adf5a59d0 |
rev | line source |
---|---|
7
c013fb703183
Empty placeholder scripts so the build will run.
David A. Holland
parents:
diff
changeset
|
1 #!@PYTHON@ |
33 | 2 |
3 import argparse | |
4 import psycopg2 | |
5 | |
6 program_description = """ | |
7 Search for and retrieve problem reports. | |
8 """ | |
9 program_version = "@VERSION@" | |
10 | |
11 ############################################################ | |
12 # settings | |
13 | |
14 outfile = sys.stdout | |
15 outmaterial = "headers" | |
16 outformat = "text" | |
17 | |
18 ############################################################ | |
47 | 19 # database field access |
20 | |
21 # | |
22 # Fields of PRs that we might search are spread across a number of | |
23 # tables and require varying joins to get them. And, because of | |
24 # classication schemes, the set of fields isn't static and we can't | |
25 # just assemble a massive view with one column for each field. | |
26 # | |
27 # The QueryBuilder class knows how to arrange for all known fields to | |
28 # be present. | |
29 # | |
30 | |
31 class QueryBuilder: | |
32 # these fields are in the PRs table | |
33 prtable_fields = [ | |
34 "id", "synopsis", "confidential", "state", "locked", | |
35 "timeout_date", "timeout_state", | |
36 "arrival_schemaversion", "arrival_date", "modified_date", | |
37 "closed_date", | |
38 "release", "environment" | |
39 ] | |
40 | |
41 # these fields are aliases for others | |
42 alias_fields = { | |
43 "number" : "id" | |
44 "date" : "arrival_date" | |
45 } | |
46 | |
47 def __init__(self): | |
48 self.present = {} | |
49 self.joined = {} | |
50 self.fromitems = [] | |
51 self.whereitems = [] | |
52 self.order = None | |
53 | |
54 def setorder(self, order): | |
55 self.order = order | |
56 | |
57 # add to present{} and return the value for convenience (internal) | |
58 def makepresent(self, field, name): | |
59 self.present[field] = name | |
60 return name | |
61 | |
62 # add a join item (once only) (internal) | |
63 def addjoin(self, table, as = None): | |
64 if as is not None: | |
65 key = table + "-" + as | |
66 val = table + " AS " + as | |
67 else: | |
68 key = table | |
69 val = table | |
70 if key not in self.joined: | |
71 self.joined[key] = True | |
72 self.fromitems.append(val) | |
73 | |
74 # returns a sql expression for the field | |
75 def getfield(self, field): | |
76 # already-fetched fields | |
77 if field in self.present: | |
78 return self.present[field] | |
79 | |
80 # aliases for other fields | |
81 if field in alias_fields: | |
82 return self.getfield(alias_fields[field]) | |
83 | |
84 # simple fields directly in the PRs table | |
85 if field in prtable_fields: | |
86 self.addjoin("PRs") | |
87 return self.makepresent(field, "PRs." + field) | |
88 | |
89 # now it gets more interesting... | |
90 if field == "closed": | |
91 self.addjoin("PRs") | |
92 self.addjoin("states") | |
93 self.addwhere("PRs.state = states.name") | |
94 return self.makepresent(field, "states.closed") | |
95 | |
96 # XXX let's pick one set of names and use them everywhere | |
97 # (e.g. change "posttime" in the schema to "message_date" | |
98 # or something) | |
99 if field == "comment_date" or field == "posttime": | |
100 self.addjoin("PRs") | |
101 self.addjoin("messages") | |
102 self.addwhere("PRs.id = messages.pr") | |
103 return self.makepresent(field, "messages.posttime") | |
104 | |
105 if field == "comment" or field == "message" or field == "post": | |
106 self.addjoin("PRs") | |
107 self.addjoin("messages") | |
108 self.addwhere("PRs.id = messages.pr") | |
109 return self.makepresent(field, "messages.body") | |
110 | |
111 if field == "attachment": | |
112 self.addjoin("PRs") | |
113 self.addjoin("messages") | |
114 self.addjoin("attachments") | |
115 self.addwhere("PRs.id = messages.pr") | |
116 self.addwhere("messages.id = attachments.msgid") | |
117 return self.makepresent(field, "attachments.body") | |
118 | |
119 if field == "patch": | |
120 self.addjoin("PRs") | |
121 self.addjoin("messages") | |
122 self.addjoin("attachments", "patches") | |
123 self.addwhere("PRs.id = messages.pr") | |
124 self.addwhere("messages.id = patches.msgid") | |
125 self.addwhere("patches.mimetype = " + | |
126 "'application/x-patch'") | |
127 return self.makepresent(field, "patches.body") | |
128 | |
129 if field == "mimetype": | |
130 subquery = "((SELECT mtmessages1.pr as pr, " + | |
131 "mtmessages1.mimetype as mimetype " + | |
132 "FROM messages as mtmessages1) " + | |
133 "UNION " + | |
134 "(SELECT mtmessages2.pr as pr, " + | |
135 "mtattach2.mimetype as mimetype " + | |
136 "FROM messages as mtmessages2, " + | |
137 " attachments as mtattach2 " + | |
138 "WHERE mtmessages2.id = mtattach2.msgid))" | |
139 self.addjoin("PRs") | |
140 self.addjoin(subquery, "mimetypes") | |
141 self.addwhere("PRs.id = mimetypes.pr") | |
142 return self.makepresent(field, "mimetypes.mimetype") | |
143 | |
144 # XXX: need view userstrings | |
145 # select (id, username as name) from users | |
146 # union select (id, realname as name) from users | |
147 # (allow searching emails? ugh) | |
148 if field == "originator" or field == "submitter": | |
149 self.addjoin("PRs") | |
150 self.addjoin("userstrings", "originators") | |
151 self.addwhere("PRs.originator = originators.id") | |
152 return self.makepresent(field, "originators.name") | |
153 | |
154 if field == "reporter" or field == "respondent": | |
155 self.addjoin("PRs") | |
156 self.addjoin("subscriptions") | |
157 self.addjoin("userstrings", "reporters") | |
158 self.addwhere("subscriptions.userid = reporters.id") | |
159 self.addwhere("subscriptions.reporter") | |
160 return self.makepresent(field, "reporters.name") | |
161 | |
162 if field == "responsible": | |
163 self.addjoin("PRs") | |
164 self.addjoin("subscriptions") | |
165 self.addjoin("userstrings", "responsibles") | |
166 self.addwhere("subscriptions.userid = responsibles.id") | |
167 self.addwhere("subscriptions.responsible") | |
168 return self.makepresent(field, "responsibles.name") | |
169 | |
170 if field in hierclasses: | |
171 col = field + "_data" | |
172 self.addjoin("PRs") | |
173 self.addjoin("hierclass_data", col) | |
174 self.addwhere("PRs.id = %s.pr" % col) | |
175 self.addwhere("%s.scheme = '%s'" % (col, field)) | |
176 return self.makepresent(field, "%s.value" % col) | |
177 | |
178 if field in flatclasses: | |
179 col = field + "_data" | |
180 self.addjoin("PRs") | |
181 self.addjoin("flatclass_data", col) | |
182 self.addwhere("PRs.id = %s.pr" % col) | |
183 self.addwhere("%s.scheme = '%s'" % (col, field)) | |
184 return self.makepresent(field, "%s.value" % col) | |
185 | |
186 if field in textclasses: | |
187 col = field + "_data" | |
188 self.addjoin("PRs") | |
189 self.addjoin("textclass_data", col) | |
190 self.addwhere("PRs.id = %s.pr" % col) | |
191 self.addwhere("%s.scheme = '%s'" % (col, field)) | |
192 return self.makepresent(field, "%s.value" % col) | |
193 | |
194 if field in tagclasses: | |
195 col = field + "_data" | |
196 self.addjoin("PRs") | |
197 self.addjoin("tagclass_data", col) | |
198 self.addwhere("PRs.id = %s.pr" % col) | |
199 self.addwhere("%s.scheme = '%s'" % (col, field)) | |
200 return self.makepresent(field, "%s.value" % col) | |
201 | |
202 sys.stderr.write("Unknown field %s" % field) | |
203 exit(1) | |
204 # end getfield | |
205 | |
206 # emit sql | |
207 def build(self, sels): | |
208 s = ", ".join(sels) | |
209 f = ", ".join(self.fromitems) | |
210 w = " and ".join(self.whereitems) | |
211 q = "SELECT %s\nFROM %s\nWHERE %s\n" % (s, f, w) | |
212 if self.order is not None: | |
213 q = q + "ORDER BY " + self.order + "\n" | |
214 return q | |
215 # endif | |
216 | |
217 # end class QueryBuilder | |
218 | |
219 # XXX we need to add dynamically: | |
220 # hierclass_names.name to hierclasses[] | |
221 # flatclass_names.name to flatclasses[] | |
222 # textclass_names.name to textclasses[] | |
223 # tagclass_names.name to tagclasses[] | |
224 | |
225 ############################################################ | |
33 | 226 # database |
227 | |
228 dblink = None | |
229 | |
230 def opendb(): | |
231 global dblink | |
232 | |
233 host = "localhost" | |
234 user = "swallowtail" | |
235 database = "swallowtail" | |
236 dblink = psycopg2.connect("host=%s user=%s dbname=%s" % | |
237 (host, user, database)) | |
238 # end opendb | |
239 | |
240 def closedb(): | |
241 global dblink | |
242 | |
243 dblink.close() | |
244 dblink = None | |
245 # end closedb | |
246 | |
247 def querydb(qtext, args): | |
248 print "Executing this query:" | |
249 print qtext | |
250 print "Args are:" | |
251 print args | |
252 | |
253 cursor = dblink.cursor() | |
254 cursor.execute(qtext, args) | |
255 result = cursor.fetchall() | |
256 cursor.close() | |
257 return result | |
258 # end querydb | |
259 | |
260 ############################################################ | |
261 # query class for searches | |
47 | 262 # XXX: obsolete, remove |
33 | 263 |
264 class Query: | |
265 __init__(self): | |
266 self.selections = [] | |
267 self.tables = [] | |
268 self.constraints = [] | |
269 self.args = [] | |
270 prtables = ["PRs"] | |
271 prconstraints = [] | |
272 | |
273 def select(self, s): | |
274 self.selections.append(s) | |
275 | |
276 def addtable(self, t): | |
277 assert(t not in self.tables) | |
278 self.tables.append(t) | |
279 | |
280 def constrain(self, expr): | |
281 self.constraints.append(t) | |
282 | |
283 def internval(self, val): | |
284 num = len(self.args) | |
285 self.args[num] = val | |
286 return "$%d" % num | |
287 | |
288 def textify(self): | |
289 s = "SELECT %s\n" % ",".join(self.selections) | |
290 f = "FROM %s\n" % ",".join(self.tables) | |
291 w = "WHERE %s\n" % " AND ".join(self.constraints) | |
292 return s + f + w | |
293 # end class Query | |
294 | |
295 def regexp_constraint(q, field, value): | |
296 cleanval = q.internval(value) | |
297 if not isregexp(value): | |
298 return "%s = %s" % (field, cleanval) | |
299 else: | |
300 # XXX what's the right operator again? | |
301 return "%s ~= %s" % (field, cleanval) | |
302 # end regexp_constraint | |
303 | |
304 def intrange_constraint(q, field, value): | |
305 (lower, upper) = args.number | |
306 if lower is not None: | |
307 assert(typeof(lower) == int) | |
308 prq.constrain("%s >= %d" % (field, lower)) | |
309 if upper is not None: | |
310 assert(typeof(upper) == int) | |
311 prq.constrain("%s <= %d" % (field, upper)) | |
312 # end intrange_constraint | |
313 | |
314 def daterange_constraint(q, field, value): | |
315 # XXX | |
316 assert(0) | |
317 # end daterange_constraint | |
318 | |
319 ############################################################ | |
320 | |
47 | 321 # this is old code that needs to be merged or deleted into the new stuff |
322 def oldstuff(): | |
323 | |
33 | 324 # If we're doing something other than a search, do it now |
325 if args.attach is not None: | |
326 get_attachment(args.attach) | |
327 exit(0) | |
328 if args.message is not None: | |
329 get_message(args.message) | |
330 exit(0) | |
331 | |
332 if args.prs is not None and len(args.prs) > 0: | |
333 show_prs(args.prs) | |
334 exit(0) | |
335 | |
336 # | |
337 # Collect up the search constraints | |
338 # | |
339 | |
340 # 1. Constraints on the PRs table | |
341 checkprtable = False | |
342 prq = Query() | |
343 prq.select("PRs.id as id") | |
344 prq.addtable("PRs") | |
345 if not args.closed: | |
346 checkprtable = True | |
347 prq.addtable("states") | |
348 prq.constrain("PRs.state = states.name") | |
349 prq.constrain("states.closed = FALSE") | |
350 if args.public: | |
351 checkprtable = True | |
352 prq.constrain("NOT PRs.confidential") | |
353 if args.number is not None: | |
354 checkprtable = True | |
355 intrange_constraint(prq, "PRs.id", args.number) | |
356 if args.synopsis is not None: | |
357 checkprtable = True | |
358 regexp_constraint(prq, "PRs.synopsis", args.synopsis) | |
359 if args.confidential is not None: | |
360 checkprtable = True | |
361 assert(typeof(args.confidential) == bool) | |
362 if args.confidential: | |
363 prq.constrain("PRs.confidential") | |
364 else: | |
365 prq.constrain("not PRs.confidential") | |
366 if args.state is not None: | |
367 checkprtable = True | |
368 regexp_constraint(prq, "PRs.state", args.state) | |
369 if args.locked is not None: | |
370 checkprtable = True | |
371 assert(typeof(args.locked) == bool) | |
372 if args.locked: | |
373 prq.constrain("PRs.locked") | |
374 else: | |
375 prq.constrain("not PRs.locked") | |
376 if args.arrival_schemaversion is not None: | |
377 checkprtable = True | |
378 intrange_constraint(prq, "PRs.arrival_schemaversion", | |
379 args.arrival_schemaversion) | |
380 if args.arrival_date is not None: | |
381 checkprtable = True | |
382 daterange_constraint(prq, "PRs.arrival_date", | |
383 args.arrival_date) | |
384 if args.closed_date is not None: | |
385 checkprtable = True | |
386 daterange_constraint(prq, "PRs.closed_date", | |
387 args.closed_date) | |
388 if args.last_modified is not None: | |
389 checkprtable = True | |
390 daterange_constraint(prq, "PRs.last_modified", | |
391 args.last_modified) | |
392 if args.release is not None: | |
393 checkprtable = True | |
394 regexp_constraint(prq, "PRs.release", args.release) | |
395 if args.environment is not None: | |
396 checkprtable = True | |
397 regexp_constraint(prq, "PRs.environment", args.environment) | |
398 | |
399 if args.originator_name is not None or | |
400 args.originator_email is not None: | |
401 prq.addtable("usermail as originator") | |
402 prq.constrain("PRs.originator = originator.id") | |
403 if args.originator_name is not None: | |
404 checkprtable = True | |
405 regexp_constraint(prq, "originator.realname", | |
406 args.originator_name) | |
407 if args.originator_email is not None: | |
408 checkprtable = True | |
409 regexp_constraint(prq, "originator.email", | |
410 args.originator_name) | |
411 if args.originator_id is not None: | |
412 checkprtable = True | |
413 intrange_constraint(prq, "PRs.originator", args.originator_id) | |
414 | |
415 queries = [] | |
416 if checkprtable: | |
417 queries.append(prq) | |
418 | |
419 if args.responsible is not None: | |
420 sq = Query() | |
421 sq.select("subscriptions.pr as id") | |
422 sq.addtable("subscriptions") | |
423 sq.addtable("users") | |
424 sq.constrain("subscriptions.userid = users.id") | |
425 regexp_constraint(sq, "users.realname", args.responsible) | |
426 sq.constrain("subscriptions.responsible") | |
427 queries.append(sq) | |
428 if args.respondent is not None: | |
429 sq = Query() | |
430 sq.select("subscriptions.pr as id") | |
431 sq.addtable("subscriptions") | |
432 sq.addtable("users as subscribed") | |
433 sq.constrain("subscriptions.userid = users.id") | |
434 regexp_constraint(sq, "users.realname", args.respondent) | |
435 sq.constrain("subscriptions.reporter") | |
436 queries.append(sq) | |
437 if args.subscribed is not None: | |
438 sq = Query() | |
439 sq.select("subscriptions.pr as id") | |
440 sq.addtable("subscriptions") | |
441 sq.addtable("users as subscribed") | |
442 sq.constrain("subscriptions.userid = users.id") | |
443 regexp_constraint(sq, "users.realname", args.subscribed) | |
444 queries.append(sq) | |
445 | |
446 if args.messages is not None: | |
447 mq = Query() | |
448 mq.select("messages.pr as id") | |
449 mq.addtable("messages") | |
450 regexp_constraint(sq, "messages.text", args.messages) | |
451 queries.append(mq) | |
452 | |
453 if args.adminlog is not None: | |
454 aq = Query() | |
455 aq.select("adminlog.pr as id") | |
456 aq.addtable("adminlog") | |
457 regexp_constraint(sq, "adminlog.change", args.adminlog) | |
458 regexp_constraint(sq, "adminlog.comment", args.adminlog) | |
459 assert(len(aq.constraints) == 2) | |
460 x = "%s OR %s" % (aq.constraints[0], aq.constraints[1]) | |
461 aq.constraints = [x] | |
462 queries.append(aq) | |
463 | |
464 if args.anytext is not None: | |
465 choke("--anytext isn't supported yet") | |
466 | |
467 for scheme in classification_schemes: | |
468 if args[scheme] is not None: | |
469 schemetype = classification_schemetypes[scheme] | |
470 tbl = "%sclass_data" % schemetype | |
471 cq = Query() | |
472 cq.select("scheme.pr as id") | |
473 cq.addtable("%s as scheme" % schemetype) | |
474 cq.constrain("scheme.scheme = '%s'" % scheme) | |
475 regexp_constraint(cq, "scheme.value", args[scheme]) | |
476 queries.append(cq) | |
477 # end loop | |
478 | |
479 querytexts = [q.textify() for q in queries] | |
480 return "INTERSECT\n".join(querytexts) | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
481 |
47 | 482 ############################################################ |
483 # printing | |
484 | |
485 class PrintText: | |
486 def __init__(self, output): | |
487 self.lines = (output == "RAW" or output == "LIST") | |
488 def printheader(self, row): | |
489 # nothing | |
490 def printrow(self, row): | |
491 # XXX | |
492 print row | |
493 def printfooter(self, row): | |
494 # nothing | |
495 # end class PrintText | |
496 | |
497 class PrintCsv: | |
498 def __init__(self, output): | |
499 # nothing | |
500 def printheader(self, row): | |
501 # XXX | |
502 def printrow(self, row): | |
503 # XXX | |
504 def printfooter(self, row): | |
505 # nothing | |
506 # end class PrintCsv | |
507 | |
508 class PrintXml: | |
509 def __init__(self, output): | |
510 # nothing | |
511 def printheader(self, row): | |
512 # XXX | |
513 def printrow(self, row): | |
514 # XXX | |
515 def printfooter(self, row): | |
516 # XXX | |
517 # end class PrintXml | |
518 | |
519 class PrintJson: | |
520 def __init__(self, output): | |
521 # nothing | |
522 def printheader(self, row): | |
523 # XXX | |
524 def printrow(self, row): | |
525 # XXX | |
526 def printfooter(self, row): | |
527 # XXX | |
528 # end class PrintJson | |
529 | |
530 class PrintRdf: | |
531 def __init__(self, output): | |
532 # nothing | |
533 def printheader(self, row): | |
534 # XXX | |
535 def printrow(self, row): | |
536 # XXX | |
537 def printfooter(self, row): | |
538 # XXX | |
539 # end class PrintRdf | |
540 | |
541 class PrintRdflike: | |
542 def __init__(self, output): | |
543 # nothing | |
544 def printheader(self, row): | |
545 # XXX | |
546 def printrow(self, row): | |
547 # XXX | |
548 def printfooter(self, row): | |
549 # XXX | |
550 # end class PrintRdflike | |
551 | |
552 def print_prs(ids): | |
553 if sel.outformat == "TEXT": | |
554 mkprinter = PrintText | |
555 elif sel.outformat == "CSV": | |
556 mkprinter = PrintCsv | |
557 elif sel.outformat == "XML": | |
558 mkprinter = PrintXml | |
559 elif sel.outformat == "JSON": | |
560 mkprinter = PrintJson | |
561 elif sel.outformat == "RDF": | |
562 mkprinter = PrintRdf | |
563 elif sel.outformat == "RDFLIKE": | |
564 mkprinter = PrintRdflike | |
565 else: | |
566 assert(False) | |
567 | |
568 # reset the printer | |
569 printer = mkprinter(sel.output) | |
570 | |
571 if sel.output == "RAW": | |
572 printer.printheader(ids[0]) | |
573 for id in ids: | |
574 printer(id) | |
575 printer.printfooter(ids[0]) | |
576 return | |
577 elif sel.output == "LIST": | |
578 # XXX is there a clean way to do this passing the | |
579 # whole list of ids at once? | |
580 query = "SELECT id, synopsis\n" + | |
581 "FROM PRs\n" + | |
582 "WHERE id = $1" | |
583 elif sel.output == "HEADERS": | |
584 query = None # XXX | |
585 elif sel.output == "META": | |
586 query = None # XXX | |
587 elif sel.output == "FULL": | |
588 query = None # XXX | |
589 else: | |
590 assert(False) | |
591 | |
592 first = True | |
593 for id in ids: | |
594 results = querydb(query, [id]) | |
595 if first: | |
596 printer.printheader(results[0]) | |
597 first = False | |
598 for r in results: | |
599 printer.printrow(r) | |
600 printer.printfooter(results[0]) | |
601 # end print_prs | |
602 | |
603 # XXX if in public mode we need to check if the PR is public | |
604 def print_message(pr, msgnum): | |
605 query = "SELECT users.username AS username,\n" + | |
606 " users.realname AS realname,\n" + | |
607 " messages.id AS id, parent_id,\n" + | |
608 " posttime, mimetype, body\n" + | |
609 "FROM messages, users\n" + | |
610 "WHERE messages.who = users.id\n" + | |
611 " AND messages.pr = $1\n" + | |
612 " AND messages.number_in_pr = $2\n" | |
613 # Note that while pr is safe, msgnum came from the commandline | |
614 # and may not be. | |
615 results = querydb(query, [pr, msgnum]) | |
616 [result] = results | |
617 (username, realname, id, parent_id, posttime, mimetype, body) = result | |
618 # XXX honor mimetype | |
619 # XXX honor output format (e.g. html) | |
620 sys.stdout.write("From swallowtail@%s %s\n" % (organization,posttime)) | |
621 sys.stdout.write("From: %s (%s)\n" % (username, realname)) | |
622 sys.stdout.write("References: %s\n" % parent_id) | |
623 sys.stdout.write("Date: %s\n" % posttime) | |
624 sys.stdout.write("Content-Type: %s\n" % mimetype) | |
625 sys.stdout.write("\n") | |
626 sys.stdout.write(body) | |
627 # end print_message | |
628 | |
629 # XXX if in public mode we need to check if the PR is public | |
630 def print_attachment(pr, attachnum): | |
631 query = "SELECT a.mimetype as mimetype, a.body as body\n" + | |
632 "FROM messages, attachments as a\n" + | |
633 "WHERE messages.pr = $1\n" + | |
634 " AND messages.id = a.msgid\n" + | |
635 " AND a.number_in_pr = $2\n" | |
636 # Note that while pr is safe, attachnum came from the | |
637 # commandline and may not be. | |
638 results = querydb(query, [pr, msgnum]) | |
639 [result] = results | |
640 (mimetype, body) = result | |
641 # XXX honor mimetype | |
642 # XXX need an http output mode so we can send the mimetype! | |
643 sys.stdout.write(body) | |
644 # end print_attachment | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
645 |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
646 ############################################################ |
47 | 647 # AST for query |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
648 |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
649 class Invocation: |
47 | 650 class Query: |
651 Q_TERM = 1 # XXX unused so far | |
652 Q_SQL = 2 | |
653 Q_AND = 3 | |
654 Q_OR = 4 | |
655 Q_TSTRING = 5 | |
656 Q_QSTRING = 6 | |
657 | |
658 def __init__(self, type): | |
659 self.type = type | |
660 def doterm(term): | |
661 self = Query(Q_TERM) | |
662 self.term = term | |
663 return self | |
664 def dosql(s): | |
665 self = Query(Q_SQL) | |
666 self.sql = s | |
667 return self | |
668 def doand(qs): | |
669 self = Query(Q_AND) | |
670 self.args = qs | |
671 return self | |
672 def door(qs): | |
673 self = Query(Q_OR) | |
674 self.args = qs | |
675 return self | |
676 # query term string | |
677 def dotstring(q): | |
678 self = Query(Q_TSTRING) | |
679 self.string = q | |
680 return self | |
681 # whole query string | |
682 def doqstring(q): | |
683 self = Query(Q_QSTRING) | |
684 self.string = q | |
685 return self | |
686 # end class Query | |
687 | |
688 class Order: | |
689 def __init__(self, field, rev = False): | |
690 self.field = field | |
691 self.rev = rev | |
692 def dooldest(ign): | |
693 return Order("number") | |
694 def donewest(ign): | |
695 return Order("number", True) | |
696 def dostaleness(ign): | |
697 return Order("modified_date", True) | |
698 def dofield(field): | |
699 return Order(field) | |
700 def dorevfield(field): | |
701 return Order(field, True) | |
702 # end class Order | |
703 | |
704 class Search: | |
705 def __init__(self, qs, openonly, publiconly, os) | |
706 self.queries = qs | |
707 self.openonly = openonly | |
708 self.publiconly = publiconly | |
709 self.orders = os | |
710 # end class Search | |
711 | |
712 class Selection: | |
713 S_PR = 1 | |
714 S_MESSAGE = 2 | |
715 S_ATTACHMENT = 3 | |
716 | |
717 def __init__(self, type): | |
718 self.type = type | |
719 def dopr(output, outformat): | |
720 self = Selection(S_PR) | |
721 self.output = output | |
722 self.outformat = outformat | |
723 return self | |
724 def domessage(arg): | |
725 self = Selection(S_MESSAGE) | |
726 self.message = arg | |
727 return self | |
728 def doattachment(arg): | |
729 self = Selection(S_ATTACHMENT) | |
730 self.attachment = arg | |
731 return self | |
732 # end class Selection | |
733 | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
734 class Op: |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
735 # operation codes |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
736 OP_FIELDS = 1 |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
737 OP_SHOW = 2 |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
738 OP_RANGE = 3 |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
739 OP_SEARCH = 4 |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
740 |
47 | 741 def __init__(self, type): |
742 self.type = type | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
743 |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
744 def dofields(): |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
745 return Op(OP_FIELDS) |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
746 def doshow(field): |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
747 self = Op(OP_SHOW) |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
748 self.field = field |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
749 return self |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
750 def dorange(field): |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
751 self = Op(OP_RANGE) |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
752 self.field = field |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
753 return self |
47 | 754 def dosearch(s, sels): |
755 self = Op(OP_SEARCH) | |
756 self.search = s | |
757 self.sels = sels | |
758 return self | |
759 # end class Op | |
760 | |
761 def __init__(self, ops): | |
762 self.ops = ops | |
763 # end class Invocation | |
764 | |
765 ############################################################ | |
766 # run (eval the SQL and print the results) | |
767 | |
768 def | |
769 | |
770 def run_sel(sel, ids): | |
771 if sel.type == S_PR: | |
772 if ids == []: | |
773 sys.stderr.write("No PRs matched.\n") | |
774 exit(1) | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
775 |
47 | 776 print_prs(ids) |
777 elif sel.type == S_MESSAGE: | |
778 if len(ids) <> 1: | |
779 sys.stderr.write("Cannot retrieve messages " + | |
780 "from multiple PRs.") | |
781 exit(1) | |
782 print_message(ids[0], sel.message) | |
783 elif sel.type == S_ATTACHMENT: | |
784 if len(ids) <> 1: | |
785 sys.stderr.write("Cannot retrieve attachments " + | |
786 "from multiple PRs.") | |
787 exit(1) | |
788 print_message(ids[0], sel.attachment) | |
789 else: | |
790 assert(False) | |
791 | |
792 def run_op(op): | |
793 if op.type == OP_FIELDS: | |
794 list_fields() | |
795 elif op.type == OP_SHOW: | |
796 describe_field(op.field) | |
797 elif op.type == OP_RANGE: | |
798 print_field_range(op.field) | |
799 elif op.type == OP_SEARCH: | |
800 sql = op.search | |
801 args = op.args # XXX not there! | |
802 ids = querydb(op.search, args) | |
803 for s in op.sels: | |
804 run_sel(s, ids) | |
805 else: | |
806 assert(False) | |
807 | |
808 def run(ast): | |
809 for op in ast.ops: | |
810 run_op(op) | |
811 | |
812 ############################################################ | |
813 # compile (convert the AST so the searches are pure SQL) | |
814 | |
815 # | |
816 # XXX this doesn't work, we need to keep the interned strings | |
817 # on return from compile_query. | |
818 # | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
819 |
47 | 820 def compile_query(q): |
821 if q.type == Q_QSTRING: | |
822 # XXX should use a split that honors quotes | |
823 terms = q.string.split() | |
824 terms = [dotstring(t) for t in terms] | |
825 return compile_query(doand(terms)) | |
826 if q.type == Q_TSTRING: | |
827 qb = QueryBuilder() | |
828 s = q.string | |
829 if s ~ "^[0-9]+$": | |
830 f = qb.getfield("number") | |
831 # Note: s is user-supplied but clean to insert directly | |
832 qb.addwhere("%s = %s" % (f, s)) | |
833 elif s ~ "^[0-9]+-[0-9]+$": | |
834 f = qb.getfield("number") | |
835 ss = s.split("-") | |
836 # Note: ss[] is user-supplied but clean | |
837 qb.addwhere("%s >= %s" % (f, ss[0])) | |
838 qb.addwhere("%s <= %s" % (f, ss[1])) | |
839 elif s ~ "^[0-9]+-$": | |
840 f = qb.getfield("number") | |
841 ss = s.split("-") | |
842 # Note: ss[] is user-supplied but clean | |
843 qb.addwhere("%s >= %s" % (f, ss[0])) | |
844 elif s ~ "^-[0-9]+$": | |
845 f = qb.getfield("number") | |
846 ss = s.split("-") | |
847 # Note: ss[] is user-supplied but clean | |
848 qb.addwhere("%s <= %s" % (f, ss[1])) | |
849 elif s ~ "^[^:]+:[^:]+$": | |
850 # XXX honor quoted terms | |
851 # XXX = or LIKE? | |
852 ss = s.split(":") | |
853 # ss[0] is not clean but if it's crap it won't match | |
854 f = qb.getfield(ss[0]) | |
855 # ss[1] is not clean, so intern it for safety | |
856 s = qb.intern(ss[1]) | |
857 qb.addwhere("%s = %s" % (f, s)) | |
858 elif s ~ "^-[^:]+:[^:]+$" | |
859 # XXX honor quoted terms | |
860 # XXX <> or NOT LIKE? | |
861 ss = s.split(":") | |
862 # ss[0] is not clean but if it's crap it won't match | |
863 f = qb.getfield(ss[0]) | |
864 # ss[1] is not clean, so intern it for safety | |
865 s = qb.intern(ss[1]) | |
866 qb.addwhere("%s <> %s" % (f, s)) | |
867 elif s ~ "^-": | |
868 # XXX <> or NOT LIKE? | |
869 f = qb.getfield("alltext") | |
870 # s is not clean, so intern it for safety | |
871 s = qb.intern(s) | |
872 qb.addwhere("%s <> %s" % (f, s)) | |
873 else: | |
874 # XXX = or LIKE? | |
875 f = qb.getfield("alltext") | |
876 # s is not clean, so intern it for safety | |
877 s = qb.intern(s) | |
878 qb.addwhere("%s = %s" % (f, s)) | |
879 | |
880 # XXX also does not handle: | |
881 # | |
882 # field: with no string (supposed to use a default | |
883 # search string) | |
884 # | |
885 # generated search fields that parse dates: | |
886 # {arrived,closed,modified,etc.}-{before,after}:date | |
887 # | |
888 # stale:time | |
889 | |
890 return qb.build("PRs.id") | |
891 # end Q_TSTRING case | |
892 if q.type == Q_OR: | |
893 subqueries = ["(" + compile_query(sq) + ")" for sq in q.args] | |
894 return " UNION ".join(subqueries) | |
895 if q.type == Q_AND: | |
896 subqueries = ["(" + compile_query(sq) + ")" for sq in q.args] | |
897 return " INTERSECT ".join(subqueries) | |
898 if q.type == Q_SQL: | |
899 return q.sql | |
900 assert(False) | |
901 # end compile_query | |
902 | |
903 def compile_order(qb, o): | |
904 str = qb.getfield(o.field) | |
905 if o.rev: | |
906 str = str + " DESCENDING" | |
907 return str | |
908 | |
909 def compile_search(s): | |
910 qb2 = QueryBuilder() | |
911 | |
912 # multiple query strings are treated as OR | |
913 query = door(s.queries) | |
914 query = compile_query(q) | |
915 | |
916 if s.openonly: | |
917 qb2.addwhere("not %s" % qb.getfield("closed")) | |
918 if s.publiconly: | |
919 qb2.addwhere("not %s" % qb.getfield("confidential")) | |
920 | |
921 orders = [compile_order(qb2, o) for o in s.orders] | |
922 order = ", ".join(orders) | |
923 if order <> "": | |
924 qb2.setorder(order) | |
925 | |
926 if qb2.nonempty(): | |
927 qb2.addjoin(query, "search") | |
928 qb2.addjoin("PRs") | |
929 qb2.addwhere("search = PRs.id") | |
930 query = qb2.build(["search"]) | |
931 | |
932 return query | |
933 # end compile_search | |
934 | |
935 def compile_op(op): | |
936 if op.type == OP_SEARCH: | |
937 op.search = compile_search(op.search) | |
938 return op | |
939 | |
940 def compile(ast): | |
941 ast.ops = [compile_op(op) for op in ast.ops] | |
942 | |
943 ############################################################ | |
944 # arg handling | |
945 | |
946 # | |
947 # I swear, all getopt interfaces suck. | |
948 # | |
949 # Provide an argparse action for constructing something out of the | |
950 # argument value and appending that somewhere, since it can't do this | |
951 # on its own. | |
952 # | |
953 # The way you use this: | |
954 # p.add_argument("--foo", action = CtorAppend, dest = 'mylist', | |
955 # const = ctor) | |
956 # where ctor is a function taking the option arg(s) and producing | |
957 # a value to append to mylist. | |
958 # | |
959 # This itself is mangy even for what it is -- it seems like we should | |
960 # be able to pass action=CtorAppend(ctor), but since it has to be a | |
961 # class that doesn't work... unless you make a new class for every | |
962 # ctor you want to use, which seems completely insane. | |
963 # | |
964 class CtorAppend(argparse.Action): | |
965 def __call__(self, parser, namespace, values, option_string=None): | |
966 items = getattr(namespace, self.dest) | |
967 item = self.const(values) | |
968 items.append(item) | |
969 setattr(namespace, self.dest, items) | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
970 |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
971 def getargs(): |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
972 p = argparse.ArgumentParser(program_description) |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
973 |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
974 # note: -h/--help is built in by default |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
975 p.add_argument("-v", "--version", |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
976 action='version', version=program_version, |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
977 help="Print program version and exit") |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
978 |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
979 p.add_argument("--show", nargs=1, |
47 | 980 action=CtorAppend, dest='ops', |
981 const=Invocation.Op.doshow | |
982 help="Show description of field") | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
983 p.add_argument("--range", nargs=1, |
47 | 984 action=CtorAppend, dest='ops', |
985 const=Invocation.Op.dorange | |
986 help="Show range of extant values for field") | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
987 |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
988 p.add_argument("--search", nargs=1, |
47 | 989 action=CtorAppend, dest='queries', |
990 const=Invocation.Query.doqstring, | |
991 help="Force string to be read as a search string") | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
992 p.add_argument("-s", "--sql", nargs=1, |
47 | 993 action=CtorAppend, dest='queries', |
994 const=Invocation.Query.dosql, | |
995 help="Supply explicit sql query as search") | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
996 |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
997 p.add_argument("--open", |
47 | 998 action='store_const', dest='openonly', const="True", |
999 help="Exclude closed PRs (default)") | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1000 p.add_argument("--closed", |
47 | 1001 action='store_const', dest='openonly', const="False", |
1002 help="Include closed PRs in search") | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1003 p.add_argument("--public", |
47 | 1004 action='store_const', dest='publiconly', const="True", |
1005 help="Exclude confidential PRs") | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1006 p.add_argument("--privileged", |
47 | 1007 action='store_const', dest='publiconly', const="False", |
1008 help="Allow confidential PRs (default)") | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1009 |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1010 p.add_argument("--oldest", |
47 | 1011 action=CtorAppend, dest='orders', |
1012 const=Invocation.Order.dooldest, | |
1013 help="Sort output with oldest PRs first") | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1014 p.add_argument("--newest", |
47 | 1015 action=CtorAppend, dest='orders', |
1016 const=Invocation.Order.donewest, | |
1017 help="Sort output with newest PRs first") | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1018 p.add_argument("--staleness", |
47 | 1019 action=CtorAppend, dest='orders', |
1020 const=Invocation.Order.dostaleness, | |
1021 help="Sort output by time since last modification") | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1022 p.add_argument("--orderby", nargs=1, |
47 | 1023 action=CtorAppend, dest='orders', |
1024 const=Invocation.Order.dofield, | |
1025 help="Sort output by specific field") | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1026 p.add_argument("--revorderby", nargs=1, |
47 | 1027 action=CtorAppend, dest='orders', |
1028 const=Invocation.Order.dorevfield, | |
1029 help="Sort output by specific field, reversed") | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1030 |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1031 p.add_argument("-m", "--message", nargs=1, |
47 | 1032 action=CtorAppend, dest='selections', |
1033 const=Invocation.Selection.domessage, | |
1034 help="Print selected message (single PR only)") | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1035 p.add_argument("-a", "--attachment", nargs=1, |
47 | 1036 action=CtorAppend, dest='selections', |
1037 const=Invocation.Selection.doattachment, | |
1038 help="Print selected attachment (single PR only)") | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1039 |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1040 p.add_argument("-r", "--raw", |
47 | 1041 action = 'store_const', const="RAW", |
1042 dest = 'output', | |
1043 help="Print exactly what the database returns") | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1044 p.add_argument("-l", "--list", |
47 | 1045 action = 'store_const', const="LIST", |
1046 dest = 'output', | |
1047 help="Print in list form (default)") | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1048 p.add_argument("--headers", |
47 | 1049 action = 'store_const', const="HEADERS", |
1050 dest = 'output', | |
1051 help="Print header information only") | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1052 p.add_argument("--meta", |
47 | 1053 action = 'store_const', const="META", |
1054 dest = 'output', | |
1055 help="Print all metadata") | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1056 p.add_argument("--metadata", |
47 | 1057 action = 'store_const', const="META", |
1058 dest = 'output') | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1059 p.add_argument("-f", "--full", |
47 | 1060 action = 'store_const', const="FULL", |
1061 dest = 'output', | |
1062 help="Print everything") | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1063 |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1064 p.add_argument("--text", |
47 | 1065 action = 'store_const', const="TEXT", |
1066 dest = 'outformat', | |
1067 help="Print in text format (default)") | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1068 p.add_argument("--csv", |
47 | 1069 action = 'store_const', const="CSV", |
1070 dest = 'outformat', | |
1071 help="Print a CSV file") | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1072 p.add_argument("--xml", |
47 | 1073 action = 'store_const', const="XML", |
1074 dest = 'outformat', | |
1075 help="Print in XML") | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1076 p.add_argument("--json", |
47 | 1077 action = 'store_const', const="JSON", |
1078 dest = 'outformat', | |
1079 help="Print in JSON") | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1080 p.add_argument("--rdf", |
47 | 1081 action = 'store_const', const="RDF", |
1082 dest = 'outbformat', | |
1083 help="Print in RDF") | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1084 p.add_argument("--rdflike", |
47 | 1085 action = 'store_const', const="RDFLIKE", |
1086 dest = 'outformat', | |
1087 help="Print RDF-like text") | |
1088 | |
1089 p.add_argument("TERM", nargs='*', | |
1090 action=CtorAppend, dest='queries', | |
1091 const=Invocation.Query.doqstring, | |
1092 help="Search term") | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1093 |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1094 args = p.parse_args() |
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1095 |
47 | 1096 ops = args.ops |
1097 if ops is None: | |
1098 ops = [] | |
1099 queries = args.queries | |
1100 if queries is not None: | |
1101 openonly = args.openonly | |
1102 if openonly is None: | |
1103 openonly = True | |
1104 publiconly = args.publiconly | |
1105 if publiconly is None: | |
1106 publiconly = False | |
1107 orders = args.orders | |
1108 if orders is None: | |
1109 orders = [Invocation.Order.dooldest(None)] | |
1110 output = args.output | |
1111 if output is None: | |
1112 output = "LIST" | |
1113 outformat = args.outformat | |
1114 if outformat is None: | |
1115 outformat = "TEXT" | |
1116 selections = args.selections | |
1117 if selections is None: | |
1118 sel = Invocation.Selection.dopr(output, outformat) | |
1119 selections = [sel] | |
1120 search = Search(queries, openonly, publiconly, orders) | |
1121 op = dosearch(search, selections) | |
1122 ops.append(op) | |
1123 # endif | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1124 |
47 | 1125 return Invocation(ops) |
33 | 1126 # end getargs |
1127 | |
1128 ############################################################ | |
1129 # main | |
1130 | |
47 | 1131 todo = getargs() |
1132 opendb() | |
1133 fetch_classifications() | |
1134 todo = compile(todo) | |
1135 run(todo) | |
1136 closedb() | |
1137 exit(0) | |
46
73e6dac29391
new stuff (checkpoint when moved between machines)
David A. Holland
parents:
33
diff
changeset
|
1138 |