| [26] | 1 | |
|---|
| 2 | ============================================================ |
|---|
| 3 | PySyck: Python bindings for the Syck YAML parser and emitter |
|---|
| 4 | ============================================================ |
|---|
| 5 | |
|---|
| 6 | :Author: Kirill Simonov |
|---|
| 7 | :Contact: xi@resolvent.net |
|---|
| [64] | 8 | :Web site: http://pyyaml.org/wiki/PySyck |
|---|
| [26] | 9 | |
|---|
| 10 | .. contents:: |
|---|
| 11 | |
|---|
| [29] | 12 | |
|---|
| [26] | 13 | Overview |
|---|
| 14 | ======== |
|---|
| 15 | |
|---|
| 16 | YAML_ is a data serialization format designed for human readability and |
|---|
| 17 | interaction with scripting languages. |
|---|
| 18 | |
|---|
| 19 | Syck_ is an extension for reading and writing YAML in scripting languages. Syck |
|---|
| 20 | provides bindings to the Python_ programming language, but they are somewhat |
|---|
| 21 | limited and leak memory. |
|---|
| 22 | |
|---|
| 23 | PySyck_ is aimed to update the current Python bindings for Syck. The new |
|---|
| [30] | 24 | bindings provide a wrapper for the Syck emitter and give access to YAML |
|---|
| 25 | representation graphs. Hopefully it will not leak memory as well. |
|---|
| [26] | 26 | |
|---|
| [29] | 27 | PySyck_ may be used for various tasks, in particular, as a replacement of the |
|---|
| [64] | 28 | module pickle_. |
|---|
| [29] | 29 | |
|---|
| [26] | 30 | .. _YAML: http://yaml.org/ |
|---|
| 31 | .. _Syck: http://whytheluckystiff.net/syck/ |
|---|
| 32 | .. _Python: http://python.org/ |
|---|
| [64] | 33 | .. _PySyck: http://pyyaml.org/wiki/PySyck |
|---|
| [29] | 34 | .. _pickle: http://docs.python.org/lib/module-pickle.html |
|---|
| [26] | 35 | |
|---|
| [64] | 36 | |
|---|
| [26] | 37 | Requirements |
|---|
| 38 | ============ |
|---|
| 39 | |
|---|
| [64] | 40 | PySyck requires Syck 0.55 or higher and Python 2.3 or higher. |
|---|
| [26] | 41 | |
|---|
| [29] | 42 | |
|---|
| [26] | 43 | Installation |
|---|
| 44 | ============ |
|---|
| 45 | |
|---|
| [64] | 46 | Please note that Syck 0.55 or higher must be installed. We recommend to use |
|---|
| 47 | Syck_ from `the Syck SVN repository`_ together with `my Syck patches`_. For |
|---|
| 48 | your convenience, a tarball is provided: |
|---|
| 49 | http://pyyaml.org/download/pysyck/syck-0.61+svn232+patches.tar.gz. |
|---|
| [26] | 50 | |
|---|
| [64] | 51 | If you install PySyck from source, unpack the source tarball and type:: |
|---|
| 52 | |
|---|
| [29] | 53 | $ python setup.py install |
|---|
| [26] | 54 | |
|---|
| [64] | 55 | Windows binaries for Python 2.3 and 2.4 are provided. Windows binaries are |
|---|
| 56 | linked against Syck_ statically. |
|---|
| [26] | 57 | |
|---|
| [64] | 58 | .. _the Syck SVN repository: http://code.whytheluckystiff.net/svn/syck/trunk |
|---|
| 59 | .. _my Syck patches: http://pyyaml.org/wiki/SyckPatches |
|---|
| [29] | 60 | |
|---|
| [64] | 61 | |
|---|
| [26] | 62 | Usage |
|---|
| 63 | ===== |
|---|
| 64 | |
|---|
| [30] | 65 | The documentation is still rough and incomplete. See `the source code`_ for |
|---|
| 66 | more information. |
|---|
| [26] | 67 | |
|---|
| [65] | 68 | .. _the source code: http://pyyaml.org/browser/pysyck/ |
|---|
| [29] | 69 | |
|---|
| 70 | Quick Example |
|---|
| 71 | ------------- |
|---|
| 72 | |
|---|
| 73 | :: |
|---|
| 74 | |
|---|
| 75 | >>> from syck import * |
|---|
| 76 | >>> print load(""" |
|---|
| 77 | ... - foo |
|---|
| 78 | ... - bar |
|---|
| 79 | ... - baz |
|---|
| 80 | ... """) |
|---|
| 81 | ['foo', 'bar', 'baz'] |
|---|
| 82 | >>> print dump(['foo', 'bar', 'baz']) |
|---|
| 83 | --- |
|---|
| 84 | - foo |
|---|
| 85 | - bar |
|---|
| 86 | - baz |
|---|
| 87 | |
|---|
| [66] | 88 | Important notice: Do not load a YAML stream from any untrusted source. |
|---|
| 89 | Like ``pickle.load``, ``syck.load`` may call an arbitrary Python function. |
|---|
| 90 | |
|---|
| 91 | |
|---|
| [29] | 92 | YAML syntax |
|---|
| 93 | ----------- |
|---|
| 94 | |
|---|
| 95 | We do not describe the YAML syntax here. Please check http://yaml.org/ for the |
|---|
| 96 | reference. |
|---|
| 97 | |
|---|
| 98 | In addition to the tags defined in `the YAML types repository`_, PySyck understands |
|---|
| 99 | the following Python-specific tags: |
|---|
| 100 | |
|---|
| 101 | * ``tag:python.yaml.org,2002:none``, |
|---|
| 102 | * ``tag:python.yaml.org,2002:bool``, |
|---|
| 103 | * ``tag:python.yaml.org,2002:int``, |
|---|
| 104 | * ``tag:python.yaml.org,2002:float``, |
|---|
| 105 | * ``tag:python.yaml.org,2002:str``, |
|---|
| 106 | * ``tag:python.yaml.org,2002:unicode``, |
|---|
| 107 | * ``tag:python.yaml.org,2002:list``, |
|---|
| 108 | * ``tag:python.yaml.org,2002:tuple``, |
|---|
| 109 | * ``tag:python.yaml.org,2002:dict``, |
|---|
| 110 | * ``tag:python.yaml.org,2002:name:...``, |
|---|
| 111 | * ``tag:python.yaml.org,2002:object:...``, |
|---|
| 112 | * ``tag:python.yaml.org,2002:new:...``, |
|---|
| 113 | * ``tag:python.yaml.org,2002:apply:...``. |
|---|
| 114 | |
|---|
| 115 | Most of these tags are self-explanatory. The tags ``!python/name:...``, |
|---|
| 116 | ``!python/object:...``, ``!python/new:...``, and ``!python/apply:...`` are used |
|---|
| 117 | for constructing Python functions, classes, and objects. See the sections `Use |
|---|
| [30] | 118 | Python-specific tags in YAML documents`_ and `Use Python-specific tags to |
|---|
| [29] | 119 | construct Python objects`_ for some examples. |
|---|
| 120 | |
|---|
| 121 | .. _the YAML types repository: http://yaml.org/type/index.html |
|---|
| 122 | |
|---|
| 123 | Common Tasks |
|---|
| 124 | ------------ |
|---|
| 125 | |
|---|
| 126 | Import the module |
|---|
| 127 | ~~~~~~~~~~~~~~~~~ |
|---|
| 128 | |
|---|
| 129 | :: |
|---|
| 130 | |
|---|
| 131 | >>> from syck import * |
|---|
| 132 | |
|---|
| 133 | or |
|---|
| 134 | |
|---|
| 135 | :: |
|---|
| 136 | |
|---|
| 137 | >>> import syck |
|---|
| 138 | |
|---|
| 139 | Load a document from a string |
|---|
| 140 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 141 | |
|---|
| 142 | :: |
|---|
| 143 | |
|---|
| 144 | >>> source = "..." |
|---|
| 145 | >>> object = load(source) |
|---|
| 146 | |
|---|
| 147 | Load a document from a file |
|---|
| 148 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 149 | |
|---|
| 150 | :: |
|---|
| 151 | |
|---|
| 152 | >>> source = file(..., 'r') |
|---|
| 153 | >>> object = load(source) |
|---|
| 154 | |
|---|
| 155 | Convert a Python object to a YAML document |
|---|
| 156 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 157 | |
|---|
| 158 | :: |
|---|
| 159 | |
|---|
| 160 | >>> object = ... |
|---|
| 161 | >>> document = dump(object) |
|---|
| 162 | |
|---|
| 163 | Dump a Python object to a YAML stream |
|---|
| 164 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 165 | |
|---|
| 166 | :: |
|---|
| 167 | |
|---|
| 168 | >>> object = ... |
|---|
| 169 | >>> output = file(..., 'w') |
|---|
| 170 | >>> dump(object, output) |
|---|
| 171 | |
|---|
| 172 | Format the output YAML stream |
|---|
| 173 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 174 | |
|---|
| 175 | :: |
|---|
| 176 | |
|---|
| 177 | >>> object = ... |
|---|
| 178 | >>> output = file(..., 'w') |
|---|
| 179 | >>> dump(object, output, |
|---|
| 180 | ... headless=False, use_header=False, use_version=False, |
|---|
| 181 | ... explicit_typing=True, style=None, best_width=80, indent=2) |
|---|
| 182 | |
|---|
| 183 | Load several documents from a YAML stream |
|---|
| 184 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 185 | |
|---|
| 186 | :: |
|---|
| 187 | |
|---|
| 188 | >>> source = ... |
|---|
| 189 | >>> objects = load_documents(source) |
|---|
| 190 | >>> for object in objects: |
|---|
| 191 | ... # ... |
|---|
| 192 | |
|---|
| 193 | Create several documents in a YAML stream |
|---|
| 194 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 195 | |
|---|
| 196 | :: |
|---|
| 197 | |
|---|
| 198 | >>> objects = [...] |
|---|
| 199 | >>> output = file(..., 'w') |
|---|
| 200 | >>> dump_documents(objects, output) |
|---|
| 201 | |
|---|
| [30] | 202 | Construct a representation tree of a YAML document |
|---|
| 203 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| [29] | 204 | |
|---|
| 205 | :: |
|---|
| 206 | |
|---|
| 207 | >>> source = ... |
|---|
| 208 | >>> root_node = parse(source) |
|---|
| 209 | |
|---|
| 210 | Convert a representation tree to a YAML document |
|---|
| 211 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 212 | |
|---|
| 213 | :: |
|---|
| 214 | |
|---|
| 215 | >>> scalar_node = Scalar('...', tag='tag:...', |
|---|
| 216 | ... style='...', indent=.., width=..) |
|---|
| 217 | >>> sequence_node = Seq(list_of_nodes, tag='tag:...', inline=..) |
|---|
| 218 | >>> mapping_node = Map(dictionary_of_nodes, tag='tag:...', inline=..) |
|---|
| 219 | >>> root_node = ... |
|---|
| 220 | >>> output = file(..., 'w') |
|---|
| 221 | >>> emit(root_node, output) |
|---|
| 222 | |
|---|
| 223 | Use PySyck as a pickle replacement |
|---|
| 224 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 225 | |
|---|
| 226 | :: |
|---|
| 227 | |
|---|
| 228 | >>> object = ... |
|---|
| 229 | >>> stream = ... |
|---|
| 230 | >>> dump(object, stream) |
|---|
| 231 | |
|---|
| 232 | >>> stream = ... |
|---|
| 233 | >>> object = load(stream) |
|---|
| 234 | |
|---|
| 235 | Use PySyck to display the structure of a complex object |
|---|
| 236 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 237 | |
|---|
| 238 | :: |
|---|
| 239 | |
|---|
| 240 | >>> object = ... |
|---|
| 241 | >>> print dump(object) |
|---|
| 242 | |
|---|
| 243 | Use PySyck to display a YAML representation graph |
|---|
| 244 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 245 | |
|---|
| 246 | :: |
|---|
| 247 | |
|---|
| 248 | >>> source = ... |
|---|
| 249 | >>> node = parse(source) |
|---|
| 250 | >>> print dump(node) |
|---|
| 251 | |
|---|
| [30] | 252 | Use Python-specific tags in YAML documents |
|---|
| 253 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| [29] | 254 | |
|---|
| 255 | :: |
|---|
| 256 | |
|---|
| 257 | --- %YAML:1.0 |
|---|
| 258 | - !python/none '' # You may also use '!null'. |
|---|
| 259 | - !python/bool 'False' # You may also use '!bool'. |
|---|
| 260 | - !python/int '123' # You may also use '!int'. |
|---|
| 261 | - !python/long '1234567890' |
|---|
| 262 | - !python/float '123.456789' # Also '!float'. |
|---|
| 263 | - !python/str 'a string' # Also '!str'. |
|---|
| 264 | - !python/unicode 'a unicode string encoded in utf-8' |
|---|
| 265 | - !python/list [1, 2, 3] # The same as '!seq' or no tag. |
|---|
| 266 | - !python/tuple [1, 2, 3] |
|---|
| 267 | - !python/dict { 1: foo, 2: bar } # The same as '!map' or no tag. |
|---|
| 268 | |
|---|
| 269 | Use Python-specific tags to construct functions or classes |
|---|
| 270 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 271 | |
|---|
| 272 | :: |
|---|
| 273 | |
|---|
| 274 | --- %YAML:1.0 |
|---|
| 275 | - !python/name:package.module.function_name '' |
|---|
| 276 | - !python/name:package.module.class_name '' |
|---|
| 277 | |
|---|
| 278 | Use Python-specific tags to construct Python objects |
|---|
| 279 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 280 | |
|---|
| 281 | :: |
|---|
| 282 | |
|---|
| 283 | --- %YAML:1.0 |
|---|
| 284 | - !python/object:package.module.type |
|---|
| 285 | attribute1: value1 |
|---|
| 286 | attribute2: value2 |
|---|
| 287 | # ... |
|---|
| 288 | - !python/new:package.module.type |
|---|
| 289 | - parameter1 |
|---|
| 290 | - parameter2 |
|---|
| 291 | # ... |
|---|
| 292 | - !python/new:package.module.type |
|---|
| 293 | args: [parameter1, parameter2, ...] |
|---|
| 294 | kwds: {kwd1: val1, kwd2: val2, ...} |
|---|
| 295 | state: {attr1: val1, attr2: val2, ...} |
|---|
| 296 | # ... |
|---|
| 297 | - !python/apply:package.module.function |
|---|
| 298 | - parameter1 |
|---|
| 299 | - parameter2 |
|---|
| 300 | # ... |
|---|
| 301 | - !python/apply:package.module.function |
|---|
| 302 | args: [parameter1, parameter2, ...] |
|---|
| 303 | kwds: {kwd1: val1, kwd2: val2, ...} |
|---|
| 304 | state: {attr1: val1, attr2: val2, ...} |
|---|
| 305 | # ... |
|---|
| 306 | |
|---|
| 307 | Use application specific tags |
|---|
| 308 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 309 | |
|---|
| 310 | :: |
|---|
| 311 | |
|---|
| 312 | >>> class MyClass: |
|---|
| 313 | ... # ... |
|---|
| 314 | |
|---|
| 315 | >>> class MyLoader(Loader): |
|---|
| 316 | ... def construct_private_my_tag(self, node): |
|---|
| 317 | ... # ... |
|---|
| 318 | ... return MyClass(...) |
|---|
| 319 | |
|---|
| 320 | >>> class MyDumper(Dumper): |
|---|
| 321 | ... def represent_MyDumper(self, object): |
|---|
| 322 | ... # ... |
|---|
| 323 | ... return Map(...) |
|---|
| 324 | |
|---|
| 325 | >>> source = """--- !!my_tag { ... }""" |
|---|
| 326 | >>> my_instance = load(source, Loader=MyLoader) |
|---|
| 327 | |
|---|
| 328 | >>> my_instance = MyClass(...) |
|---|
| 329 | >>> output = dump(my_instance, Dumper=MyDumper) |
|---|
| 330 | |
|---|
| 331 | Reference |
|---|
| 332 | --------- |
|---|
| 333 | |
|---|
| 334 | Functions |
|---|
| 335 | ~~~~~~~~~ |
|---|
| 336 | |
|---|
| 337 | ``load`` : function |
|---|
| 338 | ``load(source, Loader=Loader, **parameters)`` |
|---|
| 339 | |
|---|
| 340 | The function ``load()`` returns a Python object corresponding to the first |
|---|
| 341 | document in the source. If the source is empty, ``load()`` returns ``None``. |
|---|
| 342 | ``source`` must be a string or a file-like object that has the method |
|---|
| 343 | ``read(max_length)``. |
|---|
| 344 | |
|---|
| 345 | By default, the function ``load()`` uses an instance of the class ``Loader`` |
|---|
| 346 | for parsing. You may use another class or pass additional parameters to the |
|---|
| 347 | class constructor. See the section Parser_ for more details. |
|---|
| 348 | |
|---|
| 349 | Example:: |
|---|
| 350 | |
|---|
| 351 | >>> load(""" |
|---|
| 352 | ... - foo |
|---|
| 353 | ... - bar |
|---|
| 354 | ... - baz |
|---|
| 355 | ... """) |
|---|
| 356 | ['foo', 'bar', 'baz'] |
|---|
| 357 | |
|---|
| 358 | ``parse`` : function |
|---|
| 359 | ``parse(source, Loader=Loader, **parameters)`` |
|---|
| 360 | |
|---|
| 361 | The function ``parse()`` parses the source and returns a representation tree |
|---|
| 362 | of the first document. ``source`` must be a string or a file-like object |
|---|
| 363 | that has the method ``read(max_length)``. |
|---|
| 364 | |
|---|
| 365 | By default, the function ``parse()`` uses an instance of the class ``Loader`` |
|---|
| 366 | for parsing. You may use another class or pass additional parameters to the |
|---|
| 367 | class constructor. See the section Parser_ for more details. |
|---|
| 368 | |
|---|
| 369 | Example:: |
|---|
| 370 | |
|---|
| 371 | >>> parse(""" |
|---|
| 372 | ... - foo |
|---|
| 373 | ... - bar |
|---|
| 374 | ... - baz |
|---|
| 375 | ... """) |
|---|
| 376 | <_syck.Seq object at 0xb7a3f2fc> |
|---|
| 377 | |
|---|
| 378 | ``load_documents`` : function |
|---|
| 379 | ``load_documents(source, Loader=Loader, **parameters)`` |
|---|
| 380 | |
|---|
| 381 | The function ``load_documents()`` parses the source and an iterator. The |
|---|
| 382 | iterator produces Python objects corresponding the documents of the source |
|---|
| 383 | stream. ``source`` must be a string or a file-like object that has the method |
|---|
| 384 | ``read(max_length)``. |
|---|
| 385 | |
|---|
| 386 | By default, the function ``load_documents()`` uses an instance of the class |
|---|
| 387 | ``Loader`` for parsing. You may use another class or pass additional |
|---|
| 388 | parameters to the class constructor. See the section Parser_ for more |
|---|
| 389 | details. |
|---|
| 390 | |
|---|
| 391 | Example:: |
|---|
| 392 | |
|---|
| 393 | >>> source = """ |
|---|
| 394 | ... --- > |
|---|
| 395 | ... This is the |
|---|
| 396 | ... first document. |
|---|
| 397 | ... --- > |
|---|
| 398 | ... This is the |
|---|
| 399 | ... next document. |
|---|
| 400 | ... --- > |
|---|
| 401 | ... This is the |
|---|
| 402 | ... last document. |
|---|
| 403 | ... """ |
|---|
| 404 | >>> for object in load_documents(source): print object |
|---|
| 405 | ... |
|---|
| 406 | This is the first document. |
|---|
| 407 | |
|---|
| 408 | This is the next document. |
|---|
| 409 | |
|---|
| 410 | This is the last document. |
|---|
| 411 | |
|---|
| 412 | ``parse_documents`` : function |
|---|
| 413 | ``parse_documents(source, Loader=Loader, **parameters)`` |
|---|
| 414 | |
|---|
| 415 | The function ``parse_documents()`` is similar to ``load_documents()``, but |
|---|
| 416 | produces representation graphs for all documents in the source. |
|---|
| 417 | |
|---|
| 418 | ``dump`` : function |
|---|
| 419 | ``dump(object, output=None, Dumper=Dumper, **parameters)`` |
|---|
| 420 | |
|---|
| 421 | The function ``dump()`` converts ``object`` to a representation graph |
|---|
| 422 | and write it to ``output``. ``output`` must be ``None`` or a file-like |
|---|
| 423 | object that has the method ``write(data)``. If ``output`` is ``None``, |
|---|
| 424 | ``dump()`` returns the generated document. |
|---|
| 425 | |
|---|
| 426 | By default, the function ``dump()`` uses an instance of the class ``Dumper`` |
|---|
| 427 | for emitting. You may use another class or pass additional parameters to the |
|---|
| 428 | class constructor. See the section Emitter_ for more details. |
|---|
| 429 | |
|---|
| 430 | Example:: |
|---|
| 431 | |
|---|
| 432 | >>> object = ['foo', 'bar', ['baz']] |
|---|
| 433 | >>> dump(object, sys.stdout) |
|---|
| 434 | --- |
|---|
| 435 | - foo |
|---|
| 436 | - bar |
|---|
| 437 | - - baz |
|---|
| 438 | >>> print dump(object) |
|---|
| 439 | --- |
|---|
| 440 | - foo |
|---|
| 441 | - bar |
|---|
| 442 | - - baz |
|---|
| 443 | |
|---|
| 444 | >>> print dump(object, use_version=True, indent=5) |
|---|
| 445 | --- %YAML:1.0 |
|---|
| 446 | - foo |
|---|
| 447 | - bar |
|---|
| 448 | - - baz |
|---|
| 449 | |
|---|
| 450 | ``emit`` : function |
|---|
| 451 | ``emit(node, output=None, Dumper=Dumper, **parameters)`` |
|---|
| 452 | |
|---|
| 453 | The function ``emit()`` write the representation graph to the output stream. |
|---|
| 454 | ``output`` must be ``None`` or a file-like object that has the method |
|---|
| 455 | ``write(data)``. If ``output`` is ``None``, ``emit()`` returns the generated |
|---|
| 456 | document. |
|---|
| 457 | |
|---|
| 458 | By default, the function ``emit()`` uses an instance of the class ``Dumper`` |
|---|
| 459 | for emitting. You may use another class or pass additional parameters to the |
|---|
| 460 | class constructor. See the section Emitter_ for more details. |
|---|
| 461 | |
|---|
| 462 | Example:: |
|---|
| 463 | |
|---|
| 464 | >>> foo = Scalar('a string') |
|---|
| 465 | >>> bar = Scalar('a unicode string', tag="tag:python.yaml.org,2002:unicode") |
|---|
| 466 | >>> baz = Scalar('12345', tag="tag:yaml.org,2002:int") |
|---|
| 467 | >>> seq = Seq([foo, bar, baz], tag="tag:python.taml.org,2002:tuple") |
|---|
| 468 | >>> print emit(seq, use_version=True) |
|---|
| 469 | --- %YAML:1.0 !python.taml.org,2002/tuple |
|---|
| 470 | - a string |
|---|
| 471 | - !python/unicode a unicode string |
|---|
| 472 | - 12345 |
|---|
| 473 | |
|---|
| 474 | ``dump_documents`` : function |
|---|
| 475 | ``dump_documents(objects, output=None, Dumper=Dumper, **parameters)`` |
|---|
| 476 | |
|---|
| 477 | The function ``dump_documents()`` takes a list of objects and converts |
|---|
| 478 | each object to a YAML document. If ``output`` is ``None``, it returns |
|---|
| 479 | the produced documents. Otherwise it writes down them to ``output``, |
|---|
| 480 | which must be a file-like object with the method ``write(data)``. |
|---|
| 481 | |
|---|
| 482 | By default, the function ``dump_documents()`` uses an instance of the class |
|---|
| 483 | ``Dumper`` for emitting. You may use another class or pass additional |
|---|
| 484 | parameters to the class constructor. See the section Emitter_ for more |
|---|
| 485 | details. |
|---|
| 486 | |
|---|
| 487 | Example:: |
|---|
| 488 | |
|---|
| 489 | >>> print dump_documents(['foo', 'bar', 'baz']) |
|---|
| 490 | --- foo |
|---|
| 491 | --- bar |
|---|
| 492 | --- baz |
|---|
| 493 | |
|---|
| 494 | ``emit_documents`` : function |
|---|
| 495 | ``emit_documents(nodes, output=None, Dumper=Dumper, **parameters)`` |
|---|
| 496 | |
|---|
| 497 | The function ``emit_documents()`` is similar to ``dump_documents()``, but |
|---|
| 498 | it requires a list of representation graphs. |
|---|
| 499 | |
|---|
| 500 | Exceptions |
|---|
| 501 | ~~~~~~~~~~ |
|---|
| 502 | |
|---|
| 503 | ``error`` : exception |
|---|
| 504 | This exception is raised by the Syck parser when it detects a syntax error. |
|---|
| 505 | |
|---|
| 506 | The attribute ``args`` of the exception is a triple: *message*, *row*, |
|---|
| 507 | *column*. |
|---|
| 508 | |
|---|
| 509 | Example:: |
|---|
| 510 | |
|---|
| 511 | >>> load("""--- |
|---|
| 512 | ... - foo |
|---|
| 513 | ... - ''' |
|---|
| 514 | ... - bar |
|---|
| 515 | ... """) |
|---|
| 516 | Traceback (most recent call last): |
|---|
| 517 | File "<stdin>", line 1, in ? |
|---|
| 518 | File "build/lib.linux-i686-2.3/syck/loaders.py", line 384, in load |
|---|
| 519 | File "build/lib.linux-i686-2.3/syck/loaders.py", line 42, in load |
|---|
| 520 | _syck.error: ('syntax error', 4, 2) |
|---|
| 521 | |
|---|
| 522 | Nodes |
|---|
| 523 | ~~~~~ |
|---|
| 524 | |
|---|
| 525 | The following four classes represents nodes in the YAML representation graph: |
|---|
| 526 | |
|---|
| 527 | ``Node`` : class |
|---|
| 528 | ``Node`` is an abstract class; you cannot create an instance of the class |
|---|
| 529 | ``Node``. ``Node`` is the base class of ``Scalar``, ``Seq``, and ``Map``. |
|---|
| 530 | |
|---|
| 531 | ``Scalar`` : subclass of ``Node`` |
|---|
| 532 | ``Scalar`` represents a scalar node. Its value is a string. |
|---|
| 533 | |
|---|
| 534 | ``Seq`` : subclass of ``Node`` |
|---|
| 535 | ``Seq`` represents a sequence node. Its value is a list of nodes. |
|---|
| 536 | |
|---|
| 537 | ``Map`` : subclass of ``Node`` |
|---|
| 538 | ``Map`` represents a mapping node. Its value is a list of pairs or a |
|---|
| 539 | dictionary. |
|---|
| 540 | |
|---|
| 541 | All instances of ``Scalar``, ``Seq``, and ``Map`` have the following |
|---|
| 542 | attributes: |
|---|
| 543 | |
|---|
| 544 | ``kind`` : string |
|---|
| 545 | ``'scalar'``, ``'seq'``, or ``'map'``; read-only. |
|---|
| 546 | |
|---|
| 547 | ``anchor`` : string or ``None`` |
|---|
| 548 | The node anchor. |
|---|
| 549 | |
|---|
| 550 | ``tag`` : string or ``None`` |
|---|
| 551 | The node tag. |
|---|
| 552 | |
|---|
| 553 | ``value`` |
|---|
| 554 | The node value. For scalar nodes, it should be a string. For sequence nodes, |
|---|
| 555 | it should be a list. For mapping nodes, it should be a list of pairs or a |
|---|
| 556 | dictionary. |
|---|
| 557 | |
|---|
| 558 | ``Scalar`` instances have additional attributes: |
|---|
| 559 | |
|---|
| 560 | ``style`` : string or ``None`` |
|---|
| 561 | The node style. Possible values are ``None`` (means literal or plain), |
|---|
| 562 | ``'1quote'``, ``'2quote'``, ``'fold'``, ``'literal'``, ``'plain'``. |
|---|
| 563 | |
|---|
| 564 | ``indent`` : integer |
|---|
| 565 | The node indentation. ``0`` means the default value. |
|---|
| 566 | |
|---|
| 567 | ``width`` : integer |
|---|
| 568 | The width of the node field. Longer scalars will be broken on several lines |
|---|
| 569 | to fit the field. ``0`` means the default value. |
|---|
| 570 | |
|---|
| 571 | ``chomp`` : string or ``None`` |
|---|
| 572 | The chomping method. Possible values are ``None`` (clip), ``'-'`` (strip), |
|---|
| 573 | ``'+'`` (keep). |
|---|
| 574 | |
|---|
| 575 | ``Seq`` and ``Map`` instances have an additional attribute: |
|---|
| 576 | |
|---|
| 577 | ``inline`` : boolean |
|---|
| 578 | The block/flow flag. |
|---|
| 579 | |
|---|
| 580 | For example, let us create a representation graph and transform it into a YAML |
|---|
| 581 | stream:: |
|---|
| 582 | |
|---|
| 583 | >>> # Create three scalar nodes: |
|---|
| 584 | >>> foo = Scalar('foo', tag="tag:example.com,2005:foo", style='fold', |
|---|
| 585 | ... indent=5) |
|---|
| 586 | >>> bar = Scalar('bar', style='1quote') |
|---|
| 587 | >>> baz = Scalar('baz') |
|---|
| 588 | |
|---|
| 589 | >>> # Create a sequence node: |
|---|
| 590 | >>> seq = Seq([foo, bar, baz], tag="x-private:seq") |
|---|
| 591 | |
|---|
| 592 | >>> # Emit it into a YAML stream: |
|---|
| 593 | >>> print emit(seq) |
|---|
| 594 | --- !!seq |
|---|
| 595 | - !example.com,2005/foo >- |
|---|
| 596 | foo |
|---|
| 597 | - 'bar' |
|---|
| 598 | - baz |
|---|
| 599 | |
|---|
| 600 | Now let us construct a representation graph from a YAML document:: |
|---|
| 601 | |
|---|
| 602 | >>> # The function 'parse' generates a representation graph: |
|---|
| 603 | >>> root = parse(""" |
|---|
| 604 | ... - foo |
|---|
| 605 | ... - bar |
|---|
| 606 | ... - baz |
|---|
| 607 | ... """) |
|---|
| 608 | |
|---|
| 609 | >>> # The object 'root' is a sequence node: |
|---|
| 610 | >>> root |
|---|
| 611 | <_syck.Seq object at 0xb7e124b4> |
|---|
| 612 | |
|---|
| 613 | >>> # We can transform 'root' back into a YAML stream: |
|---|
| 614 | >>> print emit(root) |
|---|
| 615 | --- |
|---|
| 616 | - foo |
|---|
| 617 | - bar |
|---|
| 618 | - baz |
|---|
| 619 | |
|---|
| 620 | >>> # We can also display the structure of the representation tree using a |
|---|
| 621 | >>> # clever trick: |
|---|
| 622 | >>> print dump(root) |
|---|
| 623 | --- !python/object:_syck.Seq |
|---|
| 624 | value: |
|---|
| 625 | - !python/object:_syck.Scalar |
|---|
| 626 | value: foo |
|---|
| 627 | tag: tag:yaml.org,2002:str |
|---|
| 628 | - !python/object:_syck.Scalar |
|---|
| 629 | value: bar |
|---|
| 630 | tag: tag:yaml.org,2002:str |
|---|
| 631 | - !python/object:_syck.Scalar |
|---|
| 632 | value: baz |
|---|
| 633 | tag: tag:yaml.org,2002:str |
|---|
| 634 | |
|---|
| 635 | Parser |
|---|
| 636 | ~~~~~~ |
|---|
| 637 | |
|---|
| 638 | ``Parser`` : class |
|---|
| 639 | The class ``Parser`` is a low-level wrapper of a Syck YAML parser. It can |
|---|
| 640 | generate a representation graph from a YAML stream. |
|---|
| 641 | |
|---|
| 642 | The class constructor has the following arguments: |
|---|
| 643 | |
|---|
| 644 | * ``Parser(source, implicit_typing=True, taguri_expansion=True)``. |
|---|
| 645 | |
|---|
| 646 | The parameter ``source`` is a YAML stream. It must be a string |
|---|
| 647 | or a file-like object. If it is not a string, it should have a |
|---|
| 648 | method named ``read(max_length)`` that returns a string. |
|---|
| 649 | |
|---|
| 650 | It is not recommended to change the default values of the parameters |
|---|
| 651 | ``implicit_typing`` and ``taguri_expansion``. See the Syck documentation |
|---|
| 652 | for more details about them. |
|---|
| 653 | |
|---|
| 654 | The class defines a single method: |
|---|
| 655 | |
|---|
| 656 | * ``Parser.parse()``. |
|---|
| 657 | |
|---|
| 658 | It parses the source and returns the root node of the corresponding |
|---|
| 659 | representation graph. If the stream is finished, it returns ``None`` and |
|---|
| 660 | set the flag ``eof`` on. |
|---|
| 661 | |
|---|
| 662 | ``GenericLoader`` : subclass of ``Parser`` |
|---|
| 663 | The subclass ``GenericLoader`` defines two additional methods: |
|---|
| 664 | |
|---|
| 665 | * ``GenericLoader.load()``, |
|---|
| 666 | |
|---|
| 667 | * ``GenericLoader.construct(node)``. |
|---|
| 668 | |
|---|
| 669 | The method ``load()`` parses the source and constructs the corresponding |
|---|
| 670 | Python object. To generate an object by a node, ``load()`` uses the |
|---|
| 671 | ``construct()`` method. The ``construct()`` method defined in |
|---|
| 672 | ``GenericLoader`` just returns the value of the node: a string, a list, |
|---|
| 673 | or a dictionary. |
|---|
| 674 | |
|---|
| 675 | ``Loader`` : subclass of ``GenericLoader`` |
|---|
| 676 | |
|---|
| 677 | ``Loader`` redefines the method |
|---|
| 678 | |
|---|
| 679 | * ``Loader.construct(node)``, |
|---|
| 680 | |
|---|
| 681 | defines an additional method: |
|---|
| 682 | |
|---|
| 683 | * ``Loader.find_constructor(node)``, |
|---|
| 684 | |
|---|
| 685 | and add many other auxiliary methods for constructing Python objects. |
|---|
| 686 | |
|---|
| 687 | ``Loader.construct()`` calls ``find_constructor()`` for the given node, |
|---|
| 688 | and uses the returned constructor to generate a Python object. |
|---|
| 689 | |
|---|
| 690 | ``Loader.find_constructor()`` determines the constructor of a node by |
|---|
| 691 | the following rules: |
|---|
| 692 | |
|---|
| 693 | * If the node tag has the form ``tag:yaml.org,2002:type_id``, returns the |
|---|
| 694 | method ``Loader.construct_type_id``. |
|---|
| 695 | |
|---|
| 696 | * If the node tag has the form ``tag:python.yaml.org,2002:type_id``, returns |
|---|
| 697 | the method ``Loader.construct_python_type_id``. |
|---|
| 698 | |
|---|
| 699 | * If the node tag has the form ``x-private:type_id``, returns |
|---|
| 700 | ``Loader.construct_private_type_id``. |
|---|
| 701 | |
|---|
| 702 | * If the node tag has the form ``tag:domain.tld,year:type_id``, returns |
|---|
| 703 | ``Loader.construct_domain_tld_year_type_id``. |
|---|
| 704 | |
|---|
| 705 | See the source for more details. |
|---|
| 706 | |
|---|
| 707 | Let us show how ``Parser``, ``GenericLoader``, and ``Loader`` parse the same |
|---|
| 708 | document:: |
|---|
| 709 | |
|---|
| 710 | >>> # The source stream includes PySyck specific tags '!python/tuple' |
|---|
| 711 | >>> # and '!python/unicode'. It also includes implicitly typed integer |
|---|
| 712 | >>> # '12345' |
|---|
| 713 | >>> source = """--- !python/tuple |
|---|
| 714 | ... - a string |
|---|
| 715 | ... - !python/unicode a unicode string |
|---|
| 716 | ... - 12345 |
|---|
| 717 | ... """ |
|---|
| 718 | |
|---|
| 719 | >>> # 'Parser.parse()' returns the root node of the representation tree: |
|---|
| 720 | >>> p = Parser(source) |
|---|
| 721 | >>> print p.parse() |
|---|
| 722 | <_syck.Seq object at 0xb7a33f54> |
|---|
| 723 | |
|---|
| 724 | >>> # 'GenericLoader.load()' returns a Python object, but ignores the tags: |
|---|
| 725 | >>> gl = GenericLoader(source) |
|---|
| 726 | >>> print gl.load() |
|---|
| 727 | ['a string', 'a unicode string', '12345'] |
|---|
| 728 | |
|---|
| 729 | >>> # 'Loader.load()' is aware of the tags: |
|---|
| 730 | >>> l = Loader(source) |
|---|
| 731 | >>> print l.load() |
|---|
| 732 | ('a string', u'a unicode string', 12345) |
|---|
| 733 | |
|---|
| 734 | Emitter |
|---|
| 735 | ~~~~~~~ |
|---|
| 736 | |
|---|
| 737 | ``Emitter`` : class |
|---|
| 738 | The class ``Emitter`` is a low-level wrapper of a Syck YAML emitter. It can |
|---|
| 739 | generate a YAML stream from a representation graph. |
|---|
| 740 | |
|---|
| 741 | The class constructor has the following signature: |
|---|
| 742 | |
|---|
| 743 | * ``Emitter(output, headless=False, use_header=False, use_version=False, |
|---|
| 744 | explicit_typing=True, style=None, best_width=80, indent=2)``. |
|---|
| 745 | |
|---|
| 746 | The parameter ``output`` must be a file-like object that provides a method |
|---|
| 747 | ``write(data)``. The other parameters describe the formatting of the output |
|---|
| 748 | document. |
|---|
| 749 | |
|---|
| 750 | The class defines a single method: |
|---|
| 751 | |
|---|
| 752 | * ``emit(node)``. |
|---|
| 753 | |
|---|
| 754 | The parameter ``node`` must be the root node of a YAML representation graph. |
|---|
| 755 | The method ``emit()`` writes the generated YAML document to the ``output`` |
|---|
| 756 | stream. |
|---|
| 757 | |
|---|
| 758 | ``GenericDumper`` : subclass of ``Emitter`` |
|---|
| 759 | The subclass ``GenericDumper`` adds the following methods: |
|---|
| 760 | |
|---|
| 761 | * ``GenericDumper.dump(object)``, |
|---|
| 762 | |
|---|
| 763 | * ``GenericDumper.represent(object)``, |
|---|
| 764 | |
|---|
| 765 | * ``GenericDumper.allow_aliases(object)``. |
|---|
| 766 | |
|---|
| 767 | The method ``dump()`` converts the given object into a representation graph, |
|---|
| 768 | generates a YAML document, and writes it to the ``output`` stream. It uses |
|---|
| 769 | the method ``represent()`` to convert an object to a representation node. |
|---|
| 770 | The method ``represent()`` defined in ``GenericDumper`` generates a sequence |
|---|
| 771 | node for a list object and a mapping node for a dictionary object. Otherwise |
|---|
| 772 | it generates a scalar node with the value equal to ``str(object)``. |
|---|
| 773 | |
|---|
| 774 | The Syck YAML emitter automatically detects if the same object is reffered |
|---|
| 775 | from different parts of the graph and generates aliases for it. Unfortunately |
|---|
| 776 | it does not work well with immutable Python objects such as strings, numbers, |
|---|
| 777 | and tuples. To prevent generating unnecessary aliases, the method |
|---|
| 778 | ``allow_aliases()`` is used. If ``allow_aliases()`` for a given object |
|---|
| 779 | returns ``False``, the alias will never be generated. |
|---|
| 780 | |
|---|
| 781 | The ``allow_aliases()`` method defined in ``GenericDumper`` always returns |
|---|
| 782 | ``True``. |
|---|
| 783 | |
|---|
| 784 | ``Dumper`` : subclass of ``GenericDumper`` |
|---|
| 785 | The subclass ``Dumpers`` redefines the methods: |
|---|
| 786 | |
|---|
| 787 | * ``Dumper.represent(object)``, |
|---|
| 788 | |
|---|
| 789 | * ``Dumper.allow_aliases(object)``, |
|---|
| 790 | |
|---|
| 791 | defines the method |
|---|
| 792 | |
|---|
| 793 | * ``Dumper.find_representer(object)``, |
|---|
| 794 | |
|---|
| 795 | and add many other auxiliary methods for representing objects as nodes. |
|---|
| 796 | |
|---|
| 797 | ``Dumper.find_representer()`` finds a method that can represent the given |
|---|
| 798 | object as a node in a representation tree. ``find_representer()`` checks the |
|---|
| 799 | class of the object. If the class has the form ``package.module.type``, |
|---|
| 800 | ``find_representer()`` returns the method |
|---|
| 801 | ``Dumper.represent_package_module_type`` if it exists. If this method does |
|---|
| 802 | not exists, ``find_representer()`` consults its base class, and so on. |
|---|
| 803 | |
|---|
| 804 | ``Dumper.represent()`` calls ``Dumper.find_representer()`` for the given |
|---|
| 805 | object and uses the returned method to generate a representation node. |
|---|
| 806 | |
|---|
| 807 | See the source for more details. |
|---|
| 808 | |
|---|
| 809 | Let us show how ``Emitter``, ``GenericDumper``, and ``Dumper`` work:: |
|---|
| 810 | |
|---|
| 811 | >>> # For our demonstration, we define a representation tree named 'seq' |
|---|
| 812 | >>> # and a Python tuple named 'object': |
|---|
| 813 | >>> foo = Scalar('a string') |
|---|
| 814 | >>> bar = Scalar('a unicode string', tag="tag:python.yaml.org,2002:unicode") |
|---|
| 815 | >>> baz = Scalar('12345', tag="tag:yaml.org,2002:int") |
|---|
| 816 | >>> seq = Seq([foo, bar, baz], tag="tag:python.taml.org,2002:tuple") |
|---|
| 817 | >>> object = ('a string', u'a unicode string', 12345) |
|---|
| 818 | |
|---|
| 819 | >>> # An 'Emitter' instance can dump a representation tree into a stream, |
|---|
| 820 | >>> # but obviously failed to dump a Python object: |
|---|
| 821 | >>> e = Emitter(sys.stdout) |
|---|
| 822 | >>> e.emit(seq) |
|---|
| 823 | --- !python.taml.org,2002/tuple |
|---|
| 824 | - a string |
|---|
| 825 | - !python/unicode a unicode string |
|---|
| 826 | - 12345 |
|---|
| 827 | >>> e.emit(object) |
|---|
| 828 | Traceback (most recent call last): |
|---|
| 829 | File "<stdin>", line 1, in ? |
|---|
| 830 | TypeError: Node instance is required |
|---|
| 831 | |
|---|
| 832 | >>> # A 'GenericDumper' instance dumps almost everything as a scalar: |
|---|
| 833 | >>> gd = GenericDumper(sys.stdout) |
|---|
| 834 | >>> gd.dump(seq) |
|---|
| 835 | --- <_syck.Seq object at 0xb7a3c2fc> |
|---|
| 836 | >>> gd.dump(object) |
|---|
| 837 | --- ('a string', u'a unicode string', 12345) |
|---|
| 838 | |
|---|
| 839 | >>> # Finally, a 'Dumper' instance dumps a representation tree as a complex |
|---|
| 840 | >>> # Python object: |
|---|
| 841 | >>> d = Dumper(sys.stdout) |
|---|
| 842 | >>> d.dump(seq) |
|---|
| 843 | --- !python/object:_syck.Seq |
|---|
| 844 | value: |
|---|
| 845 | - !python/object:_syck.Scalar |
|---|
| 846 | value: a string |
|---|
| 847 | - !python/object:_syck.Scalar |
|---|
| 848 | value: a unicode string |
|---|
| 849 | tag: tag:python.yaml.org,2002:unicode |
|---|
| 850 | - !python/object:_syck.Scalar |
|---|
| 851 | value: "12345" |
|---|
| 852 | tag: tag:yaml.org,2002:int |
|---|
| 853 | tag: tag:python.taml.org,2002:tuple |
|---|
| 854 | >>> # It also dumps the 'object' object as expected: |
|---|
| 855 | >>> d.dump(object) |
|---|
| 856 | --- !python/tuple |
|---|
| 857 | - a string |
|---|
| 858 | - !python/unicode a unicode string |
|---|
| 859 | - 12345 |
|---|
| 860 | |
|---|
| 861 | |
|---|
| [26] | 862 | Development and Bug Reports |
|---|
| 863 | =========================== |
|---|
| 864 | |
|---|
| 865 | You may check out the PySyck_ source code from `PySyck SVN repository`_. |
|---|
| 866 | |
|---|
| 867 | If you find a bug in PySyck_, please file a bug report to `PySyck BTS`_. You |
|---|
| 868 | may review open bugs on `the list of active tickets`_. |
|---|
| 869 | |
|---|
| [29] | 870 | You may use `YAML-core mailing list`_ for discussions of PySyck_. |
|---|
| 871 | |
|---|
| [64] | 872 | .. _PySyck SVN repository: http://svn.pyyaml.org/pysyck/ |
|---|
| 873 | .. _PySyck BTS: http://pyyaml.org/newticket?component=pysyck |
|---|
| 874 | .. _the list of active tickets: http://pyyaml.org/query?action=view&component=pysyck&order=priority |
|---|
| [29] | 875 | .. _YAML-core mailing list: http://lists.sourceforge.net/lists/listinfo/yaml-core |
|---|
| [26] | 876 | |
|---|
| [29] | 877 | |
|---|
| 878 | Known Bugs |
|---|
| 879 | ========== |
|---|
| 880 | |
|---|
| [64] | 881 | PySyck_ does not support Unicode for real. It is a Syck_ limitation. |
|---|
| [29] | 882 | |
|---|
| 883 | |
|---|
| 884 | History |
|---|
| 885 | ======= |
|---|
| 886 | |
|---|
| [67] | 887 | * PySyck-0.61.1 (2006-03-15): |
|---|
| [64] | 888 | |
|---|
| 889 | - ``setup.py``: check if ``syck.h`` is present, complain if it doesn't. |
|---|
| 890 | - ``ext/_syckmodule.c``: release GIL_ before calling Syck_. Note that this |
|---|
| 891 | change broke Python 2.2 compatibility. |
|---|
| 892 | - ``lib/syck/loader.py``, ``lib/syck/dumper.py``: change treatment of the |
|---|
| 893 | ``!str`` tag. Now ``!str``-tagged scalars are converted to Unicode strings |
|---|
| 894 | if they are valid UTF-8, but are not valid ASCII. |
|---|
| 895 | - Windows binaries are compiled against the latest Syck_ from |
|---|
| 896 | `the Syck SVN repository`_ with `my Syck patches`_. |
|---|
| 897 | - The site is moved to http://pyyaml.org/wiki/PySyck. |
|---|
| 898 | |
|---|
| [30] | 899 | * PySyck-0.55.1 (2005-08-30): Initial release. |
|---|
| [29] | 900 | |
|---|
| [64] | 901 | .. _GIL: http://docs.python.org/api/threads.html |
|---|
| [29] | 902 | |
|---|
| [64] | 903 | |
|---|
| [26] | 904 | Author and Copyright |
|---|
| 905 | ==================== |
|---|
| 906 | |
|---|
| 907 | The PySyck_ module was written by `Kirill Simonov`_. |
|---|
| 908 | |
|---|
| 909 | PySyck_ is released under the BSD license as Syck_ itself. |
|---|
| 910 | |
|---|
| 911 | .. _Kirill Simonov: mailto:xi@resolvent.net |
|---|
| 912 | |
|---|
| [29] | 913 | .. |
|---|
| 914 | vim: ft=rst: |
|---|