Coverage for dak/queue_report.py: 42%
384 statements
« prev ^ index » next coverage.py v7.6.0, created at 2026-02-10 22:10 +0000
« prev ^ index » next coverage.py v7.6.0, created at 2026-02-10 22:10 +0000
1#! /usr/bin/env python3
3"""Produces a report on NEW and BYHAND packages"""
4# Copyright (C) 2001, 2002, 2003, 2005, 2006 James Troup <james@nocrew.org>
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2 of the License, or
9# (at your option) any later version.
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20################################################################################
22# <o-o> XP runs GCC, XFREE86, SSH etc etc,.,, I feel almost like linux....
23# <o-o> I am very confident that I can replicate any Linux application on XP
24# <willy> o-o: *boggle*
25# <o-o> building from source.
26# <o-o> Viiru: I already run GIMP under XP
27# <willy> o-o: why do you capitalise the names of all pieces of software?
28# <o-o> willy: because I want the EMPHASIZE them....
29# <o-o> grr s/the/to/
30# <willy> o-o: it makes you look like ZIPPY the PINHEAD
31# <o-o> willy: no idea what you are talking about.
32# <willy> o-o: do some research
33# <o-o> willy: for what reason?
35################################################################################
37import datetime
38import functools
39import html
40import os
41import sys
42import time
43from typing import IO, Any, Literal, NoReturn, cast
45import apt_pkg
46from sqlalchemy import sql
47from sqlalchemy.engine import CursorResult
49from daklib import utils
50from daklib.dak_exceptions import ParseMaintError
51from daklib.dbconn import DBConn, PolicyQueue, get_uid_from_fingerprint, has_new_comment
52from daklib.policy import PolicyQueueUploadHandler
53from daklib.textutils import fix_maintainer
54from daklib.utils import get_logins_from_ldap
56Cnf: apt_pkg.Configuration
57direction: list[tuple[int, int, str | Literal[0]]] = []
59################################################################################
62def usage(exit_code=0) -> NoReturn:
63 print(
64 """Usage: dak queue-report
65Prints a report of packages in queues (usually new and byhand).
67 -h, --help show this help and exit.
68 -8, --822 writes 822 formated output to the location set in dak.conf
69 -n, --new produce html-output
70 -s, --sort=key sort output according to key, see below.
71 -a, --age=key if using sort by age, how should time be treated?
72 If not given a default of hours will be used.
73 -r, --rrd=key Directory where rrd files to be updated are stored
74 -d, --directories=key A comma separated list of queues to be scanned
76 Sorting Keys: ao=age, oldest first. an=age, newest first.
77 na=name, ascending nd=name, descending
78 nf=notes, first nl=notes, last
80 Age Keys: m=minutes, h=hours, d=days, w=weeks, o=months, y=years
82"""
83 )
84 sys.exit(exit_code)
87################################################################################
90def plural(x: float | int) -> str:
91 if x > 1:
92 return "s"
93 else:
94 return ""
97################################################################################
100def time_pp(x: float | int) -> str:
101 if x < 60: 101 ↛ 103line 101 didn't jump to line 103 because the condition on line 101 was always true
102 unit = "second"
103 elif x < 3600:
104 x /= 60
105 unit = "minute"
106 elif x < 86400:
107 x /= 3600
108 unit = "hour"
109 elif x < 604800:
110 x /= 86400
111 unit = "day"
112 elif x < 2419200:
113 x /= 604800
114 unit = "week"
115 elif x < 29030400:
116 x /= 2419200
117 unit = "month"
118 else:
119 x /= 29030400
120 unit = "year"
121 x = int(x)
122 return "%s %s%s" % (x, unit, plural(x))
125################################################################################
128def sg_compare(a, b) -> int:
129 a1 = a[1]
130 b1 = b[1]
131 # Sort by have pending action, have note, time of oldest upload.
132 # Sort by have pending action
133 a_note_state = a1["processed"]
134 b_note_state = b1["processed"]
135 if a_note_state < b_note_state: 135 ↛ 136line 135 didn't jump to line 136 because the condition on line 135 was never true
136 return -1
137 elif a_note_state > b_note_state: 137 ↛ 138line 137 didn't jump to line 138 because the condition on line 137 was never true
138 return 1
140 # Sort by have note
141 a_note_state = a1["note_state"]
142 b_note_state = b1["note_state"]
143 if a_note_state < b_note_state: 143 ↛ 144line 143 didn't jump to line 144 because the condition on line 143 was never true
144 return -1
145 elif a_note_state > b_note_state: 145 ↛ 146line 145 didn't jump to line 146 because the condition on line 145 was never true
146 return 1
148 # Sort by time of oldest upload
149 return a1["oldest"] - b1["oldest"]
152############################################################
155def sortfunc(a, b) -> int:
156 for sorting in direction: 156 ↛ 157line 156 didn't jump to line 157 because the loop on line 156 never started
157 (sortkey, way, time) = sorting
158 ret = 0
159 if time == "m":
160 x = int(a[sortkey] / 60)
161 y = int(b[sortkey] / 60)
162 elif time == "h":
163 x = int(a[sortkey] / 3600)
164 y = int(b[sortkey] / 3600)
165 elif time == "d":
166 x = int(a[sortkey] / 86400)
167 y = int(b[sortkey] / 86400)
168 elif time == "w":
169 x = int(a[sortkey] / 604800)
170 y = int(b[sortkey] / 604800)
171 elif time == "o":
172 x = int(a[sortkey] / 2419200)
173 y = int(b[sortkey] / 2419200)
174 elif time == "y":
175 x = int(a[sortkey] / 29030400)
176 y = int(b[sortkey] / 29030400)
177 else:
178 x = a[sortkey]
179 y = b[sortkey]
180 if x < y:
181 ret = -1
182 elif x > y:
183 ret = 1
184 if ret != 0:
185 if way < 0:
186 ret = ret * -1
187 return ret
188 return 0
191############################################################
194def header() -> None:
195 print(
196 """<!DOCTYPE html>
197<html lang="en">
198 <head>
199 <meta charset="utf-8">
200 <link rel="stylesheet" href="style.css">
201 <link rel="shortcut icon" href="https://www.debian.org/favicon.ico">
202 <title>
203 Debian NEW and BYHAND Packages
204 </title>
205 <script>
206 function togglePkg() {
207 for (const el of document.getElementsByClassName('sourceNEW')) {
208 el.style.display = el.style.display == '' ? 'none' : '';
209 }
210 }
211 </script>
212 </head>
213 <body id="NEW">
214 <div id="logo">
215 <a href="https://www.debian.org/">
216 <img src="https://www.debian.org/logos/openlogo-nd-50.png"
217 alt=""></a>
218 <a href="https://www.debian.org/">
219 <img src="https://www.debian.org/Pics/debian.png"
220 alt="Debian Project"></a>
221 </div>
222 <div id="titleblock">
224 <img src="https://www.debian.org/Pics/red-upperleft.png"
225 id="red-upperleft" alt="">
226 <img src="https://www.debian.org/Pics/red-lowerleft.png"
227 id="red-lowerleft" alt="">
228 <img src="https://www.debian.org/Pics/red-upperright.png"
229 id="red-upperright" alt="">
230 <img src="https://www.debian.org/Pics/red-lowerright.png"
231 id="red-lowerright" alt="">
232 <span class="title">
233 Debian NEW and BYHAND Packages
234 </span>
235 </div>
236 """
237 )
240def footer() -> None:
241 print(
242 '<p class="timestamp">Timestamp: %s (UTC)</p>'
243 % (time.strftime("%d.%m.%Y / %H:%M:%S", time.gmtime()))
244 )
245 print(
246 """
247 <p>
248 There are <a href=\"/stat.html\">graphs about the queues</a> available.
249 You can also look at the <a href="/new.822">RFC822 version</a>.
250 </p>
251 """
252 )
254 print(
255 """
256 <div class="footer">
257 <p>Hint: Age is the youngest upload of the package, if there is more than
258 one version.<br>
259 You may want to look at <a href="https://ftp-master.debian.org/REJECT-FAQ.html">the REJECT-FAQ</a>
260 for possible reasons why one of the above packages may get rejected.</p>
261 </div> </body> </html>
262 """
263 )
266def table_header(type: str, source_count: int, total_count: int) -> None:
267 print("<h1 class='sourceNEW'>Summary for: %s</h1>" % (type))
268 print(
269 "<h1 class='sourceNEW' style='display: none'>Summary for: binary-%s only</h1>"
270 % (type)
271 )
272 print(
273 """
274 <p class="togglepkg" onclick="togglePkg()">Click to toggle all/binary-NEW packages</p>
275 <table class="NEW">
276 <caption class="sourceNEW">
277 """
278 )
279 print(
280 "Package count in <strong>%s</strong>: <em>%s</em> | Total Package count: <em>%s</em>"
281 % (type, source_count, total_count)
282 )
283 print(
284 """
285 </caption>
286 <thead>
287 <tr>
288 <th>Package</th>
289 <th>Version</th>
290 <th>Arch</th>
291 <th>Distribution</th>
292 <th>Age</th>
293 <th>Upload info</th>
294 <th>Closes</th>
295 </tr>
296 </thead>
297 <tbody>
298 """
299 )
302def table_footer(type) -> None:
303 print("</tbody></table>")
306def table_row(
307 source: str,
308 version: str,
309 arch: str,
310 last_mod,
311 maint,
312 distribution,
313 closes,
314 fingerprint,
315 sponsor,
316 changedby,
317) -> None:
318 trclass = "sid"
319 session = DBConn().session()
320 for dist in distribution:
321 if dist == "experimental":
322 trclass = "exp"
324 query = """SELECT source
325 FROM source_suite
326 WHERE source = :source
327 AND suite_name IN ('unstable', 'experimental')"""
328 if not cast(
329 CursorResult, session.execute(sql.text(query), {"source": source})
330 ).rowcount:
331 trclass += " sourceNEW"
332 session.commit()
334 print('<tr class="%s">' % (trclass))
336 if "sourceNEW" in trclass:
337 print(
338 '<td class="package"><a href="https://dfsg-new-queue.debian.org/reviews/%(source)s">%(source)s</a></td>'
339 % {"source": source}
340 )
341 else:
342 print(
343 '<td class="package"><a href="https://tracker.debian.org/pkg/%(source)s">%(source)s</a></td>'
344 % {"source": source}
345 )
346 print('<td class="version">')
347 for vers in version.split():
348 print(
349 '<a href="https://dfsg-new-queue.debian.org/reviews/%s/%s">%s</a><br>'
350 % (source, html.escape(vers), html.escape(vers, quote=False))
351 )
352 print("</td>")
353 print('<td class="arch">%s</td>' % (arch))
354 print('<td class="distribution">')
355 for dist in distribution:
356 print("%s<br>" % (dist))
357 print("</td>")
358 print(
359 '<td class="age"><abbr title="%s">%s</abbr></td>'
360 % (
361 datetime.datetime.utcfromtimestamp(int(time.time()) - last_mod).strftime(
362 "%a, %d %b %Y %T UTC"
363 ),
364 time_pp(last_mod),
365 )
366 )
367 (name, mail) = maint.split(":", 1)
369 print('<td class="upload-data">')
370 print(
371 '<span class="maintainer">Maintainer: <a href="https://qa.debian.org/developer.php?login=%s">%s</a></span><br>'
372 % (html.escape(mail), html.escape(name, quote=False))
373 )
374 (name, mail) = changedby.split(":", 1)
375 print(
376 '<span class="changed-by">Changed-By: <a href="https://qa.debian.org/developer.php?login=%s">%s</a></span><br>'
377 % (html.escape(mail), html.escape(name, quote=False))
378 )
380 if sponsor:
381 print(
382 '<span class="sponsor">Sponsor: <a href="https://qa.debian.org/developer.php?login=%s">%s</a>@debian.org</span><br>'
383 % (html.escape(sponsor), html.escape(sponsor, quote=False))
384 )
386 print('<span class="signature">Fingerprint: %s</span>' % (fingerprint))
387 print("</td>")
389 print('<td class="closes">')
390 for close in closes:
391 print(
392 '<a href="https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=%s">#%s</a><br>'
393 % (html.escape(close), html.escape(close, quote=False))
394 )
395 print("</td></tr>")
398############################################################
401def update_graph_database(
402 rrd_dir: str | None, type: str, n_source: int, n_binary: int
403) -> None:
404 if not rrd_dir: 404 ↛ 407line 404 didn't jump to line 407 because the condition on line 404 was always true
405 return
407 import rrdtool
409 rrd_file = os.path.join(rrd_dir, type.lower() + ".rrd")
410 update = [rrd_file, "N:%s:%s" % (n_source, n_binary)]
412 try:
413 rrdtool.update(*update)
414 except rrdtool.error:
415 create = (
416 [rrd_file]
417 + """
418--step
419300
420--start
4210
422DS:ds0:GAUGE:7200:0:1000
423DS:ds1:GAUGE:7200:0:1000
424RRA:AVERAGE:0.5:1:599
425RRA:AVERAGE:0.5:6:700
426RRA:AVERAGE:0.5:24:775
427RRA:AVERAGE:0.5:288:795
428RRA:MAX:0.5:1:600
429RRA:MAX:0.5:6:700
430RRA:MAX:0.5:24:775
431RRA:MAX:0.5:288:795
432""".strip().split(
433 "\n"
434 )
435 )
436 try:
437 rrdtool.create(*create)
438 rrdtool.update(*update)
439 except rrdtool.error as e:
440 print(
441 (
442 "warning: queue_report: rrdtool error, skipping %s.rrd: %s"
443 % (type, e)
444 )
445 )
446 except NameError:
447 pass
450############################################################
453def process_queue(queue: PolicyQueue, log: IO[str] | None, rrd_dir: str | None) -> None:
454 msg = ""
455 type = queue.queue_name
456 session = DBConn().session()
458 # Divide the .changes into per-source groups
459 per_source: dict[str, dict[str, Any]] = {}
460 total_pending = 0
461 for upload in queue.uploads:
462 source = upload.changes.source
463 if source not in per_source: 463 ↛ 471line 463 didn't jump to line 471 because the condition on line 463 was always true
464 per_source[source] = {}
465 per_source[source]["list"] = []
466 per_source[source]["processed"] = ""
467 handler = PolicyQueueUploadHandler(upload, session)
468 if handler.get_action(): 468 ↛ 469line 468 didn't jump to line 469 because the condition on line 468 was never true
469 per_source[source]["processed"] = "PENDING %s" % handler.get_action()
470 total_pending += 1
471 per_source[source]["list"].append(upload)
472 per_source[source]["list"].sort(key=lambda x: x.changes.created, reverse=True)
473 # Determine oldest time and have note status for each source group
474 for source in list(per_source.keys()):
475 source_list = per_source[source]["list"]
476 first = source_list[0]
477 oldest = time.mktime(first.changes.created.timetuple())
478 have_note = 0
479 for d in per_source[source]["list"]:
480 mtime = time.mktime(d.changes.created.timetuple())
481 if "Queue-Report::Options::New" in Cnf: 481 ↛ 482line 481 didn't jump to line 482 because the condition on line 481 was never true
482 if mtime > oldest:
483 oldest = mtime
484 else:
485 if mtime < oldest: 485 ↛ 486line 485 didn't jump to line 486 because the condition on line 485 was never true
486 oldest = mtime
487 have_note += has_new_comment(
488 d.policy_queue, d.changes.source, d.changes.version
489 )
490 per_source[source]["oldest"] = oldest
491 if not have_note: 491 ↛ 493line 491 didn't jump to line 493 because the condition on line 491 was always true
492 per_source[source]["note_state"] = 0 # none
493 elif have_note < len(source_list):
494 per_source[source]["note_state"] = 1 # some
495 else:
496 per_source[source]["note_state"] = 2 # all
497 per_source_items = list(per_source.items())
498 per_source_items.sort(key=functools.cmp_to_key(sg_compare))
500 update_graph_database(rrd_dir, type, len(per_source_items), len(queue.uploads))
502 entries = []
503 max_source_len = 0
504 max_version_len = 0
505 max_arch_len = 0
506 try:
507 logins = get_logins_from_ldap()
508 except:
509 logins = dict()
510 for i in per_source_items:
511 maintainer = {}
512 maint = ""
513 distribution = ""
514 closes = ""
515 fingerprint = ""
516 changeby = {}
517 changedby = ""
518 sponsor = ""
519 filename = i[1]["list"][0].changes.changesname
520 last_modified = time.time() - i[1]["oldest"]
521 source = i[1]["list"][0].changes.source
522 if len(source) > max_source_len:
523 max_source_len = len(source)
524 binary_list = i[1]["list"][0].binaries
525 binary = ", ".join([b.package for b in binary_list])
526 arches = set()
527 versions = set()
528 for j in i[1]["list"]:
529 dbc = j.changes
531 if ( 531 ↛ 535line 531 didn't jump to line 535
532 "Queue-Report::Options::New" in Cnf
533 or "Queue-Report::Options::822" in Cnf
534 ):
535 try:
536 (
537 maintainer["maintainer822"],
538 maintainer["maintainer2047"],
539 maintainer["maintainername"],
540 maintainer["maintaineremail"],
541 ) = fix_maintainer(dbc.maintainer)
542 except ParseMaintError:
543 print("Problems while parsing maintainer address\n")
544 maintainer["maintainername"] = "Unknown"
545 maintainer["maintaineremail"] = "Unknown"
546 maint = "%s:%s" % (
547 maintainer["maintainername"],
548 maintainer["maintaineremail"],
549 )
550 # ...likewise for the Changed-By: field if it exists.
551 try:
552 (
553 changeby["changedby822"],
554 changeby["changedby2047"],
555 changeby["changedbyname"],
556 changeby["changedbyemail"],
557 ) = fix_maintainer(dbc.changedby)
558 except ParseMaintError:
559 (
560 changeby["changedby822"],
561 changeby["changedby2047"],
562 changeby["changedbyname"],
563 changeby["changedbyemail"],
564 ) = ("", "", "", "")
565 changedby = "%s:%s" % (
566 changeby["changedbyname"],
567 changeby["changedbyemail"],
568 )
570 distribution = dbc.distribution.split()
571 closes = dbc.closes
573 fingerprint = dbc.fingerprint
574 sponsor_uid = get_uid_from_fingerprint(fingerprint, session)
575 sponsor_name = sponsor_uid.name if sponsor_uid else "(Unknown)"
576 sponsor_login = sponsor_uid.uid if sponsor_uid else "(Unknown)"
577 if "@" in sponsor_login:
578 if fingerprint in logins:
579 sponsor_login = logins[fingerprint]
580 if (
581 sponsor_name != maintainer["maintainername"]
582 and sponsor_name != changeby["changedbyname"]
583 and sponsor_login + "@debian.org" != maintainer["maintaineremail"]
584 and sponsor_name != changeby["changedbyemail"]
585 ):
586 sponsor = sponsor_login
588 for arch in dbc.architecture.split():
589 arches.add(arch)
590 versions.add(dbc.version)
591 arches_list = sorted(arches, key=utils.ArchKey)
592 arch_list = " ".join(arches_list)
593 version_list = " ".join(sorted(versions, reverse=True))
594 if len(version_list) > max_version_len:
595 max_version_len = len(version_list)
596 if len(arch_list) > max_arch_len:
597 max_arch_len = len(arch_list)
598 if i[1]["note_state"]: 598 ↛ 599line 598 didn't jump to line 599 because the condition on line 598 was never true
599 note = " | [N]"
600 else:
601 note = ""
602 entries.append(
603 [
604 source,
605 binary,
606 version_list,
607 arch_list,
608 per_source[source]["processed"],
609 note,
610 last_modified,
611 maint,
612 distribution,
613 closes,
614 fingerprint,
615 sponsor,
616 changedby,
617 filename,
618 ]
619 )
621 # direction entry consists of "Which field, which direction, time-consider" where
622 # time-consider says how we should treat last_modified. Thats all.
624 # Look for the options for sort and then do the sort.
625 age = "h"
626 if "Queue-Report::Options::Age" in Cnf: 626 ↛ 627line 626 didn't jump to line 627 because the condition on line 626 was never true
627 age = Cnf["Queue-Report::Options::Age"]
628 if "Queue-Report::Options::New" in Cnf: 628 ↛ 630line 628 didn't jump to line 630 because the condition on line 628 was never true
629 # If we produce html we always have oldest first.
630 direction.append((6, -1, "ao"))
631 else:
632 if "Queue-Report::Options::Sort" in Cnf: 632 ↛ 633line 632 didn't jump to line 633 because the condition on line 632 was never true
633 for j in Cnf["Queue-Report::Options::Sort"].split(","):
634 if j == "ao":
635 # Age, oldest first.
636 direction.append((6, -1, age))
637 elif j == "an":
638 # Age, newest first.
639 direction.append((6, 1, age))
640 elif j == "na":
641 # Name, Ascending.
642 direction.append((0, 1, 0))
643 elif j == "nd":
644 # Name, Descending.
645 direction.append((0, -1, 0))
646 elif j == "nl":
647 # Notes last.
648 direction.append((5, 1, 0))
649 elif j == "nf":
650 # Notes first.
651 direction.append((5, -1, 0))
652 entries.sort(key=functools.cmp_to_key(sortfunc))
653 # Yes, in theory you can add several sort options at the commandline with. But my mind is to small
654 # at the moment to come up with a real good sorting function that considers all the sidesteps you
655 # have with it. (If you combine options it will simply take the last one at the moment).
656 # Will be enhanced in the future.
658 if log is not None: 658 ↛ 660line 658 didn't jump to line 660 because the condition on line 658 was never true
659 # print stuff out in 822 format
660 for entry in entries:
661 (
662 source,
663 binary,
664 version_list,
665 arch_list,
666 processed,
667 note,
668 last_modified,
669 maint,
670 distribution,
671 closes,
672 fingerprint,
673 sponsor,
674 changedby,
675 changes_file,
676 ) = entry
678 # We'll always have Source, Version, Arch, Mantainer, and Dist
679 # For the rest, check to see if we have them, then print them out
680 log.write("Source: " + source + "\n")
681 log.write("Binary: " + binary + "\n")
682 log.write("Version: " + version_list + "\n")
683 log.write("Architectures: ")
684 log.write((", ".join(arch_list.split(" "))) + "\n")
685 log.write("Age: " + time_pp(last_modified) + "\n")
686 log.write(
687 "Last-Modified: " + str(int(time.time()) - int(last_modified)) + "\n"
688 )
689 log.write("Queue: " + type + "\n")
691 (name, mail) = maint.split(":", 1)
692 log.write("Maintainer: " + name + " <" + mail + ">" + "\n")
693 if changedby:
694 (name, mail) = changedby.split(":", 1)
695 log.write("Changed-By: " + name + " <" + mail + ">" + "\n")
696 if sponsor:
697 log.write("Sponsored-By: %s@debian.org\n" % sponsor)
698 log.write("Distribution:")
699 for dist in distribution:
700 log.write(" " + dist)
701 log.write("\n")
702 log.write("Fingerprint: " + fingerprint + "\n")
703 if closes:
704 bug_string = ""
705 for bugs in closes:
706 bug_string += "#" + bugs + ", "
707 log.write("Closes: " + bug_string[:-2] + "\n")
708 log.write("Changes-File: " + os.path.basename(changes_file) + "\n")
709 log.write("\n")
711 total_count = len(queue.uploads)
712 source_count = len(per_source_items)
714 if "Queue-Report::Options::New" in Cnf: 714 ↛ 715line 714 didn't jump to line 715 because the condition on line 714 was never true
715 direction.append((6, 1, "ao"))
716 entries.sort(key=functools.cmp_to_key(sortfunc))
717 # Output for a html file. First table header. then table_footer.
718 # Any line between them is then a <tr> printed from subroutine table_row.
719 if len(entries) > 0:
720 table_header(type.upper(), source_count, total_count)
721 for entry in entries:
722 (
723 source,
724 binary,
725 version_list,
726 arch_list,
727 processed,
728 note,
729 last_modified,
730 maint,
731 distribution,
732 closes,
733 fingerprint,
734 sponsor,
735 changedby,
736 _,
737 ) = entry
738 table_row(
739 source,
740 version_list,
741 arch_list,
742 last_modified,
743 maint,
744 distribution,
745 closes,
746 fingerprint,
747 sponsor,
748 changedby,
749 )
750 table_footer(type.upper())
751 elif "Queue-Report::Options::822" not in Cnf: 751 ↛ exitline 751 didn't return from function 'process_queue' because the condition on line 751 was always true
752 # The "normal" output without any formatting.
753 msg = ""
754 for entry in entries:
755 (
756 source,
757 binary,
758 version_list,
759 arch_list,
760 processed,
761 note,
762 last_modified,
763 _,
764 _,
765 _,
766 _,
767 _,
768 _,
769 _,
770 ) = entry
771 if processed: 771 ↛ 772line 771 didn't jump to line 772 because the condition on line 771 was never true
772 format = "%%-%ds | %%-%ds | %%-%ds | %%s\n" % (
773 max_source_len,
774 max_version_len,
775 max_arch_len,
776 )
777 msg += format % (source, version_list, arch_list, processed)
778 else:
779 format = "%%-%ds | %%-%ds | %%-%ds%%s | %%s old\n" % (
780 max_source_len,
781 max_version_len,
782 max_arch_len,
783 )
784 msg += format % (
785 source,
786 version_list,
787 arch_list,
788 note,
789 time_pp(last_modified),
790 )
792 if msg:
793 print(type.upper())
794 print("-" * len(type))
795 print()
796 print(msg)
797 print(
798 (
799 "%s %s source package%s / %s %s package%s in total / %s %s package%s to be processed."
800 % (
801 source_count,
802 type,
803 plural(source_count),
804 total_count,
805 type,
806 plural(total_count),
807 total_pending,
808 type,
809 plural(total_pending),
810 )
811 )
812 )
813 print()
816################################################################################
819def main() -> None:
820 global Cnf
822 Cnf = utils.get_conf()
823 Arguments = [
824 ("h", "help", "Queue-Report::Options::Help"),
825 ("n", "new", "Queue-Report::Options::New"),
826 ("8", "822", "Queue-Report::Options::822"),
827 ("s", "sort", "Queue-Report::Options::Sort", "HasArg"),
828 ("a", "age", "Queue-Report::Options::Age", "HasArg"),
829 ("r", "rrd", "Queue-Report::Options::Rrd", "HasArg"),
830 ("d", "directories", "Queue-Report::Options::Directories", "HasArg"),
831 ]
832 for i in ["help"]:
833 key = "Queue-Report::Options::%s" % i
834 if key not in Cnf: 834 ↛ 832line 834 didn't jump to line 832 because the condition on line 834 was always true
835 Cnf[key] = "" # type: ignore[index]
837 apt_pkg.parse_commandline(Cnf, Arguments, sys.argv) # type: ignore[attr-defined]
839 Options = Cnf.subtree("Queue-Report::Options") # type: ignore[attr-defined]
840 if Options["Help"]:
841 usage()
843 if "Queue-Report::Options::New" in Cnf: 843 ↛ 844line 843 didn't jump to line 844 because the condition on line 843 was never true
844 header()
846 queue_names = []
848 if "Queue-Report::Options::Directories" in Cnf: 848 ↛ 849line 848 didn't jump to line 849 because the condition on line 848 was never true
849 for i in Cnf["Queue-Report::Options::Directories"].split(","):
850 queue_names.append(i)
851 elif "Queue-Report::Directories" in Cnf: 851 ↛ 852line 851 didn't jump to line 852 because the condition on line 851 was never true
852 queue_names = Cnf.value_list("Queue-Report::Directories")
853 else:
854 queue_names = ["byhand", "new"]
856 rrd_dir = Cnf.get("Queue-Report::Options::Rrd") or Cnf.get("Dir::Rrd") or None
858 f = None
859 if "Queue-Report::Options::822" in Cnf: 859 ↛ 861line 859 didn't jump to line 861 because the condition on line 859 was never true
860 # Open the report file
861 f = sys.stdout
862 filename822 = Cnf.get("Queue-Report::ReportLocations::822Location")
863 if filename822:
864 f = open(filename822, "w")
866 session = DBConn().session()
868 for queue_name in queue_names:
869 queue = session.query(PolicyQueue).filter_by(queue_name=queue_name).first()
870 if queue is not None: 870 ↛ 873line 870 didn't jump to line 873 because the condition on line 870 was always true
871 process_queue(queue, f, rrd_dir)
872 else:
873 utils.warn("Cannot find queue %s" % queue_name)
875 if f is not None: 875 ↛ 876line 875 didn't jump to line 876 because the condition on line 875 was never true
876 f.close()
878 if "Queue-Report::Options::New" in Cnf: 878 ↛ 879line 878 didn't jump to line 879 because the condition on line 878 was never true
879 footer()
882################################################################################
885if __name__ == "__main__":
886 main()