]> git.decadent.org.uk Git - dak.git/blob - lisa
Fix editing of indices >= 10.
[dak.git] / lisa
1 #!/usr/bin/env python
2
3 # Handles NEW and BYHAND packages
4 # Copyright (C) 2001, 2002, 2003  James Troup <james@nocrew.org>
5 # $Id: lisa,v 1.21 2003-02-11 19:39:28 troup Exp $
6
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
11
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20
21 ################################################################################
22
23 # 23:12|<aj> I will not hush!
24 # 23:12|<elmo> :>
25 # 23:12|<aj> Where there is injustice in the world, I shall be there!
26 # 23:13|<aj> I shall not be silenced!
27 # 23:13|<aj> The world shall know!
28 # 23:13|<aj> The world *must* know!
29 # 23:13|<elmo> oh dear, he's gone back to powerpuff girls... ;-)
30 # 23:13|<aj> yay powerpuff girls!!
31 # 23:13|<aj> buttercup's my favourite, who's yours?
32 # 23:14|<aj> you're backing away from the keyboard right now aren't you?
33 # 23:14|<aj> *AREN'T YOU*?!
34 # 23:15|<aj> I will not be treated like this.
35 # 23:15|<aj> I shall have my revenge.
36 # 23:15|<aj> I SHALL!!!
37
38 ################################################################################
39
40 import copy, errno, os, readline, stat, sys, tempfile;
41 import apt_pkg, apt_inst;
42 import db_access, fernanda, katie, logging, utils;
43
44 # Globals
45 lisa_version = "$Revision: 1.21 $";
46
47 Cnf = None;
48 Options = None;
49 Katie = None;
50 projectB = None;
51 Logger = None;
52
53 Priorities = None;
54 Sections = None;
55
56 reject_message = "";
57
58 ################################################################################
59 ################################################################################
60 ################################################################################
61
62 def reject (str, prefix="Rejected: "):
63     global reject_message;
64     if str:
65         reject_message += prefix + str + "\n";
66
67 def recheck():
68     global reject_message;
69     files = Katie.pkg.files;
70     reject_message = "";
71
72     for file in files.keys():
73         # Check that the source still exists
74         if files[file]["type"] == "deb":
75             source_version = files[file]["source version"];
76             source_package = files[file]["source package"];
77             if not Katie.pkg.changes["architecture"].has_key("source") \
78                and not Katie.source_exists(source_package, source_version):
79                 source_epochless_version = utils.re_no_epoch.sub('', source_version);
80                 dsc_filename = "%s_%s.dsc" % (source_package, source_epochless_version);
81                 if not os.path.exists(Cnf["Dir::Queue::Accepted"] + '/' + dsc_filename):
82                     reject("no source found for %s %s (%s)." % (source_package, source_version, file));
83
84         # Version and file overwrite checks
85         if files[file]["type"] == "deb":
86             reject(Katie.check_binary_against_db(file));
87         elif files[file]["type"] == "dsc":
88             reject(Katie.check_source_against_db(file));
89             (reject_msg, is_in_incoming) = Katie.check_dsc_against_db(file);
90             reject(reject_msg);
91
92     if reject_message:
93         answer = "XXX";
94         if Options["No-Action"] or Options["Automatic"]:
95             answer = 'S'
96
97         print "REJECT\n" + reject_message,;
98         prompt = "[R]eject, Skip, Quit ?";
99
100         while prompt.find(answer) == -1:
101             answer = utils.our_raw_input(prompt);
102             m = katie.re_default_answer.match(prompt);
103             if answer == "":
104                 answer = m.group(1);
105             answer = answer[:1].upper();
106
107         if answer == 'R':
108             Katie.do_reject(0, reject_message);
109             os.unlink(Katie.pkg.changes_file[:-8]+".katie");
110             return 0;
111         elif answer == 'S':
112             return 0;
113         elif answer == 'Q':
114             sys.exit(0);
115
116     return 1;
117
118 ################################################################################
119
120 def determine_new (changes, files):
121     new = {};
122
123     # Build up a list of potentially new things
124     for file in files.keys():
125         f = files[file];
126         # Skip byhand elements
127         if f["type"] == "byhand":
128             continue;
129         pkg = f["package"];
130         priority = f["priority"];
131         section = f["section"];
132         # FIXME: unhardcode
133         if section == "non-US/main":
134             section = "non-US";
135         type = get_type(f);
136         component = f["component"];
137
138         if type == "dsc":
139             priority = "source";
140         if not new.has_key(pkg):
141             new[pkg] = {};
142             new[pkg]["priority"] = priority;
143             new[pkg]["section"] = section;
144             new[pkg]["type"] = type;
145             new[pkg]["component"] = component;
146             new[pkg]["files"] = [];
147         else:
148             old_type = new[pkg]["type"];
149             if old_type != type:
150                 # source gets trumped by deb or udeb
151                 if old_type == "dsc":
152                     new[pkg]["priority"] = priority;
153                     new[pkg]["section"] = section;
154                     new[pkg]["type"] = type;
155                     new[pkg]["component"] = component;
156         new[pkg]["files"].append(file);
157         if f.has_key("othercomponents"):
158             new[pkg]["othercomponents"] = f["othercomponents"];
159
160     for suite in changes["suite"].keys():
161         suite_id = db_access.get_suite_id(suite);
162         for pkg in new.keys():
163             component_id = db_access.get_component_id(new[pkg]["component"]);
164             type_id = db_access.get_override_type_id(new[pkg]["type"]);
165             q = projectB.query("SELECT package FROM override WHERE package = '%s' AND suite = %s AND component = %s AND type = %s" % (pkg, suite_id, component_id, type_id));
166             ql = q.getresult();
167             if ql:
168                 for file in new[pkg]["files"]:
169                     if files[file].has_key("new"):
170                         del files[file]["new"];
171                 del new[pkg];
172
173     if changes["suite"].has_key("stable"):
174         print "WARNING: overrides will be added for stable!";
175     for pkg in new.keys():
176         if new[pkg].has_key("othercomponents"):
177             print "WARNING: %s already present in %s distribution." % (pkg, new[pkg]["othercomponents"]);
178
179     return new;
180
181 ################################################################################
182
183 def indiv_sg_compare (a, b):
184     """Sort by source name, source, version, 'have source', and
185        finally by filename."""
186     # Sort by source version
187     q = apt_pkg.VersionCompare(a["version"], b["version"]);
188     if q:
189         return -q;
190
191     # Sort by 'have source'
192     a_has_source = a["architecture"].get("source");
193     b_has_source = b["architecture"].get("source");
194     if a_has_source and not b_has_source:
195         return -1;
196     elif b_has_source and not a_has_source:
197         return 1;
198
199     return cmp(a["filename"], b["filename"]);
200
201 ############################################################
202
203 def sg_compare (a, b):
204     a = a[1];
205     b = b[1];
206     """Sort by have note, time of oldest upload."""
207     # Sort by have note
208     a_note_state = a["note_state"];
209     b_note_state = b["note_state"];
210     if a_note_state < b_note_state:
211         return -1;
212     elif a_note_state > b_note_state:
213         return 1;
214
215     # Sort by time of oldest upload
216     return cmp(a["oldest"], b["oldest"]);
217
218 def sort_changes(changes_files):
219     """Sort into source groups, then sort each source group by version,
220     have source, filename.  Finally, sort the source groups by have
221     note, time of oldest upload of each source upload."""
222     if len(changes_files) == 1:
223         return changes_files;
224
225     sorted_list = [];
226     cache = {};
227     # Read in all the .changes files
228     for filename in changes_files:
229         try:
230             Katie.pkg.changes_file = filename;
231             Katie.init_vars();
232             Katie.update_vars();
233             cache[filename] = copy.copy(Katie.pkg.changes);
234             cache[filename]["filename"] = filename;
235         except:
236             sorted_list.append(filename);
237             break;
238     # Divide the .changes into per-source groups
239     per_source = {};
240     for filename in cache.keys():
241         source = cache[filename]["source"];
242         if not per_source.has_key(source):
243             per_source[source] = {};
244             per_source[source]["list"] = [];
245         per_source[source]["list"].append(cache[filename]);
246     # Determine oldest time and have note status for each source group
247     for source in per_source.keys():
248         source_list = per_source[source]["list"];
249         first = source_list[0];
250         oldest = os.stat(first["filename"])[stat.ST_CTIME];
251         have_note = 0;
252         for d in per_source[source]["list"]:
253             ctime = os.stat(d["filename"])[stat.ST_CTIME];
254             if ctime < oldest:
255                 oldest = ctime;
256             have_note += (d.has_key("lisa note"));
257         per_source[source]["oldest"] = oldest;
258         if not have_note:
259             per_source[source]["note_state"] = 0; # none
260         elif have_note < len(source_list):
261             per_source[source]["note_state"] = 1; # some
262         else:
263             per_source[source]["note_state"] = 2; # all
264         per_source[source]["list"].sort(indiv_sg_compare);
265     per_source_items = per_source.items();
266     per_source_items.sort(sg_compare);
267     for i in per_source_items:
268         for j in i[1]["list"]:
269             sorted_list.append(j["filename"]);
270     return sorted_list;
271
272 ################################################################################
273
274 class Section_Completer:
275     def __init__ (self):
276         self.sections = [];
277         q = projectB.query("SELECT section FROM section");
278         for i in q.getresult():
279             self.sections.append(i[0]);
280
281     def complete(self, text, state):
282         if state == 0:
283             self.matches = [];
284             n = len(text);
285             for word in self.sections:
286                 if word[:n] == text:
287                     self.matches.append(word);
288         try:
289             return self.matches[state]
290         except IndexError:
291             return None
292
293 ############################################################
294
295 class Priority_Completer:
296     def __init__ (self):
297         self.priorities = [];
298         q = projectB.query("SELECT priority FROM priority");
299         for i in q.getresult():
300             self.priorities.append(i[0]);
301
302     def complete(self, text, state):
303         if state == 0:
304             self.matches = [];
305             n = len(text);
306             for word in self.priorities:
307                 if word[:n] == text:
308                     self.matches.append(word);
309         try:
310             return self.matches[state]
311         except IndexError:
312             return None
313
314 ################################################################################
315
316 def check_valid (new):
317     for pkg in new.keys():
318         section = new[pkg]["section"];
319         priority = new[pkg]["priority"];
320         type = new[pkg]["type"];
321         new[pkg]["section id"] = db_access.get_section_id(section);
322         new[pkg]["priority id"] = db_access.get_priority_id(new[pkg]["priority"]);
323         # Sanity checks
324         if (section == "debian-installer" and type != "udeb") or \
325            (section != "debian-installer" and type == "udeb"):
326             new[pkg]["section id"] = -1;
327         if (priority == "source" and type != "dsc") or \
328            (priority != "source" and type == "dsc"):
329             new[pkg]["priority id"] = -1;
330
331 ################################################################################
332
333 def print_new (new, indexed, file=sys.stdout):
334     check_valid(new);
335     broken = 0;
336     index = 0;
337     for pkg in new.keys():
338         index += 1;
339         section = new[pkg]["section"];
340         priority = new[pkg]["priority"];
341         if new[pkg]["section id"] == -1:
342             section += "[!]";
343             broken = 1;
344         if new[pkg]["priority id"] == -1:
345             priority += "[!]";
346             broken = 1;
347         if indexed:
348             line = "(%s): %-20s %-20s %-20s" % (index, pkg, priority, section);
349         else:
350             line = "%-20s %-20s %-20s" % (pkg, priority, section);
351         line = line.strip()+'\n';
352         file.write(line);
353     note = Katie.pkg.changes.get("lisa note");
354     if note:
355         print "*"*75;
356         print note;
357         print "*"*75;
358     return broken, note;
359
360 ################################################################################
361
362 def get_type (f):
363     # Determine the type
364     if f.has_key("dbtype"):
365         type = f["dbtype"];
366     elif f["type"] == "orig.tar.gz" or f["type"] == "tar.gz" or f["type"] == "diff.gz" or f["type"] == "dsc":
367         type = "dsc";
368     else:
369         utils.fubar("invalid type (%s) for new.  Dazed, confused and sure as heck not continuing." % (type));
370
371     # Validate the override type
372     type_id = db_access.get_override_type_id(type);
373     if type_id == -1:
374         utils.fubar("invalid type (%s) for new.  Say wha?" % (type));
375
376     return type;
377
378 ################################################################################
379
380 def index_range (index):
381     if index == 1:
382         return "1";
383     else:
384         return "1-%s" % (index);
385
386 ################################################################################
387 ################################################################################
388
389 def edit_new (new):
390     # Write the current data to a temporary file
391     temp_filename = tempfile.mktemp();
392     fd = os.open(temp_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0700);
393     os.close(fd);
394     temp_file = utils.open_file(temp_filename, 'w');
395     print_new (new, 0, temp_file);
396     temp_file.close();
397     # Spawn an editor on that file
398     editor = os.environ.get("EDITOR","vi")
399     result = os.system("%s %s" % (editor, temp_filename))
400     if result != 0:
401         utils.fubar ("%s invocation failed for %s." % (editor, temp_filename), result)
402     # Read the edited data back in
403     temp_file = utils.open_file(temp_filename);
404     lines = temp_file.readlines();
405     temp_file.close();
406     os.unlink(temp_filename);
407     # Parse the new data
408     for line in lines:
409         line = line.strip();
410         if line == "":
411             continue;
412         s = line.split();
413         # Pad the list if necessary
414         s[len(s):3] = [None] * (3-len(s));
415         (pkg, priority, section) = s[:3];
416         if not new.has_key(pkg):
417             utils.warn("Ignoring unknown package '%s'" % (pkg));
418         else:
419             # Strip off any invalid markers, print_new will readd them.
420             if section.endswith("[!]"):
421                 section = section[:-3];
422             if priority.endswith("[!]"):
423                 priority = priority[:-3];
424             for file in new[pkg]["files"]:
425                 Katie.pkg.files[file]["section"] = section;
426                 Katie.pkg.files[file]["priority"] = priority;
427             new[pkg]["section"] = section;
428             new[pkg]["priority"] = priority;
429
430 ################################################################################
431
432 def edit_index (new, index):
433     priority = new[index]["priority"]
434     section = new[index]["section"]
435     type = new[index]["type"];
436     done = 0
437     while not done:
438         print "\t".join([index, priority, section]);
439
440         answer = "XXX";
441         if type != "dsc":
442             prompt = "[B]oth, Priority, Section, Done ? ";
443         else:
444             prompt = "[S]ection, Done ? ";
445         edit_priority = edit_section = 0;
446
447         while prompt.find(answer) == -1:
448             answer = utils.our_raw_input(prompt);
449             m = katie.re_default_answer.match(prompt)
450             if answer == "":
451                 answer = m.group(1)
452             answer = answer[:1].upper()
453
454         if answer == 'P':
455             edit_priority = 1;
456         elif answer == 'S':
457             edit_section = 1;
458         elif answer == 'B':
459             edit_priority = edit_section = 1;
460         elif answer == 'D':
461             done = 1;
462
463         # Edit the priority
464         if edit_priority:
465             readline.set_completer(Priorities.complete);
466             got_priority = 0;
467             while not got_priority:
468                 new_priority = utils.our_raw_input("New priority: ").strip();
469                 if Priorities.priorities.count(new_priority) == 0:
470                     print "E: '%s' is not a valid priority, try again." % (new_priority);
471                 else:
472                     got_priority = 1;
473                     priority = new_priority;
474
475         # Edit the section
476         if edit_section:
477             readline.set_completer(Sections.complete);
478             got_section = 0;
479             while not got_section:
480                 new_section = utils.our_raw_input("New section: ").strip();
481                 if Sections.sections.count(new_section) == 0:
482                     print "E: '%s' is not a valid section, try again." % (new_section);
483                 else:
484                     got_section = 1;
485                     section = new_section;
486
487         # Reset the readline completer
488         readline.set_completer(None);
489
490     for file in new[index]["files"]:
491         Katie.pkg.files[file]["section"] = section;
492         Katie.pkg.files[file]["priority"] = priority;
493     new[index]["priority"] = priority;
494     new[index]["section"] = section;
495     return new;
496
497 ################################################################################
498
499 def edit_overrides (new):
500     print;
501     done = 0
502     while not done:
503         print_new (new, 1);
504         new_index = {};
505         index = 0;
506         for i in new.keys():
507             index += 1;
508             new_index[index] = i;
509
510         prompt = "(%s) edit override <n>, Editor, Done ? " % (index_range(index));
511
512         got_answer = 0
513         while not got_answer:
514             answer = utils.our_raw_input(prompt);
515             if not utils.str_isnum(answer):
516                 answer = answer[:1].upper();
517             if answer == "E" or answer == "D":
518                 got_answer = 1;
519             elif katie.re_isanum.match (answer):
520                 answer = int(answer);
521                 if (answer < 1) or (answer > index):
522                     print "%s is not a valid index (%s).  Please retry." % (answer, index_range(index));
523                 else:
524                     got_answer = 1;
525
526         if answer == 'E':
527             edit_new(new);
528         elif answer == 'D':
529             done = 1;
530         else:
531             edit_index (new, new_index[answer]);
532
533     return new;
534
535 ################################################################################
536
537 def edit_note(note):
538     # Write the current data to a temporary file
539     temp_filename = tempfile.mktemp();
540     fd = os.open(temp_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0700);
541     os.close(fd);
542     temp_file = utils.open_file(temp_filename, 'w');
543     temp_file.write(note);
544     temp_file.close();
545     editor = os.environ.get("EDITOR","vi")
546     answer = 'E';
547     while answer == 'E':
548         os.system("%s %s" % (editor, temp_filename))
549         temp_file = utils.open_file(temp_filename);
550         note = temp_file.read().rstrip();
551         temp_file.close();
552         print "Note:";
553         print utils.prefix_multi_line_string(note,"  ");
554         prompt = "[D]one, Edit, Abandon, Quit ?"
555         answer = "XXX";
556         while prompt.find(answer) == -1:
557             answer = utils.our_raw_input(prompt);
558             m = katie.re_default_answer.search(prompt);
559             if answer == "":
560                 answer = m.group(1);
561             answer = answer[:1].upper();
562     os.unlink(temp_filename);
563     if answer == 'A':
564         return;
565     elif answer == 'Q':
566         sys.exit(0);
567     Katie.pkg.changes["lisa note"] = note;
568     Katie.dump_vars(Cnf["Dir::Queue::New"]);
569
570 ################################################################################
571
572 def check_pkg ():
573     try:
574         less_fd = os.popen("less -R -", 'w', 0);
575         stdout_fd = sys.stdout;
576         try:
577             sys.stdout = less_fd;
578             fernanda.display_changes(Katie.pkg.changes_file);
579             files = Katie.pkg.files;
580             for file in files.keys():
581                 if files[file].has_key("new"):
582                     type = files[file]["type"];
583                     if type == "deb":
584                         fernanda.check_deb(file);
585                     elif type == "dsc":
586                         fernanda.check_dsc(file);
587         finally:
588             sys.stdout = stdout_fd;
589     except IOError, e:
590         if errno.errorcode[e.errno] == 'EPIPE':
591             utils.warn("[fernanda] Caught EPIPE; skipping.");
592             pass;
593         else:
594             raise;
595     except KeyboardInterrupt:
596         utils.warn("[fernanda] Caught C-c; skipping.");
597         pass;
598
599 ################################################################################
600
601 ## FIXME: horribly Debian specific
602
603 def do_bxa_notification():
604     files = Katie.pkg.files;
605     summary = "";
606     for file in files.keys():
607         if files[file]["type"] == "deb":
608             control = apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file)));
609             summary += "\n";
610             summary += "Package: %s\n" % (control.Find("Package"));
611             summary += "Description: %s\n" % (control.Find("Description"));
612     Katie.Subst["__BINARY_DESCRIPTIONS__"] = summary;
613     bxa_mail = utils.TemplateSubst(Katie.Subst,Cnf["Dir::Templates"]+"/lisa.bxa_notification");
614     utils.send_mail(bxa_mail,"");
615
616 ################################################################################
617
618 def add_overrides (new):
619     changes = Katie.pkg.changes;
620     files = Katie.pkg.files;
621
622     projectB.query("BEGIN WORK");
623     for suite in changes["suite"].keys():
624         suite_id = db_access.get_suite_id(suite);
625         for pkg in new.keys():
626             component_id = db_access.get_component_id(new[pkg]["component"]);
627             type_id = db_access.get_override_type_id(new[pkg]["type"]);
628             priority_id = new[pkg]["priority id"];
629             section_id = new[pkg]["section id"];
630             projectB.query("INSERT INTO override (suite, component, type, package, priority, section) VALUES (%s, %s, %s, '%s', %s, %s)" % (suite_id, component_id, type_id, pkg, priority_id, section_id));
631             for file in new[pkg]["files"]:
632                 if files[file].has_key("new"):
633                     del files[file]["new"];
634             del new[pkg];
635
636     projectB.query("COMMIT WORK");
637
638     if Cnf.FindB("Dinstall::BXANotify"):
639         do_bxa_notification();
640
641 ################################################################################
642
643 def do_new():
644     print "NEW\n";
645     files = Katie.pkg.files;
646     changes = Katie.pkg.changes;
647
648     # Make a copy of distribution we can happily trample on
649     changes["suite"] = copy.copy(changes["distribution"]);
650
651     # Fix up the list of target suites
652     for suite in changes["suite"].keys():
653         override = Cnf.Find("Suite::%s::OverrideSuite" % (suite));
654         if override:
655             del changes["suite"][suite];
656             changes["suite"][override] = 1;
657     # Validate suites
658     for suite in changes["suite"].keys():
659         suite_id = db_access.get_suite_id(suite);
660         if suite_id == -1:
661             utils.fubar("%s has invalid suite '%s' (possibly overriden).  say wha?" % (changes, suite));
662
663     # The main NEW processing loop
664     done = 0;
665     while not done:
666         # Find out what's new
667         new = determine_new(changes, files);
668
669         if not new:
670             break;
671
672         answer = "XXX";
673         if Options["No-Action"] or Options["Automatic"]:
674             answer = 'S';
675
676         (broken, note) = print_new(new, 0);
677         prompt = "";
678
679         if not broken and not note:
680             prompt = "Add overrides, ";
681         if broken:
682             print "W: [!] marked entries must be fixed before package can be processed.";
683         if note:
684             print "W: note must be removed before package can be processed.";
685             prompt += "Remove note, ";
686
687         prompt += "Edit overrides, Check, Manual reject, Note edit, [S]kip, Quit ?";
688
689         while prompt.find(answer) == -1:
690             answer = utils.our_raw_input(prompt);
691             m = katie.re_default_answer.search(prompt);
692             if answer == "":
693                 answer = m.group(1)
694             answer = answer[:1].upper()
695
696         if answer == 'A':
697             done = add_overrides (new);
698         elif answer == 'C':
699             check_pkg();
700         elif answer == 'E':
701             new = edit_overrides (new);
702         elif answer == 'M':
703             aborted = Katie.do_reject(1, Options["Manual-Reject"]);
704             if not aborted:
705                 os.unlink(Katie.pkg.changes_file[:-8]+".katie");
706                 done = 1;
707         elif answer == 'N':
708             edit_note(changes.get("lisa note", ""));
709         elif answer == 'R':
710             confirm = utils.our_raw_input("Really clear note (y/N)? ").lower();
711             if confirm == "y":
712                 del changes["lisa note"];
713         elif answer == 'S':
714             done = 1;
715         elif answer == 'Q':
716             sys.exit(0)
717
718 ################################################################################
719 ################################################################################
720 ################################################################################
721
722 def usage (exit_code=0):
723     print """Usage: lisa [OPTION]... [CHANGES]...
724   -a, --automatic           automatic run
725   -h, --help                show this help and exit.
726   -m, --manual-reject=MSG   manual reject with `msg'
727   -n, --no-action           don't do anything
728   -V, --version             display the version number and exit"""
729     sys.exit(exit_code)
730
731 ################################################################################
732
733 def init():
734     global Cnf, Options, Logger, Katie, projectB, Sections, Priorities;
735
736     Cnf = utils.get_conf();
737
738     Arguments = [('a',"automatic","Lisa::Options::Automatic"),
739                  ('h',"help","Lisa::Options::Help"),
740                  ('m',"manual-reject","Lisa::Options::Manual-Reject", "HasArg"),
741                  ('n',"no-action","Lisa::Options::No-Action"),
742                  ('V',"version","Lisa::Options::Version")];
743
744     for i in ["automatic", "help", "manual-reject", "no-action", "version"]:
745         if not Cnf.has_key("Lisa::Options::%s" % (i)):
746             Cnf["Lisa::Options::%s" % (i)] = "";
747
748     changes_files = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv);
749     Options = Cnf.SubTree("Lisa::Options")
750
751     if Options["Help"]:
752         usage();
753
754     if Options["Version"]:
755         print "lisa %s" % (lisa_version);
756         sys.exit(0);
757
758     Katie = katie.Katie(Cnf);
759
760     if not Options["No-Action"]:
761         Logger = Katie.Logger = logging.Logger(Cnf, "lisa");
762
763     projectB = Katie.projectB;
764
765     Sections = Section_Completer();
766     Priorities = Priority_Completer();
767     readline.parse_and_bind("tab: complete");
768
769     return changes_files;
770
771 ################################################################################
772
773 def do_byhand():
774     done = 0;
775     while not done:
776         files = Katie.pkg.files;
777         will_install = 1;
778         byhand = [];
779
780         for file in files.keys():
781             if files[file]["type"] == "byhand":
782                 if os.path.exists(file):
783                     print "W: %s still present; please process byhand components and try again." % (file);
784                     will_install = 0;
785                 else:
786                     byhand.append(file);
787
788         answer = "XXXX";
789         if Options["No-Action"]:
790             answer = "S";
791         if will_install:
792             if Options["Automatic"] and not Options["No-Action"]:
793                 answer = 'A';
794             prompt = "[A]ccept, Manual reject, Skip, Quit ?";
795         else:
796             prompt = "Manual reject, [S]kip, Quit ?";
797
798         while prompt.find(answer) == -1:
799             answer = utils.our_raw_input(prompt);
800             m = katie.re_default_answer.search(prompt);
801             if answer == "":
802                 answer = m.group(1);
803             answer = answer[:1].upper();
804
805         if answer == 'A':
806             done = 1;
807             for file in byhand:
808                 del files[file];
809         elif answer == 'M':
810             Katie.do_reject(1, Options["Manual-Reject"]);
811             os.unlink(Katie.pkg.changes_file[:-8]+".katie");
812             done = 1;
813         elif answer == 'S':
814             done = 1;
815         elif answer == 'Q':
816             sys.exit(0);
817
818 ################################################################################
819
820 def do_accept():
821     print "ACCEPT";
822     if not Options["No-Action"]:
823         (summary, short_summary) = Katie.build_summaries();
824         Katie.accept(summary, short_summary);
825         os.unlink(Katie.pkg.changes_file[:-8]+".katie");
826
827 def check_status(files):
828     new = byhand = 0;
829     for file in files.keys():
830         if files[file]["type"] == "byhand":
831             byhand = 1;
832         elif files[file].has_key("new"):
833             new = 1;
834     return (new, byhand);
835
836 def do_pkg(changes_file):
837     Katie.pkg.changes_file = changes_file;
838     Katie.init_vars();
839     Katie.update_vars();
840     Katie.update_subst();
841     files = Katie.pkg.files;
842
843     if not recheck():
844         return;
845
846     (new, byhand) = check_status(files);
847     if new or byhand:
848         if new:
849             do_new();
850         if byhand:
851             do_byhand();
852         (new, byhand) = check_status(files);
853
854     if not new and not byhand:
855         do_accept();
856
857 ################################################################################
858
859 def end():
860     accept_count = Katie.accept_count;
861     accept_bytes = Katie.accept_bytes;
862
863     if accept_count:
864         sets = "set"
865         if accept_count > 1:
866             sets = "sets"
867         sys.stderr.write("Accepted %d package %s, %s.\n" % (accept_count, sets, utils.size_type(int(accept_bytes))));
868         Logger.log(["total",accept_count,accept_bytes]);
869
870     if not Options["No-Action"]:
871         Logger.close();
872
873 ################################################################################
874
875 def main():
876     changes_files = init();
877     if len(changes_files) > 50:
878         sys.stderr.write("Sorting changes...\n");
879     changes_files = sort_changes(changes_files);
880
881     # Kill me now? **FIXME**
882     Cnf["Dinstall::Options::No-Mail"] = "";
883     bcc = "X-Katie: lisa %s" % (lisa_version);
884     if Cnf.has_key("Dinstall::Bcc"):
885         Katie.Subst["__BCC__"] = bcc + "\nBcc: %s" % (Cnf["Dinstall::Bcc"]);
886     else:
887         Katie.Subst["__BCC__"] = bcc;
888
889     for changes_file in changes_files:
890         changes_file = utils.validate_changes_file_arg(changes_file, 0);
891         if not changes_file:
892             continue;
893         print "\n" + changes_file;
894         do_pkg (changes_file);
895
896     end();
897
898 ################################################################################
899
900 if __name__ == '__main__':
901     main()