]> git.decadent.org.uk Git - dak.git/blob - fernanda.py
fernanda.py: colouring support for section, depends and recomends
[dak.git] / fernanda.py
1 #!/usr/bin/env python
2
3 # Script to automate some parts of checking NEW packages
4 # Copyright (C) 2000, 2001, 2002  James Troup <james@nocrew.org>
5 # $Id: fernanda.py,v 1.6 2002-11-26 02:21:46 anonymous 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 # <Omnic> elmo wrote docs?!!?!?!?!?!?!
24 # <aj> as if he wasn't scary enough before!!
25 # * aj imagines a little red furry toy sitting hunched over a computer
26 #   tapping furiously and giggling to himself
27 # <aj> eventually he stops, and his heads slowly spins around and you
28 #      see this really evil grin and then he sees you, and picks up a
29 #      knife from beside the keyboard and throws it at you, and as you
30 #      breathe your last breath, he starts giggling again
31 # <aj> but i should be telling this to my psychiatrist, not you guys,
32 #      right? :)
33
34 ################################################################################
35
36 import errno, os, re, sys
37 import utils
38 import apt_pkg, apt_inst
39 import pg, string, db_access
40
41 ################################################################################
42
43
44 re_package = re.compile(r"^(.+?)_.*");
45 re_doc_directory = re.compile(r".*/doc/([^/]*).*");
46
47
48 re_contrib = re.compile('^contrib/')
49 re_nonfree = re.compile('^non\-free/')
50
51 re_arch = re.compile("Architecture: .*")
52 re_builddep = re.compile("Build-Depends: .*")
53 re_builddepind = re.compile("Build-Depends-Indep: .*")
54
55 re_localhost = re.compile("localhost\.localdomain") 
56 re_version = re.compile('^(.*)\((.*)\)')
57
58 re_newlinespace = re.compile('\n')
59 re_spacestrip = re.compile('(\s)')
60
61 ##############################################################################################################
62
63 # Colour definitions
64
65 # Main
66 main_colour = "\033[36m"
67 # Contrib
68 contrib_colour = "\033[33m"
69 # Non-Free
70 nonfree_colour = "\033[31m"
71 # Arch
72 arch_colour = "\033[32m"
73 # End
74 end_colour = "\033[0m"
75 # Bold 
76 bold_colour = "\033[1m"
77 # Bad maintainer
78 maintainer_colour = arch_colour
79
80
81
82 ################################################################################
83
84 Cnf = None;
85 projectB = None;
86
87 Cnf = utils.get_conf()
88 projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
89 db_access.init(Cnf, projectB);
90
91
92
93 ################################################################################
94
95 def usage (exit_code=0):
96     print """Usage: fernanda [PACKAGE]...
97 Check NEW package(s).
98
99   -h, --help                 show this help and exit
100
101 PACKAGE can be a .changes, .dsc, .deb or .udeb filename."""
102
103     sys.exit(exit_code)
104
105 ################################################################################
106
107 def get_depends_parts(depend) :
108     v_match = re_version.match(depend)
109     if v_match:
110         d_parts = { 'name' : v_match.group(1), 'version' : v_match.group(2) }
111     else :
112         d_parts = { 'name' : depend , 'version' : '' }
113     return d_parts
114                                 
115 def get_or_list(depend) :
116     or_list = string.split(depend, "|")
117     return or_list
118                                         
119 def get_comma_list(depend) :
120     dep_list = string.split(depend, ",")
121     return dep_list
122                                                 
123                                                 
124 def split_depends (d_str) :
125     # creates a list of lists of dictionaries of depends (package,version relation)
126     
127     d_str = re_spacestrip.sub('',d_str)
128     depends_tree = []
129     # first split depends string up amongs comma delimiter
130     dep_list = get_comma_list(d_str)
131     d = 0
132     while d < len(dep_list):
133         # put depends into their own list
134         depends_tree.append([dep_list[d]])
135         d = d + 1
136     d = 0
137     while d < len(depends_tree) :
138         k = 0
139         # split up Or'd depends into a multi-item list
140         depends_tree[d] = get_or_list(depends_tree[d][0])
141         while k < len(depends_tree[d]) :
142             # split depends into {package, version relation}
143             depends_tree[d][k] = get_depends_parts(depends_tree[d][k])
144             k = k + 1
145         d = d +1
146     return depends_tree
147                                                                                                                                                                                 
148 def read_control (filename) :
149     
150     recommends = []
151     depends = []
152     section = ''
153     maintainer = ''
154     arch = ''
155
156
157
158     
159     deb_file = utils.open_file(filename);
160     try:
161         extracts = apt_inst.debExtractControl(deb_file)
162         control = apt_pkg.ParseSection(extracts)
163     except:
164
165         print "can't parse control info"
166         control = ''    
167
168         
169     deb_file.close();
170     
171     control_keys = control.keys()
172     
173     
174    
175
176         
177         
178         
179     if control.has_key("Depends"):
180         depends_str = control.Find("Depends")
181         # create list of dependancy lists
182         depends = split_depends(depends_str)
183     
184         
185     if control.has_key("Recommends"):
186         recommends_str = control.Find("Recommends")
187         recommends = split_depends(recommends_str)
188     
189     if control.has_key("Section"):
190         section_str = control.Find("Section")
191    
192         c_match = re_contrib.search(section_str)
193         nf_match = re_nonfree.search(section_str)
194         if c_match :
195             # contrib colour
196             section = contrib_colour + section_str + end_colour
197         elif nf_match :
198             
199             # non-free colour
200             section = nonfree_colour + section_str + end_colour
201         else :
202             # main
203             section = main_colour +  section_str + end_colour
204     if control.has_key("Achitecture"):
205         arch_str = control.Find("Architecture")
206         arch = arch_colour + arch_str + end_colour
207     
208     if control.has_key("Maintainer"):
209         maintainer = control.Find("Maintainer") 
210         localhost = re_localhost.search(maintainer) 
211         if localhost :
212             #highlight bad email
213             maintainer = maintainer_colour + maintainer + end_colour 
214     
215
216     return (control, control_keys, section, depends, recommends, arch, maintainer)
217     
218     
219 def read_dsc (dsc_filename) :
220     buf = []
221     header = ''
222     footer = ''
223     dsc = {}
224     dsc_files = {}
225     
226     dsc_file = utils.open_file(dsc_filename)                
227     try:
228         dsc.update(utils.parse_changes(dsc_filename, dsc_whitespace_rules=0))
229         dsc_files.update(utils.build_file_list(dsc, is_a_dsc=1))
230     except:
231     
232         print "can't parse control info"
233         
234    
235                                                 
236     dsc_file.close();
237
238     filecontents = dsc["filecontents"]
239
240     if dsc.has_key("build-depends"):
241         
242         builddep = split_depends(dsc["build-depends"])
243         builddepstr = create_depends_string(builddep)
244         filecontents = re_builddep.sub("Build-Depends: "+builddepstr, filecontents)
245     
246     
247     if dsc.has_key("build-depends-indep") :
248         builddepindstr = create_depends_string(split_depends(dsc["build-depends-indep"]))
249         filecontents = re_builddepind.sub("Build-Depends-Indep: "+builddepindstr, filecontents)
250
251     
252
253     
254     if dsc.has_key("architecture") :
255
256         if (dsc["architecture"] != "any") : 
257             newarch = arch_colour + dsc["architecture"] + end_colour 
258             filecontents = re_arch.sub("Architecture: "+newarch, filecontents)
259     
260     return (filecontents)
261
262 def create_depends_string(depends_tree) :
263     # just look up unstable for now. possibly pull from .changes later
264     suite = "unstable"
265     result = ""
266     comma_count = 1
267     for l in depends_tree:
268         if (comma_count >= 2) :
269             result = result + ", "
270         or_count = 1
271         for d in l :
272             if (or_count >= 2 ) :
273                 result = result + " | "
274             # doesn't do version lookup yet.
275
276             q = projectB.query("SELECT DISTINCT(b.package), b.version, c.name, su.suite_name FROM  binaries b, files fi, location l, component c, bin_associations ba, suite su WHERE b.package='%s' AND b.file = fi.id AND fi.location = l.id AND l.component = c.id AND ba.bin=b.id AND ba.suite = su.id AND su.suite_name='%s' ORDER BY b.version desc" % (d['name'], suite))
277             ql = q.getresult();
278             if ql:
279                 i = ql[0]
280                 #for i in ql:
281                 
282                 if i[2] == "contrib" :
283                     result = result + contrib_colour + d['name']
284                 elif i[2] == "non-free" :
285                     result = result + nonfree_colour + d['name']
286                 else :
287                     result = result + main_colour + d['name']
288                     
289                 if d['version'] != '' :
290                     result = result +  " (" + d['version'] + ")"
291                 result = result +end_colour
292             else :
293                 result = result + bold_colour + d['name']
294                 if d['version'] != '' :
295                     result = result + " ("  + d['version'] + ")"
296                 result = result  + end_colour
297             or_count = or_count + 1
298         comma_count = comma_count + 1
299     return result
300
301
302 def output_deb_info(filename):
303     
304
305     (control, control_keys, section, depends, recommends, arch, maintainer) = read_control(filename)
306
307     if control == '' :
308         print "no control info" 
309     else :
310
311         
312         for key in control_keys :
313
314             output = " " + key + ": "
315             if key == 'Depends' :
316                 output = output + create_depends_string(depends)
317             elif key == 'Recommends' :
318                 output = output + create_depends_string(recommends)         
319             elif key == 'Section' :
320                 output = output + section
321             elif key == 'Architecture' :
322                 output = output + arch
323             elif key == 'Maintainer' :
324                 output = output + maintainer
325             elif key == 'Description' :
326                 desc = control.Find(key)
327                 desc = re_newlinespace.sub('\n ', desc)
328                 output = output + desc
329             else :
330                 output = output + control.Find(key)
331             print output
332         
333
334 def do_command (command, filename):
335     o = os.popen("%s %s" % (command, filename));
336     print o.read();
337
338 def print_copyright (deb_filename):
339     package = re_package.sub(r'\1', deb_filename);
340     o = os.popen("ar p %s data.tar.gz | tar tzvf - | egrep 'usr(/share)?/doc/[^/]*/copyright' | awk '{ print $6 }' | head -n 1" % (deb_filename));
341     copyright = o.read()[:-1];
342
343     if copyright == "":
344         print "WARNING: No copyright found, please check package manually."
345         return;
346
347     doc_directory = re_doc_directory.sub(r'\1', copyright);
348     if package != doc_directory:
349         print "WARNING: wrong doc directory (expected %s, got %s)." % (package, doc_directory);
350         return;
351
352     o = os.popen("ar p %s data.tar.gz | tar xzOf - %s" % (deb_filename, copyright));
353     print o.read();
354
355 def check_dsc (dsc_filename):
356     print "---- .dsc file for %s ----" % (dsc_filename);
357     (dsc) = read_dsc(dsc_filename)
358     
359     print dsc
360
361     
362
363
364 def check_deb (deb_filename):
365     filename = os.path.basename(deb_filename);
366
367     if filename.endswith(".udeb"):
368         is_a_udeb = 1;
369     else:
370         is_a_udeb = 0;
371
372     print "---- control file for %s ----" % (filename);
373     #do_command ("dpkg -I", deb_filename);
374     output_deb_info(deb_filename)
375     
376     if is_a_udeb:
377         print "---- skipping lintian check for µdeb ----";
378         print ;
379     else:
380         print "---- lintian check for %s ----" % (filename);
381         do_command ("lintian", deb_filename);
382         print "---- linda check for %s ----" % (filename);
383         do_command ("linda", deb_filename);
384
385     print "---- contents of %s ----" % (filename);
386     do_command ("dpkg -c", deb_filename);
387
388     if is_a_udeb:
389         print "---- skipping copyright for µdeb ----";
390     else:
391         print "---- copyright of %s ----" % (filename);
392         print_copyright(deb_filename);
393
394     print "---- file listing of %s ----" % (filename);
395     do_command ("ls -l", deb_filename);
396
397 def display_changes (changes_filename):
398     print "---- .changes file for %s ----" % (changes_filename);
399     file = utils.open_file (changes_filename);
400     for line in file.readlines():
401         print line[:-1]
402     print ;
403     file.close();
404
405 def check_changes (changes_filename):
406     display_changes(changes_filename);
407
408     changes = utils.parse_changes (changes_filename);
409     files = utils.build_file_list(changes);
410     for file in files.keys():
411         if file.endswith(".deb") or file.endswith(".udeb"):
412             check_deb(file);
413         if file.endswith(".dsc"):
414             check_dsc(file);
415         # else: => byhand
416
417 def main ():
418     global Cnf, projectB, db_files, waste, excluded;
419
420 #    Cnf = utils.get_conf()
421
422     Arguments = [('h',"help","Fernanda::Options::Help")];
423     for i in [ "help" ]:
424         if not Cnf.has_key("Frenanda::Options::%s" % (i)):
425             Cnf["Fernanda::Options::%s" % (i)] = "";
426
427     args = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv);
428     Options = Cnf.SubTree("Fernanda::Options")
429
430     if Options["Help"]:
431         usage();
432
433     stdout_fd = sys.stdout;
434
435     for file in args:
436         try:
437             # Pipe output for each argument through less
438             less_fd = os.popen("less -R -", 'w', 0);  
439             # -R added to display raw control chars for colour
440             sys.stdout = less_fd;
441
442             try:
443                 if file.endswith(".changes"):
444                     check_changes(file);
445                 elif file.endswith(".deb") or file.endswith(".udeb"):
446                     check_deb(file);
447                 elif file.endswith(".dsc"):
448                     check_dsc(file);
449                 else:
450                     utils.fubar("Unrecognised file type: '%s'." % (file));
451             finally:
452                 # Reset stdout here so future less invocations aren't FUBAR
453                 less_fd.close();
454                 sys.stdout = stdout_fd;
455         except IOError, e:
456             if errno.errorcode[e.errno] == 'EPIPE':
457                 utils.warn("[fernanda] Caught EPIPE; skipping.");
458                 pass;
459             else:
460                 raise;
461         except KeyboardInterrupt:
462             utils.warn("[fernanda] Caught C-c; skipping.");
463             pass;
464
465 #######################################################################################
466
467 if __name__ == '__main__':
468     main()
469