Merge lp://staging/~ibid-core/ibid/feature-discovery-399667 into lp://staging/~ibid-core/ibid/old-trunk-1.6
- feature-discovery-399667
- Merge into old-trunk-1.6
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Jonathan Hitchcock | ||||
Approved revision: | 921 | ||||
Merged at revision: | 907 | ||||
Proposed branch: | lp://staging/~ibid-core/ibid/feature-discovery-399667 | ||||
Merge into: | lp://staging/~ibid-core/ibid/old-trunk-1.6 | ||||
Diff against target: |
3065 lines (+1243/-454) 44 files modified
ibid/__init__.py (+1/-0) ibid/lib/stemmer.py (+367/-0) ibid/plugins/__init__.py (+24/-0) ibid/plugins/admin.py (+42/-23) ibid/plugins/ascii.py (+14/-6) ibid/plugins/buildbot.py (+6/-4) ibid/plugins/bzr.py (+6/-3) ibid/plugins/calc.py (+22/-10) ibid/plugins/codecontest.py (+11/-3) ibid/plugins/conversions.py (+25/-13) ibid/plugins/eval.py (+10/-7) ibid/plugins/factoid.py (+21/-18) ibid/plugins/feeds.py (+8/-5) ibid/plugins/film.py (+13/-7) ibid/plugins/fun.py (+26/-16) ibid/plugins/games.py (+22/-19) ibid/plugins/gameservers.py (+8/-6) ibid/plugins/geography.py (+25/-13) ibid/plugins/google.py (+8/-7) ibid/plugins/help.py (+218/-85) ibid/plugins/icecast.py (+6/-5) ibid/plugins/identity.py (+32/-21) ibid/plugins/karma.py (+10/-7) ibid/plugins/languages.py (+14/-8) ibid/plugins/lotto.py (+7/-4) ibid/plugins/meetings.py (+14/-8) ibid/plugins/memo.py (+10/-7) ibid/plugins/memory.py (+7/-6) ibid/plugins/network.py (+50/-25) ibid/plugins/oeis.py (+6/-5) ibid/plugins/quotes.py (+37/-19) ibid/plugins/rfc.py (+6/-3) ibid/plugins/seen.py (+7/-4) ibid/plugins/social.py (+14/-7) ibid/plugins/sources.py (+23/-13) ibid/plugins/strings.py (+38/-19) ibid/plugins/svn.py (+6/-3) ibid/plugins/sysadmin.py (+25/-13) ibid/plugins/test.py (+24/-14) ibid/plugins/trac.py (+6/-3) ibid/plugins/urlgrab.py (+5/-4) ibid/plugins/urlinfo.py (+15/-8) scripts/ibid-plugin (+3/-3) setup.py (+1/-0) |
||||
To merge this branch: | bzr merge lp://staging/~ibid-core/ibid/feature-discovery-399667 | ||||
Related bugs: |
|
||||
Related blueprints: |
Categories for plugins
(High)
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Keegan Carruthers-Smith | Abstain | ||
Jonathan Hitchcock | Approve | ||
marcog (community) | Approve | ||
Michael Gorven | Approve | ||
Review via email: mp+19785@code.staging.launchpad.net |
Commit message
Description of the change
Stefano Rivera (stefanor) wrote : | # |
- 900. By Stefano Rivera
-
Relocate usage with long descriptions
- 901. By Stefano Rivera
-
Switch from dostrings -> usage attributes
- 902. By Stefano Rivera
-
Merge from trunk
- 903. By Stefano Rivera
-
Inspect isn't needed any more
- 904. By Stefano Rivera
-
Apply spit-and-polish to help system
Stefano Rivera (stefanor) wrote : | # |
Next round of review, please. helpbid in #ibid
- 905. By Stefano Rivera
-
Update usage string
- 906. By Stefano Rivera
-
Satisfy marcog with a silly-person catcher
- 907. By Stefano Rivera
-
Spelling
Stefano Rivera (stefanor) wrote : | # |
Observed silly use:
21:22 <&Siggi> tibid: can you do Markov?
21:22 < tibid> Siggi: I'll remember that
21:23 <&Siggi> tibid: can you dance?
21:23 < tibid> Siggi: Excuse me?
Michael Gorven (mgorven) : | # |
- 908. By Stefano Rivera
-
Merge from trunk
- 909. By Stefano Rivera
-
Merge from trunk
Jonathan Hitchcock (vhata) wrote : | # |
14:58 <Vhata> tibid: how do I use message?
14:58 <tibid> Vhata: Please be more specific. I don't know if you mean memo or memo
- 910. By Stefano Rivera
-
Use set for usage search results
Stefano Rivera (stefanor) wrote : | # |
> 14:58 <Vhata> tibid: how do I use message?
> 14:58 <tibid> Vhata: Please be more specific. I don't know if you mean memo or
> memo
Fixed in r910
Keegan Carruthers-Smith (keegan-csmith) wrote : | # |
> 23:24 <casted> features
> 23:24 <tibid> I can: look things up, remember things, deliver messages, play
> games, make decisions, keep an eye on things, browse the
> internet, perform conversions, perform calculations, do silly
> fun stuff, help you with system administration, monitor software
> develpment and do south african stuff
> 23:24 <tibid> Ask me "what can you ..." for more details
s/develpment/
Also for "monitor software development" it is non-obvious you have to go "what
can you development". I was trying "what can you monitor" and "what can you
monitor software development". Maybe the second approach should be a fallback.
> 23:28 <tibid> I can look things up with: google, help, unicode, tinyurl,
> weather, imdb, aptitude, seen, factoid, flight, lotto, fml,
> dict, dns, lastfm, translate, oeis, tld, mac, mlia, bash, man,
> apt-file, distance, bible, fortune, microblog, youtube, rfc,
> tvshow, feeds and ports
It would be useful if this was shown in sorted order.
Just played with the feature for a little bit, will review code tomorrow. But for now this is all I can pick up.
- 911. By Stefano Rivera
-
Typo
- 912. By Stefano Rivera
-
Add first ellipsis to "what ... can you ..."
Stefano Rivera (stefanor) wrote : | # |
> s/develpment/
r911
> Also for "monitor software development" it is non-obvious you have to go "what
> can you development". I was trying "what can you monitor" and "what can you
> monitor software development". Maybe the second approach should be a fallback.
There's an alternate syntax that helps phrase those questions. See r912
> It would be useful if this was shown in sorted order.
r913
- 913. By Stefano Rivera
-
Sort features
Keegan Carruthers-Smith (keegan-csmith) wrote : | # |
> 13:15 <tibid> Ask me "what ... can you ..." for more details
What is the purpose of the first ...?
Stefano Rivera (stefanor) wrote : | # |
> What is the purpose of the first ...?
To help the user form valid sentences. It is used in search.
marcog (marco-gallotta) wrote : | # |
We use ellipses here, but the usage strings use <foo> notation. Consistency?
Query: what silly fun stuff do you do?
Response: Got it
Perhaps that syntax should work too?
Query: how do i use rot
Response: I'm afraid I don't know what you are asking about. Ask "what can you do" to browse my features
Would be nice if it suggested "Did you mean rot13"?
Haven't looked much at the code yet though.
Stefano Rivera (stefanor) wrote : | # |
> We use ellipses here, but the usage strings use <foo> notation. Consistency?
Yeah, not ideal but <brackets> don't seem to work there grammatically.
> Query: what silly fun stuff do you do?
> Response: Got it
There are a hell of a lot of those type of things. I'd say let's ignore them for 0.1.
> Query: how do i use rot
> Response: I'm afraid I don't know what you are asking about. Ask "what can you
> do" to browse my features
>
> Would be nice if it suggested "Did you mean rot13"?
That is possible via substring searches. Don't know how vital it is.
marcog (marco-gallotta) wrote : | # |
> > We use ellipses here, but the usage strings use <foo> notation. Consistency?
>
> Yeah, not ideal but <brackets> don't seem to work there grammatically.
Ok, I'll grudgingly let that slip for now. However, I do feel the inconsistency is quite poor long-term.
> > Query: what silly fun stuff do you do?
> > Response: Got it
>
> There are a hell of a lot of those type of things. I'd say let's ignore them
> for 0.1.
Fair enough.
> > Query: how do i use rot
> > Response: I'm afraid I don't know what you are asking about. Ask "what can
> you
> > do" to browse my features
> >
> > Would be nice if it suggested "Did you mean rot13"?
>
> That is possible via substring searches. Don't know how vital it is.
I think at the very least, we should look for the same feature in plural/singular form (i.e. drop/add an 's'). This is the source of the most failed lookups.
- 914. By Stefano Rivera
-
Merge from trunk
- 915. By Stefano Rivera
-
Use a Porter stemmer in help searches. Include a pure-Python implementation until PyStemmer is available in Debian
Stefano Rivera (stefanor) wrote : | # |
> I think at the very least, we should look for the same feature in
> plural/singular form (i.e. drop/add an 's'). This is the source of the most
> failed lookups.
r915
- 916. By Stefano Rivera
-
Reword usage text, indent usage lines
marcog (marco-gallotta) : | # |
- 917. By Stefano Rivera
-
Use noun phrases for category descriptions
- 918. By Stefano Rivera
-
Update ellipsis cather
- 919. By Stefano Rivera
-
Remove syntactic characters when generating usage keywords
Jonathan Hitchcock (vhata) wrote : | # |
Line 394: "adminstrative functions" - typo
Line 2406: "ategories" - typo
- 920. By Stefano Rivera
-
Don't treat keys in 'features' and 'categories' as being correct, they are stemmed
Stefano Rivera (stefanor) wrote : | # |
> Line 394: "adminstrative functions" - typo
>
> Line 2406: "ategories" - typo
r921
- 921. By Stefano Rivera
-
Typos spotted by Vhata
Jonathan Hitchcock (vhata) wrote : | # |
Proper categorisation -> Phase 2
Keegan Carruthers-Smith (keegan-csmith) : | # |
Preview Diff
1 | === modified file 'ibid/__init__.py' |
2 | --- ibid/__init__.py 2010-02-16 09:41:11 +0000 |
3 | +++ ibid/__init__.py 2010-03-02 19:03:13 +0000 |
4 | @@ -83,6 +83,7 @@ |
5 | config = {} |
6 | dispatcher = None |
7 | processors = [] |
8 | +categories = {} |
9 | reloader = None |
10 | databases = {} |
11 | auth = None |
12 | |
13 | === added file 'ibid/lib/stemmer.py' |
14 | --- ibid/lib/stemmer.py 1970-01-01 00:00:00 +0000 |
15 | +++ ibid/lib/stemmer.py 2010-03-02 19:03:13 +0000 |
16 | @@ -0,0 +1,367 @@ |
17 | +#!/usr/bin/env python |
18 | + |
19 | +"""Porter Stemming Algorithm |
20 | +This is the Porter stemming algorithm, ported to Python from the |
21 | +version coded up in ANSI C by the author. It may be be regarded |
22 | +as canonical, in that it follows the algorithm presented in |
23 | + |
24 | +Porter, 1980, An algorithm for suffix stripping, Program, Vol. 14, |
25 | +no. 3, pp 130-137, |
26 | + |
27 | +only differing from it at the points maked --DEPARTURE-- below. |
28 | + |
29 | +See also http://www.tartarus.org/~martin/PorterStemmer |
30 | + |
31 | +The algorithm as described in the paper could be exactly replicated |
32 | +by adjusting the points of DEPARTURE, but this is barely necessary, |
33 | +because (a) the points of DEPARTURE are definitely improvements, and |
34 | +(b) no encoding of the Porter stemmer I have seen is anything like |
35 | +as exact as this version, even with the points of DEPARTURE! |
36 | + |
37 | +Vivake Gupta (v@nano.com) |
38 | + |
39 | +Release 1: January 2001 |
40 | + |
41 | +Further adjustments by Santiago Bruno (bananabruno@gmail.com) |
42 | +to allow word input not restricted to one word per line, leading |
43 | +to: |
44 | + |
45 | +release 2: July 2008 |
46 | +""" |
47 | + |
48 | +import sys |
49 | + |
50 | +class PorterStemmer: |
51 | + |
52 | + def __init__(self): |
53 | + """The main part of the stemming algorithm starts here. |
54 | + b is a buffer holding a word to be stemmed. The letters are in b[k0], |
55 | + b[k0+1] ... ending at b[k]. In fact k0 = 0 in this demo program. k is |
56 | + readjusted downwards as the stemming progresses. Zero termination is |
57 | + not in fact used in the algorithm. |
58 | + |
59 | + Note that only lower case sequences are stemmed. Forcing to lower case |
60 | + should be done before stem(...) is called. |
61 | + """ |
62 | + |
63 | + self.b = "" # buffer for word to be stemmed |
64 | + self.k = 0 |
65 | + self.k0 = 0 |
66 | + self.j = 0 # j is a general offset into the string |
67 | + |
68 | + def cons(self, i): |
69 | + """cons(i) is TRUE <=> b[i] is a consonant.""" |
70 | + if self.b[i] == 'a' or self.b[i] == 'e' or self.b[i] == 'i' or self.b[i] == 'o' or self.b[i] == 'u': |
71 | + return 0 |
72 | + if self.b[i] == 'y': |
73 | + if i == self.k0: |
74 | + return 1 |
75 | + else: |
76 | + return (not self.cons(i - 1)) |
77 | + return 1 |
78 | + |
79 | + def m(self): |
80 | + """m() measures the number of consonant sequences between k0 and j. |
81 | + if c is a consonant sequence and v a vowel sequence, and <..> |
82 | + indicates arbitrary presence, |
83 | + |
84 | + <c><v> gives 0 |
85 | + <c>vc<v> gives 1 |
86 | + <c>vcvc<v> gives 2 |
87 | + <c>vcvcvc<v> gives 3 |
88 | + .... |
89 | + """ |
90 | + n = 0 |
91 | + i = self.k0 |
92 | + while 1: |
93 | + if i > self.j: |
94 | + return n |
95 | + if not self.cons(i): |
96 | + break |
97 | + i = i + 1 |
98 | + i = i + 1 |
99 | + while 1: |
100 | + while 1: |
101 | + if i > self.j: |
102 | + return n |
103 | + if self.cons(i): |
104 | + break |
105 | + i = i + 1 |
106 | + i = i + 1 |
107 | + n = n + 1 |
108 | + while 1: |
109 | + if i > self.j: |
110 | + return n |
111 | + if not self.cons(i): |
112 | + break |
113 | + i = i + 1 |
114 | + i = i + 1 |
115 | + |
116 | + def vowelinstem(self): |
117 | + """vowelinstem() is TRUE <=> k0,...j contains a vowel""" |
118 | + for i in range(self.k0, self.j + 1): |
119 | + if not self.cons(i): |
120 | + return 1 |
121 | + return 0 |
122 | + |
123 | + def doublec(self, j): |
124 | + """doublec(j) is TRUE <=> j,(j-1) contain a double consonant.""" |
125 | + if j < (self.k0 + 1): |
126 | + return 0 |
127 | + if (self.b[j] != self.b[j-1]): |
128 | + return 0 |
129 | + return self.cons(j) |
130 | + |
131 | + def cvc(self, i): |
132 | + """cvc(i) is TRUE <=> i-2,i-1,i has the form consonant - vowel - consonant |
133 | + and also if the second c is not w,x or y. this is used when trying to |
134 | + restore an e at the end of a short e.g. |
135 | + |
136 | + cav(e), lov(e), hop(e), crim(e), but |
137 | + snow, box, tray. |
138 | + """ |
139 | + if i < (self.k0 + 2) or not self.cons(i) or self.cons(i-1) or not self.cons(i-2): |
140 | + return 0 |
141 | + ch = self.b[i] |
142 | + if ch == 'w' or ch == 'x' or ch == 'y': |
143 | + return 0 |
144 | + return 1 |
145 | + |
146 | + def ends(self, s): |
147 | + """ends(s) is TRUE <=> k0,...k ends with the string s.""" |
148 | + length = len(s) |
149 | + if s[length - 1] != self.b[self.k]: # tiny speed-up |
150 | + return 0 |
151 | + if length > (self.k - self.k0 + 1): |
152 | + return 0 |
153 | + if self.b[self.k-length+1:self.k+1] != s: |
154 | + return 0 |
155 | + self.j = self.k - length |
156 | + return 1 |
157 | + |
158 | + def setto(self, s): |
159 | + """setto(s) sets (j+1),...k to the characters in the string s, readjusting k.""" |
160 | + length = len(s) |
161 | + self.b = self.b[:self.j+1] + s + self.b[self.j+length+1:] |
162 | + self.k = self.j + length |
163 | + |
164 | + def r(self, s): |
165 | + """r(s) is used further down.""" |
166 | + if self.m() > 0: |
167 | + self.setto(s) |
168 | + |
169 | + def step1ab(self): |
170 | + """step1ab() gets rid of plurals and -ed or -ing. e.g. |
171 | + |
172 | + caresses -> caress |
173 | + ponies -> poni |
174 | + ties -> ti |
175 | + caress -> caress |
176 | + cats -> cat |
177 | + |
178 | + feed -> feed |
179 | + agreed -> agree |
180 | + disabled -> disable |
181 | + |
182 | + matting -> mat |
183 | + mating -> mate |
184 | + meeting -> meet |
185 | + milling -> mill |
186 | + messing -> mess |
187 | + |
188 | + meetings -> meet |
189 | + """ |
190 | + if self.b[self.k] == 's': |
191 | + if self.ends("sses"): |
192 | + self.k = self.k - 2 |
193 | + elif self.ends("ies"): |
194 | + self.setto("i") |
195 | + elif self.b[self.k - 1] != 's': |
196 | + self.k = self.k - 1 |
197 | + if self.ends("eed"): |
198 | + if self.m() > 0: |
199 | + self.k = self.k - 1 |
200 | + elif (self.ends("ed") or self.ends("ing")) and self.vowelinstem(): |
201 | + self.k = self.j |
202 | + if self.ends("at"): self.setto("ate") |
203 | + elif self.ends("bl"): self.setto("ble") |
204 | + elif self.ends("iz"): self.setto("ize") |
205 | + elif self.doublec(self.k): |
206 | + self.k = self.k - 1 |
207 | + ch = self.b[self.k] |
208 | + if ch == 'l' or ch == 's' or ch == 'z': |
209 | + self.k = self.k + 1 |
210 | + elif (self.m() == 1 and self.cvc(self.k)): |
211 | + self.setto("e") |
212 | + |
213 | + def step1c(self): |
214 | + """step1c() turns terminal y to i when there is another vowel in the stem.""" |
215 | + if (self.ends("y") and self.vowelinstem()): |
216 | + self.b = self.b[:self.k] + 'i' + self.b[self.k+1:] |
217 | + |
218 | + def step2(self): |
219 | + """step2() maps double suffices to single ones. |
220 | + so -ization ( = -ize plus -ation) maps to -ize etc. note that the |
221 | + string before the suffix must give m() > 0. |
222 | + """ |
223 | + if self.b[self.k - 1] == 'a': |
224 | + if self.ends("ational"): self.r("ate") |
225 | + elif self.ends("tional"): self.r("tion") |
226 | + elif self.b[self.k - 1] == 'c': |
227 | + if self.ends("enci"): self.r("ence") |
228 | + elif self.ends("anci"): self.r("ance") |
229 | + elif self.b[self.k - 1] == 'e': |
230 | + if self.ends("izer"): self.r("ize") |
231 | + elif self.b[self.k - 1] == 'l': |
232 | + if self.ends("bli"): self.r("ble") # --DEPARTURE-- |
233 | + # To match the published algorithm, replace this phrase with |
234 | + # if self.ends("abli"): self.r("able") |
235 | + elif self.ends("alli"): self.r("al") |
236 | + elif self.ends("entli"): self.r("ent") |
237 | + elif self.ends("eli"): self.r("e") |
238 | + elif self.ends("ousli"): self.r("ous") |
239 | + elif self.b[self.k - 1] == 'o': |
240 | + if self.ends("ization"): self.r("ize") |
241 | + elif self.ends("ation"): self.r("ate") |
242 | + elif self.ends("ator"): self.r("ate") |
243 | + elif self.b[self.k - 1] == 's': |
244 | + if self.ends("alism"): self.r("al") |
245 | + elif self.ends("iveness"): self.r("ive") |
246 | + elif self.ends("fulness"): self.r("ful") |
247 | + elif self.ends("ousness"): self.r("ous") |
248 | + elif self.b[self.k - 1] == 't': |
249 | + if self.ends("aliti"): self.r("al") |
250 | + elif self.ends("iviti"): self.r("ive") |
251 | + elif self.ends("biliti"): self.r("ble") |
252 | + elif self.b[self.k - 1] == 'g': # --DEPARTURE-- |
253 | + if self.ends("logi"): self.r("log") |
254 | + # To match the published algorithm, delete this phrase |
255 | + |
256 | + def step3(self): |
257 | + """step3() dels with -ic-, -full, -ness etc. similar strategy to step2.""" |
258 | + if self.b[self.k] == 'e': |
259 | + if self.ends("icate"): self.r("ic") |
260 | + elif self.ends("ative"): self.r("") |
261 | + elif self.ends("alize"): self.r("al") |
262 | + elif self.b[self.k] == 'i': |
263 | + if self.ends("iciti"): self.r("ic") |
264 | + elif self.b[self.k] == 'l': |
265 | + if self.ends("ical"): self.r("ic") |
266 | + elif self.ends("ful"): self.r("") |
267 | + elif self.b[self.k] == 's': |
268 | + if self.ends("ness"): self.r("") |
269 | + |
270 | + def step4(self): |
271 | + """step4() takes off -ant, -ence etc., in context <c>vcvc<v>.""" |
272 | + if self.b[self.k - 1] == 'a': |
273 | + if self.ends("al"): pass |
274 | + else: return |
275 | + elif self.b[self.k - 1] == 'c': |
276 | + if self.ends("ance"): pass |
277 | + elif self.ends("ence"): pass |
278 | + else: return |
279 | + elif self.b[self.k - 1] == 'e': |
280 | + if self.ends("er"): pass |
281 | + else: return |
282 | + elif self.b[self.k - 1] == 'i': |
283 | + if self.ends("ic"): pass |
284 | + else: return |
285 | + elif self.b[self.k - 1] == 'l': |
286 | + if self.ends("able"): pass |
287 | + elif self.ends("ible"): pass |
288 | + else: return |
289 | + elif self.b[self.k - 1] == 'n': |
290 | + if self.ends("ant"): pass |
291 | + elif self.ends("ement"): pass |
292 | + elif self.ends("ment"): pass |
293 | + elif self.ends("ent"): pass |
294 | + else: return |
295 | + elif self.b[self.k - 1] == 'o': |
296 | + if self.ends("ion") and (self.b[self.j] == 's' or self.b[self.j] == 't'): pass |
297 | + elif self.ends("ou"): pass |
298 | + # takes care of -ous |
299 | + else: return |
300 | + elif self.b[self.k - 1] == 's': |
301 | + if self.ends("ism"): pass |
302 | + else: return |
303 | + elif self.b[self.k - 1] == 't': |
304 | + if self.ends("ate"): pass |
305 | + elif self.ends("iti"): pass |
306 | + else: return |
307 | + elif self.b[self.k - 1] == 'u': |
308 | + if self.ends("ous"): pass |
309 | + else: return |
310 | + elif self.b[self.k - 1] == 'v': |
311 | + if self.ends("ive"): pass |
312 | + else: return |
313 | + elif self.b[self.k - 1] == 'z': |
314 | + if self.ends("ize"): pass |
315 | + else: return |
316 | + else: |
317 | + return |
318 | + if self.m() > 1: |
319 | + self.k = self.j |
320 | + |
321 | + def step5(self): |
322 | + """step5() removes a final -e if m() > 1, and changes -ll to -l if |
323 | + m() > 1. |
324 | + """ |
325 | + self.j = self.k |
326 | + if self.b[self.k] == 'e': |
327 | + a = self.m() |
328 | + if a > 1 or (a == 1 and not self.cvc(self.k-1)): |
329 | + self.k = self.k - 1 |
330 | + if self.b[self.k] == 'l' and self.doublec(self.k) and self.m() > 1: |
331 | + self.k = self.k -1 |
332 | + |
333 | + def stem(self, p, i, j): |
334 | + """In stem(p,i,j), p is a char pointer, and the string to be stemmed |
335 | + is from p[i] to p[j] inclusive. Typically i is zero and j is the |
336 | + offset to the last character of a string, (p[j+1] == '\0'). The |
337 | + stemmer adjusts the characters p[i] ... p[j] and returns the new |
338 | + end-point of the string, k. Stemming never increases word length, so |
339 | + i <= k <= j. To turn the stemmer into a module, declare 'stem' as |
340 | + extern, and delete the remainder of this file. |
341 | + """ |
342 | + # copy the parameters into statics |
343 | + self.b = p |
344 | + self.k = j |
345 | + self.k0 = i |
346 | + if self.k <= self.k0 + 1: |
347 | + return self.b # --DEPARTURE-- |
348 | + |
349 | + # With this line, strings of length 1 or 2 don't go through the |
350 | + # stemming process, although no mention is made of this in the |
351 | + # published algorithm. Remove the line to match the published |
352 | + # algorithm. |
353 | + |
354 | + self.step1ab() |
355 | + self.step1c() |
356 | + self.step2() |
357 | + self.step3() |
358 | + self.step4() |
359 | + self.step5() |
360 | + return self.b[self.k0:self.k+1] |
361 | + |
362 | + |
363 | +if __name__ == '__main__': |
364 | + p = PorterStemmer() |
365 | + if len(sys.argv) > 1: |
366 | + for f in sys.argv[1:]: |
367 | + infile = open(f, 'r') |
368 | + while 1: |
369 | + output = '' |
370 | + word = '' |
371 | + line = infile.readline() |
372 | + if line == '': |
373 | + break |
374 | + for c in line: |
375 | + if c.isalpha(): |
376 | + word += c.lower() |
377 | + else: |
378 | + if word: |
379 | + output += p.stem(word, 0,len(word)-1) |
380 | + word = '' |
381 | + output += c.lower() |
382 | + print output, |
383 | + infile.close() |
384 | |
385 | === modified file 'ibid/plugins/__init__.py' |
386 | --- ibid/plugins/__init__.py 2010-02-23 11:45:13 +0000 |
387 | +++ ibid/plugins/__init__.py 2010-03-02 19:03:13 +0000 |
388 | @@ -27,6 +27,30 @@ |
389 | |
390 | __path__ = pluginPackagePaths(__name__) + __path__ |
391 | |
392 | +for cat, desc, weight in ( |
393 | + ('account', u'bot accounts and permissions', None), |
394 | + ('admin', u'administrative functions', None), |
395 | + ('calculate', u'calculations', 0), |
396 | + ('convert', u'conversions', 0), |
397 | + ('debug', u'debugging me', None), |
398 | + ('decide', u'decisions', -2), |
399 | + ('development', u'software development', 10), |
400 | + ('fun', u'silly fun stuff', 0), |
401 | + ('game', u'games', -2), |
402 | + ('lookup', u'looking things up', -10), |
403 | + ('monitor', u'monitoring things', -2), |
404 | + ('remember', u'remembering things', -5), |
405 | + ('web', u'browsing the Internet', 0), |
406 | + ('message', u'delivering messages', -5), |
407 | + ('south africa', u'South African stuff', 10), |
408 | + ('sysadmin', u'System Administration', 5), |
409 | + ): |
410 | + if cat not in ibid.categories: |
411 | + ibid.categories[cat] = { |
412 | + 'description': desc, |
413 | + 'weight': weight, |
414 | + } |
415 | + |
416 | class Processor(object): |
417 | """Base class for Ibid plugins. |
418 | Processors receive events and (optionally) do things with them. |
419 | |
420 | === modified file 'ibid/plugins/admin.py' |
421 | --- ibid/plugins/admin.py 2010-01-18 23:20:33 +0000 |
422 | +++ ibid/plugins/admin.py 2010-03-02 19:03:13 +0000 |
423 | @@ -13,12 +13,15 @@ |
424 | |
425 | log = logging.getLogger('plugins.admin') |
426 | |
427 | -help = {} |
428 | +features = {} |
429 | |
430 | -help['plugins'] = u'Lists, loads and unloads plugins.' |
431 | +features['plugins'] = { |
432 | + 'description': u'Lists, loads and unloads plugins.', |
433 | + 'categories': ('admin',), |
434 | +} |
435 | class ListPLugins(Processor): |
436 | - u"""list plugins""" |
437 | - feature = 'plugins' |
438 | + usage = u'list plugins' |
439 | + feature = ('plugins',) |
440 | |
441 | @match(r'^lsmod|list\s+plugins$') |
442 | def handler(self, event): |
443 | @@ -29,10 +32,13 @@ |
444 | |
445 | event.addresponse(u'Plugins: %s', human_join(sorted(plugins)) or u'none') |
446 | |
447 | -help['core'] = u'Reloads core modules.' |
448 | +features['core'] = { |
449 | + 'description': u'Reloads core modules.', |
450 | + 'categories': ('admin',), |
451 | +} |
452 | class ReloadCoreModules(Processor): |
453 | - u"""reload (reloader|dispatcher|databases|auth)""" |
454 | - feature = 'core' |
455 | + usage = u'reload (reloader|dispatcher|databases|auth)' |
456 | + feature = ('core',) |
457 | |
458 | priority = -5 |
459 | permission = u'core' |
460 | @@ -49,8 +55,8 @@ |
461 | event.addresponse(result and u'%s reloaded' or u"Couldn't reload %s", module) |
462 | |
463 | class LoadModules(Processor): |
464 | - u"""(load|unload|reload) <plugin|processor>""" |
465 | - feature = 'plugins' |
466 | + usage = u'(load|unload|reload) <plugin|processor>' |
467 | + feature = ('plugins',) |
468 | |
469 | permission = u'plugins' |
470 | |
471 | @@ -67,10 +73,13 @@ |
472 | result = ibid.reloader.unload_processor(plugin) |
473 | event.addresponse(result and u'%s unloaded' or u"Couldn't unload %s", plugin) |
474 | |
475 | -help['die'] = u'Terminates the bot' |
476 | +features['die'] = { |
477 | + 'description': u'Terminates the bot', |
478 | + 'categories': ('admin',), |
479 | +} |
480 | class Die(Processor): |
481 | - u"""die""" |
482 | - feature = 'die' |
483 | + usage = u'die' |
484 | + feature = ('die',) |
485 | |
486 | permission = u'admin' |
487 | |
488 | @@ -79,11 +88,14 @@ |
489 | def die(self, event): |
490 | reactor.stop() |
491 | |
492 | -help['sources'] = u'Controls and lists the configured sources.' |
493 | +features['sources'] = { |
494 | + 'description': u'Controls and lists the configured sources.', |
495 | + 'categories': ('admin',), |
496 | +} |
497 | class Admin(Processor): |
498 | - u"""(connect|disconnect) (to|from) <source> |
499 | + usage = u"""(connect|disconnect) (to|from) <source> |
500 | load <source> source""" |
501 | - feature = 'sources' |
502 | + feature = ('sources',) |
503 | |
504 | permission = u'sources' |
505 | |
506 | @@ -116,8 +128,8 @@ |
507 | event.addresponse(u"Couldn't load %s source", source) |
508 | |
509 | class Info(Processor): |
510 | - u"""(sources|list configured sources)""" |
511 | - feature = 'sources' |
512 | + usage = u'(sources|list configured sources)' |
513 | + feature = ('sources',) |
514 | |
515 | @match(r'^sources$') |
516 | def list(self, event): |
517 | @@ -131,10 +143,13 @@ |
518 | def listall(self, event): |
519 | event.addresponse(u'Configured sources: %s', human_join(sorted(ibid.config.sources.keys())) or u'none') |
520 | |
521 | -help['version'] = u"Show the Ibid version currently running" |
522 | +features['version'] = { |
523 | + 'description': u'Show the Ibid version currently running', |
524 | + 'categories': ('admin',), |
525 | +} |
526 | class Version(Processor): |
527 | - u"""version""" |
528 | - feature = 'version' |
529 | + usage = u'version' |
530 | + feature = ('version',) |
531 | |
532 | @match(r'^version$') |
533 | def show_version(self, event): |
534 | @@ -143,12 +158,16 @@ |
535 | else: |
536 | event.addresponse(u"I don't know what version I am :-(") |
537 | |
538 | -help['config'] = u'Gets and sets configuration settings, and rereads the configuration file.' |
539 | +features['config'] = { |
540 | + 'description': u'Gets and sets configuration settings, and rereads the ' |
541 | + u'configuration file.', |
542 | + 'categories': ('admin',), |
543 | +} |
544 | class Config(Processor): |
545 | - u"""reread config |
546 | + usage = u"""reread config |
547 | set config <name> to <value> |
548 | get config <name>""" |
549 | - feature = 'config' |
550 | + feature = ('config',) |
551 | |
552 | priority = -10 |
553 | permission = u'config' |
554 | |
555 | === modified file 'ibid/plugins/ascii.py' |
556 | --- ibid/plugins/ascii.py 2010-01-24 18:38:02 +0000 |
557 | +++ ibid/plugins/ascii.py 2010-03-02 19:03:13 +0000 |
558 | @@ -27,11 +27,15 @@ |
559 | * pyfiglet (http://sourceforge.net/projects/pyfiglet/) |
560 | """ |
561 | |
562 | -help = { 'draw' : u'Retrieve images from the web and render them in ascii-art.', |
563 | - 'figlet': u'Render text in ascii-art using figlet.' } |
564 | +features = {} |
565 | + |
566 | +features['draw-aa'] = { |
567 | + 'description': u'Retrieve images from the web and render them in ASCII-art.', |
568 | + 'categories': ('fun', 'web',), |
569 | +} |
570 | class DrawImage(Processor): |
571 | - u"""draw <url> [in colour] [width <width>] [height <height>]""" |
572 | - feature = 'draw' |
573 | + usage = u'draw <url> [in colour] [width <width>] [height <height>]' |
574 | + feature = ('draw-aa',) |
575 | |
576 | max_filesize = IntOption('max_filesize', 'Only request this many KiB', 200) |
577 | def_height = IntOption('def_height', 'Default height for libaa output', 10) |
578 | @@ -142,10 +146,14 @@ |
579 | else: |
580 | event.addresponse(u"Sorry, that doesn't look like an image") |
581 | |
582 | +features['figlet'] = { |
583 | + 'description': u'Render text in ASCII-art using figlet.', |
584 | + 'categories': ('fun',), |
585 | +} |
586 | class WriteFiglet(Processor): |
587 | - u"""figlet <text> [in <font>] |
588 | + usage = u"""figlet <text> [in <font>] |
589 | list figlet fonts [from <index>]""" |
590 | - feature = 'figlet' |
591 | + feature = ('figlet',) |
592 | |
593 | max_width = IntOption('max_width', 'Maximum width for ascii output', 60) |
594 | fonts_ = Option('fonts', 'Directory or Zip file containing figlet fonts', |
595 | |
596 | === modified file 'ibid/plugins/buildbot.py' |
597 | --- ibid/plugins/buildbot.py 2010-01-18 23:20:33 +0000 |
598 | +++ ibid/plugins/buildbot.py 2010-03-02 19:03:13 +0000 |
599 | @@ -9,11 +9,13 @@ |
600 | from ibid.plugins import Processor, match, RPC |
601 | from ibid.config import Option, IntOption |
602 | |
603 | -help = {'buildbot': u'Displays buildbot build results and triggers builds.'} |
604 | - |
605 | +features = {'buildbot': { |
606 | + 'description': u'Displays buildbot build results and triggers builds.', |
607 | + 'categories': ('development',), |
608 | +}} |
609 | class BuildBot(Processor, RPC): |
610 | - u"""rebuild <branch> [ (revision|r) <number> ]""" |
611 | - feature = 'buildbot' |
612 | + usage = u'rebuild <branch> [ (revision|r) <number> ]' |
613 | + feature = ('buildbot',) |
614 | autoload = False |
615 | |
616 | server = Option('server', 'Buildbot server hostname', 'localhost') |
617 | |
618 | === modified file 'ibid/plugins/bzr.py' |
619 | --- ibid/plugins/bzr.py 2010-02-20 07:38:38 +0000 |
620 | +++ ibid/plugins/bzr.py 2010-03-02 19:03:13 +0000 |
621 | @@ -14,7 +14,10 @@ |
622 | from ibid.config import DictOption, IntOption |
623 | from ibid.utils import ago, format_date, human_join |
624 | |
625 | -help = {'bzr': u'Retrieves commit logs from a Bazaar repository.'} |
626 | +features = {'bzr': { |
627 | + 'description': u'Retrieves commit logs from a Bazaar repository.', |
628 | + 'categories': ('development',), |
629 | +}} |
630 | |
631 | class LogFormatter(log.LogFormatter): |
632 | |
633 | @@ -57,9 +60,9 @@ |
634 | self.to_file.write(commit) |
635 | |
636 | class Bazaar(Processor, RPC): |
637 | - u"""(last commit|commit <revno>) [to <repo>] [full] |
638 | + usage = u"""(last commit|commit <revno>) [to <repo>] [full] |
639 | repositories""" |
640 | - feature = 'bzr' |
641 | + feature = ('bzr',) |
642 | autoload = False |
643 | |
644 | repositories = DictOption('repositories', 'Dict of repositories names and URLs') |
645 | |
646 | === modified file 'ibid/plugins/calc.py' |
647 | --- ibid/plugins/calc.py 2010-02-24 12:47:25 +0000 |
648 | +++ ibid/plugins/calc.py 2010-03-02 19:03:13 +0000 |
649 | @@ -27,14 +27,17 @@ |
650 | class FD_ExpressionCodeGenerator(pycodegen.ExpressionCodeGenerator): |
651 | futures = ('division',) |
652 | |
653 | -help = {} |
654 | +features = {} |
655 | log = logging.getLogger('calc') |
656 | |
657 | -help['bc'] = u'Calculate mathematical expressions using bc' |
658 | +features['bc'] = { |
659 | + 'description': u'Calculate mathematical expressions using bc', |
660 | + 'categories': ('calculate',), |
661 | +} |
662 | class BC(Processor): |
663 | - u"""bc <expression>""" |
664 | + usage = u'bc <expression>' |
665 | |
666 | - feature = 'bc' |
667 | + feature = ('bc',) |
668 | |
669 | bc = Option('bc', 'Path to bc executable', 'bc') |
670 | bc_timeout = FloatOption('bc_timeout', 'Maximum BC execution time (sec)', 2.0) |
671 | @@ -78,7 +81,11 @@ |
672 | error = unicode_output(error.strip()) |
673 | raise Exception("BC Error: %s" % error) |
674 | |
675 | -help['calc'] = u'Returns the anwser to mathematical expressions. Uses Python syntax and semantics (i.e. radians)' |
676 | +features['calc'] = { |
677 | + 'description': u'Returns the anwser to mathematical expressions. ' |
678 | + u'Uses Python syntax and semantics (i.e. radians)', |
679 | + 'categories': ('calculate',), |
680 | +} |
681 | class LimitException(Exception): |
682 | pass |
683 | |
684 | @@ -133,8 +140,8 @@ |
685 | raise AccessException |
686 | |
687 | class Calc(Processor): |
688 | - u"""[calc] <expression>""" |
689 | - feature = 'calc' |
690 | + usage = u'<expression>' |
691 | + feature = ('calc',) |
692 | |
693 | priority = 500 |
694 | |
695 | @@ -191,16 +198,21 @@ |
696 | |
697 | |
698 | class ExplicitCalc(Calc): |
699 | + usage = u'calc <expression>' |
700 | priority = 0 |
701 | |
702 | @match(r'^calc(?:ulate)?\s+(.+)$') |
703 | def calculate(self, event, expression): |
704 | super(ExplicitCalc, self).calculate(event, expression) |
705 | |
706 | -help['random'] = u'Generates random numbers.' |
707 | + |
708 | +features['random'] = { |
709 | + 'description': u'Generates random numbers.', |
710 | + 'categories': ('calculate', 'fun',), |
711 | +} |
712 | class Random(Processor): |
713 | - u"""random [ <max> | <min> <max> ]""" |
714 | - feature = 'random' |
715 | + usage = u'random [ <max> | <min> <max> ]' |
716 | + feature = ('random',) |
717 | |
718 | @match('^rand(?:om)?(?:\s+(\d+)(?:\s+(\d+))?)?$') |
719 | def random(self, event, begin, end): |
720 | |
721 | === modified file 'ibid/plugins/codecontest.py' |
722 | --- ibid/plugins/codecontest.py 2010-01-25 18:31:04 +0000 |
723 | +++ ibid/plugins/codecontest.py 2010-03-02 19:03:13 +0000 |
724 | @@ -13,7 +13,15 @@ |
725 | from ibid.utils import cacheable_download |
726 | from ibid.utils.html import get_html_parse_tree |
727 | |
728 | -help = {u'usaco': u'Query USACO sections, divisions and more. Since this info is private, users are required to provide their USACO password when linking their USACO account to their ibid account and only linked accounts can be queried. Your password is used only to confirm that the account is yours and is discarded immediately.'} |
729 | +features = {'usaco': { |
730 | + 'description': u'Query USACO sections, divisions and more. Since this info ' |
731 | + u'is private, users are required to provide their USACO ' |
732 | + u'password when linking their USACO account to their ibid ' |
733 | + u'account and only linked accounts can be queried. Your ' |
734 | + u'password is used only to confirm that the account is ' |
735 | + u'yours and is discarded immediately.', |
736 | + 'categories': ('monitor',), |
737 | +}} |
738 | |
739 | class UsacoException(Exception): |
740 | def __init__(self, msg): |
741 | @@ -23,14 +31,14 @@ |
742 | return unicode(self.msg) |
743 | |
744 | class Usaco(Processor): |
745 | - """usaco <section|division> for <user> |
746 | + usage = u"""usaco <section|division> for <user> |
747 | usaco <contest> results [for <name|user>] |
748 | (i am|<user> is) <usaco_username> on usaco [password <usaco_password>]""" |
749 | |
750 | admin_user = Option('admin_user', 'Admin user on USACO', None) |
751 | admin_password = Option('admin_password', 'Admin password on USACO', None) |
752 | |
753 | - feature = 'usaco' |
754 | + feature = ('usaco',) |
755 | # Clashes with identity, so lower our priority since if we match, then |
756 | # this is the better match |
757 | priority = -20 |
758 | |
759 | === modified file 'ibid/plugins/conversions.py' |
760 | --- ibid/plugins/conversions.py 2010-02-06 14:26:08 +0000 |
761 | +++ ibid/plugins/conversions.py 2010-03-02 19:03:13 +0000 |
762 | @@ -13,16 +13,19 @@ |
763 | from ibid.utils import file_in_path, unicode_output, human_join |
764 | from ibid.utils.html import get_country_codes, get_html_parse_tree |
765 | |
766 | -help = {} |
767 | +features = {} |
768 | log = logging.getLogger('plugins.conversions') |
769 | |
770 | -help['base'] = u'Convert numbers between bases (radixes)' |
771 | +features['base'] = { |
772 | + 'description': u'Convert numbers between bases (radixes)', |
773 | + 'categories': ('calculate', 'convert',), |
774 | +} |
775 | class BaseConvert(Processor): |
776 | - u"""[convert] <number> [from base <number>] to base <number> |
777 | + usage = u"""[convert] <number> [from base <number>] to base <number> |
778 | [convert] ascii <text> to base <number> |
779 | [convert] <sequence> from base <number> to ascii""" |
780 | |
781 | - feature = "base" |
782 | + feature = ('base',) |
783 | |
784 | abbr_named_bases = { |
785 | "hex": 16, |
786 | @@ -212,10 +215,13 @@ |
787 | if base_from == 64 and [True for plugin in ibid.processors if getattr(plugin, 'feature', None) == 'base64']: |
788 | event.addresponse(u'If you want a base64 encoding, use the "base64" feature') |
789 | |
790 | -help['units'] = 'Converts values between various units.' |
791 | +features['units'] = { |
792 | + 'description': 'Converts values between various units.', |
793 | + 'categories': ('convert',), |
794 | +} |
795 | class Units(Processor): |
796 | - u"""convert [<value>] <unit> to <unit>""" |
797 | - feature = 'units' |
798 | + usage = u'convert [<value>] <unit> to <unit>' |
799 | + feature = ('units',) |
800 | priority = 10 |
801 | |
802 | units = Option('units', 'Path to units executable', 'units') |
803 | @@ -288,12 +294,15 @@ |
804 | else: |
805 | event.addresponse(u"I can't do that: %s", result) |
806 | |
807 | -help['currency'] = u'Converts amounts between currencies.' |
808 | +features['currency'] = { |
809 | + 'description': u'Converts amounts between currencies.', |
810 | + 'categories': ('convert',), |
811 | +} |
812 | class Currency(Processor): |
813 | - u"""exchange <amount> <currency> for <currency> |
814 | + usage = u"""exchange <amount> <currency> for <currency> |
815 | currencies for <country>""" |
816 | |
817 | - feature = "currency" |
818 | + feature = ('currency',) |
819 | |
820 | headers = {'User-Agent': 'Mozilla/5.0', 'Referer': 'http://www.xe.com/'} |
821 | currencies = {} |
822 | @@ -415,12 +424,15 @@ |
823 | |
824 | class UnassignedCharacter(Exception): pass |
825 | |
826 | -help['unicode'] = """Look up characters in the Unicode database.""" |
827 | +features['unicode'] = { |
828 | + 'description': u'Look up characters in the Unicode database.', |
829 | + 'categories': ('lookup', 'convert',), |
830 | +} |
831 | class UnicodeData(Processor): |
832 | - """U+<hex code> |
833 | + usage = u"""U+<hex code> |
834 | unicode (<character>|<character name>|<decimal code>|0x<hex code>)""" |
835 | |
836 | - feature = 'unicode' |
837 | + feature = ('unicode',) |
838 | |
839 | bidis = {'AL': u'right-to-left Arabic', 'AN': u'Arabic number', |
840 | 'B': u'paragraph separator', 'BN': u'boundary neutral', |
841 | |
842 | === modified file 'ibid/plugins/eval.py' |
843 | --- ibid/plugins/eval.py 2010-01-18 23:20:33 +0000 |
844 | +++ ibid/plugins/eval.py 2010-03-02 19:03:13 +0000 |
845 | @@ -13,11 +13,14 @@ |
846 | |
847 | from ibid.plugins import Processor, match, authorise |
848 | |
849 | -help = {'eval': u'Evaluates Python, Perl and Lua code.'} |
850 | +features = {'eval': { |
851 | + 'description': u'Evaluates Python, Perl and Lua code.', |
852 | + 'categories': ('debug',), |
853 | +}} |
854 | |
855 | class Python(Processor): |
856 | - u"""py <code>""" |
857 | - feature = 'eval' |
858 | + usage = u'py <code>' |
859 | + feature = ('eval',) |
860 | |
861 | permission = u'eval' |
862 | |
863 | @@ -36,8 +39,8 @@ |
864 | event.addresponse(repr(result)) |
865 | |
866 | class Perl(Processor): |
867 | - u"""pl <code>""" |
868 | - feature = 'eval' |
869 | + usage = u'pl <code>' |
870 | + feature = ('eval',) |
871 | |
872 | permission = u'eval' |
873 | |
874 | @@ -52,8 +55,8 @@ |
875 | event.addresponse(repr(result)) |
876 | |
877 | class Lua(Processor): |
878 | - u"""lua <code>""" |
879 | - feature = 'eval' |
880 | + usage = u'lua <code>' |
881 | + feature = ('eval',) |
882 | |
883 | permission = u'eval' |
884 | |
885 | |
886 | === modified file 'ibid/plugins/factoid.py' |
887 | --- ibid/plugins/factoid.py 2010-02-21 19:50:14 +0000 |
888 | +++ ibid/plugins/factoid.py 2010-03-02 19:03:13 +0000 |
889 | @@ -18,9 +18,14 @@ |
890 | from ibid.plugins.identity import get_identities |
891 | from ibid.utils import format_date |
892 | |
893 | -help = {'factoid': u'Factoids are arbitrary pieces of information stored by a key. ' |
894 | - u'Factoids beginning with a command such as "<action>" or "<reply>" will supress the "name verb value" output. ' |
895 | - u"Search and replace functions won't use real regexs unless appended with the 'r' flag."} |
896 | +features = {'factoid': { |
897 | + 'description': u'Factoids are arbitrary pieces of information stored by a ' |
898 | + u'key. Factoids beginning with a command such as "<action>" ' |
899 | + u'or "<reply>" will supress the "name verb value" output. ' |
900 | + u"Search and replace functions won't use real regexs unless " |
901 | + u"appended with the 'r' flag.", |
902 | + 'categories': ('lookup', 'remember',), |
903 | +}} |
904 | |
905 | log = logging.getLogger('plugins.factoid') |
906 | |
907 | @@ -261,8 +266,8 @@ |
908 | return [] |
909 | |
910 | class Utils(Processor): |
911 | - u"""literal <name> [( #<from number> | /<pattern>/[r] )]""" |
912 | - feature = 'factoid' |
913 | + usage = u'literal <name> [( #<from number> | /<pattern>/[r] )]' |
914 | + feature = ('factoid',) |
915 | |
916 | @match(r'^literal\s+(.+?)(?:\s+#(\d+)|\s+(?:/(.+?)/(r?)))?$') |
917 | def literal(self, event, name, number, pattern, is_regex): |
918 | @@ -275,9 +280,9 @@ |
919 | for index, (factoid, name, value) in enumerate(factoids))) |
920 | |
921 | class Forget(Processor): |
922 | - u"""forget <name> [( #<number> | /<pattern>/[r] )] |
923 | + usage = u"""forget <name> [( #<number> | /<pattern>/[r] )] |
924 | <name> is the same as <other name>""" |
925 | - feature = 'factoid' |
926 | + feature = ('factoid',) |
927 | |
928 | priority = 10 |
929 | permission = u'factoid' |
930 | @@ -372,8 +377,8 @@ |
931 | event.addresponse(u"I don't know about %s", source) |
932 | |
933 | class Search(Processor): |
934 | - u"""search [for] [<limit>] [(facts|values) [containing]] (<pattern>|/<pattern>/[r]) [from <start>]""" |
935 | - feature = 'factoid' |
936 | + usage = u'search [for] [<limit>] [(facts|values) [containing]] (<pattern>|/<pattern>/[r]) [from <start>]' |
937 | + feature = ('factoid',) |
938 | |
939 | limit = IntOption('search_limit', u'Maximum number of results to return', 30) |
940 | default = IntOption('search_default', u'Default number of results to return', 10) |
941 | @@ -447,8 +452,8 @@ |
942 | return message |
943 | |
944 | class Get(Processor, RPC): |
945 | - u"""<factoid> [( #<number> | /<pattern>/[r] )]""" |
946 | - feature = 'factoid' |
947 | + usage = u'<factoid> [( #<number> | /<pattern>/[r] )]' |
948 | + feature = ('factoid',) |
949 | |
950 | priority = 200 |
951 | |
952 | @@ -498,11 +503,9 @@ |
953 | return reply |
954 | |
955 | class Set(Processor): |
956 | - u""" |
957 | - <name> (<verb>|=<verb>=) [also] <value> |
958 | - last set factoid |
959 | - """ |
960 | - feature = 'factoid' |
961 | + usage = u"""<name> (<verb>|=<verb>=) [also] <value> |
962 | + last set factoid""" |
963 | + feature = ('factoid',) |
964 | |
965 | interrogatives = ListOption('interrogatives', 'Question words to strip', default_interrogatives) |
966 | verbs = ListOption('verbs', 'Verbs that split name from value', default_verbs) |
967 | @@ -578,9 +581,9 @@ |
968 | event.addresponse(u'It was: %s', self.last_set_factoid) |
969 | |
970 | class Modify(Processor): |
971 | - u"""<name> [( #<number> | /<pattern>/[r] )] += <suffix> |
972 | + usage = u"""<name> [( #<number> | /<pattern>/[r] )] += <suffix> |
973 | <name> [( #<number> | /<pattern>/[r] )] ~= ( s/<regex>/<replacement>/[g][i][r] | y/<source>/<dest>/ )""" |
974 | - feature = 'factoid' |
975 | + feature = ('factoid',) |
976 | |
977 | permission = u'factoid' |
978 | permissions = (u'factoidadmin',) |
979 | |
980 | === modified file 'ibid/plugins/feeds.py' |
981 | --- ibid/plugins/feeds.py 2010-01-18 23:20:33 +0000 |
982 | +++ ibid/plugins/feeds.py 2010-03-02 19:03:13 +0000 |
983 | @@ -16,7 +16,10 @@ |
984 | from ibid.utils import cacheable_download, human_join |
985 | from ibid.utils.html import get_html_parse_tree |
986 | |
987 | -help = {'feeds': u'Displays articles from RSS and Atom feeds'} |
988 | +features = {'feeds': { |
989 | + 'description': u'Displays articles from RSS and Atom feeds', |
990 | + 'categories': ('lookup', 'web',), |
991 | +}} |
992 | |
993 | log = logging.getLogger('plugins.feeds') |
994 | |
995 | @@ -93,14 +96,14 @@ |
996 | return self.name |
997 | |
998 | class Manage(Processor): |
999 | - u""" |
1000 | + usage = u""" |
1001 | add feed <url> as <name> |
1002 | remove <name> feed |
1003 | list feeds |
1004 | poll <name> feed notify <channel> on <source> |
1005 | stop polling <name> feed |
1006 | """ |
1007 | - feature = 'feeds' |
1008 | + feature = ('feeds',) |
1009 | |
1010 | permission = u'feeds' |
1011 | |
1012 | @@ -203,9 +206,9 @@ |
1013 | event.addresponse(True) |
1014 | |
1015 | class Retrieve(Processor): |
1016 | - u"""latest [ <count> ] articles from <name> [ starting at <number> ] |
1017 | + usage = u"""latest [ <count> ] articles from <name> [ starting at <number> ] |
1018 | article ( <number> | /<pattern>/ ) from <name>""" |
1019 | - feature = 'feeds' |
1020 | + feature = ('feeds',) |
1021 | |
1022 | interval = IntOption('interval', 'Feed Poll interval (in seconds)', 300) |
1023 | |
1024 | |
1025 | === modified file 'ibid/plugins/film.py' |
1026 | --- ibid/plugins/film.py 2010-01-18 23:20:33 +0000 |
1027 | +++ ibid/plugins/film.py 2010-03-02 19:03:13 +0000 |
1028 | @@ -17,13 +17,16 @@ |
1029 | |
1030 | log = logging.getLogger('plugins.film') |
1031 | |
1032 | -help = {} |
1033 | +features = {} |
1034 | |
1035 | -help['tvshow'] = u'Retrieves TV show information from tvrage.com.' |
1036 | +features['tvshow'] = { |
1037 | + 'description': u'Retrieves TV show information from tvrage.com.', |
1038 | + 'categories': ('lookup', 'web',), |
1039 | +} |
1040 | class TVShow(Processor): |
1041 | - u"""tvshow <show>""" |
1042 | + usage = u'tvshow <show>' |
1043 | |
1044 | - feature = 'tvshow' |
1045 | + feature = ('tvshow',) |
1046 | |
1047 | def remote_tvrage(self, show): |
1048 | info_url = 'http://services.tvrage.com/tools/quickinfo.php?%s' |
1049 | @@ -79,10 +82,13 @@ |
1050 | # This isn't strictly legal: http://www.imdb.com/help/show_leaf?usedatasoftware |
1051 | # |
1052 | # Note that it will return porn movies by default. |
1053 | -help['imdb'] = u'Looks up movies on IMDB.com.' |
1054 | +features['imdb'] = { |
1055 | + 'description': u'Looks up movies on IMDB.com.', |
1056 | + 'categories': ('lookup', 'web',), |
1057 | +} |
1058 | class IMDB(Processor): |
1059 | - u"imdb [search] [character|company|episode|movie|person] <terms> [#<index>]" |
1060 | - feature = 'imdb' |
1061 | + usage = u'imdb [search] [character|company|episode|movie|person] <terms> [#<index>]' |
1062 | + feature = ('imdb',) |
1063 | |
1064 | access_system = Option("accesssystem", "Method of querying IMDB", "http") |
1065 | adult_search = BoolOption("adultsearch", "Include adult films in search results", True) |
1066 | |
1067 | === modified file 'ibid/plugins/fun.py' |
1068 | --- ibid/plugins/fun.py 2010-01-18 23:20:33 +0000 |
1069 | +++ ibid/plugins/fun.py 2010-03-02 19:03:13 +0000 |
1070 | @@ -12,12 +12,15 @@ |
1071 | from ibid.config import IntOption, ListOption |
1072 | from ibid.utils import human_join |
1073 | |
1074 | -help = {} |
1075 | +features = {} |
1076 | |
1077 | -help['nickometer'] = u'Calculates how lame a nick is.' |
1078 | +features['nickometer'] = { |
1079 | + 'description': u'Calculates how lame a nick is.', |
1080 | + 'categories': ('fun', 'calculate',), |
1081 | +} |
1082 | class Nickometer(Processor): |
1083 | - u"""nickometer [<nick>] [with reasons]""" |
1084 | - feature = 'nickometer' |
1085 | + usage = u'nickometer [<nick>] [with reasons]' |
1086 | + feature = ('nickometer',) |
1087 | |
1088 | @match(r'^(?:nick|lame)-?o-?meter(?:(?:\s+for)?\s+(.+?))?(\s+with\s+reasons)?$') |
1089 | def handle_nickometer(self, event, nick, wreasons): |
1090 | @@ -36,10 +39,13 @@ |
1091 | reasons = ((u'A good, traditional nick', 0),) |
1092 | event.addresponse(u'Because: %s', u', '.join(['%s (%s)' % reason for reason in reasons])) |
1093 | |
1094 | -help['choose'] = u'Choose one of the given options.' |
1095 | +features['choose'] = { |
1096 | + 'description': u'Choose one of the given options.', |
1097 | + 'categories': ('fun', 'decide',), |
1098 | +} |
1099 | class Choose(Processor): |
1100 | - u"""choose <choice> or <choice>...""" |
1101 | - feature = 'choose' |
1102 | + usage = u'choose <choice> or <choice>...' |
1103 | + feature = ('choose',) |
1104 | |
1105 | choose_re = re.compile(r'(?:\s*,\s*(?:or\s+)?)|(?:\s+or\s+)', re.I) |
1106 | |
1107 | @@ -47,10 +53,13 @@ |
1108 | def choose(self, event, choices): |
1109 | event.addresponse(u'I choose %s', choice(self.choose_re.split(choices))) |
1110 | |
1111 | -help['coffee'] = u"Times coffee brewing and reserves cups for people" |
1112 | +features['coffee'] = { |
1113 | + 'description': u'Times coffee brewing and reserves cups for people', |
1114 | + 'categories': ('fun', 'monitor',), |
1115 | +} |
1116 | class Coffee(Processor): |
1117 | - u"""coffee (on|please)""" |
1118 | - feature = 'coffee' |
1119 | + usage = u'coffee (on|please)' |
1120 | + feature = ('coffee',) |
1121 | |
1122 | pots = {} |
1123 | |
1124 | @@ -98,13 +107,14 @@ |
1125 | self.pots[(event.source, event.channel)].append(event.sender['nick']) |
1126 | event.addresponse(True) |
1127 | |
1128 | -help['insult'] = u"Slings verbal abuse at someone" |
1129 | +features['insult'] = { |
1130 | + 'description': u'Slings verbal abuse at someone', |
1131 | + 'categories': ('fun',), |
1132 | +} |
1133 | class Insult(Processor): |
1134 | - u""" |
1135 | - (flame | insult) <person> |
1136 | - (swear | cuss | explete) [at <person>] |
1137 | - """ |
1138 | - feature = 'insult' |
1139 | + usage = u"""(flame | insult) <person> |
1140 | + (swear | cuss | explete) [at <person>]""" |
1141 | + feature = ('insult',) |
1142 | |
1143 | adjectives = ListOption('adjectives', 'List of adjectives', ( |
1144 | u'acidic', u'antique', u'artless', u'base-court', u'bat-fowling', |
1145 | |
1146 | === modified file 'ibid/plugins/games.py' |
1147 | --- ibid/plugins/games.py 2010-01-18 23:20:33 +0000 |
1148 | +++ ibid/plugins/games.py 2010-03-02 19:03:13 +0000 |
1149 | @@ -12,20 +12,23 @@ |
1150 | from ibid.plugins import Processor, match, handler |
1151 | from ibid.utils import format_date, human_join, plural |
1152 | |
1153 | -help = {} |
1154 | +features = {} |
1155 | log = logging.getLogger('plugins.games') |
1156 | |
1157 | duels = {} |
1158 | |
1159 | -help['duel'] = u"Duel at dawn, between channel members" |
1160 | +features['duel'] = { |
1161 | + 'description': u'Duel at dawn, between channel members', |
1162 | + 'categories': ('fun', 'game',), |
1163 | +} |
1164 | class DuelInitiate(Processor): |
1165 | - u""" |
1166 | + usage = u""" |
1167 | I challenge <user> to a duel [over <something>] |
1168 | I demand satisfaction from <user> [over <something>] |
1169 | I throw the gauntlet down at <user>'s feet [over <something>] |
1170 | """ |
1171 | |
1172 | - feature = 'duel' |
1173 | + feature = ('duel',) |
1174 | |
1175 | accept_timeout = FloatOption('accept_timeout', 'How long do we wait for acceptance?', 60.0) |
1176 | start_delay = IntOption('start_delay', 'Time between acceptance and start of duel (rounded down to the highest minute)', 30) |
1177 | @@ -213,12 +216,10 @@ |
1178 | }, address=False) |
1179 | |
1180 | class DuelDraw(Processor): |
1181 | - u""" |
1182 | - draw [my <weapon>] |
1183 | - bam|pew|bang|kapow|pewpew|holyhandgrenadeofantioch |
1184 | - """ |
1185 | + usage = u"""draw [my <weapon>] |
1186 | + bam|pew|bang|kapow|pewpew|holyhandgrenadeofantioch""" |
1187 | |
1188 | - feature = 'duel' |
1189 | + feature = ('duel',) |
1190 | |
1191 | # Parameters for Processor: |
1192 | event_types = (u'message', u'action') |
1193 | @@ -401,7 +402,7 @@ |
1194 | )), duel.names[shooter], address=False) |
1195 | |
1196 | class DuelFlee(Processor): |
1197 | - feature = 'duel' |
1198 | + feature = ('duel',) |
1199 | addressed = False |
1200 | event_types = (u'state',) |
1201 | |
1202 | @@ -445,20 +446,22 @@ |
1203 | |
1204 | werewolf_games = [] |
1205 | |
1206 | -help['werewolf'] = (u'Play the werewolf game. ' |
1207 | - u'Channel becomes a village containing a werewolf, seer and villagers. ' |
1208 | - u'Every night, the werewolf can kill a villager, and the seer can test ' |
1209 | - u'a villager for werewolf symptoms. ' |
1210 | - u'Villagers then vote to lynch a wolf during the day.') |
1211 | +features['werewolf'] = { |
1212 | + 'description': u'Play the werewolf game. Channel becomes a village ' |
1213 | + u'containing a werewolf, seer and villagers. Every night, ' |
1214 | + u'the werewolf can kill a villager, and the seer can test ' |
1215 | + u'a villager for werewolf symptoms. Villagers then vote to ' |
1216 | + u'lynch a wolf during the day.', |
1217 | + 'categories': ('fun', 'game',), |
1218 | +} |
1219 | class WerewolfGame(Processor): |
1220 | - u""" |
1221 | + usage = u""" |
1222 | start a game of werewolf |
1223 | join |
1224 | ( kill | see | eat ) <villager> |
1225 | vote for <villager> |
1226 | """ |
1227 | - |
1228 | - feature = 'werewolf' |
1229 | + feature = ('werewolf',) |
1230 | state = None |
1231 | |
1232 | player_limit = IntOption('min_players', 'The minimum number of players', 5) |
1233 | @@ -845,7 +848,7 @@ |
1234 | return self.state.__name__ |
1235 | |
1236 | class WerewolfState(Processor): |
1237 | - feature = 'werewolf' |
1238 | + feature = ('werewolf',) |
1239 | event_types = (u'state',) |
1240 | |
1241 | @handler |
1242 | |
1243 | === modified file 'ibid/plugins/gameservers.py' |
1244 | --- ibid/plugins/gameservers.py 2010-02-25 20:58:58 +0000 |
1245 | +++ ibid/plugins/gameservers.py 2010-03-02 19:03:13 +0000 |
1246 | @@ -9,12 +9,14 @@ |
1247 | from ibid.config import Option, IntOption |
1248 | from ibid.utils import human_join |
1249 | |
1250 | -help = {} |
1251 | -help['gameservers'] = u'Lists the users on Game servers' |
1252 | +features = {'gameservers': { |
1253 | + 'description': u'Lists the users on Game servers', |
1254 | + 'categories': ('lookup',), |
1255 | +}} |
1256 | |
1257 | class Bnet(Processor): |
1258 | - u'dota players | who is playing dota' |
1259 | - feature = 'gameservers' |
1260 | + usage = u'dota players | who is playing dota' |
1261 | + feature = ('gameservers',) |
1262 | autoload = False |
1263 | |
1264 | bnet_host = Option('bnet_host', 'Bnet server hostname / IP', '127.0.0.1') |
1265 | @@ -51,8 +53,8 @@ |
1266 | event.addresponse(u'Nobody. Everyone must have a lives...') |
1267 | |
1268 | class CounterStrike(Processor): |
1269 | - u'cs players | who is playing cs' |
1270 | - feature = 'gameservers' |
1271 | + usage = u'cs players | who is playing cs' |
1272 | + feature = ('gameservers',) |
1273 | autoload = False |
1274 | |
1275 | cs_host = Option('cs_host', 'CS server hostname / IP', '127.0.0.1') |
1276 | |
1277 | === modified file 'ibid/plugins/geography.py' |
1278 | --- ibid/plugins/geography.py 2010-02-20 11:32:43 +0000 |
1279 | +++ ibid/plugins/geography.py 2010-03-02 19:03:13 +0000 |
1280 | @@ -23,18 +23,21 @@ |
1281 | |
1282 | log = logging.getLogger('plugins.geography') |
1283 | |
1284 | -help = {} |
1285 | +features = {} |
1286 | |
1287 | -help['distance'] = u"Returns the distance between two places" |
1288 | +features['distance'] = { |
1289 | + 'description': u'Returns the distance between two places', |
1290 | + 'categories': ('lookup', 'calculate',), |
1291 | +} |
1292 | class Distance(Processor): |
1293 | - u"""distance [in <unit>] between <source> and <destination> |
1294 | + usage = u"""distance [in <unit>] between <source> and <destination> |
1295 | place search for <placename>""" |
1296 | |
1297 | # For Mathematics, see: |
1298 | # http://www.mathforum.com/library/drmath/view/51711.html |
1299 | # http://mathworld.wolfram.com/GreatCircle.html |
1300 | |
1301 | - feature = 'distance' |
1302 | + feature = ('distance',) |
1303 | |
1304 | default_unit_names = { |
1305 | 'km': "kilometres", |
1306 | @@ -107,12 +110,15 @@ |
1307 | conjunction=u'or'), |
1308 | }) |
1309 | |
1310 | -help['weather'] = u'Retrieves current weather and forecasts for cities.' |
1311 | +features['weather'] = { |
1312 | + 'description': u'Retrieves current weather and forecasts for cities.', |
1313 | + 'categories': ('lookup', 'web',), |
1314 | +} |
1315 | class Weather(Processor): |
1316 | - u"""weather in <city> |
1317 | + usage = u"""weather in <city> |
1318 | forecast for <city>""" |
1319 | |
1320 | - feature = "weather" |
1321 | + feature = ('weather',) |
1322 | |
1323 | defaults = { 'ct': 'Cape Town, South Africa', |
1324 | 'jhb': 'Johannesburg, South Africa', |
1325 | @@ -223,11 +229,14 @@ |
1326 | 'EST': 'US/Eastern', |
1327 | } |
1328 | |
1329 | -help['timezone'] = "Converts times between timezones." |
1330 | +features['timezone'] = { |
1331 | + 'description': 'Converts times between timezones.', |
1332 | + 'categories': ('convert',), |
1333 | +} |
1334 | class TimeZone(Processor): |
1335 | - u"""when is <time> <place|timezone> in <place|timezone> |
1336 | + usage = u"""when is <time> <place|timezone> in <place|timezone> |
1337 | time in <place|timezone>""" |
1338 | - feature = 'timezone' |
1339 | + feature = ('timezone',) |
1340 | |
1341 | zoneinfo = Option('zoneinfo', 'Timezone info directory', '/usr/share/zoneinfo') |
1342 | custom_zones = DictOption('timezones', 'Custom timezone names', CUSTOM_ZONES) |
1343 | @@ -355,7 +364,10 @@ |
1344 | def time(self, event, place): |
1345 | self.convert(event, None, None, place) |
1346 | |
1347 | -help['flight'] = u'Search for flights on travelocity' |
1348 | +features['flight'] = { |
1349 | + 'description': u'Search for flights on travelocity', |
1350 | + 'categories': ('lookup', 'web',), |
1351 | +} |
1352 | class Flight: |
1353 | def __init__(self): |
1354 | self.flight, self.depart_time, self.depart_ap, self.arrive_time, \ |
1355 | @@ -387,10 +399,10 @@ |
1356 | pass |
1357 | |
1358 | class FlightSearch(Processor): |
1359 | - """airport [in] <name|location|code> |
1360 | + usage = u"""airport [in] <name|location|code> |
1361 | [<cheapest|quickest>] flight from <departure> to <destination> from <depart_date> [anytime|morning|afternoon|evening|<time>] to <return_date> [anytime|morning|afternoon|evening|<time>]""" |
1362 | |
1363 | - feature = 'flight' |
1364 | + feature = ('flight',) |
1365 | |
1366 | airports_url = u'http://openflights.svn.sourceforge.net/viewvc/openflights/openflights/data/airports.dat' |
1367 | max_results = IntOption('max_results', 'Maximum number of results to list', 5) |
1368 | |
1369 | === modified file 'ibid/plugins/google.py' |
1370 | --- ibid/plugins/google.py 2010-01-24 20:50:59 +0000 |
1371 | +++ ibid/plugins/google.py 2010-03-02 19:03:13 +0000 |
1372 | @@ -11,18 +11,19 @@ |
1373 | from ibid.config import Option |
1374 | from ibid.utils import decode_htmlentities, json_webservice |
1375 | |
1376 | -help = {} |
1377 | - |
1378 | -help['google'] = u'Retrieves results from Google and Google Calculator.' |
1379 | +features = {'google': { |
1380 | + 'description': u'Retrieves results from Google and Google Calculator.', |
1381 | + 'categories': ('lookup', 'web',), |
1382 | +}} |
1383 | |
1384 | default_user_agent = 'Mozilla/5.0' |
1385 | default_referer = "http://ibid.omnia.za.net/" |
1386 | |
1387 | class GoogleAPISearch(Processor): |
1388 | - u"""google [for] <term> |
1389 | + usage = u"""google [for] <term> |
1390 | googlefight [for] <term> and <term>""" |
1391 | |
1392 | - feature = 'google' |
1393 | + feature = ('google',) |
1394 | |
1395 | api_key = Option('api_key', 'Your Google API Key (optional)', None) |
1396 | referer = Option('referer', 'The referer string to use (API searches)', default_referer) |
1397 | @@ -85,11 +86,11 @@ |
1398 | # features. |
1399 | # Dear Google: We promise we don't bite. |
1400 | class GoogleScrapeSearch(Processor): |
1401 | - u"""gcalc <expression> |
1402 | + usage = u"""gcalc <expression> |
1403 | gdefine <term> |
1404 | google.<TLD> [for] <terms>""" |
1405 | |
1406 | - feature = 'google' |
1407 | + feature = ('google',) |
1408 | |
1409 | user_agent = Option('user_agent', 'HTTP user agent to present to Google (for non-API searches)', default_user_agent) |
1410 | google_scrape_url = "http://www.google.com/search?q=%s" |
1411 | |
1412 | === modified file 'ibid/plugins/help.py' |
1413 | --- ibid/plugins/help.py 2010-01-18 23:20:33 +0000 |
1414 | +++ ibid/plugins/help.py 2010-03-02 19:03:13 +0000 |
1415 | @@ -1,103 +1,236 @@ |
1416 | # Copyright (c) 2008-2010, Michael Gorven, Stefano Rivera, Max Rabkin |
1417 | # Released under terms of the MIT/X/Expat Licence. See COPYING for details. |
1418 | |
1419 | -import inspect |
1420 | +from copy import copy |
1421 | +import re |
1422 | import sys |
1423 | |
1424 | +try: |
1425 | + from Stemmer import Stemmer |
1426 | +except ImportError: |
1427 | + from stemmer import PorterStemmer |
1428 | + class Stemmer(PorterStemmer): |
1429 | + def __init__(self, language): |
1430 | + PorterStemmer.__init__(self) |
1431 | + def stemWord(self, word): |
1432 | + return PorterStemmer.stem(self, word, 0, len(word) - 1) |
1433 | + |
1434 | import ibid |
1435 | from ibid.plugins import Processor, match |
1436 | from ibid.utils import human_join |
1437 | |
1438 | -help = {'help': u'Provides help and usage information about plugins.'} |
1439 | +features = {'help': { |
1440 | + 'description': u'Provides help and usage information about plugins.', |
1441 | + 'categories': ('admin', 'lookup',), |
1442 | +}} |
1443 | |
1444 | class Help(Processor): |
1445 | - u"""features [for <word>] |
1446 | - help [<feature>] |
1447 | - usage <feature>""" |
1448 | - feature = 'help' |
1449 | - |
1450 | - @match(r'^help$') |
1451 | - def intro(self, event): |
1452 | - event.addresponse(u'Use "features" to get a list of available features. ' |
1453 | - u'"help <feature>" will give a description of the feature, and "usage <feature>" will describe how to use it.') |
1454 | - |
1455 | - @match(r'^features$') |
1456 | - def features(self, event): |
1457 | - features = [] |
1458 | - |
1459 | - for processor in ibid.processors: |
1460 | - module = eval(processor.__module__) |
1461 | - if hasattr(module, 'help'): |
1462 | - for feature in module.help.keys(): |
1463 | - if feature not in features: |
1464 | - features.append(feature) |
1465 | - |
1466 | - event.addresponse(u'Features: %s', human_join(sorted(features)) or u'none') |
1467 | - |
1468 | - @match(r'^help\s+(.+)$') |
1469 | - def help(self, event, feature): |
1470 | - feature = feature.lower() |
1471 | - |
1472 | - for processor in ibid.processors: |
1473 | - module = eval(processor.__module__) |
1474 | - if hasattr(module, 'help') and feature in module.help: |
1475 | - event.addresponse(module.help[feature]) |
1476 | - return |
1477 | - |
1478 | - event.addresponse(u"I can't help you with %s", feature) |
1479 | - |
1480 | - @match(r'^(?:usage|how\s+do\s+I\s+use)\s+(.+)$') |
1481 | - def usage(self, event, feature): |
1482 | - feature = feature.lower() |
1483 | - |
1484 | - output = [] |
1485 | - for processor in ibid.processors: |
1486 | - for name, klass in inspect.getmembers(processor, inspect.isclass): |
1487 | - if hasattr(klass, 'feature') and klass.feature == feature and klass.__doc__: |
1488 | - for line in klass.__doc__.strip().splitlines(): |
1489 | - output.append(line.strip()) |
1490 | - |
1491 | - if len(output) == 1: |
1492 | - event.addresponse(u'Usage: %s', output[0]) |
1493 | - elif len(output) > 1: |
1494 | - event.addresponse( |
1495 | - u"You can use %(feature)s in the following ways:\n%(usage)s", { |
1496 | - 'feature': feature, |
1497 | - 'usage': u'\n'.join(output) |
1498 | - }, conflate=False) |
1499 | - else: |
1500 | - event.addresponse(u"I don't know how to use %s either", feature) |
1501 | - |
1502 | - @match(r'^features\s+(?:for|with)\s+(.*)$') |
1503 | - def search (self, event, phrase): |
1504 | - features = map(unicode, self._search(phrase)) |
1505 | - features.sort() |
1506 | - if len(features) == 0: |
1507 | - event.addresponse(u"I couldn't find that feature.") |
1508 | - elif len(features) == 1: |
1509 | - event.addresponse( |
1510 | - u'The "%s" feature might be what you\'re looking for.', |
1511 | - features[0]) |
1512 | - else: |
1513 | - event.addresponse(u"Are you looking for %s?", |
1514 | - human_join(features, conjunction='or')) |
1515 | - |
1516 | - def _search (self, phrase): |
1517 | - phrase = phrase.lower() |
1518 | - matches = set() |
1519 | + usage = u""" |
1520 | + what can you do|help |
1521 | + help me with <category> |
1522 | + how do I use <feature> |
1523 | + help <(category|feature)> |
1524 | + """ |
1525 | + feature = ('help',) |
1526 | + stemmer = Stemmer('english') |
1527 | + |
1528 | + def _get_features(self): |
1529 | + """Walk the loaded processors and build dicts of categories and |
1530 | + features in use. Dicts are cross-referenced by string. |
1531 | + """ |
1532 | + categories = {} |
1533 | + for k, v in ibid.categories.iteritems(): |
1534 | + v = copy(v) |
1535 | + v.update({ |
1536 | + 'name': k, |
1537 | + 'features': set(), |
1538 | + }) |
1539 | + categories[k] = v |
1540 | + |
1541 | + features = {} |
1542 | processor_modules = set() |
1543 | for processor in ibid.processors: |
1544 | - if (hasattr(processor, 'feature') and processor.__doc__ and |
1545 | - phrase in processor.__doc__.lower()): |
1546 | - matches.add(processor.feature) |
1547 | + for feature in getattr(processor, 'feature', []): |
1548 | + if feature not in features: |
1549 | + features[feature] = { |
1550 | + 'name': feature, |
1551 | + 'description': None, |
1552 | + 'categories': set(), |
1553 | + 'processors': set(), |
1554 | + 'usage': [], |
1555 | + } |
1556 | + features[feature]['processors'].add(processor) |
1557 | + if hasattr(processor, 'usage'): |
1558 | + features[feature]['usage'] += [line.strip() |
1559 | + for line in processor.usage.split('\n') |
1560 | + if line.strip()] |
1561 | processor_modules.add(sys.modules[processor.__module__]) |
1562 | |
1563 | for module in processor_modules: |
1564 | - if hasattr(module, 'help'): |
1565 | - for feature, help in module.help.iteritems(): |
1566 | - if phrase in feature or phrase in help.lower(): |
1567 | - matches.add(feature) |
1568 | - |
1569 | - return matches |
1570 | + for feature, meta in getattr(module, 'features', {}).iteritems(): |
1571 | + if feature not in features: |
1572 | + continue |
1573 | + if meta.get('description'): |
1574 | + features[feature]['description'] = meta['description'] |
1575 | + for category in meta.get('categories', []): |
1576 | + features[feature]['categories'].add(category) |
1577 | + categories[category]['features'].add(feature) |
1578 | + |
1579 | + categories = dict((k, v) for k, v in categories.iteritems() |
1580 | + if v['features']) |
1581 | + |
1582 | + usere = re.compile(r'[\s()[\]<>|]+') |
1583 | + for name, feat in features.iteritems(): |
1584 | + feat['usage_keywords'] = frozenset( |
1585 | + self.stemmer.stemWord(word.strip()) |
1586 | + for word in usere.split(u' '.join(feat['usage'])) |
1587 | + if word.strip()) |
1588 | + for name, cat in categories.iteritems(): |
1589 | + cat['description_keywords'] = frozenset(self.stemmer.stemWord(word) |
1590 | + for word in cat['description'].lower().split()) |
1591 | + for name in features.keys(): |
1592 | + st_name = self.stemmer.stemWord(name) |
1593 | + features[st_name] = features[name] |
1594 | + if st_name != name: |
1595 | + del features[name] |
1596 | + for name in categories.keys(): |
1597 | + st_name = self.stemmer.stemWord(name) |
1598 | + categories[st_name] = categories[name] |
1599 | + if st_name != name: |
1600 | + del categories[name] |
1601 | + |
1602 | + return categories, features |
1603 | + |
1604 | + def _describe_category(self, event, category): |
1605 | + """Respond with the help information for a category""" |
1606 | + event.addresponse(u'I use the following features for %(description)s: ' |
1607 | + u'%(features)s\n' |
1608 | + u'Ask me "how do I use ..." for more details.', |
1609 | + { |
1610 | + 'description': category['description'].lower(), |
1611 | + 'features': human_join(sorted(category['features'])), |
1612 | + }, conflate=False) |
1613 | + |
1614 | + def _describe_feature(self, event, feature): |
1615 | + """Respond with the help information for a feature""" |
1616 | + output = [] |
1617 | + desc = feature['description'] |
1618 | + if desc is None: |
1619 | + output.append(u'You can use it like this:') |
1620 | + elif len(desc) > 100: |
1621 | + output.append(desc) |
1622 | + output.append(u'You can use it like this:') |
1623 | + elif desc.endswith('.'): |
1624 | + output.append(desc + u' You can use it like this:') |
1625 | + else: |
1626 | + output.append(desc + u'. You can use it like this:') |
1627 | + |
1628 | + for line in feature['usage']: |
1629 | + output.append(u' ' + line) |
1630 | + |
1631 | + event.addresponse(u'\n'.join(output), conflate=False) |
1632 | + |
1633 | + def _usage_search(self, event, terms, features): |
1634 | + terms = frozenset(self.stemmer.stemWord(term) for term in terms) |
1635 | + results = set() |
1636 | + for name, feat in features.iteritems(): |
1637 | + if terms.issubset(feat['usage_keywords']): |
1638 | + results.add(name) |
1639 | + results = sorted(results) |
1640 | + if len(results) == 1: |
1641 | + self._describe_feature(event, features[results[0]]) |
1642 | + elif len(results) > 1: |
1643 | + event.addresponse( |
1644 | + u"Please be more specific. I don't know if you mean %s", |
1645 | + human_join((features[result]['name'] for result in results), |
1646 | + conjunction=u'or')) |
1647 | + else: |
1648 | + event.addresponse( |
1649 | + u"I'm afraid I don't know what you are asking about. " |
1650 | + u'Ask "what can you do" to browse my features') |
1651 | + |
1652 | + @match(r'^(?:help|features|what\s+(?:can|do)\s+you\s+do)$') |
1653 | + def intro(self, event): |
1654 | + categories, features = self._get_features() |
1655 | + categories = filter(lambda c: c['weight'] is not None, |
1656 | + categories.itervalues()) |
1657 | + categories = sorted(categories, key=lambda c: c['weight']) |
1658 | + event.addresponse(u'I can help you with: %s.\n' |
1659 | + u'Ask me "help me with ..." for more details.', |
1660 | + human_join(c['description'].lower() for c in categories), |
1661 | + conflate=False) |
1662 | + |
1663 | + @match(r'^help\s+(?:me\s+)?with\s+(.+)$') |
1664 | + def describe_category(self, event, terms): |
1665 | + categories, features = self._get_features() |
1666 | + termset = frozenset(self.stemmer.stemWord(term) |
1667 | + for term in terms.lower().split()) |
1668 | + |
1669 | + if len(termset) == 1: |
1670 | + term = list(termset)[0] |
1671 | + exact = [c for c in categories.itervalues() if c['name'] == term] |
1672 | + if exact: |
1673 | + self._describe_category(event, exact[0]) |
1674 | + return |
1675 | + |
1676 | + results = [] |
1677 | + for name, cat in categories.iteritems(): |
1678 | + if termset.issubset(cat['description_keywords']): |
1679 | + results.append(name) |
1680 | + |
1681 | + if len(results) == 0: |
1682 | + for name, cat in categories.iteritems(): |
1683 | + if terms.lower() in cat['description'].lower(): |
1684 | + results.append(name) |
1685 | + |
1686 | + results.sort() |
1687 | + if len(results) == 1: |
1688 | + self._describe_category(event, categories[results[0]]) |
1689 | + return |
1690 | + elif len(results) > 1: |
1691 | + event.addresponse( |
1692 | + u"Please be more specific, I don't know if you mean %s.", |
1693 | + human_join(('%s (%s)' |
1694 | + % (categories[r]['description'].lower(), r) |
1695 | + for r in results), |
1696 | + conjunction=u'or')) |
1697 | + return |
1698 | + |
1699 | + event.addresponse(u"I'm afraid I don't know what you are asking about" |
1700 | + u'Ask "what can you do" to browse my features') |
1701 | + |
1702 | + @match(r'^(?:help|usage|modinfo)\s+(\S+)$') |
1703 | + def quick_help(self, event, terms): |
1704 | + categories, features = self._get_features() |
1705 | + terms = frozenset(terms.lower().split()) |
1706 | + if len(terms) == 1: |
1707 | + term = list(terms)[0] |
1708 | + exact = [c for c in categories.itervalues() if c['name'] == term] |
1709 | + if exact: |
1710 | + self._describe_category(event, exact[0]) |
1711 | + return |
1712 | + exact = [f for f in features.itervalues() if f['name'] == term] |
1713 | + if exact: |
1714 | + self._describe_feature(event, exact[0]) |
1715 | + return |
1716 | + |
1717 | + self._usage_search(event, terms, features) |
1718 | + |
1719 | + @match(r'^how\s+do\s+I(?:\s+use)?\s+(.+)$') |
1720 | + def describe_feature(self, event, feature): |
1721 | + categories, features = self._get_features() |
1722 | + |
1723 | + feature = feature.lower() |
1724 | + exact = [f for f in features.itervalues() if f['name'] == feature] |
1725 | + if exact: |
1726 | + self._describe_feature(event, exact[0]) |
1727 | + else: |
1728 | + self._usage_search(event, frozenset(feature.split()), features) |
1729 | + |
1730 | + @match(r'^\s*(?:help\s+me\s+with|how\s+do\s+I(?:\s+use)?)\s+\.\.\.\s*$', |
1731 | + version='deaddressed') |
1732 | + def silly_people(self, event): |
1733 | + event.addresponse( |
1734 | + u'You must replace the ellipsis with the thing you are after') |
1735 | |
1736 | # vi: set et sta sw=4 ts=4: |
1737 | |
1738 | === modified file 'ibid/plugins/icecast.py' |
1739 | --- ibid/plugins/icecast.py 2010-01-30 09:38:13 +0000 |
1740 | +++ ibid/plugins/icecast.py 2010-03-02 19:03:13 +0000 |
1741 | @@ -11,12 +11,13 @@ |
1742 | |
1743 | log = logging.getLogger('plugins.icecast') |
1744 | |
1745 | -help = {'icecast': u'Follows an ICECast stream'} |
1746 | +features = {'icecast': { |
1747 | + 'description': u'Follows an ICECast stream', |
1748 | + 'categories': ('monitor',), |
1749 | +}} |
1750 | class ICECast(Processor): |
1751 | - u""" |
1752 | - what's playing [on <stream>]? |
1753 | - """ |
1754 | - feature = 'icecast' |
1755 | + usage = u"what's playing [on <stream>]?" |
1756 | + feature = ('icecast',) |
1757 | |
1758 | interval = IntOption('interval', |
1759 | 'Interval between checking for song changes', 60) |
1760 | |
1761 | === modified file 'ibid/plugins/identity.py' |
1762 | --- ibid/plugins/identity.py 2010-01-18 23:20:33 +0000 |
1763 | +++ ibid/plugins/identity.py 2010-03-02 19:03:13 +0000 |
1764 | @@ -14,18 +14,22 @@ |
1765 | from ibid.utils import human_join |
1766 | from ibid.auth import hash |
1767 | |
1768 | -help = {} |
1769 | +features = {} |
1770 | identify_cache = {} |
1771 | |
1772 | log = logging.getLogger('plugins.identity') |
1773 | |
1774 | -help['accounts'] = u'An account represents a person. ' \ |
1775 | - 'An account has one or more identities, which is a user on a specific source.' |
1776 | +features['accounts'] = { |
1777 | + 'description': u'Manage users accounts with the bot. An account represents ' |
1778 | + u'a person. An account has one or more identities, which is ' |
1779 | + u'a user on a specific source.', |
1780 | + 'categories': ('admin', 'account',), |
1781 | +} |
1782 | class Accounts(Processor): |
1783 | - u"""create account [<name>] |
1784 | + usage = u"""create account [<name>] |
1785 | delete (my account|account <name>) |
1786 | rename (my account|account <name>) to <name>""" |
1787 | - feature = 'accounts' |
1788 | + feature = ('accounts',) |
1789 | |
1790 | @match(r'^create\s+account(?:\s+(.+))?$') |
1791 | def new_account(self, event, username): |
1792 | @@ -140,9 +144,9 @@ |
1793 | chars = [x for x in string.letters + string.digits if x not in '01lOIB86G'] |
1794 | |
1795 | class Identities(Processor): |
1796 | - u"""(I am|<username> is) <identity> on <source> |
1797 | + usage = u"""(I am|<username> is) <identity> on <source> |
1798 | remove identity <identity> on <source> [from <username>]""" |
1799 | - feature = 'accounts' |
1800 | + feature = ('accounts',) |
1801 | priority = -10 |
1802 | |
1803 | def __init__(self, name): |
1804 | @@ -314,8 +318,8 @@ |
1805 | account.username, event.account, event.identity, event.sender['connection']) |
1806 | |
1807 | class Attributes(Processor): |
1808 | - u"""set (my|<account>) <name> to <value>""" |
1809 | - feature = 'accounts' |
1810 | + usage = u'set (my|<account>) <name> to <value>' |
1811 | + feature = ('accounts',) |
1812 | |
1813 | @match(r"^set\s+(my|.+?)(?:\'s)?\s+(.+)\s+to\s+(.+)$") |
1814 | def attribute(self, event, username, name, value): |
1815 | @@ -348,8 +352,8 @@ |
1816 | event.identity, event.sender['connection']) |
1817 | |
1818 | class Describe(Processor): |
1819 | - u"""who (am I|is <username>)""" |
1820 | - feature = "accounts" |
1821 | + usage = u'who (am I|is <username>)' |
1822 | + feature = ('accounts',) |
1823 | |
1824 | @match(r'^who\s+(?:is|am)\s+(I|.+?)$') |
1825 | def describe(self, event, username): |
1826 | @@ -374,10 +378,13 @@ |
1827 | 'identities': human_join(u'%s on %s' % (identity.identity, identity.source) for identity in account.identities), |
1828 | }) |
1829 | |
1830 | -help['summon'] = u"Get the attention of a person via different source" |
1831 | +features['summon'] = { |
1832 | + 'description': u'Get the attention of a person via different source', |
1833 | + 'categories': ('message',), |
1834 | +} |
1835 | class Summon(Processor): |
1836 | - u"summon <person> [via <source>]" |
1837 | - feature = 'summon' |
1838 | + usage = u'summon <person> [via <source>]' |
1839 | + feature = ('summon',) |
1840 | permission = u'summon' |
1841 | |
1842 | default_source = Option('default_source', |
1843 | @@ -495,10 +502,14 @@ |
1844 | |
1845 | actions = {'revoke': 'Revoked', 'grant': 'Granted', 'remove': 'Removed'} |
1846 | |
1847 | -help['auth'] = u'Adds and removes authentication credentials and permissions' |
1848 | +features['auth'] = { |
1849 | + 'description': u'Adds and removes authentication credentials and ' |
1850 | + u'permissions', |
1851 | + 'categories': ('admin', 'account',), |
1852 | +} |
1853 | class AddAuth(Processor): |
1854 | - u"""authenticate <account> [on source] using <method> [<credential>]""" |
1855 | - feature = 'auth' |
1856 | + usage = u'authenticate <account> [on source] using <method> [<credential>]' |
1857 | + feature = ('auth',) |
1858 | |
1859 | @match(r'^authenticate\s+(.+?)(?:\s+on\s+(.+))?\s+using\s+(\S+)\s+(.+)$') |
1860 | def handler(self, event, user, source, method, credential): |
1861 | @@ -543,10 +554,10 @@ |
1862 | |
1863 | permission_values = {'no': '-', 'yes': '+', 'auth': ''} |
1864 | class Permissions(Processor): |
1865 | - u"""(grant|revoke|remove) <permission> (to|from|on) <username> [when authed] |
1866 | + usage = u"""(grant|revoke|remove) <permission> (to|from|on) <username> [when authed] |
1867 | permissions [for <username>] |
1868 | list permissions""" |
1869 | - feature = 'auth' |
1870 | + feature = ('auth',) |
1871 | |
1872 | permission = u'admin' |
1873 | |
1874 | @@ -631,8 +642,8 @@ |
1875 | event.addresponse(u'Permissions: %s', human_join(sorted(permissions)) or u'none') |
1876 | |
1877 | class Auth(Processor): |
1878 | - u"""auth <credential>""" |
1879 | - feature = 'auth' |
1880 | + usage = u'auth <credential>' |
1881 | + feature = ('auth',) |
1882 | |
1883 | @match(r'^auth(?:\s+(.+))?$') |
1884 | def handler(self, event, password): |
1885 | |
1886 | === modified file 'ibid/plugins/karma.py' |
1887 | --- ibid/plugins/karma.py 2010-01-18 23:20:33 +0000 |
1888 | +++ ibid/plugins/karma.py 2010-03-02 19:03:13 +0000 |
1889 | @@ -10,7 +10,10 @@ |
1890 | VersionedSchema |
1891 | from ibid.plugins import Processor, match, handler, authorise |
1892 | |
1893 | -help = {'karma': u'Keeps track of karma for people and things.'} |
1894 | +features = {'karma': { |
1895 | + 'description': u'Keeps track of karma for people and things.', |
1896 | + 'categories': ('remember',), |
1897 | +}} |
1898 | |
1899 | log = logging.getLogger('plugins.karma') |
1900 | |
1901 | @@ -47,8 +50,8 @@ |
1902 | self.time = datetime.utcnow() |
1903 | |
1904 | class Set(Processor): |
1905 | - u"""<subject> (++|--|==|ftw|ftl) [[reason]]""" |
1906 | - feature = 'karma' |
1907 | + usage = u'<subject> (++|--|==|ftw|ftl) [[reason]]' |
1908 | + feature = ('karma',) |
1909 | |
1910 | # Clashes with morse & math |
1911 | priority = 510 |
1912 | @@ -121,9 +124,9 @@ |
1913 | event.processed = True |
1914 | |
1915 | class Get(Processor): |
1916 | - u"""karma for <subject> |
1917 | + usage = u"""karma for <subject> |
1918 | [reverse] karmaladder""" |
1919 | - feature = 'karma' |
1920 | + feature = ('karma',) |
1921 | |
1922 | @match(r'^karma\s+(?:for\s+)?(.+)$') |
1923 | def handle_karma(self, event, subject): |
1924 | @@ -149,8 +152,8 @@ |
1925 | event.addresponse(u"I don't really care about anything") |
1926 | |
1927 | class Forget(Processor): |
1928 | - u"""forget karma for <subject> [[reason]]""" |
1929 | - feature = 'karma' |
1930 | + usage = u'forget karma for <subject> [[reason]]' |
1931 | + feature = ('karma',) |
1932 | |
1933 | # Clashes with factoid |
1934 | priority = -10 |
1935 | |
1936 | === modified file 'ibid/plugins/languages.py' |
1937 | --- ibid/plugins/languages.py 2010-03-01 12:13:12 +0000 |
1938 | +++ ibid/plugins/languages.py 2010-03-02 19:03:14 +0000 |
1939 | @@ -13,15 +13,18 @@ |
1940 | from ibid.utils import decode_htmlentities, json_webservice, human_join, \ |
1941 | is_url, url_to_bytestring |
1942 | |
1943 | -help = {} |
1944 | +features = {} |
1945 | |
1946 | -help['dict'] = u'Defines words and checks spellings.' |
1947 | +features['dict'] = { |
1948 | + 'description': u'Defines words and checks spellings.', |
1949 | + 'categories': ('lookup',), |
1950 | +} |
1951 | class Dict(Processor): |
1952 | - u"""spell <word> [using <strategy>] |
1953 | + usage = u"""spell <word> [using <strategy>] |
1954 | define <word> [using <dictionary>] |
1955 | (dictionaries|strategies) |
1956 | (dictionary|strategy) <name>""" |
1957 | - feature = 'dict' |
1958 | + feature = ('dict',) |
1959 | |
1960 | server = Option('server', 'Dictionary server hostname', 'localhost') |
1961 | port = IntOption('port', 'Dictionary server port number', 2628) |
1962 | @@ -130,12 +133,15 @@ |
1963 | class UnknownLanguageException (Exception): pass |
1964 | class TranslationException (Exception): pass |
1965 | |
1966 | -help['translate'] = u'''Translates a phrase using Google Translate.''' |
1967 | +features['translate'] = { |
1968 | + 'description': u'Translates a phrase using Google Translate.', |
1969 | + 'categories': ('lookup', 'convert', 'web',), |
1970 | +} |
1971 | class Translate(Processor): |
1972 | - u"""translate (<phrase>|<url>) [from <language>] [to <language>] |
1973 | - translation chain <phrase> [from <language>] [to <language>]""" |
1974 | + usage = u"""translate (<phrase>|<url>) [from <language>] [to <language>] |
1975 | + translation chain <phrase> [from <language>] [to <language>]""" |
1976 | |
1977 | - feature = 'translate' |
1978 | + feature = ('translate',) |
1979 | |
1980 | api_key = Option('api_key', 'Your Google API Key (optional)', None) |
1981 | referer = Option('referer', 'The referer string to use (API searches)', default_referer) |
1982 | |
1983 | === modified file 'ibid/plugins/lotto.py' |
1984 | --- ibid/plugins/lotto.py 2010-02-20 07:38:16 +0000 |
1985 | +++ ibid/plugins/lotto.py 2010-03-02 19:03:14 +0000 |
1986 | @@ -8,13 +8,16 @@ |
1987 | from ibid.plugins import Processor, match |
1988 | |
1989 | log = logging.getLogger('plugins.lotto') |
1990 | -help = {} |
1991 | |
1992 | -help['lotto'] = u"Gets the latest lotto results from the South African National Lottery." |
1993 | +features = {'lotto': { |
1994 | + 'description': u'Gets the latest lotto results from the South African ' |
1995 | + u'National Lottery.', |
1996 | + 'categories': ('lookup', 'south africa', 'web',), |
1997 | +}} |
1998 | class Lotto(Processor): |
1999 | - u"""lotto""" |
2000 | + usage = u'lotto' |
2001 | |
2002 | - feature = 'lotto' |
2003 | + feature = ('lotto',) |
2004 | |
2005 | za_url = 'http://www.nationallottery.co.za/' |
2006 | za_re = re.compile(r'images/(?:power_)?balls/(?:ball|power)_(\d+).gif') |
2007 | |
2008 | === modified file 'ibid/plugins/meetings.py' |
2009 | --- ibid/plugins/meetings.py 2010-02-16 08:06:35 +0000 |
2010 | +++ ibid/plugins/meetings.py 2010-03-02 19:03:14 +0000 |
2011 | @@ -19,14 +19,17 @@ |
2012 | from ibid.source.http import templates |
2013 | from ibid.utils import format_date, plural |
2014 | |
2015 | -help = {} |
2016 | +features = {} |
2017 | log = logging.getLogger('plugins.meetings') |
2018 | |
2019 | meetings = {} |
2020 | |
2021 | -help['meeting'] = u'Take minutes of an IRC Meeting' |
2022 | +features['meeting'] = { |
2023 | + 'description': u'Take minutes of an IRC Meeting', |
2024 | + 'categories': ('remember', 'monitor',), |
2025 | +} |
2026 | class Meeting(Processor): |
2027 | - u""" |
2028 | + usage = u""" |
2029 | (start | end) meeting [about <title>] |
2030 | I am <True Name> |
2031 | topic <topic> |
2032 | @@ -34,7 +37,7 @@ |
2033 | minutes so far |
2034 | meeting title is <title> |
2035 | """ |
2036 | - feature = 'meeting' |
2037 | + feature = ('meeting',) |
2038 | permission = u'chairmeeting' |
2039 | |
2040 | formats = Option('formats', u'Formats to log to. ' |
2041 | @@ -241,7 +244,7 @@ |
2042 | addressed = False |
2043 | processed = True |
2044 | priority = 1900 |
2045 | - feature = 'meeting' |
2046 | + feature = ('meeting',) |
2047 | |
2048 | def process(self, event): |
2049 | if 'channel' in event and 'source' in event \ |
2050 | @@ -270,14 +273,17 @@ |
2051 | 'time': event.time, |
2052 | }) |
2053 | |
2054 | -help['poll'] = u'Does a quick poll of channel members' |
2055 | +features['poll'] = { |
2056 | + 'description': u'Does a quick poll of channel members', |
2057 | + 'categories': ('decide',), |
2058 | +} |
2059 | class Poll(Processor): |
2060 | - u""" |
2061 | + usage = u""" |
2062 | [secret] poll on <topic> [until <time>] vote <option> [or <option>]... |
2063 | vote (<id> | <option>) [on <topic>] |
2064 | end poll |
2065 | """ |
2066 | - feature = 'poll' |
2067 | + feature = ('poll',) |
2068 | permission = u'chairmeeting' |
2069 | |
2070 | polls = {} |
2071 | |
2072 | === modified file 'ibid/plugins/memo.py' |
2073 | --- ibid/plugins/memo.py 2010-01-18 23:20:33 +0000 |
2074 | +++ ibid/plugins/memo.py 2010-03-02 19:03:14 +0000 |
2075 | @@ -15,7 +15,10 @@ |
2076 | from ibid.plugins.identity import get_identities |
2077 | from ibid.utils import ago, format_date |
2078 | |
2079 | -help = {'memo': u'Keeps messages for people.'} |
2080 | +features = {'memo': { |
2081 | + 'description': u'Keeps messages for people.', |
2082 | + 'categories': ('remember', 'message',), |
2083 | +}} |
2084 | |
2085 | nomemos_cache = set() |
2086 | notified_overlimit_cache = set() |
2087 | @@ -60,9 +63,9 @@ |
2088 | backref='recipient') |
2089 | |
2090 | class Tell(Processor): |
2091 | - u"""(tell|pm|privmsg|msg|ask) <person> [on <source>] <message> |
2092 | + usage = u"""(tell|pm|privmsg|msg|ask) <person> [on <source>] <message> |
2093 | forget my (first|last|<n>th) message for <person> [on <source>]""" |
2094 | - feature = 'memo' |
2095 | + feature = ('memo',) |
2096 | |
2097 | permission = u'sendmemo' |
2098 | permissions = (u'recvmemo',) |
2099 | @@ -225,7 +228,7 @@ |
2100 | .order_by(Memo.time.asc()).all() |
2101 | |
2102 | class Deliver(Processor): |
2103 | - feature = 'memo' |
2104 | + feature = ('memo',) |
2105 | |
2106 | addressed = False |
2107 | processed = True |
2108 | @@ -286,7 +289,7 @@ |
2109 | nomemos_cache.add(event.identity) |
2110 | |
2111 | class Notify(Processor): |
2112 | - feature = 'memo' |
2113 | + feature = ('memo',) |
2114 | |
2115 | event_types = (u'state',) |
2116 | addressed = False |
2117 | @@ -319,10 +322,10 @@ |
2118 | nomemos_cache.add(event.identity) |
2119 | |
2120 | class Messages(Processor): |
2121 | - u"""my messages |
2122 | + usage = u"""my messages |
2123 | message <number> |
2124 | my messages for <person> [on <source>]""" |
2125 | - feature = 'memo' |
2126 | + feature = ('memo',) |
2127 | |
2128 | @match(r'^my\s+messages$') |
2129 | def messages(self, event): |
2130 | |
2131 | === modified file 'ibid/plugins/memory.py' |
2132 | --- ibid/plugins/memory.py 2010-02-20 07:37:33 +0000 |
2133 | +++ ibid/plugins/memory.py 2010-03-02 19:03:14 +0000 |
2134 | @@ -14,9 +14,10 @@ |
2135 | from ibid.config import Option, IntOption |
2136 | from ibid.plugins import Processor, match |
2137 | |
2138 | -help = {} |
2139 | - |
2140 | -help['memory'] = u'Debugging module that keeps track of memory usage' |
2141 | +features = {'memory': { |
2142 | + 'description': u'Debugging module that keeps track of memory usage', |
2143 | + 'categories': ('debug',), |
2144 | +}} |
2145 | |
2146 | def get_memusage(): |
2147 | status = file('/proc/%i/status' % os.getpid(), 'r').readlines() |
2148 | @@ -25,7 +26,7 @@ |
2149 | |
2150 | class MemoryLog(Processor): |
2151 | |
2152 | - feature = 'memory' |
2153 | + feature = ('memory',) |
2154 | autoload = False |
2155 | |
2156 | mem_filename = Option('mem_filename', 'Memory log filename', 'logs/memory.log') |
2157 | @@ -96,9 +97,9 @@ |
2158 | self.obj_file.flush() |
2159 | |
2160 | class MemoryInfo(Processor): |
2161 | - u"memory usage" |
2162 | + usage = u'memory usage' |
2163 | |
2164 | - feature = 'memory' |
2165 | + feature = ('memory',) |
2166 | |
2167 | @match('^memory\s+usage$') |
2168 | def memory_usage(self, event): |
2169 | |
2170 | === modified file 'ibid/plugins/network.py' |
2171 | --- ibid/plugins/network.py 2010-02-22 08:58:15 +0000 |
2172 | +++ ibid/plugins/network.py 2010-03-02 19:03:14 +0000 |
2173 | @@ -25,13 +25,16 @@ |
2174 | url_to_bytestring, get_process_output |
2175 | from ibid.utils.html import get_country_codes |
2176 | |
2177 | -help = {} |
2178 | +features = {} |
2179 | |
2180 | -help['dns'] = u'Performs DNS lookups' |
2181 | +features['dns'] = { |
2182 | + 'description': u'Performs DNS lookups', |
2183 | + 'categories': ('lookup', 'sysadmin',), |
2184 | +} |
2185 | class DNS(Processor): |
2186 | - u"""dns [<record type>] [for] <host> [from <nameserver>]""" |
2187 | + usage = u'dns [<record type>] [for] <host> [from <nameserver>]' |
2188 | |
2189 | - feature = 'dns' |
2190 | + feature = ('dns',) |
2191 | |
2192 | def setup(self): |
2193 | if Resolver is None: |
2194 | @@ -74,10 +77,13 @@ |
2195 | |
2196 | event.addresponse(u'Records: %s', human_join(responses)) |
2197 | |
2198 | -help['ping'] = u'ICMP pings the specified host.' |
2199 | +features['ping'] = { |
2200 | + 'description': u'ICMP pings the specified host.', |
2201 | + 'categories': ('sysadmin', 'monitor',), |
2202 | +} |
2203 | class Ping(Processor): |
2204 | - u"""ping <host>""" |
2205 | - feature = 'ping' |
2206 | + usage = u'ping <host>' |
2207 | + feature = ('ping',) |
2208 | |
2209 | ping = Option('ping', 'Path to ping executable', 'ping') |
2210 | |
2211 | @@ -104,10 +110,13 @@ |
2212 | .replace(u'ping:', u'', 1).strip() |
2213 | event.addresponse(u'Error: %s', error) |
2214 | |
2215 | -help['tracepath'] = u'Traces the path to the given host.' |
2216 | +features['tracepath'] = { |
2217 | + 'description': u'Traces the path to the given host.', |
2218 | + 'categories': ('sysadmin',), |
2219 | +} |
2220 | class Tracepath(Processor): |
2221 | - u"""tracepath <host>""" |
2222 | - feature = 'tracepath' |
2223 | + usage = u'tracepath <host>' |
2224 | + feature = ('tracepath',) |
2225 | |
2226 | tracepath = Option('tracepath', 'Path to tracepath executable', 'tracepath') |
2227 | |
2228 | @@ -129,11 +138,14 @@ |
2229 | error = unicode_output(error.strip()) |
2230 | event.addresponse(u'Error: %s', error.replace(u'\n', u' ')) |
2231 | |
2232 | -help['ipcalc'] = u'IP address calculator' |
2233 | +features['ipcalc'] = { |
2234 | + 'description': u'IP address calculator', |
2235 | + 'categories': ('sysadmin', 'calculate',), |
2236 | +} |
2237 | class IPCalc(Processor): |
2238 | - u"""ipcalc <network>/<subnet> |
2239 | + usage = u"""ipcalc <network>/<subnet> |
2240 | ipcalc <address> - <address>""" |
2241 | - feature = 'ipcalc' |
2242 | + feature = ('ipcalc',) |
2243 | |
2244 | ipcalc = Option('ipcalc', 'Path to ipcalc executable', 'ipcalc') |
2245 | |
2246 | @@ -202,12 +214,15 @@ |
2247 | class HTTPException(Exception): |
2248 | pass |
2249 | |
2250 | -help['http'] = u'Tests if an HTTP site is up and retrieves HTTP URLs.' |
2251 | +features['http'] = { |
2252 | + 'description': u'Tests if an HTTP site is up and retrieves HTTP URLs.', |
2253 | + 'categories': ('monitor', 'sysadmin',), |
2254 | +} |
2255 | class HTTP(Processor): |
2256 | - u"""(get|head) <url> |
2257 | + usage = u"""(get|head) <url> |
2258 | is <domain> (up|down) |
2259 | tell me when <domain|url> is up""" |
2260 | - feature = 'http' |
2261 | + feature = ('http',) |
2262 | priority = -10 |
2263 | |
2264 | max_size = IntOption('max_size', u'Only request this many bytes', 500) |
2265 | @@ -396,11 +411,14 @@ |
2266 | return response.status, response.reason, data.decode(charset), \ |
2267 | response.getheaders() |
2268 | |
2269 | -help['tld'] = u"Resolve country TLDs (ISO 3166)" |
2270 | +features['tld'] = { |
2271 | + 'description': u'Resolve country TLDs (ISO 3166)', |
2272 | + 'categories': ('lookup', 'sysadmin',), |
2273 | +} |
2274 | class TLD(Processor): |
2275 | - u""".<tld> |
2276 | + usage = u""".<tld> |
2277 | tld for <country>""" |
2278 | - feature = 'tld' |
2279 | + feature = ('tld',) |
2280 | |
2281 | country_codes = {} |
2282 | |
2283 | @@ -434,11 +452,14 @@ |
2284 | |
2285 | event.addresponse(u"ISO doesn't know about any TLD for %s", location) |
2286 | |
2287 | -help['ports'] = u'Looks up port numbers for protocols' |
2288 | +features['ports'] = { |
2289 | + 'description': u'Looks up port numbers for protocols', |
2290 | + 'categories': ('lookup', 'sysadmin',), |
2291 | +} |
2292 | class Ports(Processor): |
2293 | - u"""port for <protocol> |
2294 | + usage = u"""port for <protocol> |
2295 | (tcp|udp) port <number>""" |
2296 | - feature = 'ports' |
2297 | + feature = ('ports',) |
2298 | priority = 10 |
2299 | |
2300 | services = Option('services', 'Path to services file', '/etc/services') |
2301 | @@ -502,12 +523,16 @@ |
2302 | else: |
2303 | event.addresponse(u"I don't know about any protocols using that port") |
2304 | |
2305 | -help['nmap'] = u'Finds open network ports on a host or scans a subnet for active hosts.' |
2306 | +features['nmap'] = { |
2307 | + 'description': u'Finds open network ports on a host or scans a subnet for ' |
2308 | + u'active hosts.', |
2309 | + 'categories': ('sysadmin',), |
2310 | +} |
2311 | class Nmap(Processor): |
2312 | - """port scan <hostname> |
2313 | + usage = u"""port scan <hostname> |
2314 | net scan <network>/<prefix>""" |
2315 | |
2316 | - feature = 'nmap' |
2317 | + feature = ('nmap',) |
2318 | permission = 'nmap' |
2319 | min_prefix = IntOption('min_prefix', 'Minimum network prefix that may be scanned', 24) |
2320 | |
2321 | |
2322 | === modified file 'ibid/plugins/oeis.py' |
2323 | --- ibid/plugins/oeis.py 2010-01-30 09:38:13 +0000 |
2324 | +++ ibid/plugins/oeis.py 2010-03-02 19:03:14 +0000 |
2325 | @@ -11,14 +11,15 @@ |
2326 | |
2327 | log = logging.getLogger('plugins.oeis') |
2328 | |
2329 | -help = {} |
2330 | - |
2331 | -help['oeis'] = 'Query the Online Encyclopedia of Integer Sequences' |
2332 | +features = {'oeis': { |
2333 | + 'description': 'Query the Online Encyclopedia of Integer Sequences', |
2334 | + 'categories': ('lookup', 'web', 'calculate',), |
2335 | +}} |
2336 | class OEIS(Processor): |
2337 | - u"""oeis (A<OEIS number>|M<EIS number>|N<HIS number>) |
2338 | + usage = u"""oeis (A<OEIS number>|M<EIS number>|N<HIS number>) |
2339 | oeis <term>[, ...]""" |
2340 | |
2341 | - feature = 'oeis' |
2342 | + feature = ('oeis',) |
2343 | |
2344 | @match(r'^oeis\s+([AMN]\d+|-?\d(?:\d|-|,|\s)*)$') |
2345 | def oeis (self, event, query): |
2346 | |
2347 | === modified file 'ibid/plugins/quotes.py' |
2348 | --- ibid/plugins/quotes.py 2010-01-30 09:38:13 +0000 |
2349 | +++ ibid/plugins/quotes.py 2010-03-02 19:03:14 +0000 |
2350 | @@ -19,12 +19,15 @@ |
2351 | |
2352 | log = logging.getLogger('plugins.quotes') |
2353 | |
2354 | -help = {} |
2355 | +features = {} |
2356 | |
2357 | -help['fortune'] = u'Returns a random fortune.' |
2358 | +features['fortune'] = { |
2359 | + 'description': u'Returns a random fortune.', |
2360 | + 'categories': ('fun', 'lookup',), |
2361 | +} |
2362 | class Fortune(Processor, RPC): |
2363 | - u"""fortune""" |
2364 | - feature = 'fortune' |
2365 | + usage = u'fortune' |
2366 | + feature = ('fortune',) |
2367 | |
2368 | fortune = Option('fortune', 'Path of the fortune executable', 'fortune') |
2369 | |
2370 | @@ -56,11 +59,14 @@ |
2371 | else: |
2372 | return None |
2373 | |
2374 | -help['bash'] = u'Retrieve quotes from bash.org.' |
2375 | +features['bash'] = { |
2376 | + 'description': u'Retrieve quotes from bash.org.', |
2377 | + 'categories': ('fun', 'lookup', 'web',), |
2378 | +} |
2379 | class Bash(Processor): |
2380 | - u"bash[.org] [(random|<number>)]" |
2381 | + usage = u'bash[.org] [(random|<number>)]' |
2382 | |
2383 | - feature = 'bash' |
2384 | + feature = ('bash',) |
2385 | |
2386 | public_browse = BoolOption('public_browse', 'Allow random quotes in public', True) |
2387 | |
2388 | @@ -87,14 +93,17 @@ |
2389 | output.append(line) |
2390 | event.addresponse(u'\n'.join(output), conflate=False) |
2391 | |
2392 | -help['fml'] = u'Retrieves quotes from fmylife.com.' |
2393 | +features['fml'] = { |
2394 | + 'description': u'Retrieves quotes from fmylife.com.', |
2395 | + 'categories': ('fun', 'lookup', 'web',), |
2396 | +} |
2397 | class FMLException(Exception): |
2398 | pass |
2399 | |
2400 | class FMyLife(Processor): |
2401 | - u"""fml (<number> | [random] | flop | top | last | love | money | kids | work | health | sex | miscellaneous )""" |
2402 | + usage = u'fml (<number> | [random] | flop | top | last | love | money | kids | work | health | sex | miscellaneous )' |
2403 | |
2404 | - feature = "fml" |
2405 | + feature = ('fml',) |
2406 | |
2407 | api_url = Option('fml_api_url', 'FML API URL base', 'http://api.betacie.com/') |
2408 | # The Ibid API Key, registered by Stefano Rivera: |
2409 | @@ -156,12 +165,15 @@ |
2410 | else: |
2411 | event.addresponse(u'Sorry, not in public. PM me') |
2412 | |
2413 | -help["tfln"] = u"Looks up quotes from textsfromlastnight.com" |
2414 | +features['tfln'] = { |
2415 | + 'description': u'Looks up quotes from textsfromlastnight.com', |
2416 | + 'categories': ('fun', 'lookup', 'web',), |
2417 | +} |
2418 | class TextsFromLastNight(Processor): |
2419 | - u"""tfln [(random|<number>)] |
2420 | + usage = u"""tfln [(random|<number>)] |
2421 | tfln (worst|best) [(today|this week|this month)]""" |
2422 | |
2423 | - feature = 'tfln' |
2424 | + feature = ('tfln',) |
2425 | |
2426 | public_browse = BoolOption('public_browse', 'Allow random quotes in public', True) |
2427 | |
2428 | @@ -226,12 +238,15 @@ |
2429 | def tfln_url(self, event, id): |
2430 | self.tfln(event, id) |
2431 | |
2432 | -help["mlia"] = u"Looks up quotes from MyLifeIsAverage.com and MyLifeIsG.com" |
2433 | +features['mlia'] = { |
2434 | + 'description': u'Looks up quotes from MyLifeIsAverage.com and MyLifeIsG.com', |
2435 | + 'categories': ('fun', 'lookup', 'web',), |
2436 | +} |
2437 | class MyLifeIsAverage(Processor): |
2438 | - u"""mlia [(<number> | random | recent | today | yesterday | this week | this month | this year )] |
2439 | + usage = u"""mlia [(<number> | random | recent | today | yesterday | this week | this month | this year )] |
2440 | mlig [(<number> | random | recent | today | yesterday | this week | this month | this year )]""" |
2441 | |
2442 | - feature = 'mlia' |
2443 | + feature = ('mlia',) |
2444 | |
2445 | public_browse = BoolOption('public_browse', |
2446 | 'Allow random quotes in public', True) |
2447 | @@ -338,12 +353,15 @@ |
2448 | def mlia_url(self, event, site, id): |
2449 | self.mlia(event, 'mli' + site[0].lower(), id) |
2450 | |
2451 | -help['bible'] = u'Retrieves Bible verses' |
2452 | +features['bible'] = { |
2453 | + 'description': u'Retrieves Bible verses', |
2454 | + 'categories': ('lookup', 'web',), |
2455 | +} |
2456 | class Bible(Processor): |
2457 | - u"""bible <passages> [in <version>] |
2458 | + usage = u"""bible <passages> [in <version>] |
2459 | <book> <verses> [in <version>]""" |
2460 | |
2461 | - feature = 'bible' |
2462 | + feature = ('bible',) |
2463 | # http://labs.bible.org/api/ is an alternative |
2464 | # Their feature set is a little different, but they should be fairly |
2465 | # compatible |
2466 | |
2467 | === modified file 'ibid/plugins/rfc.py' |
2468 | --- ibid/plugins/rfc.py 2010-01-18 23:20:33 +0000 |
2469 | +++ ibid/plugins/rfc.py 2010-03-02 19:03:14 +0000 |
2470 | @@ -9,16 +9,19 @@ |
2471 | from ibid.plugins import Processor, match |
2472 | from ibid.utils import cacheable_download |
2473 | |
2474 | -help = {"rfc": u"Looks up RFCs by number or title."} |
2475 | +features = {'rfc': { |
2476 | + 'description': u'Looks up RFCs by number or title.', |
2477 | + 'categories': ('lookup', 'web', 'development',), |
2478 | +}} |
2479 | |
2480 | cachetime = 60*60 |
2481 | log = logging.getLogger("plugin.rfc") |
2482 | |
2483 | class RFCLookup(Processor): |
2484 | - u"""rfc <number> |
2485 | + usage = u"""rfc <number> |
2486 | rfc [for] <search terms> |
2487 | rfc [for] /regex/""" |
2488 | - feature = "rfc" |
2489 | + feature = ('rfc',) |
2490 | |
2491 | indexurl = Option('index_url', "A HTTP url for the RFC Index file", "http://www.rfc-editor.org/rfc/rfc-index.txt") |
2492 | cachetime = IntOption("cachetime", "Time to cache RFC index for", cachetime) |
2493 | |
2494 | === modified file 'ibid/plugins/seen.py' |
2495 | --- ibid/plugins/seen.py 2010-01-18 23:20:33 +0000 |
2496 | +++ ibid/plugins/seen.py 2010-03-02 19:03:14 +0000 |
2497 | @@ -13,7 +13,10 @@ |
2498 | |
2499 | log = logging.getLogger('plugins.seen') |
2500 | |
2501 | -help = {'seen': u'Records when people were last seen.'} |
2502 | +features = {'seen': { |
2503 | + 'description': u'Records when people were last seen.', |
2504 | + 'categories': ('remember', 'lookup',), |
2505 | +}} |
2506 | |
2507 | class Sighting(Base): |
2508 | __table__ = Table('seen', Base.metadata, |
2509 | @@ -60,7 +63,7 @@ |
2510 | self.type, self.identity_id, self.channel, self.time, self.value) |
2511 | |
2512 | class See(Processor): |
2513 | - feature = 'seen' |
2514 | + feature = ('seen',) |
2515 | |
2516 | priority = 1500 |
2517 | event_types = (u'message', u'state') |
2518 | @@ -94,8 +97,8 @@ |
2519 | event.sender['id'], event.source) |
2520 | |
2521 | class Seen(Processor): |
2522 | - u"""seen <who>""" |
2523 | - feature = 'seen' |
2524 | + usage = u'seen <who>' |
2525 | + feature = ('seen',) |
2526 | |
2527 | @match(r'^(?:have\s+you\s+)?seen\s+(\S+)(?:\s+on\s+(\S+))?$') |
2528 | def handler(self, event, who, source): |
2529 | |
2530 | === modified file 'ibid/plugins/social.py' |
2531 | --- ibid/plugins/social.py 2010-02-25 20:59:02 +0000 |
2532 | +++ ibid/plugins/social.py 2010-03-02 19:03:14 +0000 |
2533 | @@ -15,13 +15,16 @@ |
2534 | from ibid.utils import ago, decode_htmlentities, json_webservice |
2535 | |
2536 | log = logging.getLogger('plugins.social') |
2537 | -help = {} |
2538 | +features = {} |
2539 | |
2540 | -help['lastfm'] = u'Lists the tracks last listened to by the specified user.' |
2541 | +features['lastfm'] = { |
2542 | + 'description': u'Lists the tracks last listened to by the specified user.', |
2543 | + 'categories': ('lookup', 'web',), |
2544 | +} |
2545 | class LastFm(Processor): |
2546 | - u"last.fm for <username>" |
2547 | + usage = u'last.fm for <username>' |
2548 | |
2549 | - feature = "lastfm" |
2550 | + feature = ('lastfm',) |
2551 | |
2552 | @match(r'^last\.?fm\s+for\s+(\S+?)\s*$') |
2553 | def listsongs(self, event, username): |
2554 | @@ -34,12 +37,16 @@ |
2555 | ago(event.time - dt_strptime(e.updated, '%a, %d %b %Y %H:%M:%S +0000'), 1) |
2556 | ) for e in songs['entries'])) |
2557 | |
2558 | -help["microblog"] = u"Looks up messages on microblogging services like twitter and identica." |
2559 | +features['microblog'] = { |
2560 | + 'description': u'Looks up messages on microblogging services like twitter ' |
2561 | + u'and identica.', |
2562 | + 'categories': ('lookup', 'web',), |
2563 | +} |
2564 | class Twitter(Processor): |
2565 | - u"""latest (tweet|identica) from <name> |
2566 | + usage = u"""latest (tweet|identica) from <name> |
2567 | (tweet|identica) <number>""" |
2568 | |
2569 | - feature = "microblog" |
2570 | + feature = ('microblog',) |
2571 | |
2572 | default = { |
2573 | 'twitter': {'endpoint': 'http://twitter.com/', 'api': 'twitter', 'name': 'tweet', 'user': 'twit'}, |
2574 | |
2575 | === modified file 'ibid/plugins/sources.py' |
2576 | --- ibid/plugins/sources.py 2010-01-18 23:20:33 +0000 |
2577 | +++ ibid/plugins/sources.py 2010-03-02 19:03:14 +0000 |
2578 | @@ -11,15 +11,18 @@ |
2579 | |
2580 | log = logging.getLogger('plugins.sources') |
2581 | |
2582 | -help = {} |
2583 | - |
2584 | - |
2585 | -help["actions"] = u"Provides commands for joining/parting channels on IRC and Jabber, and changing the bot's nick" |
2586 | +features = {} |
2587 | + |
2588 | +features['actions'] = { |
2589 | + 'description': u"Provides commands for joining/parting channels on IRC and " |
2590 | + u"Jabber, and changing the bot's nick", |
2591 | + 'categories': ('admin',), |
2592 | +} |
2593 | |
2594 | class Actions(Processor): |
2595 | - u"""(join|part|leave) [<channel> [on <source>]] |
2596 | + usage = u"""(join|part|leave) [<channel> [on <source>]] |
2597 | change nick to <nick> [on <source>]""" |
2598 | - feature = 'actions' |
2599 | + feature = ('actions',) |
2600 | |
2601 | permission = 'sources' |
2602 | |
2603 | @@ -98,10 +101,13 @@ |
2604 | if self.is_nickserv(event): |
2605 | log.info(u'Authenticated with NickServ') |
2606 | |
2607 | -help['saydo'] = u'Says or does stuff in a channel.' |
2608 | +features['saydo'] = { |
2609 | + 'description': u'Says or does stuff in a channel.', |
2610 | + 'categories': ('admin', 'fun',), |
2611 | +} |
2612 | class SayDo(Processor): |
2613 | - u"""(say|do) in <channel> [on <source>] <text>""" |
2614 | - feature = 'saydo' |
2615 | + usage = u'(say|do) in <channel> [on <source>] <text>' |
2616 | + feature = ('saydo',) |
2617 | |
2618 | permission = u'saydo' |
2619 | |
2620 | @@ -111,10 +117,14 @@ |
2621 | event.addresponse(what, address=False, target=channel, source=source or event.source, |
2622 | action=(action.lower() == u"do")) |
2623 | |
2624 | -help['redirect'] = u'Redirects the response to a command to a different channel.' |
2625 | +features['redirect'] = { |
2626 | + 'description': u'Redirects the response to a command to a different ' |
2627 | + u'channel.', |
2628 | + 'categories': ('admin', 'fun',), |
2629 | +} |
2630 | class RedirectCommand(Processor): |
2631 | - u"""redirect [to] <channel> [on <source>] <command>""" |
2632 | - feature = 'redirect' |
2633 | + usage = u'redirect [to] <channel> [on <source>] <command>' |
2634 | + feature = ('redirect',) |
2635 | |
2636 | priority = -1200 |
2637 | permission = u'saydo' |
2638 | @@ -131,7 +141,7 @@ |
2639 | event.message['clean'] = command |
2640 | |
2641 | class Redirect(Processor): |
2642 | - feature = 'redirect' |
2643 | + feature = ('redirect',) |
2644 | |
2645 | processed = True |
2646 | priority = 940 |
2647 | |
2648 | === modified file 'ibid/plugins/strings.py' |
2649 | --- ibid/plugins/strings.py 2010-02-25 20:43:01 +0000 |
2650 | +++ ibid/plugins/strings.py 2010-03-02 19:03:14 +0000 |
2651 | @@ -9,13 +9,16 @@ |
2652 | from ibid.compat import hashlib |
2653 | from ibid.plugins import Processor, match, authorise |
2654 | |
2655 | -help = {} |
2656 | +features = {} |
2657 | |
2658 | -help['hash'] = u'Calculates numerous cryptographic hash functions.' |
2659 | +features['hash'] = { |
2660 | + 'description': u'Calculates numerous cryptographic hash functions.', |
2661 | + 'categories': ('calculate',), |
2662 | +} |
2663 | class Hash(Processor): |
2664 | - u"""(md5|sha1|sha224|sha256|sha384|sha512) <string> |
2665 | + usage = u"""(md5|sha1|sha224|sha256|sha384|sha512) <string> |
2666 | crypt <string> <salt>""" |
2667 | - feature = 'hash' |
2668 | + feature = ('hash',) |
2669 | |
2670 | @match(r'^(md5|sha1|sha224|sha256|sha384|sha512)(?:sum)?\s+(.+?)$') |
2671 | def hash(self, event, hash, string): |
2672 | @@ -26,10 +29,13 @@ |
2673 | def handle_crypt(self, event, string, salt): |
2674 | event.addresponse(unicode(crypt(string.encode('utf-8'), salt.encode('utf-8')))) |
2675 | |
2676 | -help['base64'] = u'Encodes and decodes base 16, 32 and 64. Assumes UTF-8.' |
2677 | +features['base64'] = { |
2678 | + 'description': u'Encodes and decodes base 16, 32 and 64. Assumes UTF-8.', |
2679 | + 'categories': ('calculate', 'convert', 'development',), |
2680 | +} |
2681 | class Base64(Processor): |
2682 | - u"""base(16|32|64) (encode|decode) <string>""" |
2683 | - feature = 'base64' |
2684 | + usage = u'base(16|32|64) (encode|decode) <string>' |
2685 | + feature = ('base64',) |
2686 | |
2687 | @match(r'^b(?:ase)?(16|32|64)\s*(enc|dec)(?:ode)?\s+(.+?)$') |
2688 | def base64(self, event, base, operation, string): |
2689 | @@ -47,20 +53,26 @@ |
2690 | else: |
2691 | event.addresponse(unicode(func(string.encode('utf-8')))) |
2692 | |
2693 | -help['rot13'] = u'Transforms a string with ROT13.' |
2694 | +features['rot13'] = { |
2695 | + 'description': u'Transforms a string with ROT13.', |
2696 | + 'categories': ('convert', 'fun',), |
2697 | +} |
2698 | class Rot13(Processor): |
2699 | - u"""rot13 <string>""" |
2700 | - feature = 'rot13' |
2701 | + usage = u'rot13 <string>' |
2702 | + feature = ('rot13',) |
2703 | |
2704 | @match(r'^rot13\s+(.+)$') |
2705 | def rot13(self, event, string): |
2706 | repl = lambda x: x.group(0).encode('rot13') |
2707 | event.addresponse(re.sub('[a-zA-Z]+', repl, string)) |
2708 | |
2709 | -help['dvorak'] = u"Makes text typed on a QWERTY keyboard as if it was Dvorak work, and vice-versa" |
2710 | +features['dvorak'] = { |
2711 | + 'description': u'Makes text typed on a QWERTY keyboard as if it was Dvorak work, and vice-versa', |
2712 | + 'categories': ('convert', 'fun',), |
2713 | +} |
2714 | class Dvorak(Processor): |
2715 | - u"""(aoeu|asdf) <text>""" |
2716 | - feature = 'dvorak' |
2717 | + usage = u"""(aoeu|asdf) <text>""" |
2718 | + feature = ('dvorak',) |
2719 | |
2720 | # List of characters on each keyboard layout |
2721 | dvormap = u"""',.pyfgcrl/=aoeuidhtns-;qjkxbmwvz"<>PYFGCRL?+AOEUIDHTNS_:QJKXBMWVZ[]{}|""" |
2722 | @@ -79,10 +91,14 @@ |
2723 | def convert_from_dvorak(self, event, text): |
2724 | event.addresponse(text.translate(self.typed_on_dvorak)) |
2725 | |
2726 | -help['retest'] = u'Checks whether a regular expression matches a given string.' |
2727 | +features['retest'] = { |
2728 | + 'description': u'Checks whether a regular expression matches a given ' |
2729 | + u'string.', |
2730 | + 'categories': ('development',), |
2731 | +} |
2732 | class ReTest(Processor): |
2733 | - u"""does <pattern> match <string>""" |
2734 | - feature = 'retest' |
2735 | + usage = u'does <pattern> match <string>' |
2736 | + feature = ('retest',) |
2737 | permission = 'regex' |
2738 | |
2739 | @match('^does\s+(.+?)\s+match\s+(.+?)$') |
2740 | @@ -90,10 +106,13 @@ |
2741 | def retest(self, event, regex, string): |
2742 | event.addresponse(re.search(regex, string) and u'Yes' or u'No') |
2743 | |
2744 | -help["morse"] = u"Translates messages into and out of morse code." |
2745 | +features['morse'] = { |
2746 | + 'description': u'Translates messages into and out of morse code.', |
2747 | + 'categories': ('convert', 'fun',), |
2748 | +} |
2749 | class Morse(Processor): |
2750 | - u"""morse (text|morsecode)""" |
2751 | - feature = 'morse' |
2752 | + usage = u'morse (text|morsecode)' |
2753 | + feature = ('morse',) |
2754 | |
2755 | _table = { |
2756 | 'A': ".-", |
2757 | |
2758 | === modified file 'ibid/plugins/svn.py' |
2759 | --- ibid/plugins/svn.py 2010-02-20 07:37:19 +0000 |
2760 | +++ ibid/plugins/svn.py 2010-03-02 19:03:14 +0000 |
2761 | @@ -40,7 +40,10 @@ |
2762 | from ibid.config import DictOption, FloatOption, Option, BoolOption |
2763 | from ibid.utils import ago, format_date, human_join |
2764 | |
2765 | -help = {'svn': u'Retrieves commit logs from a Subversion repository.'} |
2766 | +features = {'svn': { |
2767 | + 'description': u'Retrieves commit logs from a Subversion repository.', |
2768 | + 'categories': ('development', 'lookup',), |
2769 | +}} |
2770 | |
2771 | HEAD_REVISION = object() |
2772 | |
2773 | @@ -387,10 +390,10 @@ |
2774 | pass |
2775 | |
2776 | class Subversion(Processor, RPC): |
2777 | - u"""(last commit|commit <revno>) [to <repo>] [full] |
2778 | + usage = u"""(last commit|commit <revno>) [to <repo>] [full] |
2779 | (svnrepos|svnrepositories) |
2780 | """ |
2781 | - feature = 'svn' |
2782 | + feature = ('svn',) |
2783 | autoload = False |
2784 | |
2785 | permission = u'svn' |
2786 | |
2787 | === modified file 'ibid/plugins/sysadmin.py' |
2788 | --- ibid/plugins/sysadmin.py 2010-02-13 12:08:22 +0000 |
2789 | +++ ibid/plugins/sysadmin.py 2010-03-02 19:03:14 +0000 |
2790 | @@ -9,12 +9,15 @@ |
2791 | from ibid.config import Option |
2792 | from ibid.utils import file_in_path, unicode_output, human_join, cacheable_download |
2793 | |
2794 | -help = {} |
2795 | +features = {} |
2796 | |
2797 | -help['aptitude'] = u'Searches for packages' |
2798 | +features['aptitude'] = { |
2799 | + 'description': u'Searches for packages', |
2800 | + 'categories': ('sysadmin', 'lookup',), |
2801 | +} |
2802 | class Aptitude(Processor): |
2803 | - u"""apt (search|show) <term>""" |
2804 | - feature = 'aptitude' |
2805 | + usage = u'apt (search|show) <term>' |
2806 | + feature = ('aptitude',) |
2807 | |
2808 | aptitude = Option('aptitude', 'Path to aptitude executable', 'aptitude') |
2809 | |
2810 | @@ -106,10 +109,13 @@ |
2811 | error = error[3:] |
2812 | event.addresponse(u"Couldn't find package: %s", error) |
2813 | |
2814 | -help['apt-file'] = u'Searches for packages containing the specified file' |
2815 | +features['apt-file'] = { |
2816 | + 'description': u'Searches for packages containing the specified file', |
2817 | + 'categories': ('sysadmin', 'lookup',), |
2818 | +} |
2819 | class AptFile(Processor): |
2820 | - u"""apt-file [search] <term>""" |
2821 | - feature = 'apt-file' |
2822 | + usage = u'apt-file [search] <term>' |
2823 | + feature = ('apt-file',) |
2824 | |
2825 | aptfile = Option('apt-file', 'Path to apt-file executable', 'apt-file') |
2826 | |
2827 | @@ -142,10 +148,13 @@ |
2828 | event.addresponse(u'Search error') |
2829 | raise Exception("apt-file: %s" % error) |
2830 | |
2831 | -help['man'] = u'Retrieves information from manpages.' |
2832 | +features['man'] = { |
2833 | + 'description': u'Retrieves information from manpages.', |
2834 | + 'categories': ('sysadmin', 'lookup',), |
2835 | +} |
2836 | class Man(Processor): |
2837 | - u"""man [<section>] <page>""" |
2838 | - feature = 'man' |
2839 | + usage = u'man [<section>] <page>' |
2840 | + feature = ('man',) |
2841 | |
2842 | man = Option('man', 'Path of the man executable', 'man') |
2843 | |
2844 | @@ -182,10 +191,13 @@ |
2845 | if index: |
2846 | event.addresponse(output[index+1].strip()) |
2847 | |
2848 | -help ['mac'] = u'Finds the organization owning the specific MAC address.' |
2849 | +features['mac'] = { |
2850 | + 'description': u'Finds the organization owning the specific MAC address.', |
2851 | + 'categories': ('sysadmin', 'lookup',), |
2852 | +} |
2853 | class Mac(Processor): |
2854 | - u"""mac <address>""" |
2855 | - feature = 'mac' |
2856 | + usage = u'mac <address>' |
2857 | + feature = ('mac',) |
2858 | |
2859 | @match(r'^((?:mac|oui|ether(?:net)?(?:\s*code)?)\s+)?((?:(?:[0-9a-f]{2}(?(1)[:-]?|:))){2,5}[0-9a-f]{2})$') |
2860 | def lookup_mac(self, event, _, mac): |
2861 | |
2862 | === modified file 'ibid/plugins/test.py' |
2863 | --- ibid/plugins/test.py 2010-01-18 23:20:33 +0000 |
2864 | +++ ibid/plugins/test.py 2010-03-02 19:03:14 +0000 |
2865 | @@ -4,40 +4,50 @@ |
2866 | from time import sleep |
2867 | |
2868 | import ibid |
2869 | -from ibid.plugins import Processor, match |
2870 | - |
2871 | -class Delay(Processor): |
2872 | +from ibid.plugins import Processor, match, authorise |
2873 | + |
2874 | +features = {'test': { |
2875 | + 'description': u'Test functions for use in bot development', |
2876 | + 'categories': ('debug',), |
2877 | +}} |
2878 | + |
2879 | +class Tests(Processor): |
2880 | + usage = u"""delay <seconds> |
2881 | + authorise <permission> |
2882 | + email <address> |
2883 | + raise exception |
2884 | + topic <topic> |
2885 | + """ |
2886 | + feature = ('test',) |
2887 | + permission = u'debug' |
2888 | |
2889 | @match(r'^delay\s+(\d+\.?\d*)$') |
2890 | - def handler(self, event, delay): |
2891 | + @authorise() |
2892 | + def sleep(self, event, delay): |
2893 | sleep(float(delay)) |
2894 | event.addresponse(True) |
2895 | |
2896 | -class Authorise(Processor): |
2897 | - |
2898 | @match(r'^authorise\s+(\S+)$') |
2899 | - def handler(self, event, permission): |
2900 | + def is_authorised(self, event, permission): |
2901 | if ibid.auth.authorise(event, permission): |
2902 | event.addresponse(u'Yes') |
2903 | else: |
2904 | event.addresponse(u'No') |
2905 | |
2906 | -class Email(Processor): |
2907 | - |
2908 | @match(r'^email\s+(.+)$') |
2909 | + @authorise() |
2910 | def email(self, event, address): |
2911 | event.addresponse(u'Test message', source='email', target=unicode(address)) |
2912 | event.addresponse(u"I've emailed %s", address) |
2913 | |
2914 | -class Except(Processor): |
2915 | - |
2916 | @match(r'^raise\s+exception$') |
2917 | - def handler(self, event): |
2918 | + @authorise() |
2919 | + def throw_up(self, event): |
2920 | raise Exception("Ow, that hurt.") |
2921 | |
2922 | -class Topic(Processor): |
2923 | @match(r'^topic\s+(.+)$') |
2924 | - def handler(self, event, topic): |
2925 | + @authorise() |
2926 | + def topic(self, event, topic): |
2927 | event.addresponse(topic, topic=True, address=False) |
2928 | |
2929 | # vi: set et sta sw=4 ts=4: |
2930 | |
2931 | === modified file 'ibid/plugins/trac.py' |
2932 | --- ibid/plugins/trac.py 2010-01-18 23:20:33 +0000 |
2933 | +++ ibid/plugins/trac.py 2010-03-02 19:03:14 +0000 |
2934 | @@ -13,7 +13,10 @@ |
2935 | from ibid.config import Option, BoolOption |
2936 | from ibid.utils import ago |
2937 | |
2938 | -help = {'trac': u'Retrieves tickets from a Trac database.'} |
2939 | +features = {'trac': { |
2940 | + 'description': u'Retrieves tickets from a Trac database.', |
2941 | + 'categories': ('development', 'lookup',), |
2942 | +}} |
2943 | |
2944 | class Ticket(object): |
2945 | pass |
2946 | @@ -25,9 +28,9 @@ |
2947 | mapper(Ticket, ticket_table) |
2948 | |
2949 | class Tickets(Processor, RPC): |
2950 | - u"""ticket <number> |
2951 | + usage = u"""ticket <number> |
2952 | (open|my|<who>'s) tickets""" |
2953 | - feature = 'trac' |
2954 | + feature = ('trac',) |
2955 | autoload = 'trac' in ibid.databases |
2956 | |
2957 | url = Option('url', 'URL of Trac instance') |
2958 | |
2959 | === modified file 'ibid/plugins/urlgrab.py' |
2960 | --- ibid/plugins/urlgrab.py 2010-02-24 09:52:01 +0000 |
2961 | +++ ibid/plugins/urlgrab.py 2010-03-02 19:03:14 +0000 |
2962 | @@ -16,12 +16,13 @@ |
2963 | from ibid.utils import url_regex |
2964 | from ibid.utils.html import get_html_parse_tree |
2965 | |
2966 | -help = {} |
2967 | - |
2968 | log = logging.getLogger('plugins.urlgrab') |
2969 | |
2970 | -help['url'] = u'Captures URLs seen in channel to database and/or to ' \ |
2971 | - u'delicious/faves' |
2972 | +features = {'urlgrab': { |
2973 | + 'description': u'Captures URLs seen in channel to database and/or to ' |
2974 | + u'delicious/faves', |
2975 | + 'categories': ('remember', 'web',), |
2976 | +}} |
2977 | |
2978 | class URL(Base): |
2979 | __table__ = Table('urls', Base.metadata, |
2980 | |
2981 | === modified file 'ibid/plugins/urlinfo.py' |
2982 | --- ibid/plugins/urlinfo.py 2010-01-25 09:23:49 +0000 |
2983 | +++ ibid/plugins/urlinfo.py 2010-03-02 19:03:14 +0000 |
2984 | @@ -16,13 +16,17 @@ |
2985 | default_user_agent = 'Mozilla/5.0' |
2986 | default_referer = "http://ibid.omnia.za.net/" |
2987 | |
2988 | -help = {} |
2989 | +features = {} |
2990 | |
2991 | log = logging.getLogger('plugins.url') |
2992 | |
2993 | +features['tinyurl'] = { |
2994 | + 'description': u'Shorten and lengthen URLs', |
2995 | + 'categories': ('lookup', 'web',), |
2996 | +} |
2997 | class Shorten(Processor): |
2998 | - u"""shorten <url>""" |
2999 | - feature = 'url' |
3000 | + usage = u'shorten <url>' |
3001 | + feature = ('tinyurl',) |
3002 | |
3003 | @match(r'^shorten\s+(\S+\.\S+)$') |
3004 | def shorten(self, event, url): |
3005 | @@ -38,9 +42,9 @@ |
3006 | return None |
3007 | |
3008 | class Lengthen(Processor): |
3009 | - u"""<url> |
3010 | + usage = u"""<url> |
3011 | expand <url>""" |
3012 | - feature = 'url' |
3013 | + feature = ('tinyurl',) |
3014 | |
3015 | services = ListOption('services', 'List of URL prefixes of URL shortening services', ( |
3016 | 'http://is.gd/', 'http://tinyurl.com/', 'http://ff.im/', |
3017 | @@ -70,11 +74,14 @@ |
3018 | |
3019 | event.addresponse(u"No redirect") |
3020 | |
3021 | -help['youtube'] = u'Determine the title and a download URL for a Youtube Video' |
3022 | +features['youtube'] = { |
3023 | + 'description': u'Determine the title and a download URL for a Youtube Video', |
3024 | + 'categories': ('lookup', 'web',), |
3025 | +} |
3026 | class Youtube(Processor): |
3027 | - u'<Youtube URL>' |
3028 | + usage = u'<Youtube URL>' |
3029 | |
3030 | - feature = 'youtube' |
3031 | + feature = ('youtube',) |
3032 | |
3033 | @match(r'^(?:youtube(?:\.com)?\s+)?' |
3034 | r'(?:http://)?(?:\w+\.)?youtube\.com/' |
3035 | |
3036 | === modified file 'scripts/ibid-plugin' |
3037 | --- scripts/ibid-plugin 2010-02-21 08:08:24 +0000 |
3038 | +++ scripts/ibid-plugin 2010-03-02 19:03:14 +0000 |
3039 | @@ -157,11 +157,11 @@ |
3040 | if isinstance(response, basestring): |
3041 | print 'Response: %s' % response |
3042 | elif response.get('action', False): |
3043 | - print 'Action: %s' % response['reply'] |
3044 | + print 'Action: %s' % response.get('reply') |
3045 | elif response.get('notice', False): |
3046 | - print 'Notice: %s' % response['reply'] |
3047 | + print 'Notice: %s' % response.get('reply') |
3048 | else: |
3049 | - print 'Response: %s' % response['reply'] |
3050 | + print 'Response: %s' % response.get('reply') |
3051 | |
3052 | event.session.close() |
3053 | |
3054 | |
3055 | === modified file 'setup.py' |
3056 | --- setup.py 2010-02-05 15:35:26 +0000 |
3057 | +++ setup.py 2010-03-02 19:03:13 +0000 |
3058 | @@ -16,6 +16,7 @@ |
3059 | 'jinja', |
3060 | 'pyopenssl', |
3061 | 'pysqlite', |
3062 | + 'PyStemmer', |
3063 | 'python-dateutil', |
3064 | 'SOAPpy', |
3065 | 'SQLAlchemy>=0.5', # Works with >=0.4.6 except on OS X |
Preliminary review to get you all thinking, testing, and hacking on this.
I'm mostly interested in the help module's interface.
TODOs:
* Do a pass through all hte processors and assign multiple features where appropriate.
* docstring to "usage" attribute conversion.