comparison shelltools/query-pr/query.py @ 47:bcd1d06838fd

more better stuff
author David A. Holland
date Wed, 13 Aug 2014 03:03:30 -0400
parents 73e6dac29391
children 3d5adf5a59d0
comparison
equal deleted inserted replaced
46:73e6dac29391 47:bcd1d06838fd
12 # settings 12 # settings
13 13
14 outfile = sys.stdout 14 outfile = sys.stdout
15 outmaterial = "headers" 15 outmaterial = "headers"
16 outformat = "text" 16 outformat = "text"
17
18 ############################################################
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[]
17 224
18 ############################################################ 225 ############################################################
19 # database 226 # database
20 227
21 dblink = None 228 dblink = None
50 return result 257 return result
51 # end querydb 258 # end querydb
52 259
53 ############################################################ 260 ############################################################
54 # query class for searches 261 # query class for searches
262 # XXX: obsolete, remove
55 263
56 class Query: 264 class Query:
57 __init__(self): 265 __init__(self):
58 self.selections = [] 266 self.selections = []
59 self.tables = [] 267 self.tables = []
107 # XXX 315 # XXX
108 assert(0) 316 assert(0)
109 # end daterange_constraint 317 # end daterange_constraint
110 318
111 ############################################################ 319 ############################################################
320
321 # this is old code that needs to be merged or deleted into the new stuff
322 def oldstuff():
112 323
113 # If we're doing something other than a search, do it now 324 # If we're doing something other than a search, do it now
114 if args.attach is not None: 325 if args.attach is not None:
115 get_attachment(args.attach) 326 get_attachment(args.attach)
116 exit(0) 327 exit(0)
266 # end loop 477 # end loop
267 478
268 querytexts = [q.textify() for q in queries] 479 querytexts = [q.textify() for q in queries]
269 return "INTERSECT\n".join(querytexts) 480 return "INTERSECT\n".join(querytexts)
270 481
271
272 ############################################################ 482 ############################################################
273 # arg handling 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
645
646 ############################################################
647 # AST for query
274 648
275 class Invocation: 649 class Invocation:
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
276 class Op: 734 class Op:
277 # operation codes 735 # operation codes
278 OP_FIELDS = 1 736 OP_FIELDS = 1
279 OP_SHOW = 2 737 OP_SHOW = 2
280 OP_RANGE = 3 738 OP_RANGE = 3
281 OP_SEARCH = 4 739 OP_SEARCH = 4
282 740
283 def __init__(self, op): 741 def __init__(self, type):
284 self.op = op 742 self.type = type
285 743
286 def dofields(): 744 def dofields():
287 return Op(OP_FIELDS) 745 return Op(OP_FIELDS)
288 def doshow(field): 746 def doshow(field):
289 self = Op(OP_SHOW) 747 self = Op(OP_SHOW)
291 return self 749 return self
292 def dorange(field): 750 def dorange(field):
293 self = Op(OP_RANGE) 751 self = Op(OP_RANGE)
294 self.field = field 752 self.field = field
295 return self 753 return self
296 def doquery( 754 def dosearch(s, sels):
297 755 self = Op(OP_SEARCH)
298 756 self.search = s
299 # output formats 757 self.sels = sels
300 FMT_TXT = 1 758 return self
301 759 # end class Op
302 def __init__(self): 760
303 self.op = OP_SEARCH 761 def __init__(self, ops):
304 self.searchstrings = [] 762 self.ops = ops
305 self.searchsql = [] 763 # end class Invocation
306 764
307 # end Invocation 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)
775
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 #
819
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)
308 970
309 def getargs(): 971 def getargs():
310 p = argparse.ArgumentParser(program_description) 972 p = argparse.ArgumentParser(program_description)
311 973
312 # note: -h/--help is built in by default 974 # note: -h/--help is built in by default
313 p.add_argument("-v", "--version", 975 p.add_argument("-v", "--version",
314 action='version', version=program_version, 976 action='version', version=program_version,
315 help="Print program version and exit") 977 help="Print program version and exit")
316 978
317 p.add_argument("--show", nargs=1, 979 p.add_argument("--show", nargs=1,
318 action = 'store', dest='show', 980 action=CtorAppend, dest='ops',
319 help="Show description of field") 981 const=Invocation.Op.doshow
982 help="Show description of field")
320 p.add_argument("--range", nargs=1, 983 p.add_argument("--range", nargs=1,
321 action = 'store', dest='range', 984 action=CtorAppend, dest='ops',
322 help="Show range of extant values for field") 985 const=Invocation.Op.dorange
986 help="Show range of extant values for field")
323 987
324 p.add_argument("--search", nargs=1, 988 p.add_argument("--search", nargs=1,
325 action = 'store', dest='search', 989 action=CtorAppend, dest='queries',
326 help="Force string to be read as a search string") 990 const=Invocation.Query.doqstring,
991 help="Force string to be read as a search string")
327 p.add_argument("-s", "--sql", nargs=1, 992 p.add_argument("-s", "--sql", nargs=1,
328 action = 'store', dest='sql', 993 action=CtorAppend, dest='queries',
329 help="Supply explicit sql query as search") 994 const=Invocation.Query.dosql,
995 help="Supply explicit sql query as search")
330 996
331 p.add_argument("--open", 997 p.add_argument("--open",
332 action = 'store_const', const="True", 998 action='store_const', dest='openonly', const="True",
333 dest = 'openonly', 999 help="Exclude closed PRs (default)")
334 help="Exclude closed PRs (default)")
335 p.add_argument("--closed", 1000 p.add_argument("--closed",
336 action = 'store_const', const="False", 1001 action='store_const', dest='openonly', const="False",
337 dest = 'openonly', 1002 help="Include closed PRs in search")
338 help="Include closed PRs in search")
339 p.add_argument("--public", 1003 p.add_argument("--public",
340 action = 'store_const', const="True", 1004 action='store_const', dest='publiconly', const="True",
341 dest = 'publiconly', 1005 help="Exclude confidential PRs")
342 help="Exclude confidential PRs")
343 p.add_argument("--privileged", 1006 p.add_argument("--privileged",
344 action = 'store_const', const="False", 1007 action='store_const', dest='publiconly', const="False",
345 dest = 'publiconly', 1008 help="Allow confidential PRs (default)")
346 help="Allow confidential PRs (default)")
347 1009
348 p.add_argument("--oldest", 1010 p.add_argument("--oldest",
349 action = 'store_const', const="OLDEST", 1011 action=CtorAppend, dest='orders',
350 dest = 'order', 1012 const=Invocation.Order.dooldest,
351 help="Sort output with oldest PRs first") 1013 help="Sort output with oldest PRs first")
352 p.add_argument("--newest", 1014 p.add_argument("--newest",
353 action = 'store_const', const="NEWEST", 1015 action=CtorAppend, dest='orders',
354 dest = 'order', 1016 const=Invocation.Order.donewest,
355 help="Sort output with newest PRs first") 1017 help="Sort output with newest PRs first")
356 p.add_argument("--staleness", 1018 p.add_argument("--staleness",
357 action = 'store_const', const="STALENESS", 1019 action=CtorAppend, dest='orders',
358 dest = 'order', 1020 const=Invocation.Order.dostaleness,
359 help="Sort output by time since last modification") 1021 help="Sort output by time since last modification")
360 p.add_argument("--orderby", nargs=1, 1022 p.add_argument("--orderby", nargs=1,
361 action = 'store', dest = 'orderfield', 1023 action=CtorAppend, dest='orders',
362 help="Sort output by specific field") 1024 const=Invocation.Order.dofield,
1025 help="Sort output by specific field")
363 p.add_argument("--revorderby", nargs=1, 1026 p.add_argument("--revorderby", nargs=1,
364 action = 'store', dest = 'revorderfield', 1027 action=CtorAppend, dest='orders',
365 help="Sort output by specific field, reversed") 1028 const=Invocation.Order.dorevfield,
1029 help="Sort output by specific field, reversed")
366 1030
367 p.add_argument("-m", "--message", nargs=1, 1031 p.add_argument("-m", "--message", nargs=1,
368 action = 'store', dest = 'message', 1032 action=CtorAppend, dest='selections',
369 help="Print selected message (single PR only)") 1033 const=Invocation.Selection.domessage,
1034 help="Print selected message (single PR only)")
370 p.add_argument("-a", "--attachment", nargs=1, 1035 p.add_argument("-a", "--attachment", nargs=1,
371 action = 'store', dest = 'attachment', 1036 action=CtorAppend, dest='selections',
372 help="Print selected attachment (single PR only)") 1037 const=Invocation.Selection.doattachment,
1038 help="Print selected attachment (single PR only)")
373 1039
374 p.add_argument("-r", "--raw", 1040 p.add_argument("-r", "--raw",
375 action = 'store_const', const="RAW", 1041 action = 'store_const', const="RAW",
376 dest = 'output', 1042 dest = 'output',
377 help="Print exactly what the database returns") 1043 help="Print exactly what the database returns")
378 p.add_argument("-l", "--list", 1044 p.add_argument("-l", "--list",
379 action = 'store_const', const="LIST", 1045 action = 'store_const', const="LIST",
380 dest = 'output', 1046 dest = 'output',
381 help="Print in list form (default)") 1047 help="Print in list form (default)")
382 p.add_argument("--headers", 1048 p.add_argument("--headers",
383 action = 'store_const', const="HEADERS", 1049 action = 'store_const', const="HEADERS",
384 dest = 'output', 1050 dest = 'output',
385 help="Print header information only") 1051 help="Print header information only")
386 p.add_argument("--meta", 1052 p.add_argument("--meta",
387 action = 'store_const', const="META", 1053 action = 'store_const', const="META",
388 dest = 'output', 1054 dest = 'output',
389 help="Print all metadata") 1055 help="Print all metadata")
390 p.add_argument("--metadata", 1056 p.add_argument("--metadata",
391 action = 'store_const', const="META", 1057 action = 'store_const', const="META",
392 dest = 'output') 1058 dest = 'output')
393 p.add_argument("-f", "--full", 1059 p.add_argument("-f", "--full",
394 action = 'store_const', const="FULL", 1060 action = 'store_const', const="FULL",
395 dest = 'output', 1061 dest = 'output',
396 help="Print everything") 1062 help="Print everything")
397 1063
398 p.add_argument("--text", 1064 p.add_argument("--text",
399 action = 'store_const', const="TEXT", 1065 action = 'store_const', const="TEXT",
400 dest = 'outformat', 1066 dest = 'outformat',
401 help="Print in text format (default)") 1067 help="Print in text format (default)")
402 p.add_argument("--csv", 1068 p.add_argument("--csv",
403 action = 'store_const', const="CSV", 1069 action = 'store_const', const="CSV",
404 dest = 'outformat', 1070 dest = 'outformat',
405 help="Print a CSV file") 1071 help="Print a CSV file")
406 p.add_argument("--xml", 1072 p.add_argument("--xml",
407 action = 'store_const', const="XML", 1073 action = 'store_const', const="XML",
408 dest = 'outformat', 1074 dest = 'outformat',
409 help="Print in XML") 1075 help="Print in XML")
410 p.add_argument("--json", 1076 p.add_argument("--json",
411 action = 'store_const', const="JSON", 1077 action = 'store_const', const="JSON",
412 dest = 'outformat', 1078 dest = 'outformat',
413 help="Print in JSON") 1079 help="Print in JSON")
414 p.add_argument("--rdf", 1080 p.add_argument("--rdf",
415 action = 'store_const', const="RDF", 1081 action = 'store_const', const="RDF",
416 dest = 'outbformat', 1082 dest = 'outbformat',
417 help="Print in RDF") 1083 help="Print in RDF")
418 p.add_argument("--rdflike", 1084 p.add_argument("--rdflike",
419 action = 'store_const', const="RDFLIKE", 1085 action = 'store_const', const="RDFLIKE",
420 dest = 'outformat', 1086 dest = 'outformat',
421 help="Print RDF-like text") 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")
422 1093
423 args = p.parse_args() 1094 args = p.parse_args()
424 1095
425 p = Invocation() 1096 ops = args.ops
426 1097 if ops is None:
427 if args.show is not None: 1098 ops = []
428 do_showfield(args.show) 1099 queries = args.queries
429 exit(0) 1100 if queries is not None:
430 if args.range is not None: 1101 openonly = args.openonly
431 do_fieldrange(args.range) 1102 if openonly is None:
432 exit(0) 1103 openonly = True
433 1104 publiconly = args.publiconly
434 searchstring = args.search 1105 if publiconly is None:
435 explicitsql = args.sql 1106 publiconly = False
436 1107 orders = args.orders
437 openonly = args.openonly 1108 if orders is None:
438 if openonly is None: 1109 orders = [Invocation.Order.dooldest(None)]
439 openonly = True 1110 output = args.output
440 publiconly = args.publiconly 1111 if output is None:
441 if publiconly is None: 1112 output = "LIST"
442 publiconly = False 1113 outformat = args.outformat
443 1114 if outformat is None:
444 if args.orderfield is not None: 1115 outformat = "TEXT"
445 orderby = args.orderfield 1116 selections = args.selections
446 orderrev = False 1117 if selections is None:
447 elif args.revorderfield is not None: 1118 sel = Invocation.Selection.dopr(output, outformat)
448 orderby = args.revorderfield 1119 selections = [sel]
449 orderrev = True 1120 search = Search(queries, openonly, publiconly, orders)
450 elif args.order == "OLDEST": 1121 op = dosearch(search, selections)
451 orderby = "number" 1122 ops.append(op)
452 orderrev = False 1123 # endif
453 elif args.order == "NEWEST": 1124
454 orderby = "number" 1125 return Invocation(ops)
455 orderrev = True
456 elif args.order == "STALENESS":
457 orderby = "last-modified"
458 orderrev = True
459 else:
460 orderby = "number"
461 orderrev = False
462
463 if args.message is not None:
464 printwhat = "MESSAGE"
465 printwhich = args.message
466 elif args.attachment is not None:
467 printwhat = "ATTACHMENT"
468 printwhich = args.attachment
469 else:
470 printwhat = "PR"
471 printwhich = None
472
473 output = args.output
474 if output is None:
475 output = "LIST"
476
477 outformat = args.outformat
478 if outformat is None:
479 outformat = "TEXT"
480
481 query = buildquery(searchstring, explicitsql,
482 orderby=orderby, orderrev=orderrev,
483 openonly=openonly, publiconly=publiconly)
484 if printwhat == "PR":
485 printer = buildprinter(output, outformat)
486 else if printwhat == "MESSAGE":
487 printer = getmessage(printwhich)
488 else if printwhat == "ATTACHMENT":
489 printer = getattachment(printwhich)
490
491 return (query, printer)
492 # end getargs 1126 # end getargs
493 1127
494 ############################################################ 1128 ############################################################
495 # main 1129 # main
496 1130
497 def main(): 1131 todo = getargs()
498 1132 opendb()
499 opendb() 1133 fetch_classifications()
500 (classification_schemes, classification_schemetypes) = getclassify() 1134 todo = compile(todo)
501 query = getargs(classification_schemes, classification_schemetypes) 1135 run(todo)
502 ids = querydb(query) 1136 closedb()
503 if len(ids) > 0: 1137 exit(0)
504 show_prs(ids) 1138
505 else:
506 sys.stderr.write("No PRs matched.\n")
507 exit(1)
508 closedb()
509 return 0
510 # end main
511
512 # real python hackers doubtless laugh at this
513 exit(main())