Merge lp:~jerith/divmod.org/short-directions-1215929 into lp:divmod.org
- short-directions-1215929
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Jean-Paul Calderone | ||||
Approved revision: | 2719 | ||||
Merged at revision: | 2717 | ||||
Proposed branch: | lp:~jerith/divmod.org/short-directions-1215929 | ||||
Merge into: | lp:divmod.org | ||||
Diff against target: |
397 lines (+213/-52) 6 files modified
Imaginary/imaginary/action.py (+24/-8) Imaginary/imaginary/objects.py (+12/-0) Imaginary/imaginary/resources/help/bury (+2/-1) Imaginary/imaginary/resources/help/dig (+2/-1) Imaginary/imaginary/resources/help/go (+2/-1) Imaginary/imaginary/test/test_actions.py (+171/-41) |
||||
To merge this branch: | bzr merge lp:~jerith/divmod.org/short-directions-1215929 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jean-Paul Calderone | Approve | ||
Review via email: mp+181830@code.launchpad.net |
Commit message
Description of the change
Adds some short aliases for standard directions.
- 2716. By Jeremy Thurgood
-
camelCase, not separated_
with_slugs. - 2717. By Jeremy Thurgood
-
Fix up some tests.
Jean-Paul Calderone (exarkun) wrote : | # |
Jeremy Thurgood (jerith) wrote : | # |
Commands currently do match prefixes, and the general `pyparsing.
- 2718. By Jeremy Thurgood
-
More general end-of-line matching for actions.
- 2719. By Jeremy Thurgood
-
Update help text with direction aliases.
Jean-Paul Calderone (exarkun) wrote : | # |
Thanks! This looks pretty good to me. A few points, all of which I'm happy to see addressed as follow-ups rather than directly in this branch:
- There's still lots of room for shortening tests. A lot of dig, bury, and movement tests are still exercising multiple things in a single method.
- There should be an explicit unit test for the new automatic `LineEnd` feature of Action.match
- It occurs to me that the expression for `Go` is a bit repetitive. It might be nice to shorten that expression.
As I mentioned above, I don't think any of these are blockers for this branch. If you're happy with it as-is, please go ahead and merge (ping me if you don't have permission to do that yet). If you could file bugs for the above three points as well, that'd be great. :)
Thanks again.
Jean-Paul Calderone (exarkun) : | # |
Preview Diff
1 | === modified file 'Imaginary/imaginary/action.py' |
2 | --- Imaginary/imaginary/action.py 2013-06-09 16:07:17 +0000 |
3 | +++ Imaginary/imaginary/action.py 2013-09-22 10:10:54 +0000 |
4 | @@ -160,15 +160,18 @@ |
5 | @classmethod |
6 | def match(cls, player, line): |
7 | """ |
8 | - @return: a list of 2-tuples of all the results of parsing the given |
9 | - C{line} using this L{Action} type's pyparsing C{expr} attribute, or |
10 | - None if the expression does not match the given line. |
11 | + Parse the given C{line} using this L{Action} type's pyparsing C{expr} |
12 | + attribute. A C{pyparsing.LineEnd} is appended to C{expr} to avoid |
13 | + accidentally matching a prefix instead of the whole line. |
14 | + |
15 | + @return: a list of 2-tuples of all the results of parsing, or None if |
16 | + the expression does not match the given line. |
17 | |
18 | @param line: a line of user input to be interpreted as an action. |
19 | |
20 | @see: L{imaginary.pyparsing} |
21 | """ |
22 | - return cls.expr.parseString(line) |
23 | + return (cls.expr + pyparsing.LineEnd()).parseString(line) |
24 | |
25 | |
26 | def do(self, player, line, **slots): |
27 | @@ -788,11 +791,21 @@ |
28 | |
29 | |
30 | |
31 | +_directionNames = objects.OPPOSITE_DIRECTIONS.keys() |
32 | +_directionNames.extend(objects.DIRECTION_ALIASES.keys()) |
33 | + |
34 | DIRECTION_LITERAL = reduce( |
35 | operator.xor, [ |
36 | pyparsing.Literal(d) |
37 | - for d |
38 | - in objects.OPPOSITE_DIRECTIONS]).setResultsName("direction") |
39 | + for d in _directionNames]).setResultsName("direction") |
40 | + |
41 | + |
42 | + |
43 | +def expandDirection(direction): |
44 | + """ |
45 | + Expand direction aliases into the names of the directions they refer to. |
46 | + """ |
47 | + return objects.DIRECTION_ALIASES.get(direction, direction) |
48 | |
49 | |
50 | |
51 | @@ -804,6 +817,7 @@ |
52 | pyparsing.restOfLine.setResultsName("name")) |
53 | |
54 | def do(self, player, line, direction, name): |
55 | + direction = expandDirection(direction) |
56 | if iimaginary.IContainer(player.thing.location).getExitNamed(direction, None) is not None: |
57 | raise eimaginary.ActionFailure(events.ThatDoesntMakeSense( |
58 | actor=player.thing, |
59 | @@ -831,6 +845,7 @@ |
60 | DIRECTION_LITERAL) |
61 | |
62 | def do(self, player, line, direction): |
63 | + direction = expandDirection(direction) |
64 | for exit in iimaginary.IContainer(player.thing.location).getExits(): |
65 | if exit.name == direction: |
66 | if exit.sibling is not None: |
67 | @@ -858,13 +873,13 @@ |
68 | |
69 | class Go(Action): |
70 | expr = ( |
71 | - DIRECTION_LITERAL | |
72 | (pyparsing.Literal("go") + pyparsing.White() + |
73 | targetString("direction")) | |
74 | (pyparsing.Literal("enter") + pyparsing.White() + |
75 | targetString("direction")) | |
76 | (pyparsing.Literal("exit") + pyparsing.White() + |
77 | - targetString("direction"))) |
78 | + targetString("direction")) | |
79 | + DIRECTION_LITERAL) |
80 | |
81 | actorInterface = iimaginary.IThing |
82 | |
83 | @@ -873,6 +888,7 @@ |
84 | Identify a direction by having the player search for L{IExit} |
85 | providers that they can see and reach. |
86 | """ |
87 | + directionName = expandDirection(directionName) |
88 | return player.obtainOrReportWhyNot( |
89 | Proximity( |
90 | 3.0, |
91 | |
92 | === modified file 'Imaginary/imaginary/objects.py' |
93 | --- Imaginary/imaginary/objects.py 2013-07-24 22:20:29 +0000 |
94 | +++ Imaginary/imaginary/objects.py 2013-09-22 10:10:54 +0000 |
95 | @@ -433,6 +433,18 @@ |
96 | |
97 | |
98 | |
99 | +DIRECTION_ALIASES = { |
100 | + u"n": u"north", |
101 | + u"s": u"south", |
102 | + u"w": u"west", |
103 | + u"e": u"east", |
104 | + u"nw": u"northwest", |
105 | + u"se": u"southeast", |
106 | + u"ne": u"northeast", |
107 | + u"sw": u"southwest"} |
108 | + |
109 | + |
110 | + |
111 | class Exit(item.Item): |
112 | """ |
113 | An L{Exit} is an oriented pathway between two L{Thing}s which each |
114 | |
115 | === modified file 'Imaginary/imaginary/resources/help/bury' |
116 | --- Imaginary/imaginary/resources/help/bury 2006-04-12 02:41:46 +0000 |
117 | +++ Imaginary/imaginary/resources/help/bury 2013-09-22 10:10:54 +0000 |
118 | @@ -2,4 +2,5 @@ |
119 | |
120 | Usage: bury <north | south | east | west> |
121 | |
122 | -Block off the exit in the indicated direction. |
123 | +Block off the exit in the indicated direction. Short directions (n, s, e, w) |
124 | +may be used in place of the full direction names. |
125 | |
126 | === modified file 'Imaginary/imaginary/resources/help/dig' |
127 | --- Imaginary/imaginary/resources/help/dig 2006-04-12 02:41:46 +0000 |
128 | +++ Imaginary/imaginary/resources/help/dig 2013-09-22 10:10:54 +0000 |
129 | @@ -5,4 +5,5 @@ |
130 | Create a new location with the indicated name and create a two-way |
131 | passage between it and the current location. The exit will be in |
132 | the specified direction from the current location to the new |
133 | -location. |
134 | +location. Short directions (n, s, e, w) may be used in place of the |
135 | +full direction names. |
136 | |
137 | === modified file 'Imaginary/imaginary/resources/help/go' |
138 | --- Imaginary/imaginary/resources/help/go 2006-04-12 02:41:46 +0000 |
139 | +++ Imaginary/imaginary/resources/help/go 2013-09-22 10:10:54 +0000 |
140 | @@ -2,4 +2,5 @@ |
141 | |
142 | Usage: go <north | south | east | west> |
143 | |
144 | -Travel in the indicated direction. |
145 | +Travel in the indicated direction. Short directions (n, s, e, w) may be used |
146 | +in place of the full direction names. |
147 | |
148 | === modified file 'Imaginary/imaginary/test/test_actions.py' |
149 | --- Imaginary/imaginary/test/test_actions.py 2013-07-26 00:20:17 +0000 |
150 | +++ Imaginary/imaginary/test/test_actions.py 2013-09-22 10:10:54 +0000 |
151 | @@ -423,7 +423,11 @@ |
152 | self.assertEquals(x[5].groups(), ()) |
153 | |
154 | |
155 | - def testDig(self): |
156 | + def test_dig(self): |
157 | + """ |
158 | + The I{dig} action creates a new location connected to the current |
159 | + location through an exit in the specified direction. |
160 | + """ |
161 | self._test( |
162 | "dig west dark tunnel", |
163 | ["You create an exit."], |
164 | @@ -447,45 +451,125 @@ |
165 | "dig west boring tunnel", |
166 | ["There is already an exit in that direction."]) |
167 | |
168 | - def testBury(self): |
169 | - self._test( |
170 | - "bury south", |
171 | - ["There isn't an exit in that direction."]) |
172 | - self.assertEquals(list(iimaginary.IContainer(self.location).getExits()), []) |
173 | - |
174 | - room = objects.Thing(store=self.store, name=u"destination", proper=True) |
175 | - objects.Container.createFor(room, capacity=1000) |
176 | - objects.Exit.link(room, self.location, u'north') |
177 | - |
178 | - self._test( |
179 | - "bury south", |
180 | - ["It's gone."], |
181 | - ["Test Player destroyed the exit to destination."]) |
182 | - |
183 | - self.assertEquals( |
184 | - list(iimaginary.IContainer(self.location).getExits()), |
185 | - []) |
186 | - |
187 | - self.assertEquals( |
188 | - list(iimaginary.IContainer(room).getExits()), |
189 | - []) |
190 | - |
191 | - objects.Exit.link(self.location, room, u'south') |
192 | - self.observer.moveTo(room) |
193 | - |
194 | - self._test( |
195 | - "bury south", |
196 | - ["It's gone."], |
197 | - ["The exit to Test Location crumbles and disappears."]) |
198 | - self.assertEquals( |
199 | - list(iimaginary.IContainer(self.location).getExits()), |
200 | - []) |
201 | - self.assertEquals( |
202 | - list(iimaginary.IContainer(room).getExits()), |
203 | - []) |
204 | - |
205 | - |
206 | - def testGo(self): |
207 | + |
208 | + def test_digWithDirectionAliases(self): |
209 | + """ |
210 | + The I{dig} action creates a new location connected to the current |
211 | + location through an exit in the specified direction even when that |
212 | + direction is an alias. |
213 | + """ |
214 | + self._test( |
215 | + "dig w dark tunnel", |
216 | + ["You create an exit."], |
217 | + ["Test Player created an exit to the west."]) |
218 | + room = iimaginary.IContainer(self.location).getExitNamed(u'west').toLocation |
219 | + self.assertEquals(room.name, u"dark tunnel") |
220 | + self.assertEquals(room.description, u'') |
221 | + self.assertIdentical(iimaginary.IContainer(room).getExitNamed(u'east').toLocation, |
222 | + self.location) |
223 | + |
224 | + self._test( |
225 | + "dig e bright tunnel", |
226 | + ["You create an exit."], |
227 | + ["Test Player created an exit to the east."]) |
228 | + room = iimaginary.IContainer(self.location).getExitNamed(u'east').toLocation |
229 | + self.assertEquals(room.name, u"bright tunnel") |
230 | + self.assertEquals(room.description, u'') |
231 | + self.assertIdentical(iimaginary.IContainer(room).getExitNamed(u'west').toLocation, self.location) |
232 | + |
233 | + self._test( |
234 | + "dig w boring tunnel", |
235 | + ["There is already an exit in that direction."]) |
236 | + |
237 | + |
238 | + def test_bury(self): |
239 | + """ |
240 | + The I{bury} action destroys an exit in the specified direction. |
241 | + """ |
242 | + self._test( |
243 | + "bury south", |
244 | + ["There isn't an exit in that direction."]) |
245 | + self.assertEquals(list(iimaginary.IContainer(self.location).getExits()), []) |
246 | + |
247 | + room = objects.Thing(store=self.store, name=u"destination", proper=True) |
248 | + objects.Container.createFor(room, capacity=1000) |
249 | + objects.Exit.link(room, self.location, u'north') |
250 | + |
251 | + self._test( |
252 | + "bury south", |
253 | + ["It's gone."], |
254 | + ["Test Player destroyed the exit to destination."]) |
255 | + |
256 | + self.assertEquals( |
257 | + list(iimaginary.IContainer(self.location).getExits()), |
258 | + []) |
259 | + |
260 | + self.assertEquals( |
261 | + list(iimaginary.IContainer(room).getExits()), |
262 | + []) |
263 | + |
264 | + objects.Exit.link(self.location, room, u'south') |
265 | + self.observer.moveTo(room) |
266 | + |
267 | + self._test( |
268 | + "bury south", |
269 | + ["It's gone."], |
270 | + ["The exit to Test Location crumbles and disappears."]) |
271 | + self.assertEquals( |
272 | + list(iimaginary.IContainer(self.location).getExits()), |
273 | + []) |
274 | + self.assertEquals( |
275 | + list(iimaginary.IContainer(room).getExits()), |
276 | + []) |
277 | + |
278 | + |
279 | + def test_buryWithDirectionAliases(self): |
280 | + """ |
281 | + The I{bury} action destroys an exit in the specified direction even |
282 | + when that direction is an alias. |
283 | + """ |
284 | + self._test( |
285 | + "bury s", |
286 | + ["There isn't an exit in that direction."]) |
287 | + self.assertEquals(list(iimaginary.IContainer(self.location).getExits()), []) |
288 | + |
289 | + room = objects.Thing(store=self.store, name=u"destination", proper=True) |
290 | + objects.Container.createFor(room, capacity=1000) |
291 | + objects.Exit.link(room, self.location, u'north') |
292 | + |
293 | + self._test( |
294 | + "bury s", |
295 | + ["It's gone."], |
296 | + ["Test Player destroyed the exit to destination."]) |
297 | + |
298 | + self.assertEquals( |
299 | + list(iimaginary.IContainer(self.location).getExits()), |
300 | + []) |
301 | + |
302 | + self.assertEquals( |
303 | + list(iimaginary.IContainer(room).getExits()), |
304 | + []) |
305 | + |
306 | + objects.Exit.link(self.location, room, u'south') |
307 | + self.observer.moveTo(room) |
308 | + |
309 | + self._test( |
310 | + "bury s", |
311 | + ["It's gone."], |
312 | + ["The exit to Test Location crumbles and disappears."]) |
313 | + self.assertEquals( |
314 | + list(iimaginary.IContainer(self.location).getExits()), |
315 | + []) |
316 | + self.assertEquals( |
317 | + list(iimaginary.IContainer(room).getExits()), |
318 | + []) |
319 | + |
320 | + |
321 | + def test_go(self): |
322 | + """ |
323 | + The I{go} action moves the player through an exit in the specified |
324 | + direction. |
325 | + """ |
326 | self._test( |
327 | "go west", |
328 | ["You can't go that way."]) |
329 | @@ -516,6 +600,41 @@ |
330 | ["Test Player arrives from the west."]) |
331 | |
332 | |
333 | + def test_goThroughDirectionAliases(self): |
334 | + """ |
335 | + The I{go} action moves the player through an exit in the specified |
336 | + direction even when that direction is an alias. |
337 | + """ |
338 | + self._test( |
339 | + "go w", |
340 | + ["You can't go that way."]) |
341 | + self._test( |
342 | + "w", |
343 | + ["You can't go that way."]) |
344 | + |
345 | + room = objects.Thing(store=self.store, name=u"destination") |
346 | + objects.Container.createFor(room, capacity=1000) |
347 | + objects.Exit.link(self.location, room, u"west") |
348 | + |
349 | + self._test( |
350 | + "w", |
351 | + [E("[ destination ]"), |
352 | + E("( east )"), |
353 | + ""], |
354 | + ["Test Player leaves west."]) |
355 | + |
356 | + self._test( |
357 | + "n", |
358 | + ["You can't go that way."]) |
359 | + self._test( |
360 | + "go e", |
361 | + [E("[ Test Location ]"), |
362 | + E("( west )"), |
363 | + "Location for testing.", |
364 | + "Here, you see Observer Player."], |
365 | + ["Test Player arrives from the west."]) |
366 | + |
367 | + |
368 | def test_goThroughOneWayExit(self): |
369 | """ |
370 | Going through a one-way exit with a known direction will announce that |
371 | @@ -559,7 +678,11 @@ |
372 | self.assertCommandOutput("go east", [E("You can't go that way.")], []) |
373 | |
374 | |
375 | - def testDirectionalMovement(self): |
376 | + def test_directionalMovement(self): |
377 | + """ |
378 | + You can move through exits in standard directions by just specifying |
379 | + the direction. |
380 | + """ |
381 | # A couple tweaks to state to make the test simpler |
382 | self.observer.location = None |
383 | self.location.description = None |
384 | @@ -579,6 +702,13 @@ |
385 | [E("[ ") + ".*" + E(" ]"), # Not testing room description |
386 | E("( ") + ".*" + E(" )"), # Just do enough to make sure it was not an error. |
387 | ""]) |
388 | + shortDirections = ["nw", "n", "ne", "e", "w", "sw", "s", "se"] |
389 | + for d, rd in zip(shortDirections, reversed(shortDirections)): |
390 | + self._test( |
391 | + d, |
392 | + [E("[ ") + ".*" + E(" ]"), # Not testing room description |
393 | + E("( ") + ".*" + E(" )"), # Just do enough to make sure it was not an error. |
394 | + ""]) |
395 | |
396 | |
397 | def test_scrutinize(self): |
I wonder about the change to explicitly use `pyparsing. LineEnd( )` in the expression for `Go`. Am I correct in concluding from this change that commands are currently allowed to match lines as long as the command is a *prefix* of the line (potentially leaving unmatched text at the end)? I can imagine this arising here since none of the existing commands are as like to be prefixes of other commands or bogus inputs as the new "n", "e", "s", and "w". If this is the case it might make sense to fix this more generally by declaring (to us, the development team) that `Action.expr` implicitly has `pyparsing. LineEnd( )` at the end and make `Action` automatically put it there for us.
Otherwise I think this is generally okay, but a couple more hopefully- straightforward comments:
- there are help files in Imaginary/ imaginary/ resources/ that should be kept up-to-date with respect to the actual implementation of commands. python. constants but that can clearly be delayed for a later ticket
- the unit tests should be broken up more so that each one is only testing one case (I know this does not reflect the prevailing style in Imaginary's test suite, sorry :/)
- I want to mumble something about twisted.
Thanks for the contribution!