1 # Copyright (C) 2009 W. Trevor King <wking@drexel.edu>
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License along
14 # with this program; if not, write to the Free Software Foundation, Inc.,
15 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
16 """(Un)subscribe to change notification"""
17 from libbe import cmdutil, bugdir, tree, diff
23 def execute(args, manipulate_encodings=True, restrict_file_access=False):
25 >>> bd = bugdir.SimpleBugDir()
26 >>> bd.set_sync_with_disk(True)
28 >>> a = bd.bug_from_shortname("a")
29 >>> print a.extra_strings
31 >>> execute(["-s","John Doe <j@doe.com>", "a"], manipulate_encodings=False) # doctest: +NORMALIZE_WHITESPACE
33 John Doe <j@doe.com> all *
34 >>> bd._clear_bugs() # resync our copy of bug
35 >>> a = bd.bug_from_shortname("a")
36 >>> print a.extra_strings
37 ['SUBSCRIBE:John Doe <j@doe.com>\\tall\\t*']
38 >>> execute(["-s","Jane Doe <J@doe.com>", "-S", "a.com,b.net", "a"], manipulate_encodings=False) # doctest: +NORMALIZE_WHITESPACE
40 Jane Doe <J@doe.com> all a.com,b.net
41 John Doe <j@doe.com> all *
42 >>> execute(["-s","Jane Doe <J@doe.com>", "-S", "a.edu", "a"], manipulate_encodings=False) # doctest: +NORMALIZE_WHITESPACE
44 Jane Doe <J@doe.com> all a.com,a.edu,b.net
45 John Doe <j@doe.com> all *
46 >>> execute(["-u", "-s","Jane Doe <J@doe.com>", "-S", "a.com", "a"], manipulate_encodings=False) # doctest: +NORMALIZE_WHITESPACE
48 Jane Doe <J@doe.com> all a.edu,b.net
49 John Doe <j@doe.com> all *
50 >>> execute(["-s","Jane Doe <J@doe.com>", "-S", "*", "a"], manipulate_encodings=False) # doctest: +NORMALIZE_WHITESPACE
52 Jane Doe <J@doe.com> all *
53 John Doe <j@doe.com> all *
54 >>> execute(["-u", "-s","Jane Doe <J@doe.com>", "a"], manipulate_encodings=False) # doctest: +NORMALIZE_WHITESPACE
56 John Doe <j@doe.com> all *
57 >>> execute(["-u", "-s","John Doe <j@doe.com>", "a"], manipulate_encodings=False)
58 >>> execute(["-s","Jane Doe <J@doe.com>", "-t", "new", "DIR"], manipulate_encodings=False) # doctest: +NORMALIZE_WHITESPACE
59 Subscriptions for bug directory:
60 Jane Doe <J@doe.com> new *
61 >>> execute(["-s","Jane Doe <J@doe.com>", "DIR"], manipulate_encodings=False) # doctest: +NORMALIZE_WHITESPACE
62 Subscriptions for bug directory:
63 Jane Doe <J@doe.com> all *
67 options, args = parser.parse_args(args)
68 cmdutil.default_complete(options, args, parser,
69 bugid_args={0: lambda bug : bug.active==True})
73 raise cmdutil.UsageError("Too many arguments.")
75 bd = bugdir.BugDir(from_disk=True,
76 manipulate_encodings=manipulate_encodings)
78 subscriber = options.subscriber
79 if subscriber == None:
80 subscriber = bd.user_id
81 if options.unsubscribe == True:
82 if options.servers == None:
83 options.servers = "INVALID"
84 if options.types == None:
85 options.types = "INVALID"
87 if options.servers == None:
89 if options.types == None:
91 servers = options.servers.split(",")
92 types = options.types.split(",")
94 if len(args) == 0 or args[0] == diff.BUGDIR_ID: # directory-wide subscriptions
95 type_root = diff.BUGDIR_TYPE_ALL
97 entity_name = "bug directory"
98 else: # bug-specific subscriptions
99 type_root = diff.BUG_TYPE_ALL
100 bug = bd.bug_from_shortname(args[0])
102 entity_name = bug.uuid
103 if options.list_all == True:
104 entity_name = "anything in the bug directory"
106 types = [diff.type_from_name(name, type_root, default=diff.INVALID_TYPE,
107 default_ok=options.unsubscribe)
109 estrs = entity.extra_strings
110 if options.list == True or options.list_all == True:
112 else: # alter subscriptions
113 if options.unsubscribe == True:
114 estrs = unsubscribe(estrs, subscriber, types, servers, type_root)
116 estrs = subscribe(estrs, subscriber, types, servers, type_root)
117 entity.extra_strings = estrs # reassign to notice change
119 if options.list_all == True:
121 subscriptions = get_bugdir_subscribers(bd, servers[0])
124 for estr in entity.extra_strings:
125 if estr.startswith(TAG):
126 subscriptions.append(estr[len(TAG):])
128 if len(subscriptions) > 0:
129 print "Subscriptions for %s:" % entity_name
130 print '\n'.join(subscriptions)
134 parser = cmdutil.CmdOptionParser("be subscribe ID")
135 parser.add_option("-u", "--unsubscribe", action="store_true",
136 dest="unsubscribe", default=False,
137 help="Unsubscribe instead of subscribing.")
138 parser.add_option("-a", "--list-all", action="store_true",
139 dest="list_all", default=False,
140 help="List all subscribers (no ID argument, read only action).")
141 parser.add_option("-l", "--list", action="store_true",
142 dest="list", default=False,
143 help="List subscribers (read only action).")
144 parser.add_option("-s", "--subscriber", dest="subscriber",
145 metavar="SUBSCRIBER",
146 help="Email address of the subscriber (defaults to bugdir.user_id).")
147 parser.add_option("-S", "--servers", dest="servers", metavar="SERVERS",
148 help="Servers from which you want notification.")
149 parser.add_option("-t", "--type", dest="types", metavar="TYPES",
150 help="Types of changes you wish to be notified about.")
154 ID can be either a bug id, or blank/"DIR", in which case it refers to the
157 SERVERS specifies the servers from which you would like to receive
158 notification. Multiple severs may be specified in a comma-separated
159 list, or you can use "*" to match all servers (the default). If you
160 have not selected a server, it should politely refrain from notifying
161 you of changes, although there is no way to guarantee this behavior.
169 For unsubscription, any listed SERVERS and TYPES are removed from your
170 subscription. Either the catch-all server "*" or type "%s" will
171 remove SUBSCRIBER entirely from the specified ID.
173 This command is intended for use primarily by public interfaces, since
174 if you're just hacking away on your private repository, you'll known
175 what's changed ;). This command just (un)sets the appropriate
176 subscriptions, and leaves it up to each interface to perform the
178 """ % (diff.BUG_TYPE_ALL.string_tree(6), diff.BUGDIR_ID,
179 diff.BUGDIR_TYPE_ALL.string_tree(6),
180 diff.BUGDIR_TYPE_ALL)
183 return get_parser().help_str() + longhelp
185 # internal helper functions
187 def _generate_string(subscriber, types, servers):
188 types = sorted([str(t) for t in types])
189 servers = sorted(servers)
190 return "%s%s\t%s\t%s" % (TAG,subscriber,",".join(types),",".join(servers))
192 def _parse_string(string, type_root):
193 assert string.startswith(TAG), string
194 string = string[len(TAG):]
195 subscriber,types,servers = string.split("\t")
196 types = [diff.type_from_name(name, type_root) for name in types.split(",")]
197 return (subscriber,types,servers.split(","))
199 def _get_subscriber(extra_strings, subscriber, type_root):
200 for i,string in enumerate(extra_strings):
201 if string.startswith(TAG):
202 s,ts,srvs = _parse_string(string, type_root)
204 return i,s,ts,srvs # match!
205 return None # no match
207 # functions exposed to other modules
209 def subscribe(extra_strings, subscriber, types, servers, type_root):
210 args = _get_subscriber(extra_strings, subscriber, type_root)
211 if args == None: # no match
212 extra_strings.append(_generate_string(subscriber, types, servers))
214 # Alter matched string
219 # remove descendant types
220 all_ts = copy.copy(ts)
223 if tt in ts and t.has_descendant(tt):
225 if "*" in servers+srvs:
228 srvs = list(set(servers+srvs))
229 extra_strings[i] = _generate_string(subscriber, ts, srvs)
232 def unsubscribe(extra_strings, subscriber, types, servers, type_root):
233 args = _get_subscriber(extra_strings, subscriber, type_root)
234 if args == None: # no match
235 return extra_strings # pass
236 # Remove matched string
238 all_ts = copy.copy(ts)
241 if tt in ts and t.has_descendant(tt):
243 if "*" in servers+srvs:
249 if len(ts) == 0 or len(srvs) == 0:
252 extra_strings[i] = _generate_string(subscriber, ts, srvs)
255 def get_subscribers(extra_strings, type, server, type_root,
256 match_ancestor_types=False,
257 match_descendant_types=False):
259 Set match_ancestor_types=True if you want to find eveyone who
260 cares about your particular type.
262 Set match_descendant_types=True if you want to find subscribers
263 who may only care about some subset of your type. This is useful
264 for generating lists of all the subscribers in a given set of
267 >>> def sgs(*args, **kwargs):
268 ... return sorted(get_subscribers(*args, **kwargs))
270 >>> es = subscribe(es, "John Doe <j@doe.com>", [diff.BUGDIR_TYPE_ALL],
271 ... ["a.com"], diff.BUGDIR_TYPE_ALL)
272 >>> es = subscribe(es, "Jane Doe <J@doe.com>", [diff.BUGDIR_TYPE_NEW],
273 ... ["*"], diff.BUGDIR_TYPE_ALL)
274 >>> sgs(es, diff.BUGDIR_TYPE_ALL, "a.com", diff.BUGDIR_TYPE_ALL)
275 ['John Doe <j@doe.com>']
276 >>> sgs(es, diff.BUGDIR_TYPE_ALL, "a.com", diff.BUGDIR_TYPE_ALL,
277 ... match_descendant_types=True)
278 ['Jane Doe <J@doe.com>', 'John Doe <j@doe.com>']
279 >>> sgs(es, diff.BUGDIR_TYPE_ALL, "b.net", diff.BUGDIR_TYPE_ALL,
280 ... match_descendant_types=True)
281 ['Jane Doe <J@doe.com>']
282 >>> sgs(es, diff.BUGDIR_TYPE_NEW, "a.com", diff.BUGDIR_TYPE_ALL)
283 ['Jane Doe <J@doe.com>']
284 >>> sgs(es, diff.BUGDIR_TYPE_NEW, "a.com", diff.BUGDIR_TYPE_ALL,
285 ... match_ancestor_types=True)
286 ['Jane Doe <J@doe.com>', 'John Doe <j@doe.com>']
288 for string in extra_strings:
289 if not string.startswith(TAG):
291 subscriber,types,servers = _parse_string(string, type_root)
295 if type_match == False and match_ancestor_types == True:
297 if t.has_descendant(type):
300 if type_match == False and match_descendant_types == True:
302 if type.has_descendant(t):
306 if server in servers or servers == ["*"] or server == "*":
308 if type_match == True and server_match == True:
311 def get_bugdir_subscribers(bugdir, server):
313 I have a bugdir. Who cares about it, and what do they care about?
314 Returns a dict of dicts:
315 subscribers[user][id] = types
316 where id is either a bug.uuid (in the case of a bug subscription)
317 or "%(bugdir_id)s" (in the case of a bugdir subscription).
319 Only checks bugs that are currently in memory, so you might want
320 to call bugdir.load_all_bugs() first.
322 >>> bd = bugdir.SimpleBugDir(sync_with_disk=False)
323 >>> a = bd.bug_from_shortname("a")
324 >>> bd.extra_strings = subscribe(bd.extra_strings, "John Doe <j@doe.com>",
325 ... [diff.BUGDIR_TYPE_ALL], ["a.com"], diff.BUGDIR_TYPE_ALL)
326 >>> bd.extra_strings = subscribe(bd.extra_strings, "Jane Doe <J@doe.com>",
327 ... [diff.BUGDIR_TYPE_NEW], ["*"], diff.BUGDIR_TYPE_ALL)
328 >>> a.extra_strings = subscribe(a.extra_strings, "John Doe <j@doe.com>",
329 ... [diff.BUG_TYPE_ALL], ["a.com"], diff.BUG_TYPE_ALL)
330 >>> subscribers = get_bugdir_subscribers(bd, "a.com")
331 >>> subscribers["Jane Doe <J@doe.com>"]["%(bugdir_id)s"]
332 [<SubscriptionType: new>]
333 >>> subscribers["John Doe <j@doe.com>"]["%(bugdir_id)s"]
334 [<SubscriptionType: all>]
335 >>> subscribers["John Doe <j@doe.com>"]["a"]
336 [<SubscriptionType: all>]
337 >>> get_bugdir_subscribers(bd, "b.net")
338 {'Jane Doe <J@doe.com>': {'%(bugdir_id)s': [<SubscriptionType: new>]}}
340 """ % {'bugdir_id':diff.BUGDIR_ID}
342 for sub in get_subscribers(bugdir.extra_strings, diff.BUGDIR_TYPE_ALL,
343 server, diff.BUGDIR_TYPE_ALL,
344 match_descendant_types=True):
345 i,s,ts,srvs = _get_subscriber(bugdir.extra_strings, sub,
346 diff.BUGDIR_TYPE_ALL)
347 subscribers[sub] = {"DIR":ts}
349 for sub in get_subscribers(bug.extra_strings, diff.BUG_TYPE_ALL,
350 server, diff.BUG_TYPE_ALL,
351 match_descendant_types=True):
352 i,s,ts,srvs = _get_subscriber(bug.extra_strings, sub,
354 if sub in subscribers:
355 subscribers[sub][bug.uuid] = ts
357 subscribers[sub] = {bug.uuid:ts}