]> git.decadent.org.uk Git - dak.git/blob - tools/queue_rss.py
html entity escape for Maintainer/Description/Changes
[dak.git] / tools / queue_rss.py
1 #!/usr/bin/python
2 # Generate two rss feeds for a directory with .changes file
3
4 # License: GPL v2 or later
5 # Author: Filippo Giunchedi <filippo@debian.org>
6 # Version: 0.4
7
8 import cgi
9 import os
10 import os.path
11 import cPickle
12 import sys
13 import encodings.ascii
14 from optparse import OptionParser
15 from datetime import datetime
16
17 import PyRSS2Gen
18
19 from debian_bundle.deb822 import Changes
20
21 inrss_filename = "changes_in.rss"
22 outrss_filename = "changes_out.rss"
23 db_filename = "status.db"
24
25 parser = OptionParser()
26 parser.set_defaults(queuedir="queue", outdir="out", datadir="status", max_entries="30")
27
28 parser.add_option("-q", "--queuedir", dest="queuedir",
29         help="The queue dir (%default)")
30 parser.add_option("-o", "--outdir", dest="outdir",
31         help="The output directory (%default)")
32 parser.add_option("-d", "--datadir", dest="datadir",
33         help="The data dir (%default)")
34 parser.add_option("-m", "--max-entries", dest="max_entries", type="int",
35         help="Max number of entries to keep (%default)")
36
37 class Status:
38     def __init__(self):
39         self.feed_in = PyRSS2Gen.RSS2(
40                        title = "Packages entering NEW",
41                        link = "http://ftp-master.debian.org/new.html",
42                        description = "Debian packages entering the NEW queue" )
43
44         self.feed_out = PyRSS2Gen.RSS2(
45                        title = "Packages leaving NEW",
46                        link = "http://ftp-master.debian.org/new.html",
47                        description = "Debian packages leaving the NEW queue" )
48
49         self.queue = {}
50
51 def utf2ascii(src):
52     """ Return an ASCII encoded copy of the input UTF-8 string """
53     try:
54         res = unicode(src, 'utf-8').encode('ascii', 'replace')
55     except UnicodeDecodeError:
56         res = None
57     return res
58
59 def purge_old_items(feed, max):
60     """ Purge RSSItem from feed, no more than max. """
61     if feed.items is None or len(feed.items) == 0:
62         return False
63
64     feed.items = feed.items[:max]
65     return True
66
67 def parse_changes(fname):
68     """ Parse a .changes file named fname.
69
70     Return {fname: parsed} """
71
72     m = Changes(open(fname))
73
74     wanted_fields = set(['Source', 'Version', 'Architecture', 'Distribution',
75                          'Date', 'Maintainer', 'Description', 'Changes'])
76
77     if not set(m.keys()).issuperset(wanted_fields):
78         return None
79
80     return {os.path.basename(fname): m}
81
82 def parse_queuedir(dir):
83     """ Parse dir for .changes files.
84
85     Return a dictionary {filename: parsed_file}"""
86
87     if not os.path.exists(dir):
88         return None
89
90     res = {}
91     for fname in os.listdir(dir):
92         if not fname.endswith(".changes"):
93             continue
94
95         parsed = parse_changes(os.path.join(dir, fname))
96         if parsed:
97             res.update(parsed)
98
99     return res
100
101 def add_rss_item(status, msg, direction):
102     if direction == "in":
103         feed = status.feed_in
104         title = "%s %s entered NEW" % (msg['Source'], msg['Version'])
105         pubdate = msg['Date']
106     elif direction == "out":
107         feed = status.feed_out
108         title = "%s %s left NEW" % (msg['Source'], msg['Version'])
109         pubdate = datetime.utcnow()
110     else:
111         return False
112
113     description = "<pre>Description: %s\nChanges: %s\n</pre>" % \
114             (utf2ascii(cgi.escape(msg['Description'])), utf2ascii(cgi.escape(msg['Changes'])))
115
116     feed.items.insert(0,
117         PyRSS2Gen.RSSItem(
118             title,
119             pubDate = pubdate,
120             description = description,
121             author = utf2ascii(cgi.escape(msg['Maintainer'])),
122             link = "http://ftp-master.debian.org/new/%s_%s.html" % \
123                     (msg['Source'], msg['Version'])
124         )
125     )
126
127 def update_feeds(curqueue, status):
128     # inrss -> append all items in curqueue not in status.queue
129     # outrss -> append all items in status.queue not in curqueue
130
131     for (name, parsed) in curqueue.items():
132         if not status.queue.has_key(name):
133             # new package
134             add_rss_item(status, parsed, "in")
135
136     for (name, parsed) in status.queue.items():
137         if not curqueue.has_key(name):
138             # removed package
139             add_rss_item(status, parsed, "out")
140
141
142
143 if __name__ == "__main__":
144
145     (settings, args) = parser.parse_args()
146
147     if not os.path.exists(settings.outdir):
148         sys.stderr.write("Outdir '%s' does not exists\n" % settings.outdir)
149         parser.print_help()
150         sys.exit(1)
151
152     if not os.path.exists(settings.datadir):
153         sys.stderr.write("Datadir '%s' does not exists\n" % settings.datadir)
154         parser.print_help()
155         sys.exit(1)
156
157     status_db = os.path.join(settings.datadir, db_filename)
158
159     try:
160         status = cPickle.load(open(status_db))
161     except IOError:
162         status = Status()
163
164     current_queue = parse_queuedir(settings.queuedir)
165     if not current_queue:
166         sys.stderr.write("Unable to scan queuedir '%s'\n" % settings.queuedir)
167         parser.print_help()
168         sys.exit(1)
169
170     update_feeds(current_queue, status)
171
172     purge_old_items(status.feed_in, settings.max_entries)
173     purge_old_items(status.feed_out, settings.max_entries)
174
175     feed_in_file = os.path.join(settings.outdir, inrss_filename)
176     feed_out_file = os.path.join(settings.outdir, outrss_filename)
177
178     try:
179         status.feed_in.write_xml(file(feed_in_file, "w+"), "utf-8")
180         status.feed_out.write_xml(file(feed_out_file, "w+"), "utf-8")
181     except IOError, why:
182         sys.stderr.write("Unable to write feeds: %s\n", why)
183         sys.exit(1)
184
185     status.queue = current_queue
186
187     try:
188         cPickle.dump(status, open(status_db, "w+"))
189     except IOError, why:
190         sys.stderr.write("Unable to save status: %s\n", why)
191         sys.exit(1)
192
193 # vim:et:ts=4