Franck Pommereau

doc update

1 -"""SNAKES is the Net Algebra Kit for Editors and Simulators 1 +"""SNAKES library is organised into three parts:
2 - 2 +
3 -SNAKES is a Python library allowing to model all sorts of Petri nets 3 + * the core library is package `snakes` and its modules, among which
4 -and to execute them. It is very general as most Petri nets annotations 4 + `snakes.nets` is the one to work with Petri nets while the others
5 -can be arbitrary Python expressions while most values can be arbitrary 5 + can be seen as auxiliary modules
6 -Python objects. 6 + * the plugin system and the plugins themselves reside into package
7 - 7 + `snakes.plugins`
8 -SNAKES can be further extended with plugins, several ones being 8 + * auxiliary tools are kept into other sub-packages: `snakes.lang`
9 -already provided, in particular two plugins implement the Petri nets 9 + for all the material related to parsing Python and other
10 -compositions defined for the Petri Box Calculus and its successors. 10 + languages, `snakes.utils` for various utilities like the ABCD
11 + compiler
11 12
12 @author: Franck Pommereau 13 @author: Franck Pommereau
13 @organization: University of Evry/Paris-Saclay 14 @organization: University of Evry/Paris-Saclay
14 @copyright: (C) 2005-2013 Franck Pommereau 15 @copyright: (C) 2005-2013 Franck Pommereau
15 @license: GNU Lesser General Public Licence (aka. GNU LGPL), see the 16 @license: GNU Lesser General Public Licence (aka. GNU LGPL), see the
16 - file `doc/COPYING` in the distribution or visit [the GNU web 17 + file `doc/COPYING` in the distribution or visit the [GNU web
17 site](http://www.gnu.org/licenses/licenses.html#LGPL) 18 site](http://www.gnu.org/licenses/licenses.html#LGPL)
18 @contact: franck.pommereau@ibisc.univ-evry.fr 19 @contact: franck.pommereau@ibisc.univ-evry.fr
19 """ 20 """
...@@ -21,8 +22,13 @@ compositions defined for the Petri Box Calculus and its successors. ...@@ -21,8 +22,13 @@ compositions defined for the Petri Box Calculus and its successors.
21 version = "0.9.16" 22 version = "0.9.16"
22 defaultencoding = "utf-8" 23 defaultencoding = "utf-8"
23 24
25 +"""## Module `snakes`
26 +
27 +This module only provides the exceptions used throughout SNAKES.
28 +"""
29 +
24 class SnakesError (Exception) : 30 class SnakesError (Exception) :
25 - "An error in SNAKES" 31 + "Generic error in SNAKES"
26 pass 32 pass
27 33
28 class ConstraintError (SnakesError) : 34 class ConstraintError (SnakesError) :
......
1 -"""Basic data types and functions used in SNAKES 1 +"""Basic data types and related functions used in SNAKES
2 """ 2 """
3 3
4 import operator, inspect 4 import operator, inspect
...@@ -8,7 +8,8 @@ from snakes.hashables import hdict ...@@ -8,7 +8,8 @@ from snakes.hashables import hdict
8 from snakes.pnml import Tree 8 from snakes.pnml import Tree
9 9
10 def cross (sets) : 10 def cross (sets) :
11 - """Cross-product. 11 + """Cross-product of some iterable collections (typically, `list`
12 + or `set`).
12 13
13 >>> list(cross([[1, 2], [3, 4, 5]])) 14 >>> list(cross([[1, 2], [3, 4, 5]]))
14 [(1, 3), (1, 4), (1, 5), (2, 3), (2, 4), (2, 5)] 15 [(1, 3), (1, 4), (1, 5), (2, 3), (2, 4), (2, 5)]
...@@ -21,10 +22,9 @@ def cross (sets) : ...@@ -21,10 +22,9 @@ def cross (sets) :
21 [] 22 []
22 23
23 @param sets: the sets of values to use 24 @param sets: the sets of values to use
24 - @type sets: `iterable(iterable(object))` 25 + @type sets: `iterable`
25 - @return: the `list` of obtained tuples (lists are used to allow 26 + @return: an iterator over the tuples in the cross-product
26 - unhashable objects) 27 + @rtype: `generator`
27 - @rtype: `generator(tuple(object))`
28 """ 28 """
29 if len(sets) == 0 : 29 if len(sets) == 0 :
30 pass 30 pass
...@@ -65,8 +65,8 @@ def iterate (value) : ...@@ -65,8 +65,8 @@ def iterate (value) :
65 class WordSet (set) : 65 class WordSet (set) :
66 """A set of words being able to generate fresh words. 66 """A set of words being able to generate fresh words.
67 """ 67 """
68 - def fresh (self, add=False, min=1, allowed="abcdefghijklmnopqrstuvwxyz", 68 + def fresh (self, add=False, min=1, base="",
69 - base="") : 69 + allowed="abcdefghijklmnopqrstuvwxyz") :
70 """Create a fresh word (ie, which is not in the set). 70 """Create a fresh word (ie, which is not in the set).
71 71
72 >>> w = WordSet(['foo', 'bar']) 72 >>> w = WordSet(['foo', 'bar'])
...@@ -87,6 +87,8 @@ class WordSet (set) : ...@@ -87,6 +87,8 @@ class WordSet (set) :
87 @type min: `int` 87 @type min: `int`
88 @param allowed: characters allowed in the new word 88 @param allowed: characters allowed in the new word
89 @type allowed: `str` 89 @type allowed: `str`
90 + @param base: prefix of generated words
91 + @type base: `str`
90 """ 92 """
91 if base : 93 if base :
92 result = [base] + [allowed[0]] * max(0, min - len(base)) 94 result = [base] + [allowed[0]] * max(0, min - len(base))
...@@ -118,7 +120,7 @@ class MultiSet (hdict) : ...@@ -118,7 +120,7 @@ class MultiSet (hdict) :
118 120
119 MultiSets support various operations, in particular: addition 121 MultiSets support various operations, in particular: addition
120 (`+`), substraction (`-`), multiplication by a non negative 122 (`+`), substraction (`-`), multiplication by a non negative
121 - integer (`*k`), comparisons (`<`, `>`, etc.), length (`len`) 123 + integer (`*k`), comparisons (`<`, `>`, etc.), length (`len`).
122 """ 124 """
123 def __init__ (self, values=[]) : 125 def __init__ (self, values=[]) :
124 """Initialise the multiset, adding values to it. 126 """Initialise the multiset, adding values to it.
...@@ -128,17 +130,18 @@ class MultiSet (hdict) : ...@@ -128,17 +130,18 @@ class MultiSet (hdict) :
128 >>> MultiSet() 130 >>> MultiSet()
129 MultiSet([]) 131 MultiSet([])
130 132
131 - @param values: a single value or an iterable object holding 133 + @param values: a single value or an iterable collection of
132 values (strings are not iterated) 134 values (strings are not iterated)
133 - @type values: any atomic object (`str` included) or an 135 + @type values: `object`
134 - iterable object
135 """ 136 """
136 self.add(values) 137 self.add(values)
137 def copy (self) : 138 def copy (self) :
138 """Copy a `MultiSet` 139 """Copy a `MultiSet`
139 140
140 - >>> MultiSet([1, 2, 3, 1, 2]).copy() 141 + >>> m1 = MultiSet([1, 2, 3, 1, 2])
141 - MultiSet([...]) 142 + >>> m2 = m1.copy()
143 + >>> m1 == m2 and m1 is not m2
144 + True
142 145
143 @return: a copy of the multiset 146 @return: a copy of the multiset
144 @rtype: `MultiSet` 147 @rtype: `MultiSet`
...@@ -147,6 +150,7 @@ class MultiSet (hdict) : ...@@ -147,6 +150,7 @@ class MultiSet (hdict) :
147 result.update(self) 150 result.update(self)
148 return result 151 return result
149 __pnmltag__ = "multiset" 152 __pnmltag__ = "multiset"
153 + # apidoc skip
150 def __pnmldump__ (self) : 154 def __pnmldump__ (self) :
151 """ 155 """
152 >>> MultiSet([1, 2, 3, 4, 1, 2]).__pnmldump__() 156 >>> MultiSet([1, 2, 3, 4, 1, 2]).__pnmldump__()
...@@ -202,6 +206,7 @@ class MultiSet (hdict) : ...@@ -202,6 +206,7 @@ class MultiSet (hdict) :
202 Tree("value", None, Tree.from_obj(value)), 206 Tree("value", None, Tree.from_obj(value)),
203 Tree("multiplicity", str(self[value])))) 207 Tree("multiplicity", str(self[value]))))
204 return Tree(self.__pnmltag__, None, *nodes) 208 return Tree(self.__pnmltag__, None, *nodes)
209 + # apidoc skip
205 @classmethod 210 @classmethod
206 def __pnmlload__ (cls, tree) : 211 def __pnmlload__ (cls, tree) :
207 """Load a multiset from its PNML representation 212 """Load a multiset from its PNML representation
...@@ -220,9 +225,10 @@ class MultiSet (hdict) : ...@@ -220,9 +225,10 @@ class MultiSet (hdict) :
220 """Add a single value `times` times. 225 """Add a single value `times` times.
221 226
222 @param value: the value to add 227 @param value: the value to add
223 - @type value: any object 228 + @type value: `object`
224 @param times: the number of times that `value` has to be added 229 @param times: the number of times that `value` has to be added
225 @type times: non negative `int` 230 @type times: non negative `int`
231 + @raise ValueError: when `times` is negative
226 """ 232 """
227 if times < 0 : 233 if times < 0 :
228 raise ValueError("negative values are forbidden") 234 raise ValueError("negative values are forbidden")
...@@ -242,10 +248,11 @@ class MultiSet (hdict) : ...@@ -242,10 +248,11 @@ class MultiSet (hdict) :
242 [1, 1, 2, 2, 2, 2, 3, 3, 5, 5, 5] 248 [1, 1, 2, 2, 2, 2, 3, 3, 5, 5, 5]
243 249
244 @param values: the values to add or a single value to add 250 @param values: the values to add or a single value to add
245 - @type values: any atomic object (`str` included) or an 251 + @type values: `object`
246 - iterable object
247 @param times: the number of times each value should be added 252 @param times: the number of times each value should be added
248 - @type times: non negative `int` 253 + (must be non-negative)
254 + @type times: `int`
255 + @raise ValueError: when `times` is negative
249 """ 256 """
250 self.__mutable__() 257 self.__mutable__()
251 for value in iterate(values) : 258 for value in iterate(values) :
...@@ -258,6 +265,7 @@ class MultiSet (hdict) : ...@@ -258,6 +265,7 @@ class MultiSet (hdict) :
258 @param times: the number of times that `value` has to be 265 @param times: the number of times that `value` has to be
259 removed 266 removed
260 @type times: non negative `int` 267 @type times: non negative `int`
268 + @raise ValueError: when `times` is negative
261 """ 269 """
262 if times < 0 : 270 if times < 0 :
263 raise ValueError("negative values are forbidden") 271 raise ValueError("negative values are forbidden")
...@@ -269,8 +277,7 @@ class MultiSet (hdict) : ...@@ -269,8 +277,7 @@ class MultiSet (hdict) :
269 def remove (self, values, times=1) : 277 def remove (self, values, times=1) :
270 """Remove values to the multiset. 278 """Remove values to the multiset.
271 279
272 - >>> m = MultiSet() 280 + >>> m = MultiSet([1, 2, 2, 3] * 2)
273 - >>> m.add([1, 2, 2, 3], 2)
274 >>> list(sorted(m.items())) 281 >>> list(sorted(m.items()))
275 [1, 1, 2, 2, 2, 2, 3, 3] 282 [1, 1, 2, 2, 2, 2, 3, 3]
276 >>> m.remove(2, 3) 283 >>> m.remove(2, 3)
...@@ -282,10 +289,11 @@ class MultiSet (hdict) : ...@@ -282,10 +289,11 @@ class MultiSet (hdict) :
282 289
283 @param values: the values to remove or a single value to 290 @param values: the values to remove or a single value to
284 remove 291 remove
285 - @type values: any atomic object (`str` included) or an 292 + @type values: `object`
286 - iterable object
287 @param times: the number of times each value should be removed 293 @param times: the number of times each value should be removed
288 - @type times: non negative `int` 294 + (must be non negative)
295 + @type times: `int`
296 + @raise ValueError: when `times` is negative
289 """ 297 """
290 self.__mutable__() 298 self.__mutable__()
291 for value in iterate(values) : 299 for value in iterate(values) :
...@@ -303,22 +311,21 @@ class MultiSet (hdict) : ...@@ -303,22 +311,21 @@ class MultiSet (hdict) :
303 """ 311 """
304 return self.get(value, 0) 312 return self.get(value, 0)
305 def __iter__ (self) : 313 def __iter__ (self) :
306 - """Iterate over the values (with repetitions). 314 + """Iterate over the values, _including repetitions_. Use
307 - 315 + `MultiSet.keys` to ignore repetitions.
308 - Use `MultiSet.keys` to ignore repetitions.
309 316
310 >>> list(sorted(iter(MultiSet([1, 2, 3, 1, 2])))) 317 >>> list(sorted(iter(MultiSet([1, 2, 3, 1, 2]))))
311 [1, 1, 2, 2, 3] 318 [1, 1, 2, 2, 3]
312 319
313 @return: an iterator on the elements 320 @return: an iterator on the elements
314 - @rtype: `iterator` 321 + @rtype: `generator`
315 """ 322 """
316 for value in dict.__iter__(self) : 323 for value in dict.__iter__(self) :
317 for count in range(self[value]) : 324 for count in range(self[value]) :
318 yield value 325 yield value
319 def items (self) : 326 def items (self) :
320 """Return the list of items with repetitions. The list without 327 """Return the list of items with repetitions. The list without
321 - repetitions can be retrieved with the `key` method. 328 + repetitions can be retrieved with `MultiSet.key`.
322 329
323 >>> m = MultiSet([1, 2, 2, 3]) 330 >>> m = MultiSet([1, 2, 2, 3])
324 >>> list(sorted(m.items())) 331 >>> list(sorted(m.items()))
...@@ -326,7 +333,7 @@ class MultiSet (hdict) : ...@@ -326,7 +333,7 @@ class MultiSet (hdict) :
326 >>> list(sorted(m.keys())) 333 >>> list(sorted(m.keys()))
327 [1, 2, 3] 334 [1, 2, 3]
328 335
329 - @return: list of items with repetitions 336 + @return: list of items including repetitions
330 @rtype: `list` 337 @rtype: `list`
331 """ 338 """
332 return list(iter(self)) 339 return list(iter(self))
...@@ -352,7 +359,7 @@ class MultiSet (hdict) : ...@@ -352,7 +359,7 @@ class MultiSet (hdict) :
352 """ 359 """
353 return "MultiSet([%s])" % ", ".join(repr(x) for x in self) 360 return "MultiSet([%s])" % ", ".join(repr(x) for x in self)
354 def __len__ (self) : 361 def __len__ (self) :
355 - """Return the number of elements, including repetitions. 362 + """Return the number of elements, _including repetitions_.
356 363
357 >>> len(MultiSet([1, 2] * 3)) 364 >>> len(MultiSet([1, 2] * 3))
358 6 365 6
...@@ -364,7 +371,7 @@ class MultiSet (hdict) : ...@@ -364,7 +371,7 @@ class MultiSet (hdict) :
364 else : 371 else :
365 return reduce(operator.add, self.values()) 372 return reduce(operator.add, self.values())
366 def size (self) : 373 def size (self) :
367 - """Return the number of elements, excluding repetitions. 374 + """Return the number of elements, _excluding repetitions_.
368 375
369 >>> MultiSet([1, 2] * 3).size() 376 >>> MultiSet([1, 2] * 3).size()
370 2 377 2
...@@ -387,7 +394,8 @@ class MultiSet (hdict) : ...@@ -387,7 +394,8 @@ class MultiSet (hdict) :
387 result._add(value, times) 394 result._add(value, times)
388 return result 395 return result
389 def __sub__ (self, other) : 396 def __sub__ (self, other) :
390 - """Substract two multisets. 397 + """Substract two multisets. The second multiset must be
398 + smaller than the first one.
391 399
392 >>> MultiSet([1, 2, 3]) - MultiSet([2, 3]) 400 >>> MultiSet([1, 2, 3]) - MultiSet([2, 3])
393 MultiSet([1]) 401 MultiSet([1])
...@@ -399,6 +407,7 @@ class MultiSet (hdict) : ...@@ -399,6 +407,7 @@ class MultiSet (hdict) :
399 @param other: the multiset to substract 407 @param other: the multiset to substract
400 @type other: `MultiSet` 408 @type other: `MultiSet`
401 @rtype: `MultiSet` 409 @rtype: `MultiSet`
410 + @raise ValueError: when the second multiset is not smaller
402 """ 411 """
403 result = self.copy() 412 result = self.copy()
404 for value, times in dict.items(other) : 413 for value, times in dict.items(other) :
...@@ -407,12 +416,13 @@ class MultiSet (hdict) : ...@@ -407,12 +416,13 @@ class MultiSet (hdict) :
407 def __mul__ (self, other) : 416 def __mul__ (self, other) :
408 """Multiplication by a non-negative integer. 417 """Multiplication by a non-negative integer.
409 418
410 - >>> MultiSet([1, 2]) * 3 419 + >>> MultiSet([1, 2]) * 3 == MultiSet([1, 2] * 3)
411 - MultiSet([...]) 420 + True
412 421
413 @param other: the integer to multiply 422 @param other: the integer to multiply
414 @type other: non-negative `int` 423 @type other: non-negative `int`
415 @rtype: `MultiSet` 424 @rtype: `MultiSet`
425 + @raise ValueError: when `other` is negative
416 """ 426 """
417 if other < 0 : 427 if other < 0 :
418 raise ValueError("negative values are forbidden") 428 raise ValueError("negative values are forbidden")
...@@ -460,7 +470,9 @@ class MultiSet (hdict) : ...@@ -460,7 +470,9 @@ class MultiSet (hdict) :
460 """ 470 """
461 return not(self == other) 471 return not(self == other)
462 def __lt__ (self, other) : 472 def __lt__ (self, other) :
463 - """Test for strict inclusion. 473 + """Test for strict inclusion. A multiset `A` is strictly
474 + included in a multiset `B` iff every element in `A` is also in
475 + `B` but less repetitions `A` than in `B`.
464 476
465 >>> MultiSet([1, 2, 3]) < MultiSet([1, 2, 3, 4]) 477 >>> MultiSet([1, 2, 3]) < MultiSet([1, 2, 3, 4])
466 True 478 True
...@@ -488,7 +500,7 @@ class MultiSet (hdict) : ...@@ -488,7 +500,7 @@ class MultiSet (hdict) :
488 result = True 500 result = True
489 return result or (dict.__len__(self) < dict.__len__(other)) 501 return result or (dict.__len__(self) < dict.__len__(other))
490 def __le__ (self, other) : 502 def __le__ (self, other) :
491 - """Test for inclusion inclusion. 503 + """Test for inclusion.
492 504
493 >>> MultiSet([1, 2, 3]) <= MultiSet([1, 2, 3, 4]) 505 >>> MultiSet([1, 2, 3]) <= MultiSet([1, 2, 3, 4])
494 True 506 True
...@@ -551,7 +563,8 @@ class MultiSet (hdict) : ...@@ -551,7 +563,8 @@ class MultiSet (hdict) :
551 """ 563 """
552 return other.__le__(self) 564 return other.__le__(self)
553 def domain (self) : 565 def domain (self) :
554 - """Return the domain of the multiset 566 + """Return the domain of the multiset, that is, the set of
567 + elements that occurr at least once in the multiset.
555 568
556 >>> list(sorted((MultiSet([1, 2, 3, 4]) + MultiSet([1, 2, 3])).domain())) 569 >>> list(sorted((MultiSet([1, 2, 3, 4]) + MultiSet([1, 2, 3])).domain()))
557 [1, 2, 3, 4] 570 [1, 2, 3, 4]
...@@ -589,6 +602,7 @@ class Substitution (object) : ...@@ -589,6 +602,7 @@ class Substitution (object) :
589 Substitution(...) 602 Substitution(...)
590 """ 603 """
591 self._dict = dict(*largs, **dargs) 604 self._dict = dict(*largs, **dargs)
605 + # apidoc skip
592 def __hash__ (self) : 606 def __hash__ (self) :
593 """ 607 """
594 >>> hash(Substitution(x=1, y=2)) == hash(Substitution(y=2, x=1)) 608 >>> hash(Substitution(x=1, y=2)) == hash(Substitution(y=2, x=1))
...@@ -599,7 +613,8 @@ class Substitution (object) : ...@@ -599,7 +613,8 @@ class Substitution (object) :
599 (hash(i) for i in self._dict.items()), 613 (hash(i) for i in self._dict.items()),
600 153913524) 614 153913524)
601 def __eq__ (self, other) : 615 def __eq__ (self, other) :
602 - """ 616 + """Test for equality.
617 +
603 >>> Substitution(x=1, y=2) == Substitution(y=2, x=1) 618 >>> Substitution(x=1, y=2) == Substitution(y=2, x=1)
604 True 619 True
605 >>> Substitution(x=1, y=2) == Substitution(y=1, x=1) 620 >>> Substitution(x=1, y=2) == Substitution(y=1, x=1)
...@@ -610,7 +625,8 @@ class Substitution (object) : ...@@ -610,7 +625,8 @@ class Substitution (object) :
610 except : 625 except :
611 return False 626 return False
612 def __ne__ (self, other) : 627 def __ne__ (self, other) :
613 - """ 628 + """Test for inequality.
629 +
614 >>> Substitution(x=1, y=2) != Substitution(y=2, x=1) 630 >>> Substitution(x=1, y=2) != Substitution(y=2, x=1)
615 False 631 False
616 >>> Substitution(x=1, y=2) != Substitution(y=1, x=1) 632 >>> Substitution(x=1, y=2) != Substitution(y=1, x=1)
...@@ -618,6 +634,7 @@ class Substitution (object) : ...@@ -618,6 +634,7 @@ class Substitution (object) :
618 """ 634 """
619 return not self.__eq__(other) 635 return not self.__eq__(other)
620 __pnmltag__ = "substitution" 636 __pnmltag__ = "substitution"
637 + # apidoc skip
621 def __pnmldump__ (self) : 638 def __pnmldump__ (self) :
622 """Dumps a substitution to a PNML tree 639 """Dumps a substitution to a PNML tree
623 640
...@@ -658,6 +675,7 @@ class Substitution (object) : ...@@ -658,6 +675,7 @@ class Substitution (object) :
658 Tree("value", None, 675 Tree("value", None,
659 Tree.from_obj(value)))) 676 Tree.from_obj(value))))
660 return Tree(self.__pnmltag__, None, *nodes) 677 return Tree(self.__pnmltag__, None, *nodes)
678 + # apidoc skip
661 @classmethod 679 @classmethod
662 def __pnmlload__ (cls, tree) : 680 def __pnmlload__ (cls, tree) :
663 """Load a substitution from its PNML representation 681 """Load a substitution from its PNML representation
...@@ -678,7 +696,8 @@ class Substitution (object) : ...@@ -678,7 +696,8 @@ class Substitution (object) :
678 result._dict[name] = value 696 result._dict[name] = value
679 return result 697 return result
680 def items (self) : 698 def items (self) :
681 - """Return the list of pairs (name, value). 699 + """Return the list of pairs `(name, value)` such that the
700 + substitution maps each `name` to the correspondign `value`.
682 701
683 >>> Substitution(x=1, y=2).items() 702 >>> Substitution(x=1, y=2).items()
684 [('...', ...), ('...', ...)] 703 [('...', ...), ('...', ...)]
...@@ -698,17 +717,17 @@ class Substitution (object) : ...@@ -698,17 +717,17 @@ class Substitution (object) :
698 """ 717 """
699 return set(self._dict.keys()) 718 return set(self._dict.keys())
700 def image (self) : 719 def image (self) :
701 - """Return the set of values associated to the names. 720 + """Return the list of values associated to the names.
702 721
703 >>> list(sorted(Substitution(x=1, y=2).image())) 722 >>> list(sorted(Substitution(x=1, y=2).image()))
704 [1, 2] 723 [1, 2]
705 724
706 - @return: the set of values associated to names 725 + @return: the list of values associated to names
707 - @rtype: `set` 726 + @rtype: `list`
708 """ 727 """
709 - return set(self._dict.values()) 728 + return list(self._dict.values())
710 def __contains__ (self, name) : 729 def __contains__ (self, name) :
711 - """Test if a name is mapped. 730 + """Test if a name is mapped by the substitution.
712 731
713 >>> 'x' in Substitution(x=1, y=2) 732 >>> 'x' in Substitution(x=1, y=2)
714 True 733 True
...@@ -758,8 +777,8 @@ class Substitution (object) : ...@@ -758,8 +777,8 @@ class Substitution (object) :
758 def dict (self) : 777 def dict (self) :
759 """Return the mapping as a dictionnary. 778 """Return the mapping as a dictionnary.
760 779
761 - >>> Substitution(x=1, y=2).dict() 780 + >>> Substitution(x=1, y=2).dict() == {'x': 1, 'y': 2}
762 - {'...': ..., '...': ...} 781 + True
763 782
764 @return: a dictionnary that does the same mapping as the 783 @return: a dictionnary that does the same mapping as the
765 substitution 784 substitution
...@@ -767,10 +786,12 @@ class Substitution (object) : ...@@ -767,10 +786,12 @@ class Substitution (object) :
767 """ 786 """
768 return self._dict.copy() 787 return self._dict.copy()
769 def copy (self) : 788 def copy (self) :
770 - """Copy the mapping. 789 + """Return a distinct copy of the mapping.
771 790
772 - >>> Substitution(x=1, y=2).copy() 791 + >>> s1 = Substitution(x=1, y=2)
773 - Substitution(...) 792 + >>> s2 = s1.copy()
793 + >>> s1 == s2 and s1 is not s2
794 + True
774 795
775 @return: a copy of the substitution 796 @return: a copy of the substitution
776 @rtype: `Substitution` 797 @rtype: `Substitution`
...@@ -793,8 +814,6 @@ class Substitution (object) : ...@@ -793,8 +814,6 @@ class Substitution (object) :
793 def __getitem__ (self, var) : 814 def __getitem__ (self, var) :
794 """Return the mapped value. 815 """Return the mapped value.
795 816
796 - Fails with `DomainError` if `var` is not mapped.
797 -
798 >>> s = Substitution(x=1, y=2) 817 >>> s = Substitution(x=1, y=2)
799 >>> s['x'] 818 >>> s['x']
800 1 819 1
...@@ -813,9 +832,8 @@ class Substitution (object) : ...@@ -813,9 +832,8 @@ class Substitution (object) :
813 except KeyError : 832 except KeyError :
814 raise DomainError("unbound variable '%s'" % var) 833 raise DomainError("unbound variable '%s'" % var)
815 def __call__ (self, var) : 834 def __call__ (self, var) :
816 - """Return the mapped value. 835 + """Return the mapped value or `var` itself if it is not
817 - 836 + mapped.
818 - Never fails but return `var` if it is not mapped.
819 837
820 >>> s = Substitution(x=1, y=2) 838 >>> s = Substitution(x=1, y=2)
821 >>> s('x') 839 >>> s('x')
...@@ -835,7 +853,6 @@ class Substitution (object) : ...@@ -835,7 +853,6 @@ class Substitution (object) :
835 return var 853 return var
836 def __add__ (self, other) : 854 def __add__ (self, other) :
837 """Add two substitution. 855 """Add two substitution.
838 -
839 Fails with `DomainError` if the two substitutions map a same 856 Fails with `DomainError` if the two substitutions map a same
840 name to different values. 857 name to different values.
841 858
...@@ -850,7 +867,7 @@ class Substitution (object) : ...@@ -850,7 +867,7 @@ class Substitution (object) :
850 @type other: `Substitution` 867 @type other: `Substitution`
851 @return: the union of the substitutions 868 @return: the union of the substitutions
852 @rtype: `Substitution` 869 @rtype: `Substitution`
853 - @raise DomainError: when one name is mapped to distinct values 870 + @raise DomainError: when a name is inconsistently mapped
854 """ 871 """
855 for var in self : 872 for var in self :
856 if var in other and (self[var] != other[var]) : 873 if var in other and (self[var] != other[var]) :
...@@ -860,8 +877,8 @@ class Substitution (object) : ...@@ -860,8 +877,8 @@ class Substitution (object) :
860 return s 877 return s
861 def __mul__ (self, other) : 878 def __mul__ (self, other) :
862 """Compose two substitutions. 879 """Compose two substitutions.
863 - 880 + The composition of `f` and `g` is such that `(f*g)(x)` is
864 - The composition of f and g is such that (f*g)(x) = f(g(x)). 881 + `f(g(x))`.
865 882
866 >>> f = Substitution(a=1, d=3, y=5) 883 >>> f = Substitution(a=1, d=3, y=5)
867 >>> g = Substitution(b='d', c=2, e=4, y=6) 884 >>> g = Substitution(b='d', c=2, e=4, y=6)
...@@ -886,7 +903,8 @@ class Symbol (object) : ...@@ -886,7 +903,8 @@ class Symbol (object) :
886 """If `export` is `True`, the created symbol is exported under 903 """If `export` is `True`, the created symbol is exported under
887 its name. If `export` is `False`, no export is made. Finally, 904 its name. If `export` is `False`, no export is made. Finally,
888 if `export` is a string, it specifies the name of the exported 905 if `export` is a string, it specifies the name of the exported
889 - symbol. 906 + symbol. Exporting the name is made by adding it to the
907 + caller's global dict.
890 908
891 @param name: the name (or value of the symbol) 909 @param name: the name (or value of the symbol)
892 @type name: `str` 910 @type name: `str`
...@@ -915,6 +933,7 @@ class Symbol (object) : ...@@ -915,6 +933,7 @@ class Symbol (object) :
915 if export : 933 if export :
916 inspect.stack()[1][0].f_globals[export] = self 934 inspect.stack()[1][0].f_globals[export] = self
917 __pnmltag__ = "symbol" 935 __pnmltag__ = "symbol"
936 + # apidoc skip
918 def __pnmldump__ (self) : 937 def __pnmldump__ (self) :
919 """ 938 """
920 >>> Symbol('egg', 'spam').__pnmldump__() 939 >>> Symbol('egg', 'spam').__pnmldump__()
...@@ -946,6 +965,7 @@ class Symbol (object) : ...@@ -946,6 +965,7 @@ class Symbol (object) :
946 else : 965 else :
947 children = [Tree.from_obj(self._export)] 966 children = [Tree.from_obj(self._export)]
948 return Tree(self.__pnmltag__, None, *children, **dict(name=self.name)) 967 return Tree(self.__pnmltag__, None, *children, **dict(name=self.name))
968 + # apidoc skip
949 @classmethod 969 @classmethod
950 def __pnmlload__ (cls, tree) : 970 def __pnmlload__ (cls, tree) :
951 """ 971 """
...@@ -963,7 +983,9 @@ class Symbol (object) : ...@@ -963,7 +983,9 @@ class Symbol (object) :
963 export = name 983 export = name
964 return cls(name, export) 984 return cls(name, export)
965 def __eq__ (self, other) : 985 def __eq__ (self, other) :
966 - """ 986 + """Test for equality of two symbols, which is the equality of
987 + their names.
988 +
967 >>> Symbol('foo', 'bar') == Symbol('foo') 989 >>> Symbol('foo', 'bar') == Symbol('foo')
968 True 990 True
969 >>> Symbol('egg') == Symbol('spam') 991 >>> Symbol('egg') == Symbol('spam')
...@@ -975,13 +997,15 @@ class Symbol (object) : ...@@ -975,13 +997,15 @@ class Symbol (object) :
975 except AttributeError : 997 except AttributeError :
976 return False 998 return False
977 def __ne__ (self, other) : 999 def __ne__ (self, other) :
978 - """ 1000 + """Test for inequality.
1001 +
979 >>> Symbol('foo', 'bar') != Symbol('foo') 1002 >>> Symbol('foo', 'bar') != Symbol('foo')
980 False 1003 False
981 >>> Symbol('egg') != Symbol('spam') 1004 >>> Symbol('egg') != Symbol('spam')
982 True 1005 True
983 """ 1006 """
984 return not (self == other) 1007 return not (self == other)
1008 + # apidoc skip
985 def __hash__ (self) : 1009 def __hash__ (self) :
986 """ 1010 """
987 >>> hash(Symbol('foo', 'bar')) == hash(Symbol('foo')) 1011 >>> hash(Symbol('foo', 'bar')) == hash(Symbol('foo'))
...@@ -989,13 +1013,15 @@ class Symbol (object) : ...@@ -989,13 +1013,15 @@ class Symbol (object) :
989 """ 1013 """
990 return hash((self.__class__.__name__, self.name)) 1014 return hash((self.__class__.__name__, self.name))
991 def __str__ (self) : 1015 def __str__ (self) :
992 - """ 1016 + """Short string representation
1017 +
993 >>> str(Symbol('foo')) 1018 >>> str(Symbol('foo'))
994 'foo' 1019 'foo'
995 """ 1020 """
996 return self.name 1021 return self.name
997 def __repr__ (self) : 1022 def __repr__ (self) :
998 - """ 1023 + """String representation suitable for `eval`
1024 +
999 >>> Symbol('foo') 1025 >>> Symbol('foo')
1000 Symbol('foo') 1026 Symbol('foo')
1001 >>> Symbol('egg', 'spam') 1027 >>> Symbol('egg', 'spam')
......
1 -"""Hashable mutable objets. 1 +"""This module proposes hashable version of the mutable containers
2 -
3 -This module proposes hashable version of the mutable containers
4 `list`, `dict` and `set`, called respectively `hlist`, `hdict` and 2 `list`, `dict` and `set`, called respectively `hlist`, `hdict` and
5 -`hset`. After one object has been hashed, it becomes not mutable and 3 +`hset`.
6 -raises a ValueError if a method which changes the object (let call a 4 +
7 -_mutation_ such a method) is invoqued. The object can be then un- 5 +After one object has been hashed, it becomes not mutable and raises a
8 -hashed by calling `unhash` on it so that it becomes mutable again. 6 +`ValueError` if a method which changes the object (let call a
7 +_mutation_ such a method) is invoqued. The object can be then
8 +un-hashed by calling `unhash` on it so that it becomes mutable again.
9 Notice that this may cause troubles if the object is stored in a set 9 Notice that this may cause troubles if the object is stored in a set
10 or dict which uses its hashcode to locate it. Notice also that 10 or dict which uses its hashcode to locate it. Notice also that
11 hashable containers cannot be hashed if they contain non hashable 11 hashable containers cannot be hashed if they contain non hashable
...@@ -53,7 +53,8 @@ from operator import xor ...@@ -53,7 +53,8 @@ from operator import xor
53 from snakes.compat import * 53 from snakes.compat import *
54 54
55 def unhash (obj) : 55 def unhash (obj) :
56 - """Make the object hashable again. 56 + """Make the object mutable again. This should be used with
57 + caution, especially if the object is stored in a dict or a set.
57 58
58 >>> l = hlist(range(3)) 59 >>> l = hlist(range(3))
59 >>> _ = hash(l) 60 >>> _ = hash(l)
...@@ -72,6 +73,7 @@ def unhash (obj) : ...@@ -72,6 +73,7 @@ def unhash (obj) :
72 except : 73 except :
73 pass 74 pass
74 75
76 +# apidoc skip
75 def hashable (cls) : 77 def hashable (cls) :
76 """Wrap methods in a class in order to make it hashable. 78 """Wrap methods in a class in order to make it hashable.
77 """ 79 """
...@@ -122,7 +124,7 @@ def hashable (cls) : ...@@ -122,7 +124,7 @@ def hashable (cls) :
122 return cls 124 return cls
123 125
124 class hlist (list) : 126 class hlist (list) :
125 - """Hashable lists. 127 + """Hashable lists. They support all standard methods from `list`.
126 128
127 >>> l = hlist(range(5)) 129 >>> l = hlist(range(5))
128 >>> l 130 >>> l
...@@ -138,6 +140,7 @@ class hlist (list) : ...@@ -138,6 +140,7 @@ class hlist (list) :
138 >>> unhash(l) 140 >>> unhash(l)
139 >>> l.append(6) 141 >>> l.append(6)
140 """ 142 """
143 + # apidoc stop
141 def __add__ (self, other) : 144 def __add__ (self, other) :
142 return self.__class__(list.__add__(self, other)) 145 return self.__class__(list.__add__(self, other))
143 def __delitem__ (self, item) : 146 def __delitem__ (self, item) :
...@@ -200,7 +203,8 @@ class hlist (list) : ...@@ -200,7 +203,8 @@ class hlist (list) :
200 hlist = hashable(hlist) 203 hlist = hashable(hlist)
201 204
202 class hdict (dict) : 205 class hdict (dict) :
203 - """Hashable dictionnaries. 206 + """Hashable dictionnaries. They support all standard methods from
207 + `dict`.
204 208
205 >>> l = hlist(range(5)) 209 >>> l = hlist(range(5))
206 >>> d = hdict([(l, 0)]) 210 >>> d = hdict([(l, 0)])
...@@ -229,6 +233,7 @@ class hdict (dict) : ...@@ -229,6 +233,7 @@ class hdict (dict) :
229 >>> d.pop(l) 233 >>> d.pop(l)
230 0 234 0
231 """ 235 """
236 + # apidoc stop
232 def __delitem__ (self, key) : 237 def __delitem__ (self, key) :
233 self.__mutable__() 238 self.__mutable__()
234 dict.__delitem__(self, key) 239 dict.__delitem__(self, key)
...@@ -271,7 +276,7 @@ class hdict (dict) : ...@@ -271,7 +276,7 @@ class hdict (dict) :
271 hdict = hashable(hdict) 276 hdict = hashable(hdict)
272 277
273 class hset (set) : 278 class hset (set) :
274 - """Hashable sets. 279 + """Hashable sets. They support all standard methods from `set`.
275 280
276 >>> s = hset() 281 >>> s = hset()
277 >>> l = hlist(range(5)) 282 >>> l = hlist(range(5))
...@@ -298,6 +303,7 @@ class hset (set) : ...@@ -298,6 +303,7 @@ class hset (set) :
298 >>> unhash(s) 303 >>> unhash(s)
299 >>> s.discard(l) 304 >>> s.discard(l)
300 """ 305 """
306 + # apidoc stop
301 def __and__ (self, other) : 307 def __and__ (self, other) :
302 return self.__class__(set.__and__(self, other)) 308 return self.__class__(set.__and__(self, other))
303 def __hash__ (self) : 309 def __hash__ (self) :
......
1 +"""This package is dedicated to parse and work with various languages,
2 +in particular Python itself and ABCD. These are mainly utilities for
3 +internal use in SNAKES, however, they may be of general interest
4 +independently of SNAKES.
5 +
6 +@todo: add documentation about how to use parsing and similar services
7 +"""
8 +
1 import sys 9 import sys
2 if sys.version_info[:2] in ((2, 6), (2, 7)) : 10 if sys.version_info[:2] in ((2, 6), (2, 7)) :
3 import ast 11 import ast
...@@ -14,9 +22,34 @@ else : ...@@ -14,9 +22,34 @@ else :
14 22
15 sys.modules["snkast"] = ast 23 sys.modules["snkast"] = ast
16 24
25 +"""### Module `ast` ###
26 +
27 +The module `ast` exported by `snakes.lang` is similar to Python's
28 +standard `ast` module (starting from version 2.6) but is available and
29 +uniform on every implementation of Python supported by SNAKES: CPython
30 +from 2.5, Jython and PyPy. (In general, these modules are available in
31 +these implementation - except CPython 2.5 - but with slight
32 +differences, so using `snakes.lang.ast` can be seen as a portable
33 +implementation.)
34 +
35 +Notice that this module is _not_ available for direct import but is
36 +exposed as a member of `snakes.lang`. Moreover, when `snakes.lang` is
37 +loaded, this module `ast` is also loaded as `snkast` in `sys.modules`,
38 +this allows to have both versions from Python and SNAKES
39 +simultaneously.
40 +
41 +>>> import snakes.lang.ast as ast
42 +ImportError ...
43 + ...
44 +ImportError: No module named ast
45 +>>> from snakes.lang import ast
46 +>>> import snkast
47 +"""
48 +
17 from . import unparse as _unparse 49 from . import unparse as _unparse
18 from snakes.compat import * 50 from snakes.compat import *
19 51
52 +# apidoc skip
20 class Names (ast.NodeVisitor) : 53 class Names (ast.NodeVisitor) :
21 def __init__ (self) : 54 def __init__ (self) :
22 ast.NodeVisitor.__init__(self) 55 ast.NodeVisitor.__init__(self)
...@@ -25,16 +58,24 @@ class Names (ast.NodeVisitor) : ...@@ -25,16 +58,24 @@ class Names (ast.NodeVisitor) :
25 self.names.add(node.id) 58 self.names.add(node.id)
26 59
27 def getvars (expr) : 60 def getvars (expr) :
28 - """ 61 + """Return the set of variables (or names in general) involved in a
62 + Python expression.
63 +
29 >>> list(sorted(getvars('x+y<z'))) 64 >>> list(sorted(getvars('x+y<z')))
30 ['x', 'y', 'z'] 65 ['x', 'y', 'z']
31 >>> list(sorted(getvars('x+y<z+f(3,t)'))) 66 >>> list(sorted(getvars('x+y<z+f(3,t)')))
32 ['f', 't', 'x', 'y', 'z'] 67 ['f', 't', 'x', 'y', 'z']
68 +
69 + @param expr: a Python expression
70 + @type expr: `str`
71 + @return: the set of variable names as strings
72 + @rtype: `set`
33 """ 73 """
34 names = Names() 74 names = Names()
35 names.visit(ast.parse(expr)) 75 names.visit(ast.parse(expr))
36 return names.names - set(['None', 'True', 'False']) 76 return names.names - set(['None', 'True', 'False'])
37 77
78 +# apidoc skip
38 class Unparser(_unparse.Unparser) : 79 class Unparser(_unparse.Unparser) :
39 boolops = {"And": 'and', "Or": 'or'} 80 boolops = {"And": 'and', "Or": 'or'}
40 def _Interactive (self, tree) : 81 def _Interactive (self, tree) :
...@@ -58,11 +99,13 @@ class Unparser(_unparse.Unparser) : ...@@ -58,11 +99,13 @@ class Unparser(_unparse.Unparser) :
58 self.dispatch(tree.body) 99 self.dispatch(tree.body)
59 self.leave() 100 self.leave()
60 101
102 +# apidoc skip
61 def unparse (st) : 103 def unparse (st) :
62 output = io.StringIO() 104 output = io.StringIO()
63 Unparser(st, output) 105 Unparser(st, output)
64 return output.getvalue().strip() 106 return output.getvalue().strip()
65 107
108 +# apidoc skip
66 class Renamer (ast.NodeTransformer) : 109 class Renamer (ast.NodeTransformer) :
67 def __init__ (self, map_names) : 110 def __init__ (self, map_names) :
68 ast.NodeTransformer.__init__(self) 111 ast.NodeTransformer.__init__(self)
...@@ -96,7 +139,8 @@ class Renamer (ast.NodeTransformer) : ...@@ -96,7 +139,8 @@ class Renamer (ast.NodeTransformer) :
96 ctx=ast.Load()), node) 139 ctx=ast.Load()), node)
97 140
98 def rename (expr, map={}, **ren) : 141 def rename (expr, map={}, **ren) :
99 - """ 142 + """Rename variables (ie, names) in a Python expression
143 +
100 >>> rename('x+y<z', x='t') 144 >>> rename('x+y<z', x='t')
101 '((t + y) < z)' 145 '((t + y) < z)'
102 >>> rename('x+y<z+f(3,t)', f='g', t='z', z='t') 146 >>> rename('x+y<z+f(3,t)', f='g', t='z', z='t')
...@@ -105,12 +149,22 @@ def rename (expr, map={}, **ren) : ...@@ -105,12 +149,22 @@ def rename (expr, map={}, **ren) :
105 '[(x + y) for x in range(3)]' 149 '[(x + y) for x in range(3)]'
106 >>> rename('[x+y for x in range(3)]', y='z') 150 >>> rename('[x+y for x in range(3)]', y='z')
107 '[(x + z) for x in range(3)]' 151 '[(x + z) for x in range(3)]'
152 +
153 + @param expr: a Python expression
154 + @type expr: `str`
155 + @param map: a mapping from old to new names (`str` to `str`)
156 + @type map: `dict`
157 + @param ren: additional mapping of old to new names
158 + @type ren: `str`
159 + @return: the new expression
160 + @rtype: `str`
108 """ 161 """
109 map_names = dict(map) 162 map_names = dict(map)
110 map_names.update(ren) 163 map_names.update(ren)
111 transf = Renamer(map_names) 164 transf = Renamer(map_names)
112 return unparse(transf.visit(ast.parse(expr))) 165 return unparse(transf.visit(ast.parse(expr)))
113 166
167 +# apidoc skip
114 class Binder (Renamer) : 168 class Binder (Renamer) :
115 def visit_Name (self, node) : 169 def visit_Name (self, node) :
116 if node.id in self.map[-1] : 170 if node.id in self.map[-1] :
...@@ -119,7 +173,10 @@ class Binder (Renamer) : ...@@ -119,7 +173,10 @@ class Binder (Renamer) :
119 return node 173 return node
120 174
121 def bind (expr, map={}, **ren) : 175 def bind (expr, map={}, **ren) :
122 - """ 176 + """Replace variables (ie, names) in an expression with other
177 + expressions. The replacements should be provided as `ast` nodes,
178 + and so could be arbitrary expression.
179 +
123 >>> bind('x+y<z', x=ast.Num(n=2)) 180 >>> bind('x+y<z', x=ast.Num(n=2))
124 '((2 + y) < z)' 181 '((2 + y) < z)'
125 >>> bind('x+y<z', y=ast.Num(n=2)) 182 >>> bind('x+y<z', y=ast.Num(n=2))
...@@ -128,6 +185,15 @@ def bind (expr, map={}, **ren) : ...@@ -128,6 +185,15 @@ def bind (expr, map={}, **ren) :
128 '[(x + y) for x in range(3)]' 185 '[(x + y) for x in range(3)]'
129 >>> bind('[x+y for x in range(3)]', y=ast.Num(n=2)) 186 >>> bind('[x+y for x in range(3)]', y=ast.Num(n=2))
130 '[(x + 2) for x in range(3)]' 187 '[(x + 2) for x in range(3)]'
188 +
189 + @param expr: a Python expression
190 + @type expr: `str`
191 + @param map: a mapping from old to new names (`str` to `ast.AST`)
192 + @type map: `dict`
193 + @param ren: additional mapping of old to new names
194 + @type ren: `ast.AST`
195 + @return: the new expression
196 + @rtype: `str`
131 """ 197 """
132 map_names = dict(map) 198 map_names = dict(map)
133 map_names.update(ren) 199 map_names.update(ren)
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
2 2
3 This module holds the various Petri net elements: arcs, places, 3 This module holds the various Petri net elements: arcs, places,
4 transitions, markings, nets themselves and marking graphs. 4 transitions, markings, nets themselves and marking graphs.
5 +
6 +@todo: revise documentation
5 """ 7 """
6 8
7 import re, operator, inspect 9 import re, operator, inspect
......
1 -"""A plugins system. 1 +"""This package implements SNAKES plugin system. SNAKES plugins
2 +themselves are available as modules within the package.
3 +
4 +Examples below are based on plugin `hello` that is distributed with
5 +SNAKES to be used as an exemple of how to build a plugin. It extends
6 +class `PetriNet` adding a method `hello` that says hello displaying
7 +the name of the net.
8 +
9 +## Loading plugins ##
2 10
3 The first example shows how to load a plugin: we load 11 The first example shows how to load a plugin: we load
4 `snakes.plugins.hello` and plug it into `snakes.nets`, which results 12 `snakes.plugins.hello` and plug it into `snakes.nets`, which results
5 -in a new module that actually `snakes.nets` extended by 13 +in a new module that is actually `snakes.nets` extended by
6 `snakes.plugins.hello`. 14 `snakes.plugins.hello`.
7 15
8 >>> import snakes.plugins as plugins 16 >>> import snakes.plugins as plugins
...@@ -18,11 +26,6 @@ The next example shows how to simulate the effect of `import module`: ...@@ -18,11 +26,6 @@ The next example shows how to simulate the effect of `import module`:
18 we give to `load` a thrid argument that is the name of the created 26 we give to `load` a thrid argument that is the name of the created
19 module, from which it becomes possible to import names or `*`. 27 module, from which it becomes possible to import names or `*`.
20 28
21 -**Warning:** this feature will not work `load` is not called from the
22 -module where we then do the `from ... import ...`. This is exactly the
23 -same when, from a module `foo` that you load a module `bar`: if `bar`
24 -loads other modules they will not be imported in `foo`.
25 -
26 >>> plugins.load('hello', 'snakes.nets', 'another_version') 29 >>> plugins.load('hello', 'snakes.nets', 'another_version')
27 <module ...> 30 <module ...>
28 >>> from another_version import PetriNet 31 >>> from another_version import PetriNet
...@@ -33,12 +36,23 @@ Hello from another net ...@@ -33,12 +36,23 @@ Hello from another net
33 >>> n.hello() 36 >>> n.hello()
34 Hi, this is yet another net! 37 Hi, this is yet another net!
35 38
36 -How to define a plugin is explained in the example `hello`. 39 +The last example shows how to load several plugins at once, instead of
40 +giving one plugin name, we just need to give a list of plugin names.
41 +
42 +>>> plugins.load(['hello', 'pos'], 'snakes.nets', 'mynets')
43 +<module ...>
44 +>>> from mynets import PetriNet
45 +>>> n = PetriNet('a net')
46 +>>> n.hello() # works thanks to plugin `hello`
47 +Hello from a net
48 +>>> n.bbox() # works thanks to plugin `pos`
49 +((0, 0), (0, 0))
37 """ 50 """
38 51
39 import imp, sys, inspect 52 import imp, sys, inspect
40 from functools import wraps 53 from functools import wraps
41 54
55 +# apidoc skip
42 def update (module, objects) : 56 def update (module, objects) :
43 """Update a module content 57 """Update a module content
44 """ 58 """
...@@ -54,48 +68,17 @@ def update (module, objects) : ...@@ -54,48 +68,17 @@ def update (module, objects) :
54 else : 68 else :
55 raise ValueError("cannot plug '%r'" % obj) 69 raise ValueError("cannot plug '%r'" % obj)
56 70
57 -def build (name, module, *objects) :
58 - """Builds an extended module.
59 -
60 - The parameter `module` is exactly that taken by the function
61 - `extend` of a plugin. This list argument `objects` holds all the
62 - objects, constructed in `extend`, that are extensions of objects
63 - from `module`. The resulting value should be returned by `extend`.
64 -
65 - @param name: the name of the constructed module
66 - @type name: `str`
67 - @param module: the extended module
68 - @type module: `module`
69 - @param objects: the sub-objects
70 - @type objects: each is a class object
71 - @return: the new module
72 - @rtype: `module`
73 - """
74 - result = imp.new_module(name)
75 - result.__dict__.update(module.__dict__)
76 - update(result, objects)
77 - result.__plugins__ = (module.__dict__.get("__plugins__",
78 - (module.__name__,))
79 - + (name,))
80 - for obj in objects :
81 - if inspect.isclass(obj) :
82 - obj.__plugins__ = result.__plugins__
83 - return result
84 -
85 def load (plugins, base, name=None) : 71 def load (plugins, base, name=None) :
86 - """Load plugins. 72 + """Load plugins, `plugins` can be a single plugin name, a module
87 - 73 + or a list of such values. If `name` is not `None`, the extended
88 - `plugins` can be a single plugin name or module or a list of such 74 + module is loaded as `name` in `sys.modules` as well as in the
89 - values. If `name` is not `None`, the extended module is loaded ad 75 + global environment from which `load` was called.
90 - `name` in `sys.modules` as well as in the global environment from
91 - which `load` was called.
92 76
93 @param plugins: the module that implements the plugin, or its 77 @param plugins: the module that implements the plugin, or its
94 - name, or a collection of such values 78 + name, or a collection (eg, list, tuple) of such values
95 - @type plugins: `str` or `module`, or a `list`/`tuple`/... of such 79 + @type plugins: `object`
96 - values
97 @param base: the module being extended or its name 80 @param base: the module being extended or its name
98 - @type base: `str` or `module` 81 + @type base: `object`
99 @param name: the name of the created module 82 @param name: the name of the created module
100 @type name: `str` 83 @type name: `str`
101 @return: the extended module 84 @return: the extended module
...@@ -125,17 +108,45 @@ def load (plugins, base, name=None) : ...@@ -125,17 +108,45 @@ def load (plugins, base, name=None) :
125 inspect.stack()[1][0].f_globals[name] = result 108 inspect.stack()[1][0].f_globals[name] = result
126 return result 109 return result
127 110
111 +"""## Creating plugins ###
112 +
113 +We show now how to develop a plugin that allows instances of
114 +`PetriNet` to say hello: a new method `PetriNet.hello` is added and
115 +the constructor `PetriNet.__init__` is added a keyword argument
116 +`hello` for the message to print when calling method `hello`.
117 +
118 +Defining a plugins required to write a module with a function called
119 +`extend` that takes as its single argument the module to be extended.
120 +Inside this function, extensions of the classes in the module are
121 +defined as normal sub-classes. Function `extend` returns the extended
122 +classes. A decorator called `plugin` must be used, it also allows to
123 +resolve plugin dependencies and conflicts.
124 +"""
125 +
126 +# apidoc include "hello.py" lang="python"
127 +
128 +"""Note that, when extending an existing method like `__init__` above,
129 +we have to take care that you may be working on an already extended
130 +class, consequently, we cannot know how its arguments have been
131 +changed already. So, we must always use those from the unextended
132 +method plus `**args`. Then, we remove from the latter what your plugin
133 +needs and pass the remaining to the method of the base class if we
134 +need to call it (which is usually the case). """
135 +
128 def plugin (base, depends=[], conflicts=[]) : 136 def plugin (base, depends=[], conflicts=[]) :
129 """Decorator for extension functions 137 """Decorator for extension functions
130 138
131 - @param base: name of base module (usually 'snakes.nets') 139 + @param base: name of base module (usually 'snakes.nets') that the
132 - @type base: str 140 + plugin extends
133 - @param depends: list of plugins on which this one depends 141 + @type base: `str`
134 - @type depends: list of str 142 + @param depends: list of plugin names (as `str`) this one depends
135 - @param conflicts: list of plugins with which this one conflicts 143 + on, prefix `snakes.plugins.` may be omitted
136 - @type conflicts: list of str 144 + @type depends: `list`
145 + @param conflicts: list of plugin names with which this one
146 + conflicts
147 + @type conflicts: `list`
137 @return: the appropriate decorator 148 @return: the appropriate decorator
138 - @rtype: function 149 + @rtype: `decorator`
139 """ 150 """
140 def wrapper (fun) : 151 def wrapper (fun) :
141 @wraps(fun) 152 @wraps(fun)
...@@ -164,9 +175,39 @@ def plugin (base, depends=[], conflicts=[]) : ...@@ -164,9 +175,39 @@ def plugin (base, depends=[], conflicts=[]) :
164 return extend 175 return extend
165 return wrapper 176 return wrapper
166 177
178 +# apidoc skip
167 def new_instance (cls, obj) : 179 def new_instance (cls, obj) :
168 """Create a copy of `obj` which is an instance of `cls` 180 """Create a copy of `obj` which is an instance of `cls`
169 """ 181 """
170 result = object.__new__(cls) 182 result = object.__new__(cls)
171 result.__dict__.update(obj.__dict__) 183 result.__dict__.update(obj.__dict__)
172 return result 184 return result
185 +
186 +# apidoc skip
187 +def build (name, module, *objects) :
188 + """Builds an extended module.
189 +
190 + The parameter `module` is exactly that taken by the function
191 + `extend` of a plugin. This list argument `objects` holds all the
192 + objects, constructed in `extend`, that are extensions of objects
193 + from `module`. The resulting value should be returned by `extend`.
194 +
195 + @param name: the name of the constructed module
196 + @type name: `str`
197 + @param module: the extended module
198 + @type module: `module`
199 + @param objects: the sub-objects
200 + @type objects: each is a class object
201 + @return: the new module
202 + @rtype: `module`
203 + """
204 + result = imp.new_module(name)
205 + result.__dict__.update(module.__dict__)
206 + update(result, objects)
207 + result.__plugins__ = (module.__dict__.get("__plugins__",
208 + (module.__name__,))
209 + + (name,))
210 + for obj in objects :
211 + if inspect.isclass(obj) :
212 + obj.__plugins__ = result.__plugins__
213 + return result
......
1 +"""
2 +@todo: revise (actually make) documentation
3 +"""
4 +
1 import snakes.plugins 5 import snakes.plugins
2 from snakes.plugins import new_instance 6 from snakes.plugins import new_instance
3 from snakes.pnml import Tree 7 from snakes.pnml import Tree
......
...@@ -26,6 +26,8 @@ ...@@ -26,6 +26,8 @@
26 >>> n.layout() 26 >>> n.layout()
27 >>> any(node.pos == (-100, -100) for node in sorted(n.node(), key=str)) 27 >>> any(node.pos == (-100, -100) for node in sorted(n.node(), key=str))
28 False 28 False
29 +
30 +@todo: revise documentation
29 """ 31 """
30 32
31 import os, os.path, subprocess, collections 33 import os, os.path, subprocess, collections
......
1 -"""An example plugin that allows instances class `PetriNet` to say hello. 1 +"""An example plugin that allows instances of `PetriNet` to
2 - 2 +say hello. The source code can be used as a starting
3 -A new method `hello` is added. The constructor is added a keyword 3 +example."""
4 -argument `hello` that must be the `str` to print when calling `hello`,
5 -with one `%s` that will be replaced by the name of the net when
6 -`hello` is called.
7 -
8 -Defining a plugins need writing a module with a single function called
9 -`extend` that takes a single argument that is the module to be
10 -extended.
11 -
12 -Inside the function, extensions of the classes in the module are
13 -defined as normal sub-classes.
14 -
15 -The function `extend` should return the extended module created by
16 -`snakes.plugins.build` that takes as arguments: the name of the
17 -extended module, the module taken as argument and the sub-classes
18 -defined (expected as a list argument `*args` in no special order).
19 -
20 -If the plugin depends on other plugins, for instance `foo` and `bar`,
21 -the function `extend` should be decorated by `@depends('foo', 'bar')`.
22 -
23 -Read the source code of this module to have an example
24 -"""
25 4
26 import snakes.plugins 5 import snakes.plugins
27 6
28 @snakes.plugins.plugin("snakes.nets") 7 @snakes.plugins.plugin("snakes.nets")
29 def extend (module) : 8 def extend (module) :
30 - """Extends `module` 9 + "Extends `module`"
31 - """
32 class PetriNet (module.PetriNet) : 10 class PetriNet (module.PetriNet) :
33 - """Extension of the class `PetriNet` in `module` 11 + "Extension of the class `PetriNet` in `module`"
34 - """
35 def __init__ (self, name, **args) : 12 def __init__ (self, name, **args) :
36 - """When extending an existing method, take care that you may 13 + """Add new keyword argument `hello`
37 - be working on an already extended class, so you so not
38 - know how its arguments have been changed. So, always use
39 - those from the unextended class plus `**args`, remove from
40 - it what your plugin needs and pass it to the method of the
41 - extended class if you need to call it.
42 14
43 >>> PetriNet('N').hello() 15 >>> PetriNet('N').hello()
44 Hello from N 16 Hello from N
...@@ -46,18 +18,13 @@ def extend (module) : ...@@ -46,18 +18,13 @@ def extend (module) :
46 Hi! This is N... 18 Hi! This is N...
47 19
48 @param args: plugin options 20 @param args: plugin options
49 - @keyword hello: the message to print, with `%s` where the 21 + @keyword hello: the message to print, with
50 - net name should appear. 22 + `%s` where the net name should appear.
51 @type hello: `str` 23 @type hello: `str`
52 """ 24 """
53 self._hello = args.pop("hello", "Hello from %s") 25 self._hello = args.pop("hello", "Hello from %s")
54 module.PetriNet.__init__(self, name, **args) 26 module.PetriNet.__init__(self, name, **args)
55 def hello (self) : 27 def hello (self) :
56 - """A new method `hello` 28 + "Ask the net to say hello"
57 -
58 - >>> n = PetriNet('N')
59 - >>> n.hello()
60 - Hello from N
61 - """
62 print(self._hello % self.name) 29 print(self._hello % self.name)
63 return PetriNet 30 return PetriNet
......
1 """A plugin to add labels to nodes and nets. 1 """A plugin to add labels to nodes and nets.
2 +
3 +@todo: revise (actually make) documentation
2 """ 4 """
3 5
4 from snakes.plugins import plugin, new_instance 6 from snakes.plugins import plugin, new_instance
......
...@@ -65,6 +65,8 @@ Buffer('buffer') ...@@ -65,6 +65,8 @@ Buffer('buffer')
65 (1, 2) 65 (1, 2)
66 >>> n._declare 66 >>> n._declare
67 ['global x; x=1'] 67 ['global x; x=1']
68 +
69 +@todo: revise documentation
68 """ 70 """
69 71
70 import snakes.plugins 72 import snakes.plugins
......
...@@ -42,6 +42,8 @@ Position(1, 3) ...@@ -42,6 +42,8 @@ Position(1, 3)
42 >>> n.transpose() 42 >>> n.transpose()
43 >>> n.node('t01').pos 43 >>> n.node('t01').pos
44 Position(-3, 1) 44 Position(-3, 1)
45 +
46 +@todo: revise documentation
45 """ 47 """
46 48
47 from snakes import SnakesError 49 from snakes import SnakesError
......
1 +"""
2 +@todo: revise (actually make) documentation
3 +"""
4 +
1 from snakes.plugins import plugin 5 from snakes.plugins import plugin
2 from snakes.pnml import Tree, loads, dumps 6 from snakes.pnml import Tree, loads, dumps
3 import imp, sys, socket, traceback, operator 7 import imp, sys, socket, traceback, operator
......
...@@ -12,6 +12,8 @@ Several status are defined by default: `entry`, `internal`, `exit`, ...@@ -12,6 +12,8 @@ Several status are defined by default: `entry`, `internal`, `exit`,
12 >>> n.add_place(Place('p1'), status=status.entry) 12 >>> n.add_place(Place('p1'), status=status.entry)
13 >>> n.place('p1') 13 >>> n.place('p1')
14 Place('p1', MultiSet([]), tAll, status=Status('entry')) 14 Place('p1', MultiSet([]), tAll, status=Status('entry'))
15 +
16 +@todo: revise documentation
15 """ 17 """
16 18
17 import operator, weakref 19 import operator, weakref
......
...@@ -6,8 +6,6 @@ The plugin proposes a generalisation of the M-nets synchronisation in ...@@ -6,8 +6,6 @@ The plugin proposes a generalisation of the M-nets synchronisation in
6 that it does not impose a fixed correspondence between action names 6 that it does not impose a fixed correspondence between action names
7 and action arities. 7 and action arities.
8 8
9 -
10 -
11 * class `Action` corresponds to a synchronisable action, it has a 9 * class `Action` corresponds to a synchronisable action, it has a
12 name, a send/receive flag and a list of parameters. Actions have 10 name, a send/receive flag and a list of parameters. Actions have
13 no predetermined arities, only conjugated actions with the same 11 no predetermined arities, only conjugated actions with the same
...@@ -66,6 +64,8 @@ t2 z>0 ...@@ -66,6 +64,8 @@ t2 z>0
66 >>> [t.name for t in sorted(n.transition(), key=str)] 64 >>> [t.name for t in sorted(n.transition(), key=str)]
67 ["((t1{...}+t2{...})[a(...)]{...}+t2{...})[a(...)]", 65 ["((t1{...}+t2{...})[a(...)]{...}+t2{...})[a(...)]",
68 "((t1{...}+t2{...})[a(...)]{...}+t2{...})[a(...)]"] 66 "((t1{...}+t2{...})[a(...)]{...}+t2{...})[a(...)]"]
67 +
68 +@todo: revise documentation
69 """ 69 """
70 70
71 from snakes import ConstraintError 71 from snakes import ConstraintError
...@@ -91,6 +91,7 @@ class Action (object) : ...@@ -91,6 +91,7 @@ class Action (object) :
91 self.send = send 91 self.send = send
92 self.params = list(params) 92 self.params = list(params)
93 __pnmltag__ = "action" 93 __pnmltag__ = "action"
94 + # apidoc skip
94 def __pnmldump__ (self) : 95 def __pnmldump__ (self) :
95 """ 96 """
96 >>> Action('a', True, [Value(1), Variable('x')]).__pnmldump__() 97 >>> Action('a', True, [Value(1), Variable('x')]).__pnmldump__()
...@@ -114,6 +115,7 @@ class Action (object) : ...@@ -114,6 +115,7 @@ class Action (object) :
114 for param in self.params : 115 for param in self.params :
115 result.add_child(Tree.from_obj(param)) 116 result.add_child(Tree.from_obj(param))
116 return result 117 return result
118 + # apidoc skip
117 @classmethod 119 @classmethod
118 def __pnmlload__ (cls, tree) : 120 def __pnmlload__ (cls, tree) :
119 """ 121 """
...@@ -136,6 +138,7 @@ class Action (object) : ...@@ -136,6 +138,7 @@ class Action (object) :
136 return "%s!(%s)" % (self.name, ",".join([str(p) for p in self])) 138 return "%s!(%s)" % (self.name, ",".join([str(p) for p in self]))
137 else : 139 else :
138 return "%s?(%s)" % (self.name, ",".join([str(p) for p in self])) 140 return "%s?(%s)" % (self.name, ",".join([str(p) for p in self]))
141 + # apidoc stop
139 def __repr__ (self) : 142 def __repr__ (self) :
140 """ 143 """
141 >>> a = Action('a', True, [Value(1), Variable('x')]) 144 >>> a = Action('a', True, [Value(1), Variable('x')])
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
3 Petri nets objects are saved in PNML, other Python objects are saved 3 Petri nets objects are saved in PNML, other Python objects are saved
4 in a readable format when possible and pickled as a last solution. 4 in a readable format when possible and pickled as a last solution.
5 This should result in a complete PNML serialization of any object. 5 This should result in a complete PNML serialization of any object.
6 +
7 +@todo: revise documentation
6 """ 8 """
7 9
8 import xml.dom.minidom 10 import xml.dom.minidom
......
...@@ -60,6 +60,8 @@ TypeError: ... ...@@ -60,6 +60,8 @@ TypeError: ...
60 Traceback (most recent call last): 60 Traceback (most recent call last):
61 ... 61 ...
62 TypeError: ... 62 TypeError: ...
63 +
64 +@todo: revise documentation
63 """ 65 """
64 66
65 import inspect, sys 67 import inspect, sys
......
1 +"""
2 +@todo: revise (actually make) documentation
3 +"""
4 +
1 from snakes import SnakesError 5 from snakes import SnakesError
2 6
3 class CompilationError (SnakesError) : 7 class CompilationError (SnakesError) :
......
1 +"""
2 +@todo: revise (actually make) documentation
3 +"""
4 +
1 import sys, os, os.path 5 import sys, os, os.path
2 -import inspect, fnmatch, collections 6 +import inspect, fnmatch, collections, re, shlex
3 -import textwrap, doctest, ast 7 +import textwrap, doctest
4 import snakes 8 import snakes
5 from snakes.lang import unparse 9 from snakes.lang import unparse
10 +from snakes.lang.python.parser import parse, ast
11 +
12 +try :
13 + import markdown
14 +except :
15 + markdown = None
6 16
7 ## 17 ##
8 ## console messages 18 ## console messages
...@@ -62,6 +72,13 @@ class DocExtract (object) : ...@@ -62,6 +72,13 @@ class DocExtract (object) :
62 self.out = None 72 self.out = None
63 self.exclude = exclude 73 self.exclude = exclude
64 self._last = "\n\n" 74 self._last = "\n\n"
75 + def md (self, text, inline=True) :
76 + if markdown is None :
77 + return text
78 + elif inline :
79 + return re.sub("</?p>", "\n", markdown.markdown(text), re.I)
80 + else :
81 + return markdown.markdown(text)
65 def openout (self, path) : 82 def openout (self, path) :
66 if self.out is not None : 83 if self.out is not None :
67 self.out.close() 84 self.out.close()
...@@ -81,6 +98,10 @@ class DocExtract (object) : ...@@ -81,6 +98,10 @@ class DocExtract (object) :
81 return False 98 return False
82 outdir = os.path.join(self.outpath, relpath) 99 outdir = os.path.join(self.outpath, relpath)
83 outpath = os.path.join(outdir, target) 100 outpath = os.path.join(outdir, target)
101 + if os.path.exists(outpath) :
102 + if os.stat(path).st_mtime <= os.stat(outpath).st_mtime :
103 + return False
104 + self.inpath = path
84 info("%s -> %r" % (self.module, outpath)) 105 info("%s -> %r" % (self.module, outpath))
85 if not os.path.exists(outdir) : 106 if not os.path.exists(outdir) :
86 os.makedirs(outdir) 107 os.makedirs(outdir)
...@@ -114,56 +135,89 @@ class DocExtract (object) : ...@@ -114,56 +135,89 @@ class DocExtract (object) :
114 def process (self) : 135 def process (self) :
115 for dirpath, dirnames, filenames in os.walk(self.path) : 136 for dirpath, dirnames, filenames in os.walk(self.path) :
116 for name in sorted(filenames) : 137 for name in sorted(filenames) :
117 - if not name.endswith(".py") : 138 + if not name.endswith(".py") or name.startswith(".") :
118 continue 139 continue
119 path = os.path.join(dirpath, name) 140 path = os.path.join(dirpath, name)
120 if not self.openout(path) : 141 if not self.openout(path) :
121 continue 142 continue
122 - node = ast.parse(open(path).read()) 143 + node = parse(open(path).read())
123 if ".plugins." in self.module : 144 if ".plugins." in self.module :
124 self.visit_plugin(node) 145 self.visit_plugin(node)
125 else : 146 else :
126 self.visit_module(node) 147 self.visit_module(node)
127 def _pass (self, node) : 148 def _pass (self, node) :
128 pass 149 pass
150 + def directive (self, node) :
151 + lines = node.st.text.lexer.lines
152 + num = node.st.srow - 2
153 + while num >= 0 and (not lines[num].strip()
154 + or lines[num].strip().startswith("@")) :
155 + num -= 1
156 + if num >= 0 and lines[num].strip().startswith("#") :
157 + dirline = lines[num].lstrip("# \t").rstrip()
158 + items = shlex.split(dirline)
159 + if len(items) >= 2 and items[0].lower() == "apidoc" :
160 + if len(items) == 2 and items[1].lower() in ("skip", "stop") :
161 + return items[1]
162 + elif items[1].lower() == "include" :
163 + path = items[2]
164 + try :
165 + args = dict(i.split("=", 1) for i in items[3:])
166 + except :
167 + err("invalid directive %r (line %s)"
168 + % (dirline, num+1))
169 + self.write_include(path, **args)
170 + else :
171 + err("unknown directive %r (line %s)"
172 + % (items[1], num+1))
173 + return None
174 + def children (self, node) :
175 + for child in ast.iter_child_nodes(node) :
176 + directive = self.directive(child)
177 + if directive == "skip" :
178 + continue
179 + elif directive == "stop" :
180 + break
181 + yield child
129 def visit (self, node) : 182 def visit (self, node) :
130 name = getattr(node, "name", "__") 183 name = getattr(node, "name", "__")
131 if (name.startswith("_") and not (name.startswith("__") 184 if (name.startswith("_") and not (name.startswith("__")
132 and name.endswith("__"))) : 185 and name.endswith("__"))) :
133 return 186 return
134 try : 187 try :
135 - getattr(self, "visit_" + node.__class__.__name__, self._pass)(node) 188 + getattr(self, "visit_" + node.__class__.__name__,
189 + self._pass)(node)
136 except : 190 except :
137 - src = unparse(node) 191 + src = node.st.source()
138 if len(src) > 40 : 192 if len(src) > 40 :
139 src = src[:40] + "..." 193 src = src[:40] + "..."
140 err("line %s source %r" % (node.lineno, src)) 194 err("line %s source %r" % (node.lineno, src))
141 raise 195 raise
142 def visit_module (self, node) : 196 def visit_module (self, node) :
143 self.write_module() 197 self.write_module()
144 - for child in ast.iter_child_nodes(node) : 198 + for child in self.children(node) :
145 self.visit(child) 199 self.visit(child)
146 def visit_plugin (self, node) : 200 def visit_plugin (self, node) :
147 self.write_module() 201 self.write_module()
148 extend = None 202 extend = None
149 - for child in ast.iter_child_nodes(node) : 203 + for child in self.children(node) :
150 if (getattr(child, "name", None) == "extend" 204 if (getattr(child, "name", None) == "extend"
151 and isinstance(child, ast.FunctionDef)) : 205 and isinstance(child, ast.FunctionDef)) :
152 extend = child 206 extend = child
153 else : 207 else :
154 self.visit(child) 208 self.visit(child)
155 self.write_plugin() 209 self.write_plugin()
156 - for child in ast.iter_child_nodes(extend) : 210 + for child in self.children(extend) :
157 self.visit(child) 211 self.visit(child)
158 def visit_ClassDef (self, node) : 212 def visit_ClassDef (self, node) :
159 self.write_class(node) 213 self.write_class(node)
160 self.classname = node.name 214 self.classname = node.name
161 - for child in ast.iter_child_nodes(node) : 215 + for child in self.children(node) :
162 self.visit(child) 216 self.visit(child)
163 self.classname = None 217 self.classname = None
164 def visit_FunctionDef (self, node) : 218 def visit_FunctionDef (self, node) :
165 self.write_function(node) 219 self.write_function(node)
166 - self.args = [n.id for n in node.args.args] 220 + self.args = [n.arg for n in node.args.args]
167 if self.args and self.args[0] == "self" : 221 if self.args and self.args[0] == "self" :
168 del self.args[0] 222 del self.args[0]
169 if node.args.vararg : 223 if node.args.vararg :
...@@ -193,26 +247,32 @@ class DocExtract (object) : ...@@ -193,26 +247,32 @@ class DocExtract (object) :
193 else : 247 else :
194 self.writeline("### Function `%s` ###" % node.name) 248 self.writeline("### Function `%s` ###" % node.name)
195 self.newline() 249 self.newline()
196 - self.writeline(" :::python") 250 + self.write_def(node)
197 - for line in unparse(node).splitlines() :
198 - if line.startswith("def") :
199 - self.writeline(" %s ..." % line)
200 - break
201 - else :
202 - self.writeline(" " + line)
203 - self.newline
204 def write_class (self, node) : 251 def write_class (self, node) :
205 self.newline() 252 self.newline()
206 self.writeline("### Class `%s` ###" % node.name) 253 self.writeline("### Class `%s` ###" % node.name)
254 + self.write_def(node)
255 + def write_def (self, node) :
256 + indent = node.st.scol
257 + if node.st[0].symbol == "decorated" :
258 + srow, scol = node.st[0][0][0][0].srow, node.st[0][0][0][0].scol
259 + erow, ecol = node.st[0][1][-2].erow, node.st[0][1][-2].ecol
260 + else :
261 + srow, scol = node.st[0].srow, node.st[0].scol
262 + erow, ecol = node.st[0][-2].erow, node.st[0][-2].ecol
263 + lines = node.st.text.lexer.lines
264 + if srow == erow :
265 + source = [lines[srow-1][scol:ecol+1]]
266 + else :
267 + source = lines[srow-1:erow]
268 + source[0] = source[0][scol:]
269 + source[1:-1] = [s[indent:] for s in source[1:-1]]
270 + source[-1] = source[-1][indent:ecol+1].rstrip() + " ..."
207 self.newline() 271 self.newline()
208 self.writeline(" :::python") 272 self.writeline(" :::python")
209 - for line in unparse(node).splitlines() : 273 + for line in source :
210 - if line.startswith("class") :
211 - self.writeline(" %s ..." % line)
212 - break
213 - else :
214 self.writeline(" " + line) 274 self.writeline(" " + line)
215 - self.newline 275 + self.newline()
216 parse = doctest.DocTestParser().parse 276 parse = doctest.DocTestParser().parse
217 def write_doc (self, doc) : 277 def write_doc (self, doc) :
218 if doc is None : 278 if doc is None :
...@@ -240,7 +300,9 @@ class DocExtract (object) : ...@@ -240,7 +300,9 @@ class DocExtract (object) :
240 if not test : 300 if not test :
241 test = True 301 test = True
242 self.newline() 302 self.newline()
243 - self.writeline(" :::python") 303 + self.writetext("<!-- this comment avoids a bug in"
304 + " Markdown parsing -->")
305 + self.writeline(" :::pycon")
244 for i, line in enumerate(doc.source.splitlines()) : 306 for i, line in enumerate(doc.source.splitlines()) :
245 if i > 0 : 307 if i > 0 :
246 self.writeline(" ... %s" % line) 308 self.writeline(" ... %s" % line)
...@@ -281,65 +343,74 @@ class DocExtract (object) : ...@@ -281,65 +343,74 @@ class DocExtract (object) :
281 if any(k in info for k in ("author", "organization", "copyright", 343 if any(k in info for k in ("author", "organization", "copyright",
282 "license", "contact")) : 344 "license", "contact")) :
283 self.newline() 345 self.newline()
284 - self.writeline('<div class="api-info">') 346 + self.writeline('<ul id="api-info">')
285 - for tag in ("author", "organization", "copyright", 347 + for tag in ("author", "organization", "contact",
286 - "license", "contact") : 348 + "copyright", "license", ) :
287 if tag in info : 349 if tag in info :
288 - self.writeline('<div class="api-%s">' % tag) 350 + self.writeline('<li id="api-%s">' % tag)
289 self.writetext('<span class="api-title">%s:</span> %s' 351 self.writetext('<span class="api-title">%s:</span> %s'
290 - % (tag.capitalize(), info[tag]), 352 + % (tag.capitalize(),
353 + self.md(info[tag])),
291 subsequent_indent=" ") 354 subsequent_indent=" ")
292 - self.writeline('</div>') 355 + self.writeline('</li>')
293 - self.writeline('</div>') 356 + self.writeline('</ul>')
294 if any(info[k] for k in 357 if any(info[k] for k in
295 ("todo", "note", "attention", "bug", "warning")) : 358 ("todo", "note", "attention", "bug", "warning")) :
296 self.newline() 359 self.newline()
297 - self.writeline('<div class="api-remarks">') 360 + self.writeline('<div id="api-remarks">')
298 - self.writeline("##### Remarks #####")
299 - self.newline()
300 for tag in ("note", "todo", "attention", "bug", "warning") : 361 for tag in ("note", "todo", "attention", "bug", "warning") :
301 for text in info[tag] : 362 for text in info[tag] :
302 self.writeline('<div class="api-%s">' % tag) 363 self.writeline('<div class="api-%s">' % tag)
303 self.writetext('<span class="api-title">%s:</span> %s' 364 self.writetext('<span class="api-title">%s:</span> %s'
304 - % (tag.capitalize(), text), 365 + % (tag.capitalize(), self.md(text)),
305 subsequent_indent=" ") 366 subsequent_indent=" ")
306 self.writeline('</div>') 367 self.writeline('</div>')
307 self.writeline('</div>') 368 self.writeline('</div>')
308 if (any(info[k] for k in ("param", "type", "keyword")) 369 if (any(info[k] for k in ("param", "type", "keyword"))
309 or any(k in info for k in ("return", "rtype"))) : 370 or any(k in info for k in ("return", "rtype"))) :
310 self.newline() 371 self.newline()
311 - self.writeline('<div class="api-call">')
312 self.writeline("##### Call API #####") 372 self.writeline("##### Call API #####")
313 self.newline() 373 self.newline()
314 for arg in self.args : 374 for arg in self.args :
315 if arg in info["param"] : 375 if arg in info["param"] :
316 - self.writelist("`%s` (%s): %s" 376 + self.writelist("`%s %s`: %s"
317 - % (arg, 377 + % (info["type"].get(arg, "object").strip("`"),
318 - info["type"].get(arg, "`object`"), 378 + arg,
319 info["param"][arg])) 379 info["param"][arg]))
320 else : 380 else :
321 - self.writelist("`%s` (%s)" 381 + self.writelist("`%s %s`"
322 - % (arg, 382 + % (info["type"].get(arg, "object").strip("`"),
323 - info["type"].get(arg, "`object`"))) 383 + arg))
324 for kw, text in sorted(info["keyword"].items()) : 384 for kw, text in sorted(info["keyword"].items()) :
325 self.writelist("`%s`: %s" % (kw, text)) 385 self.writelist("`%s`: %s" % (kw, text))
326 if any(k in info for k in ("return", "rtype")) : 386 if any(k in info for k in ("return", "rtype")) :
327 if "return" in info : 387 if "return" in info :
328 - self.writelist("return %s: %s" 388 + self.writelist("`return %s`: %s"
329 - % (info.get("rtype", "`object`"), 389 + % (info.get("rtype", "object").strip("`"),
330 info["return"])) 390 info["return"]))
331 else : 391 else :
332 - self.writelist("return %s" 392 + self.writelist("`return %s`"
333 - % (info.get("rtype", "`object`"))) 393 + % (info.get("rtype", "object").strip("`")))
334 - self.writeline('</div>')
335 if info["raise"] : 394 if info["raise"] :
336 self.newline() 395 self.newline()
337 - self.writeline('<div class="api-errors">')
338 self.writeline("##### Exceptions #####") 396 self.writeline("##### Exceptions #####")
339 self.newline() 397 self.newline()
340 for exc, reason in sorted(info["raise"].items()) : 398 for exc, reason in sorted(info["raise"].items()) :
341 self.writelist("`%s`: %s" % (exc, reason)) 399 self.writelist("`%s`: %s" % (exc, reason))
342 - self.writeline('</div>') 400 + self.newline()
401 + def write_include (self, name, lang="python") :
402 + if os.path.exists(name) :
403 + path = name
404 + else :
405 + path = os.path.join(os.path.dirname(self.inpath), name)
406 + if not os.path.exists(path) :
407 + err("include file %r not found" % name)
408 + with open(path) as infile :
409 + self.newline()
410 + self.writeline(" :::%s" % lang)
411 + for line in infile :
412 + self.writeline(" " + line.rstrip())
413 + self.newline()
343 414
344 def main (finder, args) : 415 def main (finder, args) :
345 try : 416 try :
......
1 +"""
2 +@todo: revise (actually make) documentation
3 +"""
......