Merge lp:~keegan-csmith/ibid/named-groups-656201 into lp:~ibid-core/ibid/old-trunk-1.6

Proposed by Keegan Carruthers-Smith
Status: Merged
Approved by: Stefano Rivera
Approved revision: 1023
Merged at revision: 1027
Proposed branch: lp:~keegan-csmith/ibid/named-groups-656201
Merge into: lp:~ibid-core/ibid/old-trunk-1.6
Diff against target: 226 lines (+72/-25)
7 files modified
AUTHORS (+1/-0)
docs/api/ibid.plugins.rst (+16/-1)
ibid/plugins/__init__.py (+37/-10)
ibid/plugins/factoid.py (+6/-2)
ibid/plugins/memo.py (+7/-7)
ibid/plugins/network.py (+3/-3)
ibid/plugins/social.py (+2/-2)
To merge this branch: bzr merge lp:~keegan-csmith/ibid/named-groups-656201
Reviewer Review Type Date Requested Status
Stefano Rivera Approve
Max Rabkin Approve
Keegan Carruthers-Smith Abstain
Review via email: mp+51159@code.launchpad.net

Commit message

Added named arguments to the match decorator.

Description of the change

Named arguments for the match decorator.

To post a comment you must log in.
Revision history for this message
Stefano Rivera (stefanor) wrote :

Some plugins that could benefit from this: unichr (conversions), portfor (network), forget (memo), lookup_mac (sysadmin)

Revision history for this message
Keegan Carruthers-Smith (keegan-csmith) wrote :

I thought this would support repeating named arguments, but it does not. So gonna mark it as need fixing until I work out a way around it.

review: Needs Fixing
1017. By Keegan Carruthers-Smith

Allow a named argument to be used more than once for simple matches.

1018. By Keegan Carruthers-Smith

Changed name regex from \S+ to \w+.

Revision history for this message
Max Rabkin (max-rabkin) wrote :

@match(r'^I\s+am\s+(?P<nick>\S+)\s+on\s+(?P<network>\S+)$', simple=False)

This doesn't actually work: the name needs to be nick__1_, etc. Perhaps we should allow unmangled names as well: instead of

name = re.match(r'^(\S+)__\d+_$', name).group(1)

use

name = re.match(r'^(\S+)(?:__\d+_)?$', name).group(1)

review: Needs Fixing
1019. By Keegan Carruthers-Smith

Allow unmangled grouped names when using normal regexs.

Revision history for this message
Keegan Carruthers-Smith (keegan-csmith) wrote :

> Some plugins that could benefit from this: unichr (conversions), portfor
> (network), forget (memo), lookup_mac (sysadmin)
Just did portfor and forget. unichr looked too scary and lookup_mac wouldn't really benefit from it.

Revision history for this message
Keegan Carruthers-Smith (keegan-csmith) wrote :

> @match(r'^I\s+am\s+(?P<nick>\S+)\s+on\s+(?P<network>\S+)$', simple=False)
>
> This doesn't actually work: the name needs to be nick__1_, etc. Perhaps we
> should allow unmangled names as well: instead of
>
> name = re.match(r'^(\S+)__\d+_$', name).group(1)
>
> use
>
> name = re.match(r'^(\S+)(?:__\d+_)?$', name).group(1)

Done, but needs to be \S+?

Ok I think this is ready for reviews again.

review: Needs Resubmitting
Revision history for this message
Keegan Carruthers-Smith (keegan-csmith) :
review: Abstain
1020. By Keegan Carruthers-Smith

Added my name to the Copyright header.

Revision history for this message
Max Rabkin (max-rabkin) :
review: Approve
Revision history for this message
Stefano Rivera (stefanor) wrote :

I'd approve, but I have one issue. It's ambiguous in the documentation whether {url} grabs or not.

Revision history for this message
Stefano Rivera (stefanor) wrote :

Event: {'account': 1, 'responses': [], 'source': u'atrum', 'addressed': u'tibid', 'processed': False, 'time': datetime.datetime(2011, 2, 27, 16, 19, 43, 784287), 'identity': 1, 'message': {'raw': u'tibid: foo ~= s/[0-9]/X/r', 'deaddressed': u'foo ~= s/[0-9]/X/r', 'clean': u'foo ~= s/[0-9]/X/r', 'stripped': u'tibid: foo ~= s/[0-9]/X/r'}, 'type': u'message', 'public': True, 'channel': u'#ibid', 'sender': {'nick': u'tumbleweed', 'connection': u'tumbleweed!<email address hidden>', 'id': u'tumbleweed'}}
Traceback (most recent call last):
  File "/home/stefanor/bzr/ibid/stefanor/tibid/ibid/core.py", line 33, in _process
    processor.process(event)
  File "/home/stefanor/bzr/ibid/stefanor/tibid/ibid/plugins/__init__.py", line 148, in process
    "Can't intermix named and positional arguments.")
AssertionError: Can't intermix named and positional arguments.

review: Needs Fixing
1021. By Keegan Carruthers-Smith

Make factoid.modify only use named parameters.

1022. By Keegan Carruthers-Smith

Don't use group matches in selector docs.

Revision history for this message
Keegan Carruthers-Smith (keegan-csmith) wrote :

> I'd approve, but I have one issue. It's ambiguous in the documentation whether {url} grabs or not.
Reverted to old table in r1022

> AssertionError: Can't intermix named and positional arguments.
Fixed in r1021. This seems to be the only match which mix's named and positional arguments.

Revision history for this message
Stefano Rivera (stefanor) wrote :

separator != seperator

1023. By Keegan Carruthers-Smith

seperator != separator

Revision history for this message
Stefano Rivera (stefanor) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'AUTHORS'
2--- AUTHORS 2011-03-11 21:59:06 +0000
3+++ AUTHORS 2011-03-23 15:17:28 +0000
4@@ -24,3 +24,4 @@
5 * Kevin Woodland
6 * Guy Halse
7 * Dominic Cleal
8+ * Keegan Carruthers-Smith
9\ No newline at end of file
10
11=== modified file 'docs/api/ibid.plugins.rst'
12--- docs/api/ibid.plugins.rst 2010-06-13 22:23:43 +0000
13+++ docs/api/ibid.plugins.rst 2011-03-23 15:17:28 +0000
14@@ -147,7 +147,7 @@
15 Selector expands to
16 ============ ==========================================
17 ``{alpha}`` ``[a-zA-Z]+``
18- ``{any}`` ``.*``
19+ ``{any}`` ``.+``
20 ``{chunk}`` ``\S+``
21 ``{digits}`` ``\d+``
22 ``{number}`` ``\d*\.?\d+``
23@@ -169,6 +169,21 @@
24
25 @match(r'^(?:foo|bar)\s+(\S+)$', simple=False)
26
27+ Optionally you can specify keyword arguments, which will get passed on to
28+ the function. The syntax is ``{keyword:selector}``, where the keywords will
29+ get passed onto the decorated method::
30+
31+ @match(r'I am {nick:chunk} on {network:chunk}')
32+ def register(self, event, network, nick):
33+ pass
34+
35+ The above match is equivalent to this non-simple version::
36+
37+ @match(r'^I\s+am\s+(?P<nick>\S+)\s+on\s+(?P<network>\S+)$', simple=False)
38+
39+ Note that you cannot mix positional and keyword arguments. All your
40+ selectors must either be named, or all be unnamed.
41+
42 *version* can be set to one of:
43
44 ``'clean'``
45
46=== modified file 'ibid/plugins/__init__.py'
47--- ibid/plugins/__init__.py 2011-01-21 20:30:46 +0000
48+++ ibid/plugins/__init__.py 2011-03-23 15:17:28 +0000
49@@ -1,4 +1,4 @@
50-# Copyright (c) 2008-2010, Michael Gorven, Stefano Rivera
51+# Copyright (c) 2008-2011, Michael Gorven, Stefano Rivera, Keegan Carruthers-Smith
52 # Released under terms of the MIT/X/Expat Licence. See COPYING for details.
53
54 from copy import copy
55@@ -23,7 +23,7 @@
56 if not os.path.exists(os.path.join(x, *package + ['__init__.py']))]
57
58 import ibid
59-from ibid.compat import json
60+from ibid.compat import json, defaultdict
61 from ibid.utils import url_regex
62
63 __path__ = pluginPackagePaths(__name__) + __path__
64@@ -142,10 +142,27 @@
65 event.message[method.message_version])
66 if match is not None:
67 args = match.groups()
68+ kwargs = match.groupdict()
69+ if kwargs:
70+ assert len(args) == len(kwargs), (
71+ "Can't intermix named and positional arguments.")
72+ # Convert the names from the %s__%d_ format to %s
73+ args = {}
74+ for name, value in kwargs.iteritems():
75+ name = re.match(r'^(\S+?)(?:__\d+_)?$', name).group(1)
76+ if args.get(name, None) is None:
77+ args[name] = value
78+ else:
79+ assert value is None, (
80+ 'named argument %s was matched more '
81+ 'than once.' % name)
82 if args is not None:
83 if (not getattr(method, 'auth_required', False)
84 or auth_responses(event, self.permission)):
85- method(event, *args)
86+ if isinstance(args, dict):
87+ method(event, **args)
88+ else:
89+ method(event, *args)
90 elif not getattr(method, 'auth_fallthrough', True):
91 event.processed = True
92
93@@ -214,7 +231,7 @@
94 def _match_sub_selectors(regex):
95 selector_patterns = {
96 'alpha' : r'[a-zA-Z]+',
97- 'any' : r'.*',
98+ 'any' : r'.+',
99 'chunk' : r'\S+',
100 'digits' : r'\d+',
101 'number' : r'\d*\.?\d+',
102@@ -224,12 +241,22 @@
103
104 regex = regex.replace(' ', r'(?:\s+)')
105
106- for pattern in re.finditer('{(%s)}' % '|'.join(selector_patterns.keys()),
107- regex):
108- pattern = pattern.group(1)
109- old = '{%s}' % pattern
110- new = '(%s)' % selector_patterns[pattern]
111- regex = regex.replace(old, new)
112+ name_count = defaultdict(int)
113+ def selector_to_re(match):
114+ name = match.group(1)
115+ pattern = match.group(2)
116+
117+ if name is None:
118+ return '(%s)' % selector_patterns[pattern]
119+
120+ # Prevent conflicts when reusing a name
121+ name_count[name] += 1
122+ name = '%s__%d_' % (name, name_count[name])
123+
124+ return '(?P<%s>%s)' % (name, selector_patterns[pattern])
125+
126+ regex = re.sub(r'{(?:(\w+):)?(%s)}' % '|'.join(selector_patterns.keys()),
127+ selector_to_re, regex)
128
129 if not regex.startswith('^'):
130 regex = '^' + regex
131
132=== modified file 'ibid/plugins/factoid.py'
133--- ibid/plugins/factoid.py 2011-02-24 19:07:10 +0000
134+++ ibid/plugins/factoid.py 2011-03-23 15:17:28 +0000
135@@ -684,9 +684,12 @@
136 event.identity, event.sender['connection'])
137 event.addresponse(True)
138
139- @match(r'^(.+?)(?:\s+#(\d+)|\s+/(.+?)/(r?))?\s*(?:~=|=~)\s*([sy](?P<sep>.).+(?P=sep).*(?P=sep)[gir]*)$')
140+ @match(r'(?P<name>.+?)'
141+ r'(?: #{number:digits}| /(?P<pattern>.+?)/(?P<is_regex>r?))?'
142+ r'\s*(?:~=|=~)\s*'
143+ r'(?P<operation>[sy](?P<sep>.).+(?P=sep).*(?P=sep)[gir]*)$')
144 @authorise(fallthrough=False)
145- def modify(self, event, name, number, pattern, is_regex, operation, separator):
146+ def modify(self, event, name, number, pattern, is_regex, operation, sep):
147 factoids = get_factoid(event.session, name, number, pattern, is_regex, all=True)
148 if len(factoids) == 0:
149 if pattern:
150@@ -707,6 +710,7 @@
151 return
152
153 # Not very pythonistic, but escaping is a nightmare.
154+ separator = sep
155 parts = [[]]
156 pos = 0
157 while pos < len(operation):
158
159=== modified file 'ibid/plugins/memo.py'
160--- ibid/plugins/memo.py 2011-03-11 21:59:06 +0000
161+++ ibid/plugins/memo.py 2011-03-23 15:17:28 +0000
162@@ -151,18 +151,18 @@
163 'source': to.source,
164 })
165
166- @match(r'^(?:delete|forget)\s+(?:my\s+)?'
167- r'(?:(first|last|\d+(?:st|nd|rd|th)?)\s+)?' # 1st way to specify number
168- r'(?:memo|message|msg)\s+'
169- r'(?(1)|#?(\d+)\s+)?' # 2nd way
170- r'(?:for|to)\s+(.+?)(?:\s+on\s+(\S+))?$')
171+ @match(r'(?:delete|forget) (?:my )?'
172+ r'(?:(?P<num>first|last|\d+(?:st|nd|rd|th)?) )?' # 1st way to specify number
173+ r'(?:memo|message|msg) '
174+ r'(?(1)|#?{num:digits} )?' # 2nd way
175+ r'(?:for|to) (?P<who>.+?)(?: on {source:chunk})?')
176 @authorise(fallthrough=False)
177- def forget(self, event, num1, num2, who, source):
178+ def forget(self, event, num, who, source):
179 if not source:
180 source = event.source
181 else:
182 source = source.lower()
183- number = num1 or num2 or 'last'
184+ number = num or 'last'
185 number = number.lower()
186 if number == 0:
187 # Don't wrap around to last message, that'd be unexpected
188
189=== modified file 'ibid/plugins/network.py'
190--- ibid/plugins/network.py 2011-02-20 18:44:54 +0000
191+++ ibid/plugins/network.py 2011-03-23 15:17:28 +0000
192@@ -528,10 +528,10 @@
193 break
194 self.protocols[proto.lower()].append(port)
195
196- @match(r'^(?:(.+)\s+)?ports?(?:\s+numbers?)?(?(1)|\s+for\s+(.+))$')
197- def portfor(self, event, proto1, proto2):
198+ @match(r'(?:{proto:any} )?ports?(?: numbers?)?(?(1)| for {proto:any})')
199+ def portfor(self, event, proto):
200 self._load_services()
201- protocol = (proto1 or proto2).lower()
202+ protocol = proto.lower()
203 if protocol in self.protocols:
204 event.addresponse(human_join(self.protocols[protocol]))
205 else:
206
207=== modified file 'ibid/plugins/social.py'
208--- ibid/plugins/social.py 2011-02-21 16:19:25 +0000
209+++ ibid/plugins/social.py 2011-03-23 15:17:28 +0000
210@@ -27,7 +27,7 @@
211
212 features = ('lastfm',)
213
214- @match(r'^last\.?fm\s+for\s+(\S+?)\s*$')
215+ @match(r'last\.?fm for {username:chunk}')
216 def listsongs(self, event, username):
217 songs = feedparser.parse('http://ws.audioscrobbler.com/1.0/user/%s/recenttracks.rss?%s' % (username, time()))
218 if songs['bozo']:
219@@ -147,7 +147,7 @@
220 def twitter(self, event, id):
221 self.update(event, u'twitter', id)
222
223- @match(r'^https?://(?:www\.)?identi.ca/notice/(\d+)$')
224+ @match(r'https?://(?:www\.)?identi.ca/notice/{id:digits}')
225 def identica(self, event, id):
226 self.update(event, u'identica', id)
227

Subscribers

People subscribed via source and target branches