mirror of
https://github.com/kennethreitz/tablib.git
synced 2026-06-05 23:10:17 +00:00
Compare commits
349 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7727171379 | |||
| 91bd4eb9c7 | |||
| 9b74b139fd | |||
| 823a543f41 | |||
| 1aa275bf99 | |||
| 17bb0d3b2c | |||
| 1a9aee9289 | |||
| 196edb82cc | |||
| a2990d5852 | |||
| d992ece86a | |||
| 46f302255d | |||
| 9e3ab4c13f | |||
| eaed0e48c2 | |||
| 501187b357 | |||
| ea4aef88b6 | |||
| 24d800fac3 | |||
| d8136ab613 | |||
| 36bbe2726b | |||
| 1427be2901 | |||
| 10ce000d31 | |||
| a91254117c | |||
| b67762604f | |||
| 83a8346e8f | |||
| 657ab98d04 | |||
| 9ddb4de942 | |||
| 5fad80a540 | |||
| cabab73045 | |||
| 2bb0525990 | |||
| f364bb576e | |||
| 09d057094e | |||
| 8082c4ad43 | |||
| 00e9ae0120 | |||
| f01c22213e | |||
| a58bf269d9 | |||
| 437a135dd3 | |||
| 0409ff50af | |||
| dd24edcc24 | |||
| cf28f4baa8 | |||
| 52dcf79c41 | |||
| 49f098ee22 | |||
| 642b1d8def | |||
| f6964bba8f | |||
| 8d6e75ad20 | |||
| 30487999ba | |||
| b74308e81e | |||
| 577289cbc3 | |||
| cf10703e31 | |||
| 778ad0265e | |||
| e3dedb8887 | |||
| c6e240fa52 | |||
| 5c747c9c2e | |||
| 0bbd990ed8 | |||
| fcada243a2 | |||
| fca8ad6182 | |||
| 35d9e390fd | |||
| 8ca180c461 | |||
| ff63558a67 | |||
| f621b56178 | |||
| 2b529bcb1c | |||
| 90c3435600 | |||
| 1fa28ee2ca | |||
| a5cae7c249 | |||
| 666991ca1e | |||
| 5f4162918f | |||
| b554ce36bb | |||
| e5e22d3ca2 | |||
| 8626351618 | |||
| cdfacb6d6e | |||
| 108c9de130 | |||
| 271aeebf56 | |||
| e75a00541d | |||
| 3b0e0c7991 | |||
| 23440fb7a3 | |||
| 459f310857 | |||
| f9021f53c2 | |||
| 7fda829d27 | |||
| ca08ac8a7b | |||
| 08b51113d3 | |||
| 3e391fc8e3 | |||
| a230844914 | |||
| bc82be09c5 | |||
| ed9fe01604 | |||
| e69546a0ff | |||
| d4b659ece9 | |||
| 55eb3f93e3 | |||
| be7182aea9 | |||
| 48def2cba6 | |||
| df8c0335d1 | |||
| d0b09f0fce | |||
| 9efd982bfa | |||
| a3c82804cd | |||
| 2e75e93f57 | |||
| a26d782e88 | |||
| f5c0c5c34d | |||
| d9aee8e605 | |||
| 315a082b70 | |||
| 120ce9fcd6 | |||
| 914a82eac9 | |||
| 3931bcb4e6 | |||
| 471e56c387 | |||
| 8553dbc040 | |||
| 96c93871cf | |||
| a54949bc08 | |||
| ed686c2391 | |||
| 143677be77 | |||
| b2c35c2543 | |||
| 9dfd9d0c8e | |||
| 140e23c980 | |||
| ac797f1eda | |||
| 7c90595364 | |||
| 38ac98fdb2 | |||
| 07c7d172d9 | |||
| 9c7707be60 | |||
| 14bee65208 | |||
| 4fc5e0655d | |||
| da2e670d0d | |||
| 5912bf4870 | |||
| 28e9d7e23e | |||
| 930d38cf5a | |||
| 5e433c263d | |||
| 19ac9b9716 | |||
| 6feb59504a | |||
| 817eedd6f5 | |||
| 4d1c5a9996 | |||
| 520a1986d7 | |||
| 1ea793112c | |||
| 41a7a5d329 | |||
| c4edaa2ca8 | |||
| c612bb3dae | |||
| c223dfbdf1 | |||
| 49bd48b016 | |||
| c6d90bc825 | |||
| bcd0e37a65 | |||
| 8c92e878a3 | |||
| da2b011358 | |||
| a8b0bf4b5f | |||
| 6574d3e58b | |||
| 1020799828 | |||
| 333e73f892 | |||
| bfe70066b8 | |||
| fbfbe01b70 | |||
| 06a394ea5c | |||
| 9427decdb0 | |||
| fb59035f8d | |||
| 187d12cffc | |||
| eaa4de7793 | |||
| d479c5735a | |||
| 96668bb393 | |||
| b369baba40 | |||
| 25f846a78a | |||
| 22fe18239f | |||
| 149bafa97b | |||
| 9f7fec2379 | |||
| 762ac39e27 | |||
| 2a7aa959b3 | |||
| d85523b6a6 | |||
| 6407afba3e | |||
| 25a5bcea0c | |||
| 7aada68952 | |||
| 5ba92b0f6b | |||
| f58d4b67dc | |||
| a310ab7a09 | |||
| 7f2f925ddb | |||
| 3fc898e222 | |||
| de46f45e2e | |||
| 392eaac299 | |||
| 3a9c3944cf | |||
| 8c402da729 | |||
| 8feb6e8ddf | |||
| 9072b6ddae | |||
| 9f26c23eb5 | |||
| 8136f4b09e | |||
| 7e7ad73ddd | |||
| f889910629 | |||
| 969d9d957d | |||
| 86d84b555d | |||
| 66867527d2 | |||
| 7505d8d985 | |||
| d5515c17b8 | |||
| 07ac723971 | |||
| 5d7843ea59 | |||
| b5f0cf9d37 | |||
| a73bbe1645 | |||
| f1bdf43aab | |||
| 7623bfe7b0 | |||
| 59ccc0b422 | |||
| 99154aa6d6 | |||
| 65836d5ace | |||
| 4117503ed5 | |||
| dfa26a7d53 | |||
| 4f035caf1b | |||
| a9c7a5067d | |||
| 80cb42e8dd | |||
| 8d7e5732cd | |||
| 942dd3dadf | |||
| b1d282744c | |||
| 4c0c879d65 | |||
| cab63e02c8 | |||
| 63d025888a | |||
| 5a993ac281 | |||
| 666dd1d2c7 | |||
| ac1666e3ae | |||
| 5b7e817db2 | |||
| f9c168e4bc | |||
| 82f3d84c7d | |||
| 121cf46aec | |||
| 4bb4a05bcb | |||
| e52b8dd329 | |||
| 93fb89b8b6 | |||
| c01b66a16a | |||
| c3fa29a166 | |||
| 8d6a52aaf5 | |||
| 703b1da04c | |||
| 0e6bd079cc | |||
| 579dbf0cc0 | |||
| fbabb430ca | |||
| b8f923f8c5 | |||
| fbe6fe1612 | |||
| 17e90e71e5 | |||
| dc21825f34 | |||
| 7364995eaa | |||
| 3407170b99 | |||
| dd13744c92 | |||
| 31e4c39762 | |||
| 4fc70957ac | |||
| 7f17ccf445 | |||
| fbcc3b60af | |||
| 9b3268f0ad | |||
| f386ef8ac8 | |||
| e8f5e023c4 | |||
| 81445aeec8 | |||
| f94a236122 | |||
| bfbb7c626f | |||
| be0f77f9ee | |||
| 3b44349090 | |||
| 04a16afa58 | |||
| a8632125dc | |||
| ccf2ebcde2 | |||
| 2c60ce9233 | |||
| 649c7e8bb7 | |||
| 2d3dc5ef71 | |||
| efc516f366 | |||
| b2a51fd941 | |||
| d54d70bc22 | |||
| 391ad61bef | |||
| 99a45814d1 | |||
| fad3546614 | |||
| 7ba2849829 | |||
| 7ec0f2ef07 | |||
| bd470684a4 | |||
| dbcea81c17 | |||
| 49dc4a249e | |||
| 7cd82f956f | |||
| 13c3e537fd | |||
| f913853cae | |||
| ea1de420a3 | |||
| d0c8df95a3 | |||
| bb4e97f8aa | |||
| ffaeb64639 | |||
| f31ec562b4 | |||
| 68d7204b2d | |||
| 52db1ddc3e | |||
| 4755020dd7 | |||
| 5468dd7e67 | |||
| 8673710ddb | |||
| f01cf184d4 | |||
| 1482ca4a19 | |||
| 93c6c39581 | |||
| a0cb44cc43 | |||
| b2cd061773 | |||
| 876b849950 | |||
| 40c9e09578 | |||
| 9f5379fcc7 | |||
| 9ecc57dbf7 | |||
| a7471f7302 | |||
| 70211b71e0 | |||
| ddf4b441b0 | |||
| a0509126e0 | |||
| ec5b1cf3e0 | |||
| 3fb729aac6 | |||
| 647f69044f | |||
| 3da34af76f | |||
| 5b42824871 | |||
| 89209b6bd3 | |||
| 9362d3283b | |||
| 123851a737 | |||
| 54973c276c | |||
| 275ac9d194 | |||
| 4ecd5888af | |||
| 359f12c83c | |||
| de8d76fdae | |||
| f188e3dd87 | |||
| 3dff8f5b79 | |||
| c24d2dd45d | |||
| 4e98563483 | |||
| ca17c9f965 | |||
| 41c4fcc59f | |||
| e9166b14fd | |||
| a5528d731e | |||
| 1a122f2a4d | |||
| e9d9350e43 | |||
| ac4b568cba | |||
| 8be372b8cc | |||
| f8d8d3058a | |||
| d03ba7e532 | |||
| 35102ab951 | |||
| b8587e5cb0 | |||
| da4f2013f1 | |||
| ff069b1604 | |||
| 2994a9fc0d | |||
| 310400af5b | |||
| d52537b75b | |||
| 40490d1ba5 | |||
| 9f025dc111 | |||
| 335e3b1134 | |||
| 6b4afc38d1 | |||
| bd3099897c | |||
| 4b6fbe9225 | |||
| f22357f1bc | |||
| 37ffbc71c0 | |||
| 6f7c64eb03 | |||
| 89be8f402f | |||
| 607ea4a8aa | |||
| 7044896d56 | |||
| 8fca234388 | |||
| 0862b35905 | |||
| e8bf07da1a | |||
| 5c70fe0a07 | |||
| 6c37412c76 | |||
| 54a83602e4 | |||
| f486b77a7a | |||
| fecfecfd16 | |||
| 9a65b8deed | |||
| 61231b38ac | |||
| b7a8b65c00 | |||
| 6dd71c8830 | |||
| 8d44ad8a12 | |||
| 1850a934aa | |||
| ee953512b7 | |||
| 6405ec3baf | |||
| ea64e4cfac | |||
| 0bea48ccc3 | |||
| 687670762f | |||
| 254ce62b2a | |||
| 2b3f277138 | |||
| a2f59584e4 | |||
| 4d3a31e19f | |||
| 95c98861da | |||
| 59d1f9fded |
+7
-2
@@ -1,10 +1,11 @@
|
|||||||
# application builds
|
# application builds
|
||||||
build/*
|
build/*
|
||||||
dist/*
|
dist/*
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
# python skin
|
# python skin
|
||||||
.pyc
|
*.pyc
|
||||||
.pyo
|
*.pyo
|
||||||
|
|
||||||
# osx noise
|
# osx noise
|
||||||
.DS_Store
|
.DS_Store
|
||||||
@@ -13,3 +14,7 @@ profile
|
|||||||
# pycharm noise
|
# pycharm noise
|
||||||
.idea
|
.idea
|
||||||
.idea/*
|
.idea/*
|
||||||
|
|
||||||
|
# vi noise
|
||||||
|
*.swp
|
||||||
|
docs/_build/*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
GistAPI.py is written and maintained by Kenneth Reitz and
|
Tablib is written and maintained by Kenneth Reitz and
|
||||||
various contributors:
|
various contributors:
|
||||||
|
|
||||||
Development Lead
|
Development Lead
|
||||||
@@ -10,4 +10,6 @@ Development Lead
|
|||||||
Patches and Suggestions
|
Patches and Suggestions
|
||||||
```````````````````````
|
```````````````````````
|
||||||
|
|
||||||
- A Lucky Someone
|
- Luke Lee
|
||||||
|
- Josh Ourisman
|
||||||
|
- Luca Beltrame
|
||||||
+118
@@ -0,0 +1,118 @@
|
|||||||
|
History
|
||||||
|
-------
|
||||||
|
|
||||||
|
0.9.2 (2010-11-17)
|
||||||
|
++++++++++++++++++
|
||||||
|
|
||||||
|
* Tanspose method added to Datasets
|
||||||
|
* New frozen top row in Excel output
|
||||||
|
* Pickling support for Datasets and Rows
|
||||||
|
* Support for row/column stacking
|
||||||
|
|
||||||
|
|
||||||
|
0.9.1 (2010-11-04)
|
||||||
|
++++++++++++++++++
|
||||||
|
|
||||||
|
* Minor reference shadowing bugfix
|
||||||
|
|
||||||
|
|
||||||
|
0.9.0 (2010-11-04)
|
||||||
|
++++++++++++++++++
|
||||||
|
|
||||||
|
* Massive documentation update!
|
||||||
|
* Tablib.org!
|
||||||
|
* Row taggins and Dataset filtering!
|
||||||
|
* Column insert/delete support
|
||||||
|
* Column append API change (header required)
|
||||||
|
* Internal Changes (Row object and use thereof)
|
||||||
|
|
||||||
|
|
||||||
|
0.8.5 (2010-10-06)
|
||||||
|
++++++++++++++++++
|
||||||
|
|
||||||
|
* New import system. All dependencies attempt to load from site-packages,
|
||||||
|
then fallback on vendorized modules.
|
||||||
|
|
||||||
|
|
||||||
|
0.8.4 (2010-10-04)
|
||||||
|
++++++++++++++++++
|
||||||
|
|
||||||
|
* Upated XLS output: Only wrap if '\\n' in cell.
|
||||||
|
|
||||||
|
|
||||||
|
0.8.3 (2010-10-04)
|
||||||
|
++++++++++++++++++
|
||||||
|
|
||||||
|
* Ability to append new column passing a callable
|
||||||
|
as the value that will be applied to every row.
|
||||||
|
|
||||||
|
|
||||||
|
0.8.2 (2010-10-04)
|
||||||
|
++++++++++++++++++
|
||||||
|
|
||||||
|
* Added alignment wrapping to written cells.
|
||||||
|
* Added separator support to XLS.
|
||||||
|
|
||||||
|
|
||||||
|
0.8.1 (2010-09-28)
|
||||||
|
++++++++++++++++++
|
||||||
|
|
||||||
|
* Packaging Fix
|
||||||
|
|
||||||
|
|
||||||
|
0.8.0 (2010-09-25)
|
||||||
|
++++++++++++++++++
|
||||||
|
|
||||||
|
* New format plugin system!
|
||||||
|
* Imports! ELEGANT Imports!
|
||||||
|
* Tests. Lots of tests.
|
||||||
|
|
||||||
|
|
||||||
|
0.7.1 (2010-09-20)
|
||||||
|
++++++++++++++++++
|
||||||
|
|
||||||
|
* Reverting methods back to properties.
|
||||||
|
* Windows bug compenated in documentation.
|
||||||
|
|
||||||
|
|
||||||
|
0.7.0 (2010-09-20)
|
||||||
|
++++++++++++++++++
|
||||||
|
|
||||||
|
* Renamed DataBook Databook for consistiency.
|
||||||
|
* Export properties changed to methods (XLS filename / StringIO bug).
|
||||||
|
* Optional Dataset.xls(path='filename') support (for writing on windows).
|
||||||
|
* Added utf-8 on the worksheet level.
|
||||||
|
|
||||||
|
|
||||||
|
0.6.4 (2010-09-19)
|
||||||
|
++++++++++++++++++
|
||||||
|
|
||||||
|
* Updated unicode export for XLS.
|
||||||
|
* More exhaustive unit tests.
|
||||||
|
|
||||||
|
|
||||||
|
0.6.3 (2010-09-14)
|
||||||
|
++++++++++++++++++
|
||||||
|
* Added Dataset.append() support for columns.
|
||||||
|
|
||||||
|
|
||||||
|
0.6.2 (2010-09-13)
|
||||||
|
++++++++++++++++++
|
||||||
|
* Fixed Dataset.append() error on empty dataset.
|
||||||
|
* Updated Dataset.headers property w/ validation.
|
||||||
|
* Added Testing Fixtures.
|
||||||
|
|
||||||
|
0.6.1 (2010-09-12)
|
||||||
|
++++++++++++++++++
|
||||||
|
|
||||||
|
* Packaging hotfixes.
|
||||||
|
|
||||||
|
|
||||||
|
0.6.0 (2010-09-11)
|
||||||
|
++++++++++++++++++
|
||||||
|
|
||||||
|
* Public Release.
|
||||||
|
* Export Support for XLS, JSON, YAML, and CSV.
|
||||||
|
* DataBook Export for XLS, JSON, and YAML.
|
||||||
|
* Python Dict Property Support.
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
include HISTORY.rst README.rst tabbed
|
include HISTORY.rst README.rst LICENSE AUTHORS
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
Tablib includes some vendorized python libraries: ordereddict, pyyaml,
|
||||||
|
simplejson, and xlwt.
|
||||||
|
|
||||||
|
|
||||||
|
OrderedDict License
|
||||||
|
===================
|
||||||
|
|
||||||
|
Copyright (c) 2009 Raymond Hettinger
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person
|
||||||
|
obtaining a copy of this software and associated documentation files
|
||||||
|
(the "Software"), to deal in the Software without restriction,
|
||||||
|
including without limitation the rights to use, copy, modify, merge,
|
||||||
|
publish, distribute, sublicense, and/or sell copies of the Software,
|
||||||
|
and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
PyYAML License
|
||||||
|
==============
|
||||||
|
|
||||||
|
Copyright (c) 2006 Kirill Simonov
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
SimpleJSON License
|
||||||
|
==================
|
||||||
|
|
||||||
|
Copyright (c) 2006 Bob Ippolito
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
XLWT License
|
||||||
|
============
|
||||||
|
|
||||||
|
Portions copyright © 2007, Stephen John Machin, Lingfo Pty Ltd
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. None of the names of Stephen John Machin, Lingfo Pty Ltd and any
|
||||||
|
contributors may be used to endorse or promote products derived from this
|
||||||
|
software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
|
||||||
|
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||||
|
THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
Copyright (C) 2005 Roman V. Kiseliov
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions
|
||||||
|
are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in
|
||||||
|
the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
|
||||||
|
3. All advertising materials mentioning features or use of this
|
||||||
|
software must display the following acknowledgment:
|
||||||
|
"This product includes software developed by
|
||||||
|
Roman V. Kiseliov <roman@kiseliov.ru>."
|
||||||
|
|
||||||
|
4. Redistributions of any form whatsoever must retain the following
|
||||||
|
acknowledgment:
|
||||||
|
"This product includes software developed by
|
||||||
|
Roman V. Kiseliov <roman@kiseliov.ru>."
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY Roman V. Kiseliov ``AS IS'' AND ANY
|
||||||
|
EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Roman V. Kiseliov OR
|
||||||
|
ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||||
|
OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
Roman V. Kiseliov
|
||||||
|
Russia
|
||||||
|
Kursk
|
||||||
|
Libknecht St., 4
|
||||||
|
|
||||||
|
+7(0712)56-09-83
|
||||||
|
|
||||||
|
<roman@kiseliov.ru>
|
||||||
|
Subject: pyExcelerator
|
||||||
+146
-54
@@ -1,97 +1,189 @@
|
|||||||
Tabbed: format-agnostic tabular dataset library
|
Tablib: format-agnostic tabular dataset library
|
||||||
===============================================
|
===============================================
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
_____ ______ ______ _________
|
_____ ______ ___________ ______
|
||||||
__ /_______ ____ /_ ___ /_ _____ ______ /
|
__ /_______ ____ /_ ___ /___(_)___ /_
|
||||||
_ __/_ __ `/__ __ \__ __ \_ _ \_ __ /
|
_ __/_ __ `/__ __ \__ / __ / __ __ \
|
||||||
/ /_ / /_/ / _ /_/ /_ /_/ // __// /_/ /
|
/ /_ / /_/ / _ /_/ /_ / _ / _ /_/ /
|
||||||
\__/ \__,_/ /_.___/ /_.___/ \___/ \__,_/
|
\__/ \__,_/ /_.___/ /_/ /_/ /_.___/
|
||||||
|
|
||||||
*Tabbed is under active documentation-driven development.*
|
|
||||||
|
|
||||||
|
|
||||||
Tabbed is a format-agnostic tabular dataset library, written in Python.
|
|
||||||
It is a full python module which doubles as a CLI application for quick
|
|
||||||
dataset conversions.
|
|
||||||
|
|
||||||
Formats supported:
|
Tablib is a format-agnostic tabular dataset library, written in Python.
|
||||||
|
|
||||||
- JSON
|
Output formats supported:
|
||||||
- YAML
|
|
||||||
- Excel
|
|
||||||
- CSV
|
|
||||||
- HTML
|
|
||||||
|
|
||||||
Please note that tabbed *purposefully* excludes XML support. It always will.
|
- Excel (Sets + Books)
|
||||||
|
- JSON (Sets + Books)
|
||||||
|
- YAML (Sets + Books)
|
||||||
|
- TSV (Sets)
|
||||||
|
- CSV (Sets)
|
||||||
|
|
||||||
|
Import formats supported:
|
||||||
|
|
||||||
Features
|
- JSON (Sets + Books)
|
||||||
|
- YAML (Sets + Books)
|
||||||
|
- TSV (Sets)
|
||||||
|
- CSV (Sets)
|
||||||
|
|
||||||
|
Note that tablib *purposefully* excludes XML support. It always will.
|
||||||
|
|
||||||
|
Overview
|
||||||
--------
|
--------
|
||||||
|
|
||||||
Convert datafile formats via API: ::
|
`tablib.Dataset()`
|
||||||
|
A Dataset is a table of tabular data. It may or may not have a header row. They can be build and maniuplated as raw Python datatypes (Lists of tuples|dictonaries). Datasets can be imported from JSON, YAML, and CSV; they can be exported to Excel (XLS), JSON, YAML, and CSV.
|
||||||
|
|
||||||
tablib.import(filename='data.csv').export('data.json')
|
`tablib.Databook()`
|
||||||
|
A Databook is a set of Datasets. The most common form of a Databook is an Excel file with multiple spreadsheets. Databooks can be imported from JSON and YAML; they can be exported to Excel (XLS), JSON, and YAML.
|
||||||
|
|
||||||
|
Usage
|
||||||
Convert datafile formats via CLI: ::
|
-----
|
||||||
|
|
||||||
$ tabbed data.csv data.json
|
|
||||||
|
|
||||||
Convert data formats via CLI pipe interface: ::
|
|
||||||
|
|
||||||
$ curl http://domain.dev/dataset.json | tabbed --to excel | gist -p
|
|
||||||
|
|
||||||
|
|
||||||
Populate fresh data files: ::
|
Populate fresh data files: ::
|
||||||
|
|
||||||
headers = ('first_name', 'last_name', 'gpa')
|
headers = ('first_name', 'last_name')
|
||||||
|
|
||||||
data = [
|
data = [
|
||||||
('John', 'Adams', 4.0),
|
('John', 'Adams'),
|
||||||
('George', 'Washington', 2.6),
|
('George', 'Washington')
|
||||||
('Henry', 'Ford', 2.3)
|
|
||||||
]
|
]
|
||||||
|
|
||||||
data = tablib.Data(*data, headers=headers)
|
data = tablib.Dataset(*data, headers=headers)
|
||||||
|
|
||||||
# Establish file location and save
|
|
||||||
data.save('test.xls')
|
|
||||||
|
|
||||||
|
|
||||||
Intelligently add new rows: ::
|
Intelligently add new rows: ::
|
||||||
|
|
||||||
data.add_row('Bob', 'Dylan')
|
>>> data.append(('Henry', 'Ford'))
|
||||||
# >>> Warning: Existing column count is 3
|
|
||||||
|
|
||||||
print data.headers
|
Intelligently add new columns: ::
|
||||||
# >>> ('first_name', 'last_name', 'gpa')
|
|
||||||
|
|
||||||
|
>>> data.append(col=(90, 67, 83), header='age')
|
||||||
|
|
||||||
Slice rows: ::
|
Slice rows: ::
|
||||||
|
|
||||||
print data[0:1]
|
>>> print data[:2]
|
||||||
# >>> [('John', 'Adams', 4.0), ('George', 'Washington', 2.6)]
|
[('John', 'Adams', 90), ('George', 'Washington', 67)]
|
||||||
|
|
||||||
|
|
||||||
Slice columns by header: ::
|
Slice columns by header: ::
|
||||||
|
|
||||||
print data['first_name']
|
>>> print data['first_name']
|
||||||
# >>> ['John', 'George', 'Henry']
|
['John', 'George', 'Henry']
|
||||||
|
|
||||||
|
Easily delete rows: ::
|
||||||
|
|
||||||
|
>>> del data[1]
|
||||||
|
|
||||||
|
Exports
|
||||||
|
-------
|
||||||
|
|
||||||
|
Drumroll please...........
|
||||||
|
|
||||||
|
JSON!
|
||||||
|
+++++
|
||||||
|
::
|
||||||
|
|
||||||
|
>>> print data.json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"last_name": "Adams",
|
||||||
|
"age": 90,
|
||||||
|
"first_name": "John"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"last_name": "Ford",
|
||||||
|
"age": 83,
|
||||||
|
"first_name": "Henry"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
Manipulate rows by index: ::
|
YAML!
|
||||||
|
+++++
|
||||||
|
::
|
||||||
|
|
||||||
data.delRow(0)
|
>>> print data.yaml
|
||||||
print data[0:1]
|
- {age: 90, first_name: John, last_name: Adams}
|
||||||
# >>> [('George', 'Washington', 2.6), ('Henry', 'Ford', 2.3)]
|
- {age: 83, first_name: Henry, last_name: Ford}
|
||||||
|
|
||||||
# Update saved file
|
CSV...
|
||||||
data.save()
|
++++++
|
||||||
|
::
|
||||||
|
|
||||||
|
>>> print data.csv
|
||||||
|
first_name,last_name,age
|
||||||
|
John,Adams,90
|
||||||
|
Henry,Ford,83
|
||||||
|
|
||||||
|
EXCEL!
|
||||||
|
++++++
|
||||||
|
::
|
||||||
|
|
||||||
|
>>> open('people.xls', 'wb').write(data.xls)
|
||||||
|
|
||||||
|
It's that easy.
|
||||||
|
|
||||||
|
Imports!
|
||||||
|
--------
|
||||||
|
|
||||||
|
JSON
|
||||||
|
++++
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
>>> data.json = '[{"last_name": "Adams","age": 90,"first_name": "John"}]'
|
||||||
|
>>> print data[0]
|
||||||
|
('John', 'Adams', 90)
|
||||||
|
|
||||||
|
|
||||||
Export to various formats: ::
|
YAML
|
||||||
|
++++
|
||||||
|
::
|
||||||
|
|
||||||
# Save copy as CSV
|
>>> data.yaml = '- {age: 90, first_name: John, last_name: Adams}'
|
||||||
data.export('backup.csv')
|
>>> print data[0]
|
||||||
|
('John', 'Adams', 90)
|
||||||
|
|
||||||
|
CSV
|
||||||
|
+++
|
||||||
|
::
|
||||||
|
|
||||||
|
>>> data.csv = 'age, first_name, last_name\n90, John, Adams'
|
||||||
|
>>> print data[0]
|
||||||
|
('John', 'Adams', 90)
|
||||||
|
|
||||||
|
>>> print data.yaml
|
||||||
|
- {age: 90, first_name: John, last_name: Adams}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
|
||||||
|
To install tablib, simply: ::
|
||||||
|
|
||||||
|
$ pip install tablib
|
||||||
|
|
||||||
|
Or, if you absolutely must: ::
|
||||||
|
|
||||||
|
$ easy_install tablib
|
||||||
|
|
||||||
|
Contribute
|
||||||
|
----------
|
||||||
|
|
||||||
|
If you'd like to contribute, simply fork `the repository`_, commit your changes to the **develop** branch (or branch off of it), and send a pull request. Make sure you add yourself to AUTHORS_.
|
||||||
|
|
||||||
|
|
||||||
|
Roadmap
|
||||||
|
-------
|
||||||
|
- Release CLI Interface
|
||||||
|
- Auto-detect import format
|
||||||
|
- Add possible other exports (SQL?)
|
||||||
|
- Ability to assign types to rows (set, regex=, &c.)
|
||||||
|
|
||||||
|
.. _`the repository`: http://github.com/kennethreitz/tablib
|
||||||
|
.. _AUTHORS: http://github.com/kennethreitz/tablib/blob/master/AUTHORS
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
* Backwards-compatible OrderedDict support
|
||||||
|
* Write more exhausive unit-tests.
|
||||||
|
* Write stress tests.
|
||||||
|
* Make CSV write customizable.
|
||||||
|
* HTML Table exports.
|
||||||
|
* Integrate django-tablib
|
||||||
|
* Mention django-tablib in Documention
|
||||||
|
* Dataset title usage in documentation (#17)
|
||||||
+130
@@ -0,0 +1,130 @@
|
|||||||
|
# Makefile for Sphinx documentation
|
||||||
|
#
|
||||||
|
|
||||||
|
# You can set these variables from the command line.
|
||||||
|
SPHINXOPTS =
|
||||||
|
SPHINXBUILD = sphinx-build
|
||||||
|
PAPER =
|
||||||
|
BUILDDIR = _build
|
||||||
|
|
||||||
|
# Internal variables.
|
||||||
|
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||||
|
PAPEROPT_letter = -D latex_paper_size=letter
|
||||||
|
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||||
|
|
||||||
|
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
|
||||||
|
|
||||||
|
help:
|
||||||
|
@echo "Please use \`make <target>' where <target> is one of"
|
||||||
|
@echo " html to make standalone HTML files"
|
||||||
|
@echo " dirhtml to make HTML files named index.html in directories"
|
||||||
|
@echo " singlehtml to make a single large HTML file"
|
||||||
|
@echo " pickle to make pickle files"
|
||||||
|
@echo " json to make JSON files"
|
||||||
|
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||||
|
@echo " qthelp to make HTML files and a qthelp project"
|
||||||
|
@echo " devhelp to make HTML files and a Devhelp project"
|
||||||
|
@echo " epub to make an epub"
|
||||||
|
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||||
|
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||||
|
@echo " text to make text files"
|
||||||
|
@echo " man to make manual pages"
|
||||||
|
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||||
|
@echo " linkcheck to check all external links for integrity"
|
||||||
|
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||||
|
|
||||||
|
clean:
|
||||||
|
-rm -rf $(BUILDDIR)/*
|
||||||
|
|
||||||
|
html:
|
||||||
|
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||||
|
|
||||||
|
dirhtml:
|
||||||
|
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||||
|
|
||||||
|
singlehtml:
|
||||||
|
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||||
|
|
||||||
|
pickle:
|
||||||
|
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can process the pickle files."
|
||||||
|
|
||||||
|
json:
|
||||||
|
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can process the JSON files."
|
||||||
|
|
||||||
|
htmlhelp:
|
||||||
|
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||||
|
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||||
|
|
||||||
|
qthelp:
|
||||||
|
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||||
|
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||||
|
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Tablib.qhcp"
|
||||||
|
@echo "To view the help file:"
|
||||||
|
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Tablib.qhc"
|
||||||
|
|
||||||
|
devhelp:
|
||||||
|
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||||
|
@echo
|
||||||
|
@echo "Build finished."
|
||||||
|
@echo "To view the help file:"
|
||||||
|
@echo "# mkdir -p $$HOME/.local/share/devhelp/Tablib"
|
||||||
|
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Tablib"
|
||||||
|
@echo "# devhelp"
|
||||||
|
|
||||||
|
epub:
|
||||||
|
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||||
|
|
||||||
|
latex:
|
||||||
|
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||||
|
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||||
|
"(use \`make latexpdf' here to do that automatically)."
|
||||||
|
|
||||||
|
latexpdf:
|
||||||
|
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||||
|
@echo "Running LaTeX files through pdflatex..."
|
||||||
|
make -C $(BUILDDIR)/latex all-pdf
|
||||||
|
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||||
|
|
||||||
|
text:
|
||||||
|
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||||
|
|
||||||
|
man:
|
||||||
|
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||||
|
|
||||||
|
changes:
|
||||||
|
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||||
|
@echo
|
||||||
|
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||||
|
|
||||||
|
linkcheck:
|
||||||
|
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||||
|
@echo
|
||||||
|
@echo "Link check complete; look for any errors in the above output " \
|
||||||
|
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||||
|
|
||||||
|
doctest:
|
||||||
|
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||||
|
@echo "Testing of doctests in the sources finished, look at the " \
|
||||||
|
"results in $(BUILDDIR)/doctest/output.txt."
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
.DS_Store
|
||||||
Vendored
+45
@@ -0,0 +1,45 @@
|
|||||||
|
Modifications:
|
||||||
|
|
||||||
|
Copyright (c) 2010 Kenneth Reitz.
|
||||||
|
|
||||||
|
|
||||||
|
Original Project:
|
||||||
|
|
||||||
|
Copyright (c) 2010 by Armin Ronacher.
|
||||||
|
|
||||||
|
|
||||||
|
Some rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms of the theme, with or
|
||||||
|
without modification, are permitted provided that the following conditions
|
||||||
|
are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following
|
||||||
|
disclaimer in the documentation and/or other materials provided
|
||||||
|
with the distribution.
|
||||||
|
|
||||||
|
* The names of the contributors may not be used to endorse or
|
||||||
|
promote products derived from this software without specific
|
||||||
|
prior written permission.
|
||||||
|
|
||||||
|
We kindly ask you to only use these themes in an unmodified manner just
|
||||||
|
for Flask and Flask-related products, not for unrelated projects. If you
|
||||||
|
like the visual style and want to use it for your own projects, please
|
||||||
|
consider making some larger changes to the themes (such as changing
|
||||||
|
font faces, sizes, colors or margins).
|
||||||
|
|
||||||
|
THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGE.
|
||||||
Vendored
+25
@@ -0,0 +1,25 @@
|
|||||||
|
krTheme Sphinx Style
|
||||||
|
====================
|
||||||
|
|
||||||
|
This repository contains sphinx styles Kenneth Reitz uses in most of
|
||||||
|
his projects. It is a drivative of Mitsuhiko's themes for Flask and Flask related
|
||||||
|
projects. To use this style in your Sphinx documentation, follow
|
||||||
|
this guide:
|
||||||
|
|
||||||
|
1. put this folder as _themes into your docs folder. Alternatively
|
||||||
|
you can also use git submodules to check out the contents there.
|
||||||
|
|
||||||
|
2. add this to your conf.py: ::
|
||||||
|
|
||||||
|
sys.path.append(os.path.abspath('_themes'))
|
||||||
|
html_theme_path = ['_themes']
|
||||||
|
html_theme = 'flask'
|
||||||
|
|
||||||
|
The following themes exist:
|
||||||
|
|
||||||
|
**kr**
|
||||||
|
the standard flask documentation theme for large projects
|
||||||
|
|
||||||
|
**kr_small**
|
||||||
|
small one-page theme. Intended to be used by very small addon libraries.
|
||||||
|
|
||||||
Vendored
+86
@@ -0,0 +1,86 @@
|
|||||||
|
# flasky extensions. flasky pygments style based on tango style
|
||||||
|
from pygments.style import Style
|
||||||
|
from pygments.token import Keyword, Name, Comment, String, Error, \
|
||||||
|
Number, Operator, Generic, Whitespace, Punctuation, Other, Literal
|
||||||
|
|
||||||
|
|
||||||
|
class FlaskyStyle(Style):
|
||||||
|
background_color = "#f8f8f8"
|
||||||
|
default_style = ""
|
||||||
|
|
||||||
|
styles = {
|
||||||
|
# No corresponding class for the following:
|
||||||
|
#Text: "", # class: ''
|
||||||
|
Whitespace: "underline #f8f8f8", # class: 'w'
|
||||||
|
Error: "#a40000 border:#ef2929", # class: 'err'
|
||||||
|
Other: "#000000", # class 'x'
|
||||||
|
|
||||||
|
Comment: "italic #8f5902", # class: 'c'
|
||||||
|
Comment.Preproc: "noitalic", # class: 'cp'
|
||||||
|
|
||||||
|
Keyword: "bold #004461", # class: 'k'
|
||||||
|
Keyword.Constant: "bold #004461", # class: 'kc'
|
||||||
|
Keyword.Declaration: "bold #004461", # class: 'kd'
|
||||||
|
Keyword.Namespace: "bold #004461", # class: 'kn'
|
||||||
|
Keyword.Pseudo: "bold #004461", # class: 'kp'
|
||||||
|
Keyword.Reserved: "bold #004461", # class: 'kr'
|
||||||
|
Keyword.Type: "bold #004461", # class: 'kt'
|
||||||
|
|
||||||
|
Operator: "#582800", # class: 'o'
|
||||||
|
Operator.Word: "bold #004461", # class: 'ow' - like keywords
|
||||||
|
|
||||||
|
Punctuation: "bold #000000", # class: 'p'
|
||||||
|
|
||||||
|
# because special names such as Name.Class, Name.Function, etc.
|
||||||
|
# are not recognized as such later in the parsing, we choose them
|
||||||
|
# to look the same as ordinary variables.
|
||||||
|
Name: "#000000", # class: 'n'
|
||||||
|
Name.Attribute: "#c4a000", # class: 'na' - to be revised
|
||||||
|
Name.Builtin: "#004461", # class: 'nb'
|
||||||
|
Name.Builtin.Pseudo: "#3465a4", # class: 'bp'
|
||||||
|
Name.Class: "#000000", # class: 'nc' - to be revised
|
||||||
|
Name.Constant: "#000000", # class: 'no' - to be revised
|
||||||
|
Name.Decorator: "#888", # class: 'nd' - to be revised
|
||||||
|
Name.Entity: "#ce5c00", # class: 'ni'
|
||||||
|
Name.Exception: "bold #cc0000", # class: 'ne'
|
||||||
|
Name.Function: "#000000", # class: 'nf'
|
||||||
|
Name.Property: "#000000", # class: 'py'
|
||||||
|
Name.Label: "#f57900", # class: 'nl'
|
||||||
|
Name.Namespace: "#000000", # class: 'nn' - to be revised
|
||||||
|
Name.Other: "#000000", # class: 'nx'
|
||||||
|
Name.Tag: "bold #004461", # class: 'nt' - like a keyword
|
||||||
|
Name.Variable: "#000000", # class: 'nv' - to be revised
|
||||||
|
Name.Variable.Class: "#000000", # class: 'vc' - to be revised
|
||||||
|
Name.Variable.Global: "#000000", # class: 'vg' - to be revised
|
||||||
|
Name.Variable.Instance: "#000000", # class: 'vi' - to be revised
|
||||||
|
|
||||||
|
Number: "#990000", # class: 'm'
|
||||||
|
|
||||||
|
Literal: "#000000", # class: 'l'
|
||||||
|
Literal.Date: "#000000", # class: 'ld'
|
||||||
|
|
||||||
|
String: "#4e9a06", # class: 's'
|
||||||
|
String.Backtick: "#4e9a06", # class: 'sb'
|
||||||
|
String.Char: "#4e9a06", # class: 'sc'
|
||||||
|
String.Doc: "italic #8f5902", # class: 'sd' - like a comment
|
||||||
|
String.Double: "#4e9a06", # class: 's2'
|
||||||
|
String.Escape: "#4e9a06", # class: 'se'
|
||||||
|
String.Heredoc: "#4e9a06", # class: 'sh'
|
||||||
|
String.Interpol: "#4e9a06", # class: 'si'
|
||||||
|
String.Other: "#4e9a06", # class: 'sx'
|
||||||
|
String.Regex: "#4e9a06", # class: 'sr'
|
||||||
|
String.Single: "#4e9a06", # class: 's1'
|
||||||
|
String.Symbol: "#4e9a06", # class: 'ss'
|
||||||
|
|
||||||
|
Generic: "#000000", # class: 'g'
|
||||||
|
Generic.Deleted: "#a40000", # class: 'gd'
|
||||||
|
Generic.Emph: "italic #000000", # class: 'ge'
|
||||||
|
Generic.Error: "#ef2929", # class: 'gr'
|
||||||
|
Generic.Heading: "bold #000080", # class: 'gh'
|
||||||
|
Generic.Inserted: "#00A000", # class: 'gi'
|
||||||
|
Generic.Output: "#888", # class: 'go'
|
||||||
|
Generic.Prompt: "#745334", # class: 'gp'
|
||||||
|
Generic.Strong: "bold #000000", # class: 'gs'
|
||||||
|
Generic.Subheading: "bold #800080", # class: 'gu'
|
||||||
|
Generic.Traceback: "bold #a40000", # class: 'gt'
|
||||||
|
}
|
||||||
Vendored
+16
@@ -0,0 +1,16 @@
|
|||||||
|
{%- extends "basic/layout.html" %}
|
||||||
|
{%- block extrahead %}
|
||||||
|
{{ super() }}
|
||||||
|
{% if theme_touch_icon %}
|
||||||
|
<link rel="apple-touch-icon" href="{{ pathto('_static/' ~ theme_touch_icon, 1) }}" />
|
||||||
|
{% endif %}
|
||||||
|
<link media="only screen and (max-device-width: 480px)" href="{{
|
||||||
|
pathto('_static/small_flask.css', 1) }}" type= "text/css" rel="stylesheet" />
|
||||||
|
{% endblock %}
|
||||||
|
{%- block relbar2 %}{% endblock %}
|
||||||
|
{%- block footer %}
|
||||||
|
<div class="footer">
|
||||||
|
© Copyright {{ copyright }}.
|
||||||
|
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a>.
|
||||||
|
</div>
|
||||||
|
{%- endblock %}
|
||||||
Vendored
+19
@@ -0,0 +1,19 @@
|
|||||||
|
<h3>Related Topics</h3>
|
||||||
|
<ul>
|
||||||
|
<li><a href="{{ pathto(master_doc) }}">Documentation overview</a><ul>
|
||||||
|
{%- for parent in parents %}
|
||||||
|
<li><a href="{{ parent.link|e }}">{{ parent.title }}</a><ul>
|
||||||
|
{%- endfor %}
|
||||||
|
{%- if prev %}
|
||||||
|
<li>Previous: <a href="{{ prev.link|e }}" title="{{ _('previous chapter')
|
||||||
|
}}">{{ prev.title }}</a></li>
|
||||||
|
{%- endif %}
|
||||||
|
{%- if next %}
|
||||||
|
<li>Next: <a href="{{ next.link|e }}" title="{{ _('next chapter')
|
||||||
|
}}">{{ next.title }}</a></li>
|
||||||
|
{%- endif %}
|
||||||
|
{%- for parent in parents %}
|
||||||
|
</ul></li>
|
||||||
|
{%- endfor %}
|
||||||
|
</ul></li>
|
||||||
|
</ul>
|
||||||
Vendored
+387
@@ -0,0 +1,387 @@
|
|||||||
|
/*
|
||||||
|
* flasky.css_t
|
||||||
|
* ~~~~~~~~~~~~
|
||||||
|
*
|
||||||
|
* :copyright: Copyright 2010 by Armin Ronacher. Modifications by Kenneth Reitz.
|
||||||
|
* :license: Flask Design License, see LICENSE for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
{% set page_width = '940px' %}
|
||||||
|
{% set sidebar_width = '220px' %}
|
||||||
|
|
||||||
|
@import url("basic.css");
|
||||||
|
|
||||||
|
/* -- page layout ----------------------------------------------------------- */
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'goudy old style', 'minion pro', 'bell mt', Georgia, 'Hiragino Mincho Pro';
|
||||||
|
font-size: 17px;
|
||||||
|
background-color: white;
|
||||||
|
color: #000;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.document {
|
||||||
|
width: {{ page_width }};
|
||||||
|
margin: 30px auto 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.documentwrapper {
|
||||||
|
float: left;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.bodywrapper {
|
||||||
|
margin: 0 0 0 {{ sidebar_width }};
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar {
|
||||||
|
width: {{ sidebar_width }};
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border: 1px solid #B1B4B6;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.body {
|
||||||
|
background-color: #ffffff;
|
||||||
|
color: #3E4349;
|
||||||
|
padding: 0 30px 0 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
img.floatingflask {
|
||||||
|
padding: 0 0 10px 10px;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.footer {
|
||||||
|
width: {{ page_width }};
|
||||||
|
margin: 20px auto 30px auto;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #888;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.footer a {
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.related {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar a {
|
||||||
|
color: #444;
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: 1px dotted #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar a:hover {
|
||||||
|
border-bottom: 1px solid #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebarwrapper {
|
||||||
|
padding: 18px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebarwrapper p.logo {
|
||||||
|
padding: 0 0 20px 0;
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar h3,
|
||||||
|
div.sphinxsidebar h4 {
|
||||||
|
font-family: 'Garamond', 'Georgia', serif;
|
||||||
|
color: #444;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: normal;
|
||||||
|
margin: 0 0 5px 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar h4 {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar h3 a {
|
||||||
|
color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar p.logo a,
|
||||||
|
div.sphinxsidebar h3 a,
|
||||||
|
div.sphinxsidebar p.logo a:hover,
|
||||||
|
div.sphinxsidebar h3 a:hover {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar p {
|
||||||
|
color: #555;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar ul {
|
||||||
|
margin: 10px 0;
|
||||||
|
padding: 0;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar input {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
font-family: 'Georgia', serif;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -- body styles ----------------------------------------------------------- */
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #004B6B;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #6D4100;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.body h1,
|
||||||
|
div.body h2,
|
||||||
|
div.body h3,
|
||||||
|
div.body h4,
|
||||||
|
div.body h5,
|
||||||
|
div.body h6 {
|
||||||
|
font-family: 'Garamond', 'Georgia', serif;
|
||||||
|
font-weight: normal;
|
||||||
|
margin: 30px 0px 10px 0px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; }
|
||||||
|
div.body h2 { font-size: 180%; }
|
||||||
|
div.body h3 { font-size: 150%; }
|
||||||
|
div.body h4 { font-size: 130%; }
|
||||||
|
div.body h5 { font-size: 100%; }
|
||||||
|
div.body h6 { font-size: 100%; }
|
||||||
|
|
||||||
|
a.headerlink {
|
||||||
|
color: #ddd;
|
||||||
|
padding: 0 4px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.headerlink:hover {
|
||||||
|
color: #444;
|
||||||
|
background: #eaeaea;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.body p, div.body dd, div.body li {
|
||||||
|
line-height: 1.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.admonition {
|
||||||
|
background: #fafafa;
|
||||||
|
margin: 20px -30px;
|
||||||
|
padding: 10px 30px;
|
||||||
|
border-top: 1px solid #ccc;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.admonition tt.xref, div.admonition a tt {
|
||||||
|
border-bottom: 1px solid #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
dd div.admonition {
|
||||||
|
margin-left: -60px;
|
||||||
|
padding-left: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.admonition p.admonition-title {
|
||||||
|
font-family: 'Garamond', 'Georgia', serif;
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 24px;
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
padding: 0;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.admonition p.last {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.highlight {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
dt:target, .highlight {
|
||||||
|
background: #FAF3E8;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.note {
|
||||||
|
background-color: #eee;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.seealso {
|
||||||
|
background-color: #ffc;
|
||||||
|
border: 1px solid #ff6;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.topic {
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.admonition-title {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.admonition-title:after {
|
||||||
|
content: ":";
|
||||||
|
}
|
||||||
|
|
||||||
|
pre, tt {
|
||||||
|
font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
img.screenshot {
|
||||||
|
}
|
||||||
|
|
||||||
|
tt.descname, tt.descclassname {
|
||||||
|
font-size: 0.95em;
|
||||||
|
}
|
||||||
|
|
||||||
|
tt.descname {
|
||||||
|
padding-right: 0.08em;
|
||||||
|
}
|
||||||
|
|
||||||
|
img.screenshot {
|
||||||
|
-moz-box-shadow: 2px 2px 4px #eee;
|
||||||
|
-webkit-box-shadow: 2px 2px 4px #eee;
|
||||||
|
box-shadow: 2px 2px 4px #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.docutils {
|
||||||
|
border: 1px solid #888;
|
||||||
|
-moz-box-shadow: 2px 2px 4px #eee;
|
||||||
|
-webkit-box-shadow: 2px 2px 4px #eee;
|
||||||
|
box-shadow: 2px 2px 4px #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.docutils td, table.docutils th {
|
||||||
|
border: 1px solid #888;
|
||||||
|
padding: 0.25em 0.7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.field-list, table.footnote {
|
||||||
|
border: none;
|
||||||
|
-moz-box-shadow: none;
|
||||||
|
-webkit-box-shadow: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.footnote {
|
||||||
|
margin: 15px 0;
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
background: #fdfdfd;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.footnote + table.footnote {
|
||||||
|
margin-top: -15px;
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.field-list th {
|
||||||
|
padding: 0 0.8em 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.field-list td {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.footnote td.label {
|
||||||
|
width: 0px;
|
||||||
|
padding: 0.3em 0 0.3em 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.footnote td {
|
||||||
|
padding: 0.3em 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
dl {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
dl dd {
|
||||||
|
margin-left: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
margin: 0 0 0 30px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul, ol {
|
||||||
|
margin: 10px 0 10px 30px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
background: #eee;
|
||||||
|
padding: 7px 30px;
|
||||||
|
margin: 15px -30px;
|
||||||
|
line-height: 1.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
dl pre, blockquote pre, li pre {
|
||||||
|
margin-left: -60px;
|
||||||
|
padding-left: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
dl dl pre {
|
||||||
|
margin-left: -90px;
|
||||||
|
padding-left: 90px;
|
||||||
|
}
|
||||||
|
|
||||||
|
tt {
|
||||||
|
background-color: #ecf0f3;
|
||||||
|
color: #222;
|
||||||
|
/* padding: 1px 2px; */
|
||||||
|
}
|
||||||
|
|
||||||
|
tt.xref, a tt {
|
||||||
|
background-color: #FBFBFB;
|
||||||
|
border-bottom: 1px solid white;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.reference {
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: 1px dotted #004B6B;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.reference:hover {
|
||||||
|
border-bottom: 1px solid #6D4100;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.footnote-reference {
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.7em;
|
||||||
|
vertical-align: top;
|
||||||
|
border-bottom: 1px dotted #004B6B;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.footnote-reference:hover {
|
||||||
|
border-bottom: 1px solid #6D4100;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover tt {
|
||||||
|
background: #EEE;
|
||||||
|
}
|
||||||
+70
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* small_flask.css_t
|
||||||
|
* ~~~~~~~~~~~~~~~~~
|
||||||
|
*
|
||||||
|
* :copyright: Copyright 2010 by Armin Ronacher.
|
||||||
|
* :license: Flask Design License, see LICENSE for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.documentwrapper {
|
||||||
|
float: none;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar {
|
||||||
|
display: block;
|
||||||
|
float: none;
|
||||||
|
width: 102.5%;
|
||||||
|
margin: 50px -30px -20px -30px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
background: #333;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p,
|
||||||
|
div.sphinxsidebar h3 a {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar a {
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar p.logo {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.document {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.related {
|
||||||
|
display: block;
|
||||||
|
margin: 0;
|
||||||
|
padding: 10px 0 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.related ul,
|
||||||
|
div.related ul li {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.footer {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.bodywrapper {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.body {
|
||||||
|
min-height: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
Vendored
+7
@@ -0,0 +1,7 @@
|
|||||||
|
[theme]
|
||||||
|
inherit = basic
|
||||||
|
stylesheet = flasky.css
|
||||||
|
pygments_style = flask_theme_support.FlaskyStyle
|
||||||
|
|
||||||
|
[options]
|
||||||
|
touch_icon =
|
||||||
Vendored
+22
@@ -0,0 +1,22 @@
|
|||||||
|
{% extends "basic/layout.html" %}
|
||||||
|
{% block header %}
|
||||||
|
{{ super() }}
|
||||||
|
{% if pagename == 'index' %}
|
||||||
|
<div class=indexwrapper>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
{% block footer %}
|
||||||
|
{% if pagename == 'index' %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
{# do not display relbars #}
|
||||||
|
{% block relbar1 %}{% endblock %}
|
||||||
|
{% block relbar2 %}
|
||||||
|
{% if theme_github_fork %}
|
||||||
|
<a href="http://github.com/{{ theme_github_fork }}"><img style="position: fixed; top: 0; right: 0; border: 0;"
|
||||||
|
src="http://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub" /></a>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
{% block sidebar1 %}{% endblock %}
|
||||||
|
{% block sidebar2 %}{% endblock %}
|
||||||
+287
@@ -0,0 +1,287 @@
|
|||||||
|
/*
|
||||||
|
* flasky.css_t
|
||||||
|
* ~~~~~~~~~~~~
|
||||||
|
*
|
||||||
|
* Sphinx stylesheet -- flasky theme based on nature theme.
|
||||||
|
*
|
||||||
|
* :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
|
||||||
|
* :license: BSD, see LICENSE for details.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
@import url("basic.css");
|
||||||
|
|
||||||
|
/* -- page layout ----------------------------------------------------------- */
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Georgia', serif;
|
||||||
|
font-size: 17px;
|
||||||
|
color: #000;
|
||||||
|
background: white;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.documentwrapper {
|
||||||
|
float: left;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.bodywrapper {
|
||||||
|
margin: 40px auto 0 auto;
|
||||||
|
width: 700px;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border: 1px solid #B1B4B6;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.body {
|
||||||
|
background-color: #ffffff;
|
||||||
|
color: #3E4349;
|
||||||
|
padding: 0 30px 30px 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
img.floatingflask {
|
||||||
|
padding: 0 0 10px 10px;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.footer {
|
||||||
|
text-align: right;
|
||||||
|
color: #888;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
width: 650px;
|
||||||
|
margin: 0 auto 40px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.footer a {
|
||||||
|
color: #888;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.related {
|
||||||
|
line-height: 32px;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.related ul {
|
||||||
|
padding: 0 0 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.related a {
|
||||||
|
color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -- body styles ----------------------------------------------------------- */
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #004B6B;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #6D4100;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.body {
|
||||||
|
padding-bottom: 40px; /* saved for footer */
|
||||||
|
}
|
||||||
|
|
||||||
|
div.body h1,
|
||||||
|
div.body h2,
|
||||||
|
div.body h3,
|
||||||
|
div.body h4,
|
||||||
|
div.body h5,
|
||||||
|
div.body h6 {
|
||||||
|
font-family: 'Garamond', 'Georgia', serif;
|
||||||
|
font-weight: normal;
|
||||||
|
margin: 30px 0px 10px 0px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
{% if theme_index_logo %}
|
||||||
|
div.indexwrapper h1 {
|
||||||
|
text-indent: -999999px;
|
||||||
|
background: url({{ theme_index_logo }}) no-repeat center center;
|
||||||
|
height: {{ theme_index_logo_height }};
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
div.body h2 { font-size: 180%; }
|
||||||
|
div.body h3 { font-size: 150%; }
|
||||||
|
div.body h4 { font-size: 130%; }
|
||||||
|
div.body h5 { font-size: 100%; }
|
||||||
|
div.body h6 { font-size: 100%; }
|
||||||
|
|
||||||
|
a.headerlink {
|
||||||
|
color: white;
|
||||||
|
padding: 0 4px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.headerlink:hover {
|
||||||
|
color: #444;
|
||||||
|
background: #eaeaea;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.body p, div.body dd, div.body li {
|
||||||
|
line-height: 1.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.admonition {
|
||||||
|
background: #fafafa;
|
||||||
|
margin: 20px -30px;
|
||||||
|
padding: 10px 30px;
|
||||||
|
border-top: 1px solid #ccc;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.admonition p.admonition-title {
|
||||||
|
font-family: 'Garamond', 'Georgia', serif;
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 24px;
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
padding: 0;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.admonition p.last {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.highlight{
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
dt:target, .highlight {
|
||||||
|
background: #FAF3E8;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.note {
|
||||||
|
background-color: #eee;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.seealso {
|
||||||
|
background-color: #ffc;
|
||||||
|
border: 1px solid #ff6;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.topic {
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.warning {
|
||||||
|
background-color: #ffe4e4;
|
||||||
|
border: 1px solid #f66;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.admonition-title {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.admonition-title:after {
|
||||||
|
content: ":";
|
||||||
|
}
|
||||||
|
|
||||||
|
pre, tt {
|
||||||
|
font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
|
||||||
|
font-size: 0.85em;
|
||||||
|
}
|
||||||
|
|
||||||
|
img.screenshot {
|
||||||
|
}
|
||||||
|
|
||||||
|
tt.descname, tt.descclassname {
|
||||||
|
font-size: 0.95em;
|
||||||
|
}
|
||||||
|
|
||||||
|
tt.descname {
|
||||||
|
padding-right: 0.08em;
|
||||||
|
}
|
||||||
|
|
||||||
|
img.screenshot {
|
||||||
|
-moz-box-shadow: 2px 2px 4px #eee;
|
||||||
|
-webkit-box-shadow: 2px 2px 4px #eee;
|
||||||
|
box-shadow: 2px 2px 4px #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.docutils {
|
||||||
|
border: 1px solid #888;
|
||||||
|
-moz-box-shadow: 2px 2px 4px #eee;
|
||||||
|
-webkit-box-shadow: 2px 2px 4px #eee;
|
||||||
|
box-shadow: 2px 2px 4px #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.docutils td, table.docutils th {
|
||||||
|
border: 1px solid #888;
|
||||||
|
padding: 0.25em 0.7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.field-list, table.footnote {
|
||||||
|
border: none;
|
||||||
|
-moz-box-shadow: none;
|
||||||
|
-webkit-box-shadow: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.footnote {
|
||||||
|
margin: 15px 0;
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.field-list th {
|
||||||
|
padding: 0 0.8em 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.field-list td {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.footnote td {
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
dl {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
dl dd {
|
||||||
|
margin-left: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
padding: 0;
|
||||||
|
margin: 15px -30px;
|
||||||
|
padding: 8px;
|
||||||
|
line-height: 1.3em;
|
||||||
|
padding: 7px 30px;
|
||||||
|
background: #eee;
|
||||||
|
border-radius: 2px;
|
||||||
|
-moz-border-radius: 2px;
|
||||||
|
-webkit-border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
dl pre {
|
||||||
|
margin-left: -60px;
|
||||||
|
padding-left: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
tt {
|
||||||
|
background-color: #ecf0f3;
|
||||||
|
color: #222;
|
||||||
|
/* padding: 1px 2px; */
|
||||||
|
}
|
||||||
|
|
||||||
|
tt.xref, a tt {
|
||||||
|
background-color: #FBFBFB;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover tt {
|
||||||
|
background: #EEE;
|
||||||
|
}
|
||||||
Vendored
+10
@@ -0,0 +1,10 @@
|
|||||||
|
[theme]
|
||||||
|
inherit = basic
|
||||||
|
stylesheet = flasky.css
|
||||||
|
nosidebar = true
|
||||||
|
pygments_style = flask_theme_support.FlaskyStyle
|
||||||
|
|
||||||
|
[options]
|
||||||
|
index_logo = ''
|
||||||
|
index_logo_height = 120px
|
||||||
|
github_fork = ''
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
.. _api:
|
||||||
|
|
||||||
|
===
|
||||||
|
API
|
||||||
|
===
|
||||||
|
|
||||||
|
|
||||||
|
.. module:: tablib
|
||||||
|
|
||||||
|
This part of the documentation covers all the interfaces of Tablib. For
|
||||||
|
parts where Tablib depends on external libraries, we document the most
|
||||||
|
important right here and provide links to the canonical documentation.
|
||||||
|
|
||||||
|
|
||||||
|
--------------
|
||||||
|
Dataset Object
|
||||||
|
--------------
|
||||||
|
|
||||||
|
|
||||||
|
.. autoclass:: Dataset
|
||||||
|
:inherited-members:
|
||||||
|
|
||||||
|
|
||||||
|
---------------
|
||||||
|
Databook Object
|
||||||
|
---------------
|
||||||
|
|
||||||
|
|
||||||
|
.. autoclass:: Databook
|
||||||
|
:inherited-members:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
---------
|
||||||
|
Functions
|
||||||
|
---------
|
||||||
|
|
||||||
|
|
||||||
|
.. autofunction:: detect
|
||||||
|
|
||||||
|
.. autofunction:: import_set
|
||||||
|
|
||||||
|
|
||||||
|
----------
|
||||||
|
Exceptions
|
||||||
|
----------
|
||||||
|
|
||||||
|
|
||||||
|
.. class:: InvalidDatasetType
|
||||||
|
|
||||||
|
You're trying to add something that doesn't quite look right.
|
||||||
|
|
||||||
|
|
||||||
|
.. class:: InvalidDimensions
|
||||||
|
|
||||||
|
You're trying to add something that doesn't quite fit right.
|
||||||
|
|
||||||
|
|
||||||
|
.. class:: UnsupportedFormat
|
||||||
|
|
||||||
|
You're trying to add something that doesn't quite taste right.
|
||||||
|
|
||||||
|
|
||||||
|
Now, go start some :ref:`Tablib Development <development>`.
|
||||||
+221
@@ -0,0 +1,221 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Tablib documentation build configuration file, created by
|
||||||
|
# sphinx-quickstart on Tue Oct 5 15:25:21 2010.
|
||||||
|
#
|
||||||
|
# This file is execfile()d with the current directory set to its containing dir.
|
||||||
|
#
|
||||||
|
# Note that not all possible configuration values are present in this
|
||||||
|
# autogenerated file.
|
||||||
|
#
|
||||||
|
# All configuration values have a default; values that are commented out
|
||||||
|
# serve to show the default.
|
||||||
|
|
||||||
|
import sys, os
|
||||||
|
import tablib
|
||||||
|
|
||||||
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
|
sys.path.insert(0, os.path.abspath('..'))
|
||||||
|
|
||||||
|
# -- General configuration -----------------------------------------------------
|
||||||
|
|
||||||
|
# If your documentation needs a minimal Sphinx version, state it here.
|
||||||
|
#needs_sphinx = '1.0'
|
||||||
|
|
||||||
|
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||||
|
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||||
|
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode']
|
||||||
|
|
||||||
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
|
templates_path = ['_templates']
|
||||||
|
|
||||||
|
# The suffix of source filenames.
|
||||||
|
source_suffix = '.rst'
|
||||||
|
|
||||||
|
# The encoding of source files.
|
||||||
|
#source_encoding = 'utf-8-sig'
|
||||||
|
|
||||||
|
# The master toctree document.
|
||||||
|
master_doc = 'index'
|
||||||
|
|
||||||
|
# General information about the project.
|
||||||
|
project = u'Tablib'
|
||||||
|
copyright = u'2010, Kenneth Reitz. Styles (modified) © Armin Ronacher'
|
||||||
|
|
||||||
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
|
# |version| and |release|, also used in various other places throughout the
|
||||||
|
# built documents.
|
||||||
|
#
|
||||||
|
# The short X.Y version.
|
||||||
|
version = tablib.core.__version__
|
||||||
|
# The full version, including alpha/beta/rc tags.
|
||||||
|
release = version
|
||||||
|
|
||||||
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
|
# for a list of supported languages.
|
||||||
|
#language = None
|
||||||
|
|
||||||
|
# There are two options for replacing |today|: either, you set today to some
|
||||||
|
# non-false value, then it is used:
|
||||||
|
#today = ''
|
||||||
|
# Else, today_fmt is used as the format for a strftime call.
|
||||||
|
#today_fmt = '%B %d, %Y'
|
||||||
|
|
||||||
|
# List of patterns, relative to source directory, that match files and
|
||||||
|
# directories to ignore when looking for source files.
|
||||||
|
exclude_patterns = ['_build']
|
||||||
|
|
||||||
|
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||||
|
#default_role = None
|
||||||
|
|
||||||
|
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||||
|
add_function_parentheses = True
|
||||||
|
|
||||||
|
# If true, the current module name will be prepended to all description
|
||||||
|
# unit titles (such as .. function::).
|
||||||
|
# add_module_names = True
|
||||||
|
|
||||||
|
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||||
|
# output. They are ignored by default.
|
||||||
|
#show_authors = False
|
||||||
|
|
||||||
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
|
pygments_style = 'flask_theme_support.FlaskyStyle'
|
||||||
|
|
||||||
|
# A list of ignored prefixes for module index sorting.
|
||||||
|
#modindex_common_prefix = []
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for HTML output ---------------------------------------------------
|
||||||
|
|
||||||
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
|
# a list of builtin themes.
|
||||||
|
html_theme = 'default'
|
||||||
|
|
||||||
|
# Theme options are theme-specific and customize the look and feel of a theme
|
||||||
|
# further. For a list of options available for each theme, see the
|
||||||
|
# documentation.
|
||||||
|
#html_theme_options = {}
|
||||||
|
|
||||||
|
# Add any paths that contain custom themes here, relative to this directory.
|
||||||
|
#html_theme_path = []
|
||||||
|
|
||||||
|
# The name for this set of Sphinx documents. If None, it defaults to
|
||||||
|
# "<project> v<release> documentation".
|
||||||
|
#html_title = None
|
||||||
|
|
||||||
|
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||||
|
#html_short_title = None
|
||||||
|
|
||||||
|
# The name of an image file (relative to this directory) to place at the top
|
||||||
|
# of the sidebar.
|
||||||
|
#html_logo = None
|
||||||
|
|
||||||
|
# The name of an image file (within the static path) to use as favicon of the
|
||||||
|
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||||
|
# pixels large.
|
||||||
|
#html_favicon = None
|
||||||
|
|
||||||
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
|
html_static_path = ['static']
|
||||||
|
|
||||||
|
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||||
|
# using the given strftime format.
|
||||||
|
#html_last_updated_fmt = '%b %d, %Y'
|
||||||
|
|
||||||
|
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||||
|
# typographically correct entities.
|
||||||
|
html_use_smartypants = True
|
||||||
|
|
||||||
|
# Custom sidebar templates, maps document names to template names.
|
||||||
|
#html_sidebars = {}
|
||||||
|
|
||||||
|
# Additional templates that should be rendered to pages, maps page names to
|
||||||
|
# template names.
|
||||||
|
#html_additional_pages = {}
|
||||||
|
|
||||||
|
# If false, no module index is generated.
|
||||||
|
#html_domain_indices = True
|
||||||
|
|
||||||
|
# If false, no index is generated.
|
||||||
|
#html_use_index = True
|
||||||
|
|
||||||
|
# If true, the index is split into individual pages for each letter.
|
||||||
|
#html_split_index = False
|
||||||
|
|
||||||
|
# If true, links to the reST sources are added to the pages.
|
||||||
|
html_show_sourcelink = True
|
||||||
|
|
||||||
|
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||||
|
html_show_sphinx = False
|
||||||
|
|
||||||
|
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||||
|
#html_show_copyright = True
|
||||||
|
|
||||||
|
# If true, an OpenSearch description file will be output, and all pages will
|
||||||
|
# contain a <link> tag referring to it. The value of this option must be the
|
||||||
|
# base URL from which the finished HTML is served.
|
||||||
|
#html_use_opensearch = ''
|
||||||
|
|
||||||
|
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||||
|
#html_file_suffix = None
|
||||||
|
|
||||||
|
# Output file base name for HTML help builder.
|
||||||
|
htmlhelp_basename = 'Tablibdoc'
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for LaTeX output --------------------------------------------------
|
||||||
|
|
||||||
|
# The paper size ('letter' or 'a4').
|
||||||
|
#latex_paper_size = 'letter'
|
||||||
|
|
||||||
|
# The font size ('10pt', '11pt' or '12pt').
|
||||||
|
#latex_font_size = '10pt'
|
||||||
|
|
||||||
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
|
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||||
|
latex_documents = [
|
||||||
|
('index', 'Tablib.tex', u'Tablib Documentation',
|
||||||
|
u'Kenneth Reitz', 'manual'),
|
||||||
|
]
|
||||||
|
|
||||||
|
# The name of an image file (relative to this directory) to place at the top of
|
||||||
|
# the title page.
|
||||||
|
#latex_logo = None
|
||||||
|
|
||||||
|
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||||
|
# not chapters.
|
||||||
|
#latex_use_parts = False
|
||||||
|
|
||||||
|
# If true, show page references after internal links.
|
||||||
|
#latex_show_pagerefs = False
|
||||||
|
|
||||||
|
# If true, show URL addresses after external links.
|
||||||
|
#latex_show_urls = False
|
||||||
|
|
||||||
|
# Additional stuff for the LaTeX preamble.
|
||||||
|
#latex_preamble = ''
|
||||||
|
|
||||||
|
# Documents to append as an appendix to all manuals.
|
||||||
|
#latex_appendices = []
|
||||||
|
|
||||||
|
# If false, no module index is generated.
|
||||||
|
#latex_domain_indices = True
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for manual page output --------------------------------------------
|
||||||
|
|
||||||
|
# One entry per manual page. List of tuples
|
||||||
|
# (source start file, name, description, authors, manual section).
|
||||||
|
man_pages = [
|
||||||
|
('index', 'tablib', u'Tablib Documentation',
|
||||||
|
[u'Kenneth Reitz'], 1)
|
||||||
|
]
|
||||||
|
|
||||||
|
sys.path.append(os.path.abspath('_themes'))
|
||||||
|
html_theme_path = ['_themes']
|
||||||
|
html_theme = 'kr'
|
||||||
@@ -0,0 +1,246 @@
|
|||||||
|
.. _development:
|
||||||
|
|
||||||
|
Development
|
||||||
|
===========
|
||||||
|
|
||||||
|
Tablib is under active development, and contributors are welcome.
|
||||||
|
|
||||||
|
If you have a feature request, suggestion, or bug report, please open a new issue on GitHub_. To submit patches, please send a pull request on GitHub_.
|
||||||
|
|
||||||
|
If you'd like to contribute, there's plenty to do. Here's a short todo list.
|
||||||
|
|
||||||
|
.. include:: ../TODO.rst
|
||||||
|
|
||||||
|
|
||||||
|
.. _GitHub: http://github.com/kennethreitz/tablib/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.. _design:
|
||||||
|
|
||||||
|
---------------------
|
||||||
|
Design Considerations
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Tablib was developed with a few :pep:`20` idioms in mind.
|
||||||
|
|
||||||
|
#. Beautiful is better than ugly.
|
||||||
|
#. Explicit is better than implicit.
|
||||||
|
#. Simple is better than complex.
|
||||||
|
#. Complex is better than complicated.
|
||||||
|
#. Readability counts.
|
||||||
|
|
||||||
|
A few other things to keep in mind:
|
||||||
|
|
||||||
|
#. Keep your code DRY.
|
||||||
|
#. Strive to be as simple (to use) as possible.
|
||||||
|
|
||||||
|
.. _scm:
|
||||||
|
|
||||||
|
--------------
|
||||||
|
Source Control
|
||||||
|
--------------
|
||||||
|
|
||||||
|
|
||||||
|
Tablib source is controlled with Git_, the lean, mean, distributed source control machine.
|
||||||
|
|
||||||
|
The repository is publicly accessable.
|
||||||
|
|
||||||
|
``git clone git://github.com/kennethreitz/tablib.git``
|
||||||
|
|
||||||
|
The project is hosted both on **GitHub** and **git.kennethreitz.com**.
|
||||||
|
|
||||||
|
|
||||||
|
GitHub:
|
||||||
|
http://github.com/kennethreitz/tablib
|
||||||
|
"Mirror":
|
||||||
|
http://git.kennethreitz.com/projects/tablib
|
||||||
|
|
||||||
|
|
||||||
|
Git Branch Structure
|
||||||
|
++++++++++++++++++++
|
||||||
|
|
||||||
|
Feature / Hotfix / Release branches follow a `Successful Git Branching Model`_ . Git-flow_ is a great tool for managing the repository. I highly recommend it.
|
||||||
|
|
||||||
|
``develop``
|
||||||
|
The "next release" branch. Likely unstable.
|
||||||
|
``master``
|
||||||
|
Current production release (|version|) on PyPi.
|
||||||
|
``gh-pages``
|
||||||
|
Current release of http://tablib.org.
|
||||||
|
|
||||||
|
Each release is tagged.
|
||||||
|
|
||||||
|
When submitting patches, please place your feature/change in its own branch prior to opening a pull reqeust on GitHub_.
|
||||||
|
|
||||||
|
|
||||||
|
.. _Git: http://git-scm.org
|
||||||
|
.. _`Successful Git Branching Model`: http://nvie.com/posts/a-successful-git-branching-model/
|
||||||
|
.. _git-flow: http://github.com/nvie/gitflow
|
||||||
|
|
||||||
|
|
||||||
|
.. _newformats:
|
||||||
|
|
||||||
|
------------------
|
||||||
|
Adding New Formats
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Tablib welcomes new format additions! Format suggestions include:
|
||||||
|
|
||||||
|
* Tab Seperated Values
|
||||||
|
* MySQL Dump
|
||||||
|
* HTML Table
|
||||||
|
|
||||||
|
|
||||||
|
Coding by Convention
|
||||||
|
++++++++++++++++++++
|
||||||
|
|
||||||
|
Tablib features a micro-framework for adding format support. The easiest way to understand it is to use it. So, let's define our own format, named *xxx*.
|
||||||
|
|
||||||
|
1. Write a new format interface.
|
||||||
|
|
||||||
|
:class:`tablib.core` follows a simple pattern for automatically utilizing your format throughout Tablib. Function names are crucial.
|
||||||
|
|
||||||
|
Example **tablib/formats/_xxx.py**: ::
|
||||||
|
|
||||||
|
title = 'xxx'
|
||||||
|
|
||||||
|
def export_set(dset):
|
||||||
|
....
|
||||||
|
# returns string representation of given dataset
|
||||||
|
|
||||||
|
def export_book(dbook):
|
||||||
|
....
|
||||||
|
# returns string representation of given databook
|
||||||
|
|
||||||
|
def import_set(dset, in_stream):
|
||||||
|
...
|
||||||
|
# populates given Dataset with given datastream
|
||||||
|
|
||||||
|
def import_book(dbook, in_stream):
|
||||||
|
...
|
||||||
|
# returns Databook instance
|
||||||
|
|
||||||
|
def detect(stream):
|
||||||
|
...
|
||||||
|
# returns True if given stream is parsable as xxx
|
||||||
|
|
||||||
|
.. admonition:: Excluding Support
|
||||||
|
|
||||||
|
|
||||||
|
If the format excludes support for an import/export mechanism (*eg.* :class:`csv <tablib.Dataset.csv>` excludes :class:`Databook <tablib.Databook>` support), simply don't define the respecive functions. Appropriate errors will be raised.
|
||||||
|
|
||||||
|
2.
|
||||||
|
|
||||||
|
Add your new format module to the :class:`tablib.formats.avalable` tuple.
|
||||||
|
|
||||||
|
3.
|
||||||
|
Add a mock property to the :class:`Dataset <tablib.Dataset>` class with verbose `reStructured Text`_ docstring. This alleviates IDE confusion, and allows for pretty auto-generated Sphinx_ documentation.
|
||||||
|
|
||||||
|
4. Write respective :ref:`tests <testing>`.
|
||||||
|
|
||||||
|
.. _testing:
|
||||||
|
|
||||||
|
--------------
|
||||||
|
Testing Tablib
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Testing is crucial to Tablib's stability. This stable project is used in production by many companies and developers, so it is important to be certian that every version released is fully operational. When developing a new feature for Tablib, be sure to write proper tests for it as well.
|
||||||
|
|
||||||
|
When developing a feature for Tablib, the easiest way to test your changes for potential issues is to simply run the test suite directly. ::
|
||||||
|
|
||||||
|
$ ./test_tablib.py
|
||||||
|
|
||||||
|
|
||||||
|
`Hudson CI`_, amongst other tools, supports Java's xUnit testing report format. Nose_ allows us to generate our own xUnit reports.
|
||||||
|
|
||||||
|
Installing nose is simple. ::
|
||||||
|
|
||||||
|
$ pip install nose
|
||||||
|
|
||||||
|
Once installed, we can generate our xUnit report with a single command. ::
|
||||||
|
|
||||||
|
$ nosetests test_tablib.py --with-xunit
|
||||||
|
|
||||||
|
This will generate a **nosetests.xml** file, which can then be analyzed.
|
||||||
|
|
||||||
|
.. _Nose: http://somethingaboutorange.com/mrl/projects/nose/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.. _hudson:
|
||||||
|
|
||||||
|
----------------------
|
||||||
|
Continuous Integration
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
Every commit made to the **develop** branch is automatically tested and inspected upon receipt with `Hudson CI`_. If you have access to the main respository and broke the build, you will receive an email accordingly.
|
||||||
|
|
||||||
|
Anyone may view the build status and history at any time.
|
||||||
|
|
||||||
|
http://git.kennethreitz.com/ci/
|
||||||
|
|
||||||
|
|
||||||
|
If you are trustworthy and plan to contribute to tablib on a regular basis, please contact `Kenneth Reitz`_ to get an account on the Hudson Server.
|
||||||
|
|
||||||
|
|
||||||
|
Additional reports will also be included here in the future, including :pep:`8` checks and stress reports for extremely large datasets.
|
||||||
|
|
||||||
|
.. _`Hudson CI`: http://hudson.dev.java.net
|
||||||
|
.. _`Kenneth Reitz`: http://kennethreitz.com/contact-me/
|
||||||
|
|
||||||
|
|
||||||
|
.. _docs:
|
||||||
|
|
||||||
|
-----------------
|
||||||
|
Building the Docs
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Documentation is written in the powerful, flexible, and standard Python documentation format, `reStructured Text`_.
|
||||||
|
Documentation builds are powered by the powerful Pocoo project, Sphinx_. The :ref:`API Documentation <api>` is mostly documented inline throught the module.
|
||||||
|
|
||||||
|
The Docs live in ``tablib/docs``. In order to build them, you will first need to install Sphinx. ::
|
||||||
|
|
||||||
|
$ pip install sphinx
|
||||||
|
|
||||||
|
|
||||||
|
Then, to build an HTML version of the docs, simply run the following from the **docs** directory: ::
|
||||||
|
|
||||||
|
$ make html
|
||||||
|
|
||||||
|
Your ``docs/_build/html`` directory will then contain an HTML representation of the documentation, ready for publication on most web servers.
|
||||||
|
|
||||||
|
You can also generate the documentation in **ebpub**, **latex**, **json**, *&c* similarly.
|
||||||
|
|
||||||
|
.. admonition:: GitHub Pages
|
||||||
|
|
||||||
|
To push the documentation up to `GitHub Pages`_, you will first need to run `sphinx-to-github`_ against your ``docs/_build/html`` directory.
|
||||||
|
|
||||||
|
GitHub Pages are powered by an HTML generation system called Jeckyl_, which is configured to ignore files and folders that begin with "``_``" (*ie.* **_static**).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
and `sphinx-to-github`_. ::
|
||||||
|
|
||||||
|
Installing sphinx-to-github is simple. ::
|
||||||
|
|
||||||
|
$ pip install sphinx-to-github
|
||||||
|
|
||||||
|
Running it against the docs is even simpler. ::
|
||||||
|
|
||||||
|
$ sphinx-to-github _build/html
|
||||||
|
|
||||||
|
Move the resulting files to the **gh-pages** branch of your repository, and push it up to GitHub.
|
||||||
|
|
||||||
|
.. _`reStructured Text`: http://docutils.sourceforge.net/rst.html
|
||||||
|
.. _Sphinx: http://sphinx.pocoo.org
|
||||||
|
.. _`GitHub Pages`: http://pages.github.com
|
||||||
|
.. _Jeckyl: http://github.com/mojombo/jekyll
|
||||||
|
.. _`sphinx-to-github`: http://github.com/michaeljones/sphinx-to-github
|
||||||
|
|
||||||
|
----------
|
||||||
|
|
||||||
|
Make sure to check out the :ref:`API Documentation <api>`.
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
.. Tablib documentation master file, created by
|
||||||
|
sphinx-quickstart on Tue Oct 5 15:25:21 2010.
|
||||||
|
You can adapt this file completely to your liking, but it should at least
|
||||||
|
contain the root `toctree` directive.
|
||||||
|
|
||||||
|
Tablib: Pythonic Tabular Data
|
||||||
|
=============================
|
||||||
|
|
||||||
|
.. Contents:
|
||||||
|
..
|
||||||
|
.. .. toctree::
|
||||||
|
.. :maxdepth: 2
|
||||||
|
..
|
||||||
|
|
||||||
|
.. Indices and tables
|
||||||
|
.. ==================
|
||||||
|
..
|
||||||
|
.. * :ref:`genindex`
|
||||||
|
.. * :ref:`modindex`
|
||||||
|
.. * :ref:`search`
|
||||||
|
|
||||||
|
|
||||||
|
Tablib is an :ref:`MIT Lisenced <mit>` format-agnostic tabular dataset library, written in Python. It allows you to import, export, and manipulate tabular data sets. Advanced features include, segregation, dynamic columns, tags & filtering, and seamless format import & exmport.
|
||||||
|
|
||||||
|
I recommend you start with :ref:`Installation <install>`.
|
||||||
|
|
||||||
|
User's Guide
|
||||||
|
------------
|
||||||
|
|
||||||
|
This part of the documentation, which is mostly prose, begins with some background information about Tablib, then focuses on step-by-step instructions for getting the most out of your datasets.
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
intro
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
install
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
tutorial
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
development
|
||||||
|
|
||||||
|
|
||||||
|
API Reference
|
||||||
|
-------------
|
||||||
|
|
||||||
|
If you are looking for information on a specific function, class or
|
||||||
|
method, this part of the documentation is for you.
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
api
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
.. _install:
|
||||||
|
Installation
|
||||||
|
============
|
||||||
|
|
||||||
|
This part of the documentation covers the installation of Tablib. The first step to using any software package is getting it properly installed. Please read this section carefully, or you may miss out on some nice :ref:`speed enhancments <peed-extentions>`.
|
||||||
|
|
||||||
|
|
||||||
|
.. _installing:
|
||||||
|
|
||||||
|
-----------------
|
||||||
|
Installing Tablib
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
To install Tablib, it only takes one simple command. ::
|
||||||
|
|
||||||
|
$ pip install tablib
|
||||||
|
|
||||||
|
Or, if you must: ::
|
||||||
|
|
||||||
|
$ easy_install tablib
|
||||||
|
|
||||||
|
But, you really shouldn't do that.
|
||||||
|
|
||||||
|
|
||||||
|
-------------------
|
||||||
|
Download the Source
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
You can also install tablib from source. The latest release (|version|) is available from GitHub.
|
||||||
|
|
||||||
|
* tarball_
|
||||||
|
* zipball_
|
||||||
|
|
||||||
|
.. _
|
||||||
|
Once you have a copy of the source, you can embed it in your Python package, or install it into your site-packages easily. ::
|
||||||
|
|
||||||
|
$ python setup.py install
|
||||||
|
|
||||||
|
|
||||||
|
To download the full source history from Git, see :ref:`Source Control <scm>`.
|
||||||
|
|
||||||
|
.. _tarball: http://github.com/kennethreitz/tablib/tarball/master
|
||||||
|
.. _zipball: http://github.com/kennethreitz/tablib/zipball/master
|
||||||
|
|
||||||
|
|
||||||
|
.. _speed-extentions:
|
||||||
|
Speed Extentions
|
||||||
|
----------------
|
||||||
|
|
||||||
|
.. versionadded:: 0.8.5
|
||||||
|
|
||||||
|
Tablib is partially dependent on the **pyyaml**, **simplejson**, and **xlwt** modules. To reduce installation issues, fully integrated versions of all required libraries are included in Tablib.
|
||||||
|
|
||||||
|
However, if performance is important to you (and it should be), you can install **pyyaml** with C extentions from PyPi. ::
|
||||||
|
|
||||||
|
$ pip install PyYAML
|
||||||
|
|
||||||
|
If you're using Python 2.5 (currently unsupported), you should also install the **simplejson** module. If you're using Python 2.6+, the built-in **json** module is already optimized and in use. ::
|
||||||
|
|
||||||
|
$ pip install simplejson
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.. _updates:
|
||||||
|
Staying Updated
|
||||||
|
---------------
|
||||||
|
|
||||||
|
The latest version of Tablib will always be available here:
|
||||||
|
|
||||||
|
* PyPi: http://pypi.python.org/pypi/tablib/
|
||||||
|
* GitHub: http://github.com/kennethreitz/tablib/
|
||||||
|
|
||||||
|
When a new version is available, upgrading is simple. ::
|
||||||
|
|
||||||
|
$ pip install tablib --upgrade
|
||||||
|
|
||||||
|
|
||||||
|
Now, go get a :ref:`Quick Start <quickstart>`.
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
.. _intro:
|
||||||
|
|
||||||
|
Introduction
|
||||||
|
============
|
||||||
|
|
||||||
|
This part of the documentation covers all the interfaces of Tablib.
|
||||||
|
Tablib is a format-agnostic tabular dataset library, written in Python. It allows you to Pythonically import, export, and manipulate tabular data sets. Advanced features include, segregation, dynamic columns, tags / filtering, and seamless format import/exmport.
|
||||||
|
|
||||||
|
|
||||||
|
Philosphy
|
||||||
|
---------
|
||||||
|
|
||||||
|
Tablib was developed with a few :pep:`20` idioms in mind.
|
||||||
|
|
||||||
|
|
||||||
|
#. Beautiful is better than ugly.
|
||||||
|
#. Explicit is better than implicit.
|
||||||
|
#. Simple is better than complex.
|
||||||
|
#. Complex is better than complicated.
|
||||||
|
#. Readability counts.
|
||||||
|
|
||||||
|
All contributions to Tablib should keep these important rules in mind.
|
||||||
|
|
||||||
|
.. _mit:
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
-----------
|
||||||
|
|
||||||
|
A large number of open source projects you find today are `GPL Licensed`_. While the GPL certianly has essential applications, it should most certianly not be your go-to license for your next open source project.
|
||||||
|
|
||||||
|
A project that is released as GPL cannot be usd in any commercial product without the product itself also being offered as open source. The MIT and BSD licenses are great alternatives to the GPL that allow your open-source software to be used in proprietary, closed-source software.
|
||||||
|
|
||||||
|
Tablib is released under terms of `The MIT License`_.
|
||||||
|
|
||||||
|
.. _`GPL Licensed`: http://www.opensource.org/licenses/gpl-license.php
|
||||||
|
.. _`The MIT License`: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.. _pythonsupport:
|
||||||
|
|
||||||
|
Pythons Supported
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
At this time, the following Python platforms are officially supported:
|
||||||
|
|
||||||
|
* Python 2.6
|
||||||
|
* Python 2.7
|
||||||
|
|
||||||
|
Support for other Pythons will be rolled out soon.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Now, go :ref:`Install Tablib <install>`.
|
||||||
@@ -0,0 +1,353 @@
|
|||||||
|
.. _quickstart:
|
||||||
|
|
||||||
|
==========
|
||||||
|
Quickstart
|
||||||
|
==========
|
||||||
|
|
||||||
|
|
||||||
|
.. module:: tablib
|
||||||
|
|
||||||
|
|
||||||
|
Eager to get started? This page gives a good introduction in how to get started with Tablib. This assumes you already have Tablib installed. If you do not, head over to the :ref:`Installation <install>` section.
|
||||||
|
|
||||||
|
First, make sure that:
|
||||||
|
|
||||||
|
* Tablib is :ref:`installed <install>`
|
||||||
|
* Tablib is :ref:`up-to-date <updates>`
|
||||||
|
|
||||||
|
|
||||||
|
Lets gets started with some simple use cases and examples.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
------------------
|
||||||
|
Creating a Dataset
|
||||||
|
------------------
|
||||||
|
|
||||||
|
|
||||||
|
A :class:`Dataset <tablib.Dataset>` is nothing more than what its name implies—a set of data.
|
||||||
|
|
||||||
|
Creating your own instance of the :class:`tablib.Dataset` object is simple. ::
|
||||||
|
|
||||||
|
data = tablib.Dataset()
|
||||||
|
|
||||||
|
You can now start filling this :class:`Dataset <tablib.Dataset>` object with data.
|
||||||
|
|
||||||
|
.. admonition:: Example Context
|
||||||
|
|
||||||
|
From here on out, if you see ``data``, assume that it's a fresh :class:`Dataset <tablib.Dataset>` object.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-----------
|
||||||
|
Adding Rows
|
||||||
|
-----------
|
||||||
|
|
||||||
|
|
||||||
|
Let's say you want to collect a simple list of names. ::
|
||||||
|
|
||||||
|
# collection of names
|
||||||
|
names = ['Kenneth Reitz', 'Bessie Monke']
|
||||||
|
|
||||||
|
for name in names:
|
||||||
|
# split name appropriately
|
||||||
|
fname, lname = name.split()
|
||||||
|
|
||||||
|
# add names to Dataset
|
||||||
|
data.append([fname, lname])
|
||||||
|
|
||||||
|
You can get a nice, Pythonic view of the dataset at any time with :class:`Dataset.dict`.
|
||||||
|
|
||||||
|
>>> data.dict
|
||||||
|
[('Kenneth', 'Reitz'), ('Bessie', 'Monke')]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--------------
|
||||||
|
Adding Headers
|
||||||
|
--------------
|
||||||
|
|
||||||
|
|
||||||
|
It's time enhance our :class:`Dataset` by giving our columns some titles. To do so, set :class:`Dataset.headers`. ::
|
||||||
|
|
||||||
|
data.headers = ['First Name', 'Last Name']
|
||||||
|
|
||||||
|
Now our data looks a little different. ::
|
||||||
|
|
||||||
|
>>> data.dict
|
||||||
|
[{'Last Name': 'Reitz', 'First Name': 'Kenneth'}, {'Last Name': 'Monke', 'First Name': 'Bessie'}]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--------------
|
||||||
|
Adding Columns
|
||||||
|
--------------
|
||||||
|
|
||||||
|
|
||||||
|
Now that we have a basic :class:`Dataset` in place, let's add a column of **ages** to it. ::
|
||||||
|
|
||||||
|
data.append(col=[22, 20], header='Age')
|
||||||
|
|
||||||
|
Let's view the data now. ::
|
||||||
|
|
||||||
|
>>> data.dict
|
||||||
|
[{'Last Name': 'Reitz', 'First Name': 'Kenneth', 'Age': 22}, {'Last Name': 'Monke', 'First Name': 'Bessie', 'Age': 20}]
|
||||||
|
|
||||||
|
It's that easy.
|
||||||
|
|
||||||
|
|
||||||
|
--------------
|
||||||
|
Exporting Data
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Tablib's killer feature is the ability to export your :class:`Dataset` objects into a number of formats.
|
||||||
|
|
||||||
|
**Comma-Seperated Values** ::
|
||||||
|
|
||||||
|
>>> data.csv
|
||||||
|
Last Name,First Name,Age
|
||||||
|
Reitz,Kenneth,22
|
||||||
|
Monke,Bessie,20
|
||||||
|
|
||||||
|
**JavaScript Object Notation** ::
|
||||||
|
|
||||||
|
>>> data.json
|
||||||
|
[{"Last Name": "Reitz", "First Name": "Kenneth", "Age": 22}, {"Last Name": "Monke", "First Name": "Bessie", "Age": 20}]
|
||||||
|
|
||||||
|
|
||||||
|
**YAML Ain't Markup Language** ::
|
||||||
|
|
||||||
|
>>> data.yaml
|
||||||
|
- {Age: 22, First Name: Kenneth, Last Name: Reitz}
|
||||||
|
- {Age: 20, First Name: Bessie, Last Name: Monke}
|
||||||
|
|
||||||
|
|
||||||
|
**Microsoft Excel** ::
|
||||||
|
|
||||||
|
>>> data.xls
|
||||||
|
<censored binary data>
|
||||||
|
|
||||||
|
|
||||||
|
------------------------
|
||||||
|
Selecting Rows & Columns
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
|
||||||
|
You can slice and dice your data, just like a standard Python list. ::
|
||||||
|
|
||||||
|
>>> data[0]
|
||||||
|
('Kenneth', 'Reitz', 22)
|
||||||
|
|
||||||
|
|
||||||
|
If we had a set of data consisting of thousands of rows, it could be useful to get a list of values in a column.
|
||||||
|
To do so, we access the :class:`Dataset` as if it were a standard Python dictionary. ::
|
||||||
|
|
||||||
|
>>> data['First Name']
|
||||||
|
['Kenneth', 'Bessie']
|
||||||
|
|
||||||
|
Let's find the average age. ::
|
||||||
|
|
||||||
|
>>> ages = data['Age']
|
||||||
|
>>> float(sum(ages)) / len(ages)
|
||||||
|
21.0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------
|
||||||
|
Removing Rows & Columns
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
It's easier than you could imagine. ::
|
||||||
|
|
||||||
|
>>> del data['Col Name']
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
>>> del data[0:12]
|
||||||
|
|
||||||
|
|
||||||
|
==============
|
||||||
|
Advanced Usage
|
||||||
|
==============
|
||||||
|
|
||||||
|
|
||||||
|
This part of the documentation services to give you an idea that are otherwise hard to extract from the :ref:`API Documentation <api>`
|
||||||
|
|
||||||
|
And now for something completely different.
|
||||||
|
|
||||||
|
|
||||||
|
.. _dyncols:
|
||||||
|
|
||||||
|
---------------
|
||||||
|
Dynamic Columns
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. versionadded:: 0.8.3
|
||||||
|
|
||||||
|
Thanks to Josh Ourisman, Tablib now supports adding dynamic columns. A dynamic column is a single callable object (*ie.* a function).
|
||||||
|
|
||||||
|
Let's add a dynamic column to our :class:`Dataset` object. In this example, we have a function that generates a random grade for our students. ::
|
||||||
|
|
||||||
|
import random
|
||||||
|
|
||||||
|
def random_grade(row):
|
||||||
|
"""Returns a random integer for entry."""
|
||||||
|
return (random.randint(60,100)/100.0)
|
||||||
|
|
||||||
|
data.append(col=[random_grade], header='Grade')
|
||||||
|
|
||||||
|
Let's have a look at our data. ::
|
||||||
|
|
||||||
|
>>> data.yaml
|
||||||
|
- {Age: 22, First Name: Kenneth, Grade: 0.6, Last Name: Reitz}
|
||||||
|
- {Age: 20, First Name: Bessie, Grade: 0.75, Last Name: Monke}
|
||||||
|
|
||||||
|
|
||||||
|
Let's remove that column. ::
|
||||||
|
|
||||||
|
>>> del data['Grade']
|
||||||
|
|
||||||
|
|
||||||
|
When you add a dynamic column, the first argument that is passed in to the given callable is the current data row. You can use this to perform calculations against your data row.
|
||||||
|
|
||||||
|
For example, we can use the data available in the row to guess the gender of a student. ::
|
||||||
|
|
||||||
|
def guess_gender(row):
|
||||||
|
"""Calculates gender of given student data row."""
|
||||||
|
m_names = ('Kenneth', 'Mike', 'Yuri')
|
||||||
|
f_names = ('Bessie', 'Samantha', 'Heather')
|
||||||
|
|
||||||
|
name = row[0]
|
||||||
|
|
||||||
|
if name in m_names:
|
||||||
|
return 'Male'
|
||||||
|
elif name in f_names:
|
||||||
|
return 'Female'
|
||||||
|
else:
|
||||||
|
return 'Unknown'
|
||||||
|
|
||||||
|
Adding this function to our dataset as a dynamic column would result in: ::
|
||||||
|
|
||||||
|
>>> data.yaml
|
||||||
|
- {Age: 22, First Name: Kenneth, Gender: Male, Last Name: Reitz}
|
||||||
|
- {Age: 20, First Name: Bessie, Gender: Female, Last Name: Monke}
|
||||||
|
|
||||||
|
|
||||||
|
.. _tags:
|
||||||
|
|
||||||
|
----------------------------
|
||||||
|
Filtering Datasets with Tags
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
.. versionadded:: 0.9.0
|
||||||
|
|
||||||
|
|
||||||
|
When constructing a :class:`Dataset` object, you can add tags to rows by specifying the ``tags`` parameter.
|
||||||
|
This allows you to filter your :class:`Dataset` later. This can be useful so seperate rows of data based on
|
||||||
|
arbitrary criteria (*e.g.* origin) that you don't want to include in your :class:`Dataset`.
|
||||||
|
|
||||||
|
Let's tag some students. ::
|
||||||
|
|
||||||
|
students = tablib.Dataset()
|
||||||
|
|
||||||
|
students.headers = ['first', 'last']
|
||||||
|
|
||||||
|
students.append(['Kenneth', 'Reitz'], tags=['male', 'technical'])
|
||||||
|
students.append(['Bessie', 'Monke'], tags=['female', 'creative'])
|
||||||
|
|
||||||
|
Now that we have extra meta-data on our rows, we can use easily filter our :class:`Dataset`. Let's just see Male students. ::
|
||||||
|
|
||||||
|
|
||||||
|
>>> data.filter(['male']).yaml
|
||||||
|
- {first: Kenneth, Last: Reitz}
|
||||||
|
|
||||||
|
It's that simple. The original :class:`Dataset` is untouched.
|
||||||
|
|
||||||
|
|
||||||
|
Excel Workbook With Multiple Sheets
|
||||||
|
------------------------------------
|
||||||
|
|
||||||
|
When dealine with a large number of :class:`Datasets <Dataset>` in spreadsheet format, it's quite common to group mulitple spreadsheets into a single Excel file, known as a Workbook. Tablib makes it extremely easy to build webooks with the handy, :class:`Databook` class.
|
||||||
|
|
||||||
|
|
||||||
|
Let's say we have 3 different :class:`Datasets <Dataset>`. All we have to do is add then to a :class:`Databook` object... ::
|
||||||
|
|
||||||
|
book = tablib.Databook([data1, data2, data3])
|
||||||
|
|
||||||
|
... and export to Excel just like :class:`Datasets <Dataset>`. ::
|
||||||
|
|
||||||
|
with open('students.xls', 'wb') as f:
|
||||||
|
f.write(book.xls)
|
||||||
|
|
||||||
|
The resulting **students.xls** file will contain a seperate spreadsheet for each :class:`Dataset` object in the :class:`Databook`.
|
||||||
|
|
||||||
|
.. admonition:: Binary Warning
|
||||||
|
|
||||||
|
Make sure to open the output file in binary mode.
|
||||||
|
|
||||||
|
|
||||||
|
.. _seperators:
|
||||||
|
|
||||||
|
----------
|
||||||
|
Seperators
|
||||||
|
----------
|
||||||
|
|
||||||
|
.. versionadded:: 0.8.2
|
||||||
|
|
||||||
|
When, it's often useful to create a blank row containing information on the upcomming data. So,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
daniel_tests = [
|
||||||
|
('11/24/09', 'Math 101 Mid-term Exam', 56.),
|
||||||
|
('05/24/10', 'Math 101 Final Exam', 62.)
|
||||||
|
]
|
||||||
|
|
||||||
|
suzie_tests = [
|
||||||
|
('11/24/09', 'Math 101 Mid-term Exam', 56.),
|
||||||
|
('05/24/10', 'Math 101 Final Exam', 62.)
|
||||||
|
]
|
||||||
|
|
||||||
|
# Create new dataset
|
||||||
|
tests = tablib.Dataset()
|
||||||
|
tests.headers = ['Date', 'Test Name', 'Grade']
|
||||||
|
|
||||||
|
# Daniel's Tests
|
||||||
|
tests.append_seperator('Daniel\'s Scores')
|
||||||
|
|
||||||
|
for test_row in daniel_tests:
|
||||||
|
tests.append(test_row)
|
||||||
|
|
||||||
|
# Susie's Tests
|
||||||
|
tests.append_seperator('Susie\'s Scores')
|
||||||
|
|
||||||
|
for test_row in suzie_tests:
|
||||||
|
tests.append(test_row)
|
||||||
|
|
||||||
|
# Write spreadsheet to disk
|
||||||
|
with open('grades.xls', 'wb') as f:
|
||||||
|
f.write(tests.xls)
|
||||||
|
|
||||||
|
The resulting **tests.xls** will have the following layout:
|
||||||
|
|
||||||
|
|
||||||
|
Daniel's Scores:
|
||||||
|
* '11/24/09', 'Math 101 Mid-term Exam', 56.
|
||||||
|
* '05/24/10', 'Math 101 Final Exam', 62.
|
||||||
|
|
||||||
|
Suzie's Scores:
|
||||||
|
* '11/24/09', 'Math 101 Mid-term Exam', 56.
|
||||||
|
* '05/24/10', 'Math 101 Final Exam', 62.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.. admonition:: Format Support
|
||||||
|
|
||||||
|
At this time, only :class:`Excel <Dataset.xls>` output supports seperators.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
Now, go check out the :ref:`API Documentation <api>` or begin :ref:`Tablib Development <development>`.
|
||||||
Vendored
+10
-20
@@ -1,27 +1,17 @@
|
|||||||
|
import os
|
||||||
from fabric.api import *
|
from fabric.api import *
|
||||||
|
|
||||||
|
|
||||||
def scrub():
|
def scrub():
|
||||||
""" Death to the bytecode! """
|
""" Death to the bytecode! """
|
||||||
local("rm -fr dist build")
|
local('rm -fr dist build')
|
||||||
local("find . -name \"*.pyc\" -exec rm '{}' ';'")
|
local("find . -name \"*.pyc\" -exec rm '{}' ';'")
|
||||||
|
|
||||||
def test():
|
def docs():
|
||||||
""" Test parsing! """
|
"""Build docs."""
|
||||||
local("rm output/*")
|
os.system('make html')
|
||||||
local("./strata.py --nsanity_files 'strata/tests/samples/nsanity' -d")
|
os.chdir('_build/html')
|
||||||
|
os.system('sphinxtogithub .')
|
||||||
def build():
|
os.system('git add -A')
|
||||||
""" Build application"""
|
os.system('git commit -m \'documentation update\'')
|
||||||
pass
|
os.system('git push origin gh-pages')
|
||||||
|
|
||||||
def init():
|
|
||||||
""" Initialize Environment """
|
|
||||||
# TODO: Possibly add Virtual Environment?
|
|
||||||
local("sudo pip install -r REQUIREMENTS")
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# TODO: Remove (for testing purposes)
|
|
||||||
# TODO: [Possibly] add doctests
|
|
||||||
test()
|
|
||||||
|
|
||||||
@@ -1,36 +1,50 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import tablib
|
|
||||||
|
|
||||||
from distutils.core import setup
|
from distutils.core import setup
|
||||||
|
|
||||||
|
|
||||||
def publish():
|
def publish():
|
||||||
"""Publish to PyPi"""
|
"""Publish to PyPi"""
|
||||||
os.system("python setup.py sdist upload")
|
os.system("python setup.py sdist upload")
|
||||||
|
|
||||||
if sys.argv[-1] == "publish":
|
if sys.argv[-1] == "publish":
|
||||||
publish()
|
publish()
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
setup(name='tablib',
|
required = []
|
||||||
version=tablib.__version__,
|
|
||||||
description='Python wrapper for Gist API',
|
setup(
|
||||||
long_description=open('README.rst').read() + '\n\n' +
|
name='tablib',
|
||||||
open('HISTORY.rst').read(),
|
version='0.9.2',
|
||||||
author='Kenneth Reitz',
|
description='Format agnostic tabular data library (XLS, JSON, YAML, CSV)',
|
||||||
author_email='me@kennethreitz.com',
|
long_description=open('README.rst').read() + '\n\n' +
|
||||||
url='http://github.com/kennethreitz/tabbed',
|
open('HISTORY.rst').read(),
|
||||||
packages=['tablib'],
|
author='Kenneth Reitz',
|
||||||
license='MIT',
|
author_email='me@kennethreitz.com',
|
||||||
classifiers=(
|
url='http://tablib.org',
|
||||||
"Development Status :: 4 - Beta",
|
packages= [
|
||||||
"License :: OSI Approved :: MIT License",
|
'tablib', 'tablib.formats',
|
||||||
"Programming Language :: Python",
|
'tablib.packages',
|
||||||
"Programming Language :: Python :: 2.5",
|
'tablib.packages.simplejson',
|
||||||
"Programming Language :: Python :: 2.6",
|
'tablib.packages.xlwt',
|
||||||
"Programming Language :: Python :: 2.7",
|
'tablib.packages.yaml',
|
||||||
)
|
],
|
||||||
)
|
install_requires=required,
|
||||||
|
license='MIT',
|
||||||
|
classifiers=(
|
||||||
|
'Development Status :: 5 - Production/Stable',
|
||||||
|
'Intended Audience :: Developers',
|
||||||
|
'Natural Language :: English',
|
||||||
|
'License :: OSI Approved :: MIT License',
|
||||||
|
'Programming Language :: Python',
|
||||||
|
# 'Programming Language :: Python :: 2.5',
|
||||||
|
'Programming Language :: Python :: 2.6',
|
||||||
|
'Programming Language :: Python :: 2.7',
|
||||||
|
# 'Programming Language :: Python :: 3.0',
|
||||||
|
# 'Programming Language :: Python :: 3.1',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
|
||||||
Tabbed
|
|
||||||
Copyright (c) 2010 Kenneth Reitz. MIT License.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import tablib.cli
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
|
|
||||||
tablib.cli.start()
|
|
||||||
+8
-1
@@ -1 +1,8 @@
|
|||||||
from core import *
|
""" Tablib.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from tablib.core import (
|
||||||
|
Databook, Dataset, detect, import_set,
|
||||||
|
InvalidDatasetType, InvalidDimensions, UnsupportedFormat
|
||||||
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# encoding: utf-8
|
|
||||||
|
|
||||||
""" Tabbed CLI Inteface Application
|
|
||||||
"""
|
|
||||||
|
|
||||||
import io
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from helpers import *
|
|
||||||
import tablib.core
|
|
||||||
|
|
||||||
from packages import opster
|
|
||||||
|
|
||||||
|
|
||||||
FORMATS = ('json', 'yaml', 'xls', 'csv', 'html')
|
|
||||||
|
|
||||||
opts = []
|
|
||||||
|
|
||||||
opts.append(('v', 'version', False, 'Report tabbed version'))
|
|
||||||
|
|
||||||
for format in FORMATS:
|
|
||||||
opts.append(('', format, False, 'Output to %s' % (format.upper())))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@opster.command(options=opts, usage='[FILE] [--FORMAT | FILE]')
|
|
||||||
def start(in_file=None, out_file=None, **opts):
|
|
||||||
"""Covertly convert dataset formats"""
|
|
||||||
|
|
||||||
opts = Object(**opts)
|
|
||||||
|
|
||||||
if opts.version:
|
|
||||||
print('Tabbed, Ver. %s' % tabbed.core.__version__)
|
|
||||||
sys.sys.exit(0)
|
|
||||||
|
|
||||||
stdin = piped()
|
|
||||||
|
|
||||||
if stdin:
|
|
||||||
print stdin
|
|
||||||
|
|
||||||
elif in_file:
|
|
||||||
|
|
||||||
try:
|
|
||||||
in_file = io.open(in_file, 'r')
|
|
||||||
except Exception, e:
|
|
||||||
print(' %s cannot be read.' % in_file)
|
|
||||||
sys.exit(65)
|
|
||||||
|
|
||||||
file_ext = in_file.name.split('.')[-1]
|
|
||||||
|
|
||||||
if file_ext.lower() in FORMATS:
|
|
||||||
setattr(opts, file_ext, True)
|
|
||||||
else:
|
|
||||||
print('Import format not supported.')
|
|
||||||
sys.exit(65)
|
|
||||||
else:
|
|
||||||
print('Please provide input.')
|
|
||||||
sys.exit(65)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
_formats_sum = sum(opts[f] for f in FORMATS)
|
|
||||||
|
|
||||||
# Multiple output formats given
|
|
||||||
if _formats_sum > 1:
|
|
||||||
print('Please specify a single output format.')
|
|
||||||
sys.exit(64)
|
|
||||||
|
|
||||||
# No output formats given
|
|
||||||
elif _formats_sum < 1:
|
|
||||||
print('Please specify an output format.')
|
|
||||||
sys.exit(64)
|
|
||||||
|
|
||||||
|
|
||||||
# fetch options.formats list
|
|
||||||
# if sum(()) > 1
|
|
||||||
# log only one data format please
|
|
||||||
# if sum of formats == 0, specity format
|
|
||||||
|
|
||||||
# look for filename
|
|
||||||
|
|
||||||
print opts.__dict__
|
|
||||||
print in_file
|
|
||||||
print out_file
|
|
||||||
+688
-17
@@ -1,38 +1,709 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
tablib.core
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
# _____ ______ ______ _________
|
This module implements the central tablib objects.
|
||||||
# __ /_______ ____ /_ ___ /_ _____ ______ /
|
|
||||||
# _ __/_ __ `/__ __ \__ __ \_ _ \_ __ /
|
|
||||||
# / /_ / /_/ / _ /_/ /_ /_/ // __// /_/ /
|
|
||||||
# \__/ \__,_/ /_.___/ /_.___/ \___/ \__,_/
|
|
||||||
|
|
||||||
__version__ = '0.0.3'
|
:copyright: (c) 2010 by Kenneth Reitz.
|
||||||
__build__ = '0x000003'
|
:license: MIT, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from copy import copy
|
||||||
|
|
||||||
|
from tablib import formats
|
||||||
|
|
||||||
|
|
||||||
|
__title__ = 'tablib'
|
||||||
|
__version__ = '0.9.2'
|
||||||
|
__build__ = 0x000902
|
||||||
__author__ = 'Kenneth Reitz'
|
__author__ = 'Kenneth Reitz'
|
||||||
__license__ = 'MIT'
|
__license__ = 'MIT'
|
||||||
__copyright__ = 'Copyright 2010 Kenneth Reitz'
|
__copyright__ = 'Copyright 2010 Kenneth Reitz'
|
||||||
|
|
||||||
|
|
||||||
def importer():
|
class Row(object):
|
||||||
"""docstring for import"""
|
"""Internal Row object. Mainly used for filtering."""
|
||||||
|
|
||||||
|
__slots__ = ['tuple', '_row', 'tags']
|
||||||
|
|
||||||
|
def __init__(self, row=list(), tags=list()):
|
||||||
|
self._row = list(row)
|
||||||
|
self.tags = list(tags)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return (col for col in self._row)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self._row)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return repr(self._row)
|
||||||
|
|
||||||
|
def __getslice__(self, i, j):
|
||||||
|
return self._row[i,j]
|
||||||
|
|
||||||
|
def __getitem__(self, i):
|
||||||
|
return self._row[i]
|
||||||
|
|
||||||
|
def __setitem__(self, i, value):
|
||||||
|
self._row[i] = value
|
||||||
|
|
||||||
|
def __delitem__(self, i):
|
||||||
|
del self._row[i]
|
||||||
|
|
||||||
|
def __getstate__(self):
|
||||||
|
result = dict()
|
||||||
|
result['_row'] = self._row
|
||||||
|
result['tags'] = self.tags
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def __setstate__(self, state):
|
||||||
|
self._row = state['_row']
|
||||||
|
self.tags = state['tags']
|
||||||
|
|
||||||
|
def append(self, value):
|
||||||
|
self._row.append(value)
|
||||||
|
|
||||||
|
def insert(self, index, value):
|
||||||
|
self._row.insert(index, value)
|
||||||
|
|
||||||
|
def __contains__(self, item):
|
||||||
|
return (item in self._row)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tuple(self):
|
||||||
|
'''Tuple representation of :class:`Row`.'''
|
||||||
|
return tuple(self._row)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def list(self):
|
||||||
|
'''List representation of :class:`Row`.'''
|
||||||
|
return list(self._row)
|
||||||
|
|
||||||
|
def has_tag(self, tag):
|
||||||
|
"""Returns true if current row contains tag."""
|
||||||
|
|
||||||
|
if tag == None:
|
||||||
|
return False
|
||||||
|
elif isinstance(tag, basestring):
|
||||||
|
return (tag in self.tags)
|
||||||
|
else:
|
||||||
|
return True if len(set(tag) & set(self.tags)) else False
|
||||||
|
|
||||||
|
|
||||||
|
class Dataset(object):
|
||||||
|
"""The :class:`Dataset` object is the heart of Tablib. It provides all core
|
||||||
|
functionality.
|
||||||
|
|
||||||
class Data(object):
|
Usually you create a :class:`Dataset` instance in your main module, and append
|
||||||
"""test"""
|
rows and columns as you collect data. ::
|
||||||
def __init__(self):
|
|
||||||
pass
|
data = tablib.Dataset()
|
||||||
self.headers = None
|
data.headers = ('name', 'age')
|
||||||
|
|
||||||
|
for (name, age) in some_collector():
|
||||||
|
data.append((name, age))
|
||||||
|
|
||||||
|
You can also set rows and headers upon instantiation. This is useful if dealing
|
||||||
|
with dozens or hundres of :class:`Dataset` objects. ::
|
||||||
|
|
||||||
|
headers = ('first_name', 'last_name')
|
||||||
|
data = [('John', 'Adams'), ('George', 'Washington')]
|
||||||
|
|
||||||
|
data = tablib.Dataset(*data, headers=headers)
|
||||||
|
|
||||||
|
|
||||||
|
:param \*args: (optional) list of rows to populate Dataset
|
||||||
|
:param headers: (optional) list strings for Dataset header row
|
||||||
|
|
||||||
|
|
||||||
def add_row(self, index=None):
|
.. admonition:: Format Attributes Definition
|
||||||
|
|
||||||
|
If you look at the code, the various output/import formats are not
|
||||||
|
defined within the :class:`Dataset` object. To add support for a new format, see
|
||||||
|
:ref:`Adding New Formats <newformats>`.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self._data = list(Row(arg) for arg in args)
|
||||||
|
self.__headers = None
|
||||||
|
|
||||||
|
# ('title', index) tuples
|
||||||
|
self._separators = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.headers = kwargs['headers']
|
||||||
|
except KeyError:
|
||||||
|
self.headers = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.title = kwargs['title']
|
||||||
|
except KeyError:
|
||||||
|
self.title = None
|
||||||
|
|
||||||
|
self._register_formats()
|
||||||
|
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return self.height
|
||||||
|
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
if isinstance(key, basestring):
|
||||||
|
if key in self.headers:
|
||||||
|
pos = self.headers.index(key) # get 'key' index from each data
|
||||||
|
return [row[pos] for row in self._data]
|
||||||
|
else:
|
||||||
|
raise KeyError
|
||||||
|
else:
|
||||||
|
_results = self._data[key]
|
||||||
|
if isinstance(_results, Row):
|
||||||
|
return _results.tuple
|
||||||
|
else:
|
||||||
|
return [result.tuple for result in _results]
|
||||||
|
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
self._validate(value)
|
||||||
|
self._data[key] = Row(value)
|
||||||
|
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
if isinstance(key, basestring):
|
||||||
|
|
||||||
|
if key in self.headers:
|
||||||
|
|
||||||
|
pos = self.headers.index(key)
|
||||||
|
del self.headers[pos]
|
||||||
|
|
||||||
|
for i, row in enumerate(self._data):
|
||||||
|
|
||||||
|
del row[pos]
|
||||||
|
self._data[i] = row
|
||||||
|
else:
|
||||||
|
raise KeyError
|
||||||
|
else:
|
||||||
|
del self._data[key]
|
||||||
|
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
try:
|
||||||
|
return '<%s dataset>' % (self.title.lower())
|
||||||
|
except AttributeError:
|
||||||
|
return '<dataset object>'
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _register_formats(cls):
|
||||||
|
"""Adds format properties."""
|
||||||
|
for fmt in formats.available:
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
setattr(cls, fmt.title, property(fmt.export_set, fmt.import_set))
|
||||||
|
except AttributeError:
|
||||||
|
setattr(cls, fmt.title, property(fmt.export_set))
|
||||||
|
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _validate(self, row=None, col=None, safety=False):
|
||||||
|
"""Assures size of every row in dataset is of proper proportions."""
|
||||||
|
if row:
|
||||||
|
is_valid = (len(row) == self.width) if self.width else True
|
||||||
|
elif col:
|
||||||
|
if len(col) < 1:
|
||||||
|
is_valid = True
|
||||||
|
else:
|
||||||
|
is_valid = (len(col) == self.height) if self.height else True
|
||||||
|
else:
|
||||||
|
is_valid = all((len(x) == self.width for x in self._data))
|
||||||
|
|
||||||
|
if is_valid:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
if not safety:
|
||||||
|
raise InvalidDimensions
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _package(self, dicts=True):
|
||||||
|
"""Packages Dataset into lists of dictionaries for transmission."""
|
||||||
|
|
||||||
|
if self.headers:
|
||||||
|
if dicts:
|
||||||
|
data = [dict(zip(self.headers, data_row)) for data_row in self ._data]
|
||||||
|
else:
|
||||||
|
data = [list(self.headers)] + list(self._data)
|
||||||
|
else:
|
||||||
|
data = [list(row) for row in self._data]
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def _clean_col(self, col):
|
||||||
|
"""Prepares the given column for insert/append."""
|
||||||
|
|
||||||
|
col = list(col)
|
||||||
|
|
||||||
|
if self.headers:
|
||||||
|
header = [col.pop(0)]
|
||||||
|
else:
|
||||||
|
header = []
|
||||||
|
|
||||||
|
if len(col) == 1 and callable(col[0]):
|
||||||
|
col = map(col[0], self._data)
|
||||||
|
col = tuple(header + col)
|
||||||
|
|
||||||
|
return col
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def height(self):
|
||||||
|
"""The number of rows currently in the :class:`Dataset`.
|
||||||
|
Cannot be directly modified.
|
||||||
|
"""
|
||||||
|
return len(self._data)
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def width(self):
|
||||||
|
"""The number of columns currently in the :class:`Dataset`.
|
||||||
|
Cannot be directly modified.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
return len(self._data[0])
|
||||||
|
except IndexError:
|
||||||
|
try:
|
||||||
|
return len(self.headers)
|
||||||
|
except TypeError:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def headers(self):
|
||||||
|
"""An *optional* list of strings to be used for header rows and attribute names.
|
||||||
|
|
||||||
|
This must be set manually. The given list length must equal :class:`Dataset.width`.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self.__headers
|
||||||
|
|
||||||
|
|
||||||
|
@headers.setter
|
||||||
|
def headers(self, collection):
|
||||||
|
"""Validating headers setter."""
|
||||||
|
self._validate(collection)
|
||||||
|
if collection:
|
||||||
|
try:
|
||||||
|
self.__headers = list(collection)
|
||||||
|
except TypeError:
|
||||||
|
raise TypeError
|
||||||
|
else:
|
||||||
|
self.__headers = None
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dict(self):
|
||||||
|
"""A JSON representation of the :class:`Dataset` object. If headers have been
|
||||||
|
set, a JSON list of objects will be returned. If no headers have
|
||||||
|
been set, a JSON list of lists (rows) will be returned instead.
|
||||||
|
|
||||||
|
A dataset object can also be imported by setting the `Dataset.json` attribute: ::
|
||||||
|
|
||||||
|
data = tablib.Dataset()
|
||||||
|
data.json = '[{"last_name": "Adams","age": 90,"first_name": "John"}]'
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self._package()
|
||||||
|
|
||||||
|
|
||||||
|
@dict.setter
|
||||||
|
def dict(self, pickle):
|
||||||
|
"""A native Python representation of the Dataset object. If headers have been
|
||||||
|
set, a list of Python dictionaries will be returned. If no headers have been
|
||||||
|
set, a list of tuples (rows) will be returned instead.
|
||||||
|
|
||||||
|
A dataset object can also be imported by setting the :class:`Dataset.dict` attribute. ::
|
||||||
|
|
||||||
|
data = tablib.Dataset()
|
||||||
|
data.dict = [{'age': 90, 'first_name': 'Kenneth', 'last_name': 'Reitz'}]
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not len(pickle):
|
||||||
|
return
|
||||||
|
|
||||||
|
# if list of rows
|
||||||
|
if isinstance(pickle[0], list):
|
||||||
|
self.wipe()
|
||||||
|
for row in pickle:
|
||||||
|
self.append(Row(row))
|
||||||
|
|
||||||
|
# if list of objects
|
||||||
|
elif isinstance(pickle[0], dict):
|
||||||
|
self.wipe()
|
||||||
|
self.headers = pickle[0].keys()
|
||||||
|
for row in pickle:
|
||||||
|
self.append(Row(row.values()))
|
||||||
|
else:
|
||||||
|
raise UnsupportedFormat
|
||||||
|
|
||||||
|
@property
|
||||||
|
def xls():
|
||||||
|
"""An Excel Spreadsheet representation of the :class:`Dataset` object, with :ref:`seperators`. Cannot be set.
|
||||||
|
|
||||||
|
.. admonition:: Binary Warning
|
||||||
|
|
||||||
|
:class:`Dataset.xls` contains binary data, so make sure to write in binary mode::
|
||||||
|
|
||||||
|
with open('output.xls', 'wb') as f:
|
||||||
|
f.write(data.xls)'
|
||||||
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def del_row(self):
|
|
||||||
|
@property
|
||||||
|
def csv():
|
||||||
|
"""A CSV representation of the :class:`Dataset` object. The top row will contain
|
||||||
|
headers, if they have been set. Otherwise, the top row will contain
|
||||||
|
the first row of the dataset.
|
||||||
|
|
||||||
|
A dataset object can also be imported by setting the :class:`Dataset.csv` attribute. ::
|
||||||
|
|
||||||
|
data = tablib.Dataset()
|
||||||
|
data.csv = 'age, first_name, last_name\\n90, John, Adams'
|
||||||
|
|
||||||
|
Import assumes (for now) that headers exist.
|
||||||
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def save(self):
|
@property
|
||||||
|
def tsv():
|
||||||
|
"""A TSV representation of the :class:`Dataset` object. The top row will contain
|
||||||
|
headers, if they have been set. Otherwise, the top row will contain
|
||||||
|
the first row of the dataset.
|
||||||
|
|
||||||
|
A dataset object can also be imported by setting the :class:`Dataset.tsv` attribute. ::
|
||||||
|
|
||||||
|
data = tablib.Dataset()
|
||||||
|
data.tsv = 'age\tfirst_name\tlast_name\\n90\tJohn\tAdams'
|
||||||
|
|
||||||
|
Import assumes (for now) that headers exist.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def yaml():
|
||||||
|
"""A YAML representation of the :class:`Dataset` object. If headers have been
|
||||||
|
set, a YAML list of objects will be returned. If no headers have
|
||||||
|
been set, a YAML list of lists (rows) will be returned instead.
|
||||||
|
|
||||||
|
A dataset object can also be imported by setting the :class:`Dataset.json` attribute: ::
|
||||||
|
|
||||||
|
data = tablib.Dataset()
|
||||||
|
data.yaml = '- {age: 90, first_name: John, last_name: Adams}'
|
||||||
|
|
||||||
|
Import assumes (for now) that headers exist.
|
||||||
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def json():
|
||||||
|
"""A JSON representation of the :class:`Dataset` object. If headers have been
|
||||||
|
set, a JSON list of objects will be returned. If no headers have
|
||||||
|
been set, a JSON list of lists (rows) will be returned instead.
|
||||||
|
|
||||||
|
A dataset object can also be imported by setting the :class:`Dataset.json` attribute: ::
|
||||||
|
|
||||||
|
data = tablib.Dataset()
|
||||||
|
data.json = '[{age: 90, first_name: "John", liast_name: "Adams"}]'
|
||||||
|
|
||||||
|
Import assumes (for now) that headers exist.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def append(self, row=None, col=None, header=None, tags=list()):
|
||||||
|
"""Adds a row or column to the :class:`Dataset`.
|
||||||
|
Usage is :class:`Dataset.insert` for documentation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if row is not None:
|
||||||
|
self.insert(self.height, row=row, tags=tags)
|
||||||
|
elif col is not None:
|
||||||
|
self.insert(self.width, col=col, header=header)
|
||||||
|
|
||||||
|
def insert_separator(self, index, text='-'):
|
||||||
|
"""Adds a separator to :class:`Dataset` at given index."""
|
||||||
|
|
||||||
|
sep = (index, text)
|
||||||
|
self._separators.append(sep)
|
||||||
|
|
||||||
|
|
||||||
|
def append_separator(self, text='-'):
|
||||||
|
"""Adds a :ref:`seperator <seperators>` to the :class:`Dataset`."""
|
||||||
|
|
||||||
|
# change offsets if headers are or aren't defined
|
||||||
|
if not self.headers:
|
||||||
|
index = self.height if self.height else 0
|
||||||
|
else:
|
||||||
|
index = (self.height + 1) if self.height else 1
|
||||||
|
|
||||||
|
self.insert_separator(index, text)
|
||||||
|
|
||||||
|
|
||||||
|
def insert(self, index, row=None, col=None, header=None, tags=list()):
|
||||||
|
"""Inserts a row or column to the :class:`Dataset` at the given index.
|
||||||
|
|
||||||
|
Rows and columns inserted must be the correct size (height or width).
|
||||||
|
|
||||||
|
The default behaviour is to insert the given row to the :class:`Dataset`
|
||||||
|
object at the given index. If the ``col`` parameter is given, however,
|
||||||
|
a new column will be insert to the :class:`Dataset` object instead.
|
||||||
|
|
||||||
|
You can also insert a column of a single callable object, which will
|
||||||
|
add a new column with the return values of the callable each as an
|
||||||
|
item in the column. ::
|
||||||
|
|
||||||
|
data.append(col=random.randint)
|
||||||
|
|
||||||
|
See :ref:`dyncols` for an in-depth example.
|
||||||
|
|
||||||
|
.. versionchanged:: 0.9.0
|
||||||
|
If inserting a column, and :class:`Dataset.headers` is set, the
|
||||||
|
header attribute must be set, and will be considered the header for
|
||||||
|
that row.
|
||||||
|
|
||||||
|
.. versionadded:: 0.9.0
|
||||||
|
If inserting a row, you can add :ref:`tags <tags>` to the row you are inserting.
|
||||||
|
This gives you the ability to :class:`filter <Dataset.filter>` your
|
||||||
|
:class:`Dataset` later.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if row:
|
||||||
|
self._validate(row)
|
||||||
|
self._data.insert(index, Row(row, tags=tags))
|
||||||
|
elif col:
|
||||||
|
col = list(col)
|
||||||
|
|
||||||
|
# Callable Columns...
|
||||||
|
if len(col) == 1 and callable(col[0]):
|
||||||
|
col = map(col[0], self._data)
|
||||||
|
|
||||||
|
col = self._clean_col(col)
|
||||||
|
self._validate(col=col)
|
||||||
|
|
||||||
|
if self.headers:
|
||||||
|
# pop the first item off, add to headers
|
||||||
|
if not header:
|
||||||
|
raise HeadersNeeded()
|
||||||
|
self.headers.insert(index, header)
|
||||||
|
|
||||||
|
if self.height and self.width:
|
||||||
|
|
||||||
|
for i, row in enumerate(self._data):
|
||||||
|
|
||||||
|
row.insert(index, col[i])
|
||||||
|
self._data[i] = row
|
||||||
|
else:
|
||||||
|
self._data = [Row([row]) for row in col]
|
||||||
|
|
||||||
|
def filter(self, tag):
|
||||||
|
"""Returns a new instance of the :class:`Dataset`, excluding any rows
|
||||||
|
that do not contain the given :ref:`tags <tags>`.
|
||||||
|
"""
|
||||||
|
_dset = copy(self)
|
||||||
|
_dset._data = [row for row in _dset._data if row.has_tag(tag)]
|
||||||
|
|
||||||
|
return _dset
|
||||||
|
|
||||||
|
def transpose(self):
|
||||||
|
"""Transpose a :class:`Dataset`, turning rows into columns and vice
|
||||||
|
versa, returning a new ``Dataset`` instance. The first row of the
|
||||||
|
original instance becomes the new header row."""
|
||||||
|
|
||||||
|
# Don't transpose if there is no data
|
||||||
|
if not self:
|
||||||
|
return
|
||||||
|
|
||||||
|
_dset = Dataset()
|
||||||
|
# The first element of the headers stays in the headers,
|
||||||
|
# it is our "hinge" on which we rotate the data
|
||||||
|
new_headers = [self.headers[0]] + self[self.headers[0]]
|
||||||
|
|
||||||
|
_dset.headers = new_headers
|
||||||
|
for column in self.headers:
|
||||||
|
|
||||||
|
if column == self.headers[0]:
|
||||||
|
# It's in the headers, so skip it
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Adding the column name as now they're a regular column
|
||||||
|
row_data = [column] + self[column]
|
||||||
|
row_data = Row(row_data)
|
||||||
|
_dset.append(row=row_data)
|
||||||
|
|
||||||
|
return _dset
|
||||||
|
|
||||||
|
|
||||||
|
def stack_rows(self, other):
|
||||||
|
"""Stack two :class:`Dataset` instances together by
|
||||||
|
joining at the row level, and return new combined
|
||||||
|
``Dataset`` instance."""
|
||||||
|
|
||||||
|
if not isinstance(other, Dataset):
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.width != other.width:
|
||||||
|
raise InvalidDimensions
|
||||||
|
|
||||||
|
# Copy the source data
|
||||||
|
_dset = copy(self)
|
||||||
|
|
||||||
|
rows_to_stack = [row for row in _dset._data]
|
||||||
|
other_rows = [row for row in other._data]
|
||||||
|
|
||||||
|
rows_to_stack.extend(other_rows)
|
||||||
|
_dset._data = rows_to_stack
|
||||||
|
|
||||||
|
return _dset
|
||||||
|
|
||||||
|
|
||||||
|
def stack_columns(self, other):
|
||||||
|
"""Stack two :class:`Dataset` instances together by
|
||||||
|
joining at the column level, and return a new
|
||||||
|
combined ``Dataset`` instance. If either ``Dataset``
|
||||||
|
has headers set, than the other must as well."""
|
||||||
|
|
||||||
|
if not isinstance(other, Dataset):
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.headers or other.headers:
|
||||||
|
if not self.headers or not other.headers:
|
||||||
|
raise HeadersNeeded
|
||||||
|
|
||||||
|
if self.height != other.height:
|
||||||
|
raise InvalidDimensions
|
||||||
|
|
||||||
|
try:
|
||||||
|
new_headers = self.headers + other.headers
|
||||||
|
except TypeError:
|
||||||
|
new_headers = None
|
||||||
|
|
||||||
|
_dset = Dataset()
|
||||||
|
|
||||||
|
for column in self.headers:
|
||||||
|
_dset.append(col=self[column])
|
||||||
|
|
||||||
|
for column in other.headers:
|
||||||
|
_dset.append(col=other[column])
|
||||||
|
|
||||||
|
_dset.headers = new_headers
|
||||||
|
|
||||||
|
return _dset
|
||||||
|
|
||||||
|
def wipe(self):
|
||||||
|
"""Removes all content and headers from the :class:`Dataset` object."""
|
||||||
|
self._data = list()
|
||||||
|
self.__headers = None
|
||||||
|
|
||||||
|
|
||||||
|
class Databook(object):
|
||||||
|
"""A book of :class:`Dataset` objects.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, sets=[]):
|
||||||
|
self._datasets = sets
|
||||||
|
self._register_formats()
|
||||||
|
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
try:
|
||||||
|
return '<%s databook>' % (self.title.lower())
|
||||||
|
except AttributeError:
|
||||||
|
return '<databook object>'
|
||||||
|
|
||||||
|
|
||||||
|
def wipe(self):
|
||||||
|
"""Removes all :class:`Dataset` objects from the :class:`Databook`."""
|
||||||
|
self._datasets = []
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _register_formats(cls):
|
||||||
|
"""Adds format properties."""
|
||||||
|
for fmt in formats.available:
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
setattr(cls, fmt.title, property(fmt.export_book, fmt.import_book))
|
||||||
|
except AttributeError:
|
||||||
|
setattr(cls, fmt.title, property(fmt.export_book))
|
||||||
|
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def add_sheet(self, dataset):
|
||||||
|
"""Adds given :class:`Dataset` to the :class:`Databook`."""
|
||||||
|
if type(dataset) is Dataset:
|
||||||
|
self._datasets.append(dataset)
|
||||||
|
else:
|
||||||
|
raise InvalidDatasetType
|
||||||
|
|
||||||
|
|
||||||
|
def _package(self):
|
||||||
|
"""Packages :class:`Databook` for delivery."""
|
||||||
|
collector = []
|
||||||
|
for dset in self._datasets:
|
||||||
|
collector.append(dict(
|
||||||
|
title = dset.title,
|
||||||
|
data = dset.dict
|
||||||
|
))
|
||||||
|
return collector
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def size(self):
|
||||||
|
"""The number of the :class:`Dataset` objects within :class:`Databook`."""
|
||||||
|
return len(self._datasets)
|
||||||
|
|
||||||
|
|
||||||
|
def detect(stream):
|
||||||
|
"""Return (format, stream) of given stream."""
|
||||||
|
for fmt in formats.available:
|
||||||
|
try:
|
||||||
|
if fmt.detect(stream):
|
||||||
|
return (fmt, stream)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
return (None, stream)
|
||||||
|
|
||||||
|
|
||||||
|
def import_set(stream):
|
||||||
|
"""Return dataset of given stream."""
|
||||||
|
(format, stream) = detect(stream)
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = Dataset()
|
||||||
|
format.import_set(data, stream)
|
||||||
|
return data
|
||||||
|
|
||||||
|
except AttributeError, e:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidDatasetType(Exception):
|
||||||
|
"Only Datasets can be added to a DataBook"
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidDimensions(Exception):
|
||||||
|
"Invalid size"
|
||||||
|
|
||||||
|
class HeadersNeeded(Exception):
|
||||||
|
"Header parameter must be given when appending a column in this Dataset."
|
||||||
|
|
||||||
|
class UnsupportedFormat(NotImplementedError):
|
||||||
|
"Format is not supported"
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
""" Tablib - formats
|
||||||
|
"""
|
||||||
|
|
||||||
|
import _csv as csv
|
||||||
|
import _json as json
|
||||||
|
import _xls as xls
|
||||||
|
import _yaml as yaml
|
||||||
|
import _tsv as tsv
|
||||||
|
|
||||||
|
available = (json, xls, yaml, csv, tsv)
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
""" Tablib - CSV Support.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import cStringIO
|
||||||
|
import csv
|
||||||
|
import os
|
||||||
|
|
||||||
|
import tablib
|
||||||
|
|
||||||
|
|
||||||
|
title = 'csv'
|
||||||
|
extentions = ('csv',)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def export_set(dataset):
|
||||||
|
"""Returns CSV representation of Dataset."""
|
||||||
|
stream = cStringIO.StringIO()
|
||||||
|
_csv = csv.writer(stream)
|
||||||
|
|
||||||
|
for row in dataset._package(dicts=False):
|
||||||
|
_csv.writerow(row)
|
||||||
|
|
||||||
|
return stream.getvalue()
|
||||||
|
|
||||||
|
|
||||||
|
def import_set(dset, in_stream, headers=True):
|
||||||
|
"""Returns dataset from CSV stream."""
|
||||||
|
|
||||||
|
dset.wipe()
|
||||||
|
|
||||||
|
rows = csv.reader(in_stream.split())
|
||||||
|
for i, row in enumerate(rows):
|
||||||
|
|
||||||
|
if (i == 0) and (headers):
|
||||||
|
dset.headers = row
|
||||||
|
else:
|
||||||
|
dset.append(row)
|
||||||
|
|
||||||
|
|
||||||
|
def detect(stream):
|
||||||
|
"""Returns True if given stream is valid CSV."""
|
||||||
|
try:
|
||||||
|
rows = dialect = csv.Sniffer().sniff(stream)
|
||||||
|
return True
|
||||||
|
except csv.Error:
|
||||||
|
return False
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
""" Tablib - JSON Support
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
import json # load system JSON (Python >= 2.6)
|
||||||
|
except ImportError:
|
||||||
|
try:
|
||||||
|
import simplejson as json
|
||||||
|
except ImportError:
|
||||||
|
import tablib.packages.simplejson as json # use the vendorized copy
|
||||||
|
|
||||||
|
import tablib.core
|
||||||
|
|
||||||
|
|
||||||
|
title = 'json'
|
||||||
|
extentions = ('json', 'jsn')
|
||||||
|
|
||||||
|
|
||||||
|
def export_set(dataset):
|
||||||
|
"""Returns JSON representation of Dataset."""
|
||||||
|
return json.dumps(dataset.dict)
|
||||||
|
|
||||||
|
|
||||||
|
def export_book(databook):
|
||||||
|
"""Returns JSON representation of Databook."""
|
||||||
|
return json.dumps(databook._package())
|
||||||
|
|
||||||
|
|
||||||
|
def import_set(dset, in_stream):
|
||||||
|
"""Returns dataset from JSON stream."""
|
||||||
|
|
||||||
|
dset.wipe()
|
||||||
|
dset.dict = json.loads(in_stream)
|
||||||
|
|
||||||
|
|
||||||
|
def import_book(dbook, in_stream):
|
||||||
|
"""Returns databook from JSON stream."""
|
||||||
|
|
||||||
|
dbook.wipe()
|
||||||
|
for sheet in json.loads(in_stream):
|
||||||
|
data = tablib.core.Dataset()
|
||||||
|
data.title = sheet['title']
|
||||||
|
data.dict = sheet['data']
|
||||||
|
dbook.add_sheet(data)
|
||||||
|
|
||||||
|
|
||||||
|
def detect(stream):
|
||||||
|
"""Returns True if given stream is valid JSON."""
|
||||||
|
try:
|
||||||
|
json.loads(stream)
|
||||||
|
return True
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
""" Tablib - TSV (Tab Separated Values) Support.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import cStringIO
|
||||||
|
import csv
|
||||||
|
import os
|
||||||
|
|
||||||
|
import tablib
|
||||||
|
|
||||||
|
|
||||||
|
title = 'tsv'
|
||||||
|
extentions = ('tsv',)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def export_set(dataset):
|
||||||
|
"""Returns a TSV representation of Dataset."""
|
||||||
|
stream = cStringIO.StringIO()
|
||||||
|
_tsv = csv.writer(stream, delimiter='\t')
|
||||||
|
|
||||||
|
for row in dataset._package(dicts=False):
|
||||||
|
_tsv.writerow(row)
|
||||||
|
|
||||||
|
return stream.getvalue()
|
||||||
|
|
||||||
|
|
||||||
|
def import_set(dset, in_stream, headers=True):
|
||||||
|
"""Returns dataset from TSV stream."""
|
||||||
|
dset.wipe()
|
||||||
|
|
||||||
|
rows = csv.reader(in_stream.split('\r\n'), delimiter='\t')
|
||||||
|
for i, row in enumerate(rows):
|
||||||
|
# Skip empty rows
|
||||||
|
if not row:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if (i == 0) and (headers):
|
||||||
|
dset.headers = row
|
||||||
|
else:
|
||||||
|
dset.append(row)
|
||||||
|
|
||||||
|
|
||||||
|
def detect(stream):
|
||||||
|
"""Returns True if given stream is valid TSV."""
|
||||||
|
try:
|
||||||
|
rows = dialect = csv.Sniffer().sniff(stream, delimiters='\t')
|
||||||
|
return True
|
||||||
|
except csv.Error:
|
||||||
|
return False
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
""" Tablib - XLS Support.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import cStringIO
|
||||||
|
|
||||||
|
try:
|
||||||
|
import xlwt
|
||||||
|
except ImportError:
|
||||||
|
import tablib.packages.xlwt as xlwt
|
||||||
|
|
||||||
|
|
||||||
|
title = 'xls'
|
||||||
|
extentions = ('xls',)
|
||||||
|
|
||||||
|
# special styles
|
||||||
|
wrap = xlwt.easyxf("alignment: wrap on")
|
||||||
|
bold = xlwt.easyxf("font: bold on")
|
||||||
|
|
||||||
|
|
||||||
|
def export_set(dataset):
|
||||||
|
"""Returns XLS representation of Dataset."""
|
||||||
|
|
||||||
|
wb = xlwt.Workbook(encoding='utf8')
|
||||||
|
ws = wb.add_sheet(dataset.title if dataset.title else 'Tabbed Dataset')
|
||||||
|
|
||||||
|
dset_sheet(dataset, ws)
|
||||||
|
|
||||||
|
stream = cStringIO.StringIO()
|
||||||
|
wb.save(stream)
|
||||||
|
return stream.getvalue()
|
||||||
|
|
||||||
|
|
||||||
|
def export_book(databook):
|
||||||
|
"""Returns XLS representation of DataBook."""
|
||||||
|
|
||||||
|
wb = xlwt.Workbook(encoding='utf8')
|
||||||
|
|
||||||
|
for i, dset in enumerate(databook._datasets):
|
||||||
|
ws = wb.add_sheet(dset.title if dset.title else 'Sheet%s' % (i))
|
||||||
|
|
||||||
|
dset_sheet(dset, ws)
|
||||||
|
|
||||||
|
|
||||||
|
stream = cStringIO.StringIO()
|
||||||
|
wb.save(stream)
|
||||||
|
return stream.getvalue()
|
||||||
|
|
||||||
|
|
||||||
|
def dset_sheet(dataset, ws):
|
||||||
|
"""Completes given worksheet from given Dataset."""
|
||||||
|
_package = dataset._package(dicts=False)
|
||||||
|
|
||||||
|
for i, sep in enumerate(dataset._separators):
|
||||||
|
_offset = i
|
||||||
|
_package.insert((sep[0] + _offset), (sep[1],))
|
||||||
|
|
||||||
|
for i, row in enumerate(_package):
|
||||||
|
for j, col in enumerate(row):
|
||||||
|
|
||||||
|
# bold headers
|
||||||
|
if (i == 0) and dataset.headers:
|
||||||
|
ws.write(i, j, col, bold)
|
||||||
|
|
||||||
|
# frozen header row
|
||||||
|
ws.panes_frozen = True
|
||||||
|
ws.horz_split_pos = 1
|
||||||
|
|
||||||
|
|
||||||
|
# bold separators
|
||||||
|
elif len(row) < dataset.width:
|
||||||
|
ws.write(i, j, col, bold)
|
||||||
|
|
||||||
|
# wrap the rest
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
if '\n' in col:
|
||||||
|
ws.write(i, j, col, wrap)
|
||||||
|
else:
|
||||||
|
ws.write(i, j, col)
|
||||||
|
except TypeError:
|
||||||
|
ws.write(i, j, col)
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
""" Tablib - YAML Support.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
import yaml
|
||||||
|
except ImportError:
|
||||||
|
import tablib.packages.yaml as yaml
|
||||||
|
|
||||||
|
import tablib
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
title = 'yaml'
|
||||||
|
extentions = ('yaml', 'yml')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def export_set(dataset):
|
||||||
|
"""Returns YAML representation of Dataset."""
|
||||||
|
return yaml.dump(dataset.dict)
|
||||||
|
|
||||||
|
|
||||||
|
def export_book(databook):
|
||||||
|
"""Returns YAML representation of Databook."""
|
||||||
|
return yaml.dump(databook._package())
|
||||||
|
|
||||||
|
|
||||||
|
def import_set(dset, in_stream):
|
||||||
|
"""Returns dataset from YAML stream."""
|
||||||
|
|
||||||
|
dset.wipe()
|
||||||
|
dset.dict = yaml.load(in_stream)
|
||||||
|
|
||||||
|
|
||||||
|
def import_book(dbook, in_stream):
|
||||||
|
"""Returns databook from YAML stream."""
|
||||||
|
|
||||||
|
dbook.wipe()
|
||||||
|
|
||||||
|
for sheet in yaml.load(in_stream):
|
||||||
|
data = tablib.core.Dataset()
|
||||||
|
data.title = sheet['title']
|
||||||
|
data.dict = sheet['data']
|
||||||
|
dbook.add_sheet(data)
|
||||||
|
|
||||||
|
def detect(stream):
|
||||||
|
"""Returns True if given stream is valid YAML."""
|
||||||
|
try:
|
||||||
|
_yaml = yaml.load(stream)
|
||||||
|
if isinstance(_yaml, (list, tuple, dict)):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
except yaml.parser.ParserError:
|
||||||
|
return False
|
||||||
+21
-4
@@ -1,20 +1,37 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
""" Tablib - General Helpers.
|
||||||
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
class Object(object):
|
class Struct(object):
|
||||||
"""Your attributes are belong to us."""
|
"""Your attributes are belong to us."""
|
||||||
|
|
||||||
def __init__(self, **entries):
|
def __init__(self, **entries):
|
||||||
self.__dict__.update(entries)
|
self.__dict__.update(entries)
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
return getattr(self, key)
|
return getattr(self, key, None)
|
||||||
|
|
||||||
|
def dictionary(self):
|
||||||
|
"""Returns dictionary representation of object."""
|
||||||
|
return self.__dict__
|
||||||
|
|
||||||
|
def items(self):
|
||||||
|
"""Returns items within object."""
|
||||||
|
return self.__dict__.items()
|
||||||
|
|
||||||
|
def keys(self):
|
||||||
|
"""Returns keys within object."""
|
||||||
|
return self.__dict__.keys()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def piped():
|
def piped():
|
||||||
"""Returns piped input via stdin, else False"""
|
"""Returns piped input via stdin, else False."""
|
||||||
|
|
||||||
with sys.stdin as stdin:
|
with sys.stdin as stdin:
|
||||||
|
# TTY is only way to detect if stdin contains data
|
||||||
return stdin.read() if not stdin.isatty() else None
|
return stdin.read() if not stdin.isatty() else None
|
||||||
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
all = ['simplejson', 'typecheck', 'xlwt', 'opster']
|
|
||||||
@@ -1,612 +0,0 @@
|
|||||||
# (c) Alexander Solovyov, 2009, under terms of the new BSD License
|
|
||||||
'''Command line arguments parser
|
|
||||||
'''
|
|
||||||
|
|
||||||
import sys, traceback, getopt, types, textwrap, inspect, os
|
|
||||||
from itertools import imap
|
|
||||||
|
|
||||||
__all__ = ['command', 'dispatch']
|
|
||||||
__version__ = '0.9.10'
|
|
||||||
__author__ = 'Alexander Solovyov'
|
|
||||||
__email__ = 'piranha@piranha.org.ua'
|
|
||||||
|
|
||||||
write = sys.stdout.write
|
|
||||||
err = sys.stderr.write
|
|
||||||
|
|
||||||
CMDTABLE = {}
|
|
||||||
|
|
||||||
# --------
|
|
||||||
# Public interface
|
|
||||||
# --------
|
|
||||||
|
|
||||||
def command(options=None, usage=None, name=None, shortlist=False, hide=False):
|
|
||||||
'''Decorator to mark function to be used for command line processing.
|
|
||||||
|
|
||||||
All arguments are optional:
|
|
||||||
|
|
||||||
- ``options``: options in format described in docs. If not supplied,
|
|
||||||
will be determined from function.
|
|
||||||
- ``usage``: usage string for function, replaces ``%name`` with name
|
|
||||||
of program or subcommand. In case if it's subcommand and ``%name``
|
|
||||||
is not present, usage is prepended by ``name``
|
|
||||||
- ``name``: used for multiple subcommands. Defaults to wrapped
|
|
||||||
function name
|
|
||||||
- ``shortlist``: if command should be included in shortlist. Used
|
|
||||||
only with multiple subcommands
|
|
||||||
- ``hide``: if command should be hidden from help listing. Used only
|
|
||||||
with multiple subcommands, overrides ``shortlist``
|
|
||||||
'''
|
|
||||||
def wrapper(func):
|
|
||||||
try:
|
|
||||||
options_ = list(guess_options(func))
|
|
||||||
except TypeError:
|
|
||||||
options_ = []
|
|
||||||
try:
|
|
||||||
options_ = options_ + list(options)
|
|
||||||
except TypeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
name_ = name or func.__name__.replace('_', '-')
|
|
||||||
if usage is None:
|
|
||||||
usage_ = guess_usage(func, options_)
|
|
||||||
else:
|
|
||||||
usage_ = usage
|
|
||||||
prefix = hide and '~' or (shortlist and '^' or '')
|
|
||||||
CMDTABLE[prefix + name_] = (func, options_, usage_)
|
|
||||||
|
|
||||||
def help_func(name=None):
|
|
||||||
return help_cmd(func, replace_name(usage_, sysname()), options_)
|
|
||||||
|
|
||||||
@wraps(func)
|
|
||||||
def inner(*args, **opts):
|
|
||||||
# look if we need to add 'help' option
|
|
||||||
try:
|
|
||||||
(True for option in reversed(options_)
|
|
||||||
if option[1] == 'help').next()
|
|
||||||
except StopIteration:
|
|
||||||
options_.append(('h', 'help', False, 'show help'))
|
|
||||||
|
|
||||||
argv = opts.pop('argv', sys.argv[1:])
|
|
||||||
if opts.pop('help', False):
|
|
||||||
return help_func()
|
|
||||||
|
|
||||||
if args or opts:
|
|
||||||
# no catcher here because this is call from Python
|
|
||||||
return call_cmd_regular(func, options_)(*args, **opts)
|
|
||||||
|
|
||||||
try:
|
|
||||||
opts, args = catcher(lambda: parse(argv, options_), help_func)
|
|
||||||
except Abort:
|
|
||||||
return -1
|
|
||||||
|
|
||||||
try:
|
|
||||||
if opts.pop('help', False):
|
|
||||||
return help_func()
|
|
||||||
return catcher(lambda: call_cmd(name_, func)(*args, **opts),
|
|
||||||
help_func)
|
|
||||||
except Abort:
|
|
||||||
return -1
|
|
||||||
|
|
||||||
return inner
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
def dispatch(args=None, cmdtable=None, globaloptions=None,
|
|
||||||
middleware=lambda x: x):
|
|
||||||
'''Dispatch command arguments based on subcommands.
|
|
||||||
|
|
||||||
- ``args``: list of arguments, default: ``sys.argv[1:]``
|
|
||||||
- ``cmdtable``: dict of commands in format described below.
|
|
||||||
If not supplied, will use functions decorated with ``@command``.
|
|
||||||
- ``globaloptions``: list of options which are applied to all
|
|
||||||
commands, will contain ``--help`` option at least.
|
|
||||||
- ``middleware``: global decorator for all commands.
|
|
||||||
|
|
||||||
cmdtable format description::
|
|
||||||
|
|
||||||
{'name': (function, options, usage)}
|
|
||||||
|
|
||||||
- ``name`` is the name used on command-line. Can contain aliases
|
|
||||||
(separate them with ``|``), pointer to a fact that this command
|
|
||||||
should be displayed in short help (start name with ``^``), or to
|
|
||||||
a fact that this command should be hidden (start name with ``~``)
|
|
||||||
- ``function`` is the actual callable
|
|
||||||
- ``options`` is options list in format described in docs
|
|
||||||
- ``usage`` is the short string of usage
|
|
||||||
'''
|
|
||||||
args = args or sys.argv[1:]
|
|
||||||
cmdtable = cmdtable or CMDTABLE
|
|
||||||
|
|
||||||
globaloptions = globaloptions or []
|
|
||||||
globaloptions.append(('h', 'help', False, 'display help'))
|
|
||||||
|
|
||||||
cmdtable['help'] = (help_(cmdtable, globaloptions), [], '[TOPIC]')
|
|
||||||
help_func = cmdtable['help'][0]
|
|
||||||
|
|
||||||
autocomplete(cmdtable, args)
|
|
||||||
|
|
||||||
try:
|
|
||||||
name, func, args, kwargs = catcher(
|
|
||||||
lambda: _dispatch(args, cmdtable, globaloptions),
|
|
||||||
help_func)
|
|
||||||
return catcher(
|
|
||||||
lambda: call_cmd(name, middleware(func))(*args, **kwargs),
|
|
||||||
help_func)
|
|
||||||
except Abort:
|
|
||||||
return -1
|
|
||||||
|
|
||||||
# --------
|
|
||||||
# Help
|
|
||||||
# --------
|
|
||||||
|
|
||||||
def help_(cmdtable, globalopts):
|
|
||||||
def help_inner(name=None):
|
|
||||||
'''Show help for a given help topic or a help overview
|
|
||||||
|
|
||||||
With no arguments, print a list of commands with short help messages.
|
|
||||||
|
|
||||||
Given a command name, print help for that command.
|
|
||||||
'''
|
|
||||||
def helplist():
|
|
||||||
hlp = {}
|
|
||||||
# determine if any command is marked for shortlist
|
|
||||||
shortlist = (name == 'shortlist' and
|
|
||||||
any(imap(lambda x: x.startswith('^'), cmdtable)))
|
|
||||||
|
|
||||||
for cmd, info in cmdtable.items():
|
|
||||||
if cmd.startswith('~'):
|
|
||||||
continue # do not display hidden commands
|
|
||||||
if shortlist and not cmd.startswith('^'):
|
|
||||||
continue # short help contains only marked commands
|
|
||||||
cmd = cmd.lstrip('^~')
|
|
||||||
doc = info[0].__doc__ or '(no help text available)'
|
|
||||||
hlp[cmd] = doc.splitlines()[0].rstrip()
|
|
||||||
|
|
||||||
hlplist = sorted(hlp)
|
|
||||||
maxlen = max(map(len, hlplist))
|
|
||||||
|
|
||||||
write('usage: %s <command> [options]\n' % sysname())
|
|
||||||
write('\ncommands:\n\n')
|
|
||||||
for cmd in hlplist:
|
|
||||||
doc = hlp[cmd]
|
|
||||||
if False: # verbose?
|
|
||||||
write(' %s:\n %s\n' % (cmd.replace('|', ', '), doc))
|
|
||||||
else:
|
|
||||||
write(' %-*s %s\n' % (maxlen, cmd.split('|', 1)[0],
|
|
||||||
doc))
|
|
||||||
|
|
||||||
if not cmdtable:
|
|
||||||
return err('No commands specified!\n')
|
|
||||||
|
|
||||||
if not name or name == 'shortlist':
|
|
||||||
return helplist()
|
|
||||||
|
|
||||||
aliases, (cmd, options, usage) = findcmd(name, cmdtable)
|
|
||||||
return help_cmd(cmd,
|
|
||||||
replace_name(usage, sysname() + ' ' + aliases[0]),
|
|
||||||
options + globalopts)
|
|
||||||
return help_inner
|
|
||||||
|
|
||||||
def help_cmd(func, usage, options):
|
|
||||||
'''show help for given command
|
|
||||||
|
|
||||||
- ``func``: function to generate help for (``func.__doc__`` is taken)
|
|
||||||
- ``usage``: usage string
|
|
||||||
- ``options``: options in usual format
|
|
||||||
|
|
||||||
>>> def test(*args, **opts):
|
|
||||||
... """that's a test command
|
|
||||||
...
|
|
||||||
... you can do nothing with this command"""
|
|
||||||
... pass
|
|
||||||
>>> opts = [('l', 'listen', 'localhost',
|
|
||||||
... 'ip to listen on'),
|
|
||||||
... ('p', 'port', 8000,
|
|
||||||
... 'port to listen on'),
|
|
||||||
... ('d', 'daemonize', False,
|
|
||||||
... 'daemonize process'),
|
|
||||||
... ('', 'pid-file', '',
|
|
||||||
... 'name of file to write process ID to')]
|
|
||||||
>>> help_cmd(test, 'test [-l HOST] [NAME]', opts)
|
|
||||||
test [-l HOST] [NAME]
|
|
||||||
<BLANKLINE>
|
|
||||||
that's a test command
|
|
||||||
<BLANKLINE>
|
|
||||||
you can do nothing with this command
|
|
||||||
<BLANKLINE>
|
|
||||||
options:
|
|
||||||
<BLANKLINE>
|
|
||||||
-l --listen ip to listen on (default: localhost)
|
|
||||||
-p --port port to listen on (default: 8000)
|
|
||||||
-d --daemonize daemonize process
|
|
||||||
--pid-file name of file to write process ID to
|
|
||||||
<BLANKLINE>
|
|
||||||
'''
|
|
||||||
write(usage + '\n')
|
|
||||||
doc = func.__doc__
|
|
||||||
if not doc:
|
|
||||||
doc = '(no help text available)'
|
|
||||||
write('\n' + doc.strip() + '\n\n')
|
|
||||||
if options:
|
|
||||||
write(''.join(help_options(options)))
|
|
||||||
|
|
||||||
def help_options(options):
|
|
||||||
yield 'options:\n\n'
|
|
||||||
output = []
|
|
||||||
for short, name, default, desc in options:
|
|
||||||
if hasattr(default, '__call__'):
|
|
||||||
default = default(None)
|
|
||||||
default = default and ' (default: %s)' % default or ''
|
|
||||||
output.append(('%2s%s' % (short and '-%s' % short,
|
|
||||||
name and ' --%s' % name),
|
|
||||||
'%s%s' % (desc, default)))
|
|
||||||
|
|
||||||
opts_len = max([len(first) for first, second in output if second] or [0])
|
|
||||||
for first, second in output:
|
|
||||||
if second:
|
|
||||||
# wrap description at 78 chars
|
|
||||||
second = textwrap.wrap(second, width=(78 - opts_len - 3))
|
|
||||||
pad = '\n' + ' ' * (opts_len + 3)
|
|
||||||
yield ' %-*s %s\n' % (opts_len, first, pad.join(second))
|
|
||||||
else:
|
|
||||||
yield '%s\n' % first
|
|
||||||
|
|
||||||
|
|
||||||
# --------
|
|
||||||
# Options parsing
|
|
||||||
# --------
|
|
||||||
|
|
||||||
def parse(args, options):
|
|
||||||
'''
|
|
||||||
>>> opts = [('l', 'listen', 'localhost',
|
|
||||||
... 'ip to listen on'),
|
|
||||||
... ('p', 'port', 8000,
|
|
||||||
... 'port to listen on'),
|
|
||||||
... ('d', 'daemonize', False,
|
|
||||||
... 'daemonize process'),
|
|
||||||
... ('', 'pid-file', '',
|
|
||||||
... 'name of file to write process ID to')]
|
|
||||||
>>> print parse(['-l', '0.0.0.0', '--pi', 'test', 'all'], opts)
|
|
||||||
({'pid_file': 'test', 'daemonize': False, 'port': 8000, 'listen': '0.0.0.0'}, ['all'])
|
|
||||||
|
|
||||||
'''
|
|
||||||
argmap, defmap, state = {}, {}, {}
|
|
||||||
shortlist, namelist, funlist = '', [], []
|
|
||||||
|
|
||||||
for short, name, default, comment in options:
|
|
||||||
if short and len(short) != 1:
|
|
||||||
raise FOError('Short option should be only a single'
|
|
||||||
' character: %s' % short)
|
|
||||||
if not name:
|
|
||||||
raise FOError(
|
|
||||||
'Long name should be defined for every option')
|
|
||||||
# change name to match Python styling
|
|
||||||
pyname = name.replace('-', '_')
|
|
||||||
argmap['-' + short] = argmap['--' + name] = pyname
|
|
||||||
defmap[pyname] = default
|
|
||||||
|
|
||||||
# copy defaults to state
|
|
||||||
if isinstance(default, list):
|
|
||||||
state[pyname] = default[:]
|
|
||||||
elif hasattr(default, '__call__'):
|
|
||||||
funlist.append(pyname)
|
|
||||||
state[pyname] = None
|
|
||||||
else:
|
|
||||||
state[pyname] = default
|
|
||||||
|
|
||||||
# getopt wants indication that it takes a parameter
|
|
||||||
if not (default is None or default is True or default is False):
|
|
||||||
if short: short += ':'
|
|
||||||
if name: name += '='
|
|
||||||
if short:
|
|
||||||
shortlist += short
|
|
||||||
if name:
|
|
||||||
namelist.append(name)
|
|
||||||
|
|
||||||
opts, args = getopt.gnu_getopt(args, shortlist, namelist)
|
|
||||||
|
|
||||||
# transfer result to state
|
|
||||||
for opt, val in opts:
|
|
||||||
name = argmap[opt]
|
|
||||||
t = type(defmap[name])
|
|
||||||
if t is types.FunctionType:
|
|
||||||
del funlist[funlist.index(name)]
|
|
||||||
state[name] = defmap[name](val)
|
|
||||||
elif t is types.ListType:
|
|
||||||
state[name].append(val)
|
|
||||||
elif t in (types.NoneType, types.BooleanType):
|
|
||||||
state[name] = not defmap[name]
|
|
||||||
else:
|
|
||||||
state[name] = t(val)
|
|
||||||
|
|
||||||
for name in funlist:
|
|
||||||
state[name] = defmap[name](None)
|
|
||||||
|
|
||||||
return state, args
|
|
||||||
# --------
|
|
||||||
# Subcommand system
|
|
||||||
# --------
|
|
||||||
|
|
||||||
def _dispatch(args, cmdtable, globalopts):
|
|
||||||
cmd, func, args, options = cmdparse(args, cmdtable, globalopts)
|
|
||||||
|
|
||||||
if options.pop('help', False):
|
|
||||||
return 'help', cmdtable['help'][0], [cmd], {}
|
|
||||||
elif not cmd:
|
|
||||||
return 'help', cmdtable['help'][0], ['shortlist'], {}
|
|
||||||
|
|
||||||
return cmd, func, args, options
|
|
||||||
|
|
||||||
def cmdparse(args, cmdtable, globalopts):
|
|
||||||
# command is the first non-option
|
|
||||||
cmd = None
|
|
||||||
for arg in args:
|
|
||||||
if not arg.startswith('-'):
|
|
||||||
cmd = arg
|
|
||||||
break
|
|
||||||
|
|
||||||
if cmd:
|
|
||||||
args.pop(args.index(cmd))
|
|
||||||
|
|
||||||
aliases, info = findcmd(cmd, cmdtable)
|
|
||||||
cmd = aliases[0]
|
|
||||||
possibleopts = list(info[1])
|
|
||||||
else:
|
|
||||||
possibleopts = []
|
|
||||||
|
|
||||||
possibleopts.extend(globalopts)
|
|
||||||
|
|
||||||
try:
|
|
||||||
options, args = parse(args, possibleopts)
|
|
||||||
except getopt.GetoptError, e:
|
|
||||||
raise ParseError(cmd, e)
|
|
||||||
|
|
||||||
return (cmd, cmd and info[0] or None, args, options)
|
|
||||||
|
|
||||||
def aliases_(cmdtable_key):
|
|
||||||
return cmdtable_key.lstrip("^~").split("|")
|
|
||||||
|
|
||||||
def findpossible(cmd, table):
|
|
||||||
"""
|
|
||||||
Return cmd -> (aliases, command table entry)
|
|
||||||
for each matching command.
|
|
||||||
"""
|
|
||||||
choice = {}
|
|
||||||
for e in table.keys():
|
|
||||||
aliases = aliases_(e)
|
|
||||||
found = None
|
|
||||||
if cmd in aliases:
|
|
||||||
found = cmd
|
|
||||||
else:
|
|
||||||
for a in aliases:
|
|
||||||
if a.startswith(cmd):
|
|
||||||
found = a
|
|
||||||
break
|
|
||||||
if found is not None:
|
|
||||||
choice[found] = (aliases, table[e])
|
|
||||||
|
|
||||||
return choice
|
|
||||||
|
|
||||||
def findcmd(cmd, table):
|
|
||||||
"""Return (aliases, command table entry) for command string."""
|
|
||||||
choice = findpossible(cmd, table)
|
|
||||||
|
|
||||||
if cmd in choice:
|
|
||||||
return choice[cmd]
|
|
||||||
|
|
||||||
if len(choice) > 1:
|
|
||||||
clist = choice.keys()
|
|
||||||
clist.sort()
|
|
||||||
raise AmbiguousCommand(cmd, clist)
|
|
||||||
|
|
||||||
if choice:
|
|
||||||
return choice.values()[0]
|
|
||||||
|
|
||||||
raise UnknownCommand(cmd)
|
|
||||||
|
|
||||||
# --------
|
|
||||||
# Helpers
|
|
||||||
# --------
|
|
||||||
|
|
||||||
def guess_options(func):
|
|
||||||
args, varargs, varkw, defaults = inspect.getargspec(func)
|
|
||||||
for name, option in zip(args[-len(defaults):], defaults):
|
|
||||||
try:
|
|
||||||
sname, default, hlp = option
|
|
||||||
yield (sname, name.replace('_', '-'), default, hlp)
|
|
||||||
except TypeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def guess_usage(func, options):
|
|
||||||
usage = '%name '
|
|
||||||
if options:
|
|
||||||
usage += '[OPTIONS] '
|
|
||||||
args, varargs = inspect.getargspec(func)[:2]
|
|
||||||
argnum = len(args) - len(options)
|
|
||||||
if argnum > 0:
|
|
||||||
usage += args[0].upper()
|
|
||||||
if argnum > 1:
|
|
||||||
usage += 'S'
|
|
||||||
elif varargs:
|
|
||||||
usage += '[%s]' % varargs.upper()
|
|
||||||
return usage
|
|
||||||
|
|
||||||
def catcher(target, help_func):
|
|
||||||
'''Catches all exceptions and prints human-readable information on them
|
|
||||||
'''
|
|
||||||
try:
|
|
||||||
return target()
|
|
||||||
except UnknownCommand, e:
|
|
||||||
err("unknown command: '%s'\n" % e)
|
|
||||||
except AmbiguousCommand, e:
|
|
||||||
err("command '%s' is ambiguous:\n %s\n" %
|
|
||||||
(e.args[0], ' '.join(e.args[1])))
|
|
||||||
except ParseError, e:
|
|
||||||
err('%s: %s\n' % (e.args[0], e.args[1]))
|
|
||||||
help_func(e.args[0])
|
|
||||||
except getopt.GetoptError, e:
|
|
||||||
err('error: %s\n' % e)
|
|
||||||
help_func()
|
|
||||||
except FOError, e:
|
|
||||||
err('%s\n' % e)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
err('interrupted!\n')
|
|
||||||
except SystemExit:
|
|
||||||
raise
|
|
||||||
except:
|
|
||||||
err('unknown exception encountered')
|
|
||||||
raise
|
|
||||||
|
|
||||||
raise Abort
|
|
||||||
|
|
||||||
def call_cmd(name, func):
|
|
||||||
def inner(*args, **kwargs):
|
|
||||||
try:
|
|
||||||
return func(*args, **kwargs)
|
|
||||||
except TypeError:
|
|
||||||
if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
|
|
||||||
raise ParseError(name, "invalid arguments")
|
|
||||||
raise
|
|
||||||
return inner
|
|
||||||
|
|
||||||
def call_cmd_regular(func, opts):
|
|
||||||
def inner(*args, **kwargs):
|
|
||||||
funcargs, _, varkw, defaults = inspect.getargspec(func)
|
|
||||||
if len(args) > len(funcargs):
|
|
||||||
raise TypeError('You have supplied more positional arguments'
|
|
||||||
' than applicable')
|
|
||||||
|
|
||||||
funckwargs = dict((lname.replace('-', '_'), default)
|
|
||||||
for _, lname, default, _ in opts)
|
|
||||||
if 'help' not in (defaults or ()) and not varkw:
|
|
||||||
funckwargs.pop('help', None)
|
|
||||||
funckwargs.update(kwargs)
|
|
||||||
return func(*args, **funckwargs)
|
|
||||||
return inner
|
|
||||||
|
|
||||||
def replace_name(usage, name):
|
|
||||||
if '%name' in usage:
|
|
||||||
return usage.replace('%name', name, 1)
|
|
||||||
return name + ' ' + usage
|
|
||||||
|
|
||||||
def sysname():
|
|
||||||
name = sys.argv[0]
|
|
||||||
if name.startswith('./'):
|
|
||||||
return name[2:]
|
|
||||||
return name
|
|
||||||
|
|
||||||
try:
|
|
||||||
from functools import wraps
|
|
||||||
except ImportError:
|
|
||||||
def wraps(wrapped, assigned=('__module__', '__name__', '__doc__'),
|
|
||||||
updated=('__dict__',)):
|
|
||||||
def inner(wrapper):
|
|
||||||
for attr in assigned:
|
|
||||||
setattr(wrapper, attr, getattr(wrapped, attr))
|
|
||||||
for attr in updated:
|
|
||||||
getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
|
|
||||||
return wrapper
|
|
||||||
return inner
|
|
||||||
|
|
||||||
# --------
|
|
||||||
# Autocomplete system
|
|
||||||
# --------
|
|
||||||
|
|
||||||
# Borrowed from PIP
|
|
||||||
def autocomplete(cmdtable, args):
|
|
||||||
"""Command and option completion.
|
|
||||||
|
|
||||||
Enable by sourcing one of the completion shell scripts (bash or zsh).
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Don't complete if user hasn't sourced bash_completion file.
|
|
||||||
if not os.environ.has_key('OPSTER_AUTO_COMPLETE'):
|
|
||||||
return
|
|
||||||
cwords = os.environ['COMP_WORDS'].split()[1:]
|
|
||||||
cword = int(os.environ['COMP_CWORD'])
|
|
||||||
|
|
||||||
try:
|
|
||||||
current = cwords[cword-1]
|
|
||||||
except IndexError:
|
|
||||||
current = ''
|
|
||||||
|
|
||||||
commands = []
|
|
||||||
for k in cmdtable.keys():
|
|
||||||
commands += aliases_(k)
|
|
||||||
|
|
||||||
# command
|
|
||||||
if cword == 1:
|
|
||||||
print ' '.join(filter(lambda x: x.startswith(current), commands))
|
|
||||||
|
|
||||||
# command options
|
|
||||||
elif cwords[0] in commands:
|
|
||||||
options = []
|
|
||||||
aliases, (cmd, opts, usage) = findcmd(cwords[0], cmdtable)
|
|
||||||
for (short, long, default, help) in opts:
|
|
||||||
options.append('-%s' % short)
|
|
||||||
options.append('--%s' % long)
|
|
||||||
|
|
||||||
options = [o for o in options if o.startswith(current)]
|
|
||||||
print ' '.join(filter(lambda x: x.startswith(current), options))
|
|
||||||
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
COMPLETIONS = {
|
|
||||||
'bash':
|
|
||||||
"""
|
|
||||||
# opster bash completion start
|
|
||||||
_opster_completion()
|
|
||||||
{
|
|
||||||
COMPREPLY=( $( COMP_WORDS="${COMP_WORDS[*]}" \\
|
|
||||||
COMP_CWORD=$COMP_CWORD \\
|
|
||||||
OPSTER_AUTO_COMPLETE=1 $1 ) )
|
|
||||||
}
|
|
||||||
complete -o default -F _opster_completion %s
|
|
||||||
# opster bash completion end
|
|
||||||
""",
|
|
||||||
'zsh':
|
|
||||||
"""
|
|
||||||
# opster zsh completion start
|
|
||||||
function _opster_completion {
|
|
||||||
local words cword
|
|
||||||
read -Ac words
|
|
||||||
read -cn cword
|
|
||||||
reply=( $( COMP_WORDS="$words[*]" \\
|
|
||||||
COMP_CWORD=$(( cword-1 )) \\
|
|
||||||
OPSTER_AUTO_COMPLETE=1 $words[1] ) )
|
|
||||||
}
|
|
||||||
compctl -K _opster_completion %s
|
|
||||||
# opster zsh completion end
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
|
|
||||||
@command(name='_completion', hide=True)
|
|
||||||
def completion(type=('t', 'bash', 'Completion type (bash or zsh)')):
|
|
||||||
"""Outputs completion script for bash or zsh."""
|
|
||||||
|
|
||||||
prog_name = os.path.split(sys.argv[0])[1]
|
|
||||||
print COMPLETIONS[type] % prog_name
|
|
||||||
|
|
||||||
# --------
|
|
||||||
# Exceptions
|
|
||||||
# --------
|
|
||||||
|
|
||||||
# Command exceptions
|
|
||||||
class CommandException(Exception):
|
|
||||||
'Base class for command exceptions'
|
|
||||||
|
|
||||||
class AmbiguousCommand(CommandException):
|
|
||||||
'Raised if command is ambiguous'
|
|
||||||
|
|
||||||
class UnknownCommand(CommandException):
|
|
||||||
'Raised if command is unknown'
|
|
||||||
|
|
||||||
class ParseError(CommandException):
|
|
||||||
'Raised on error in command line parsing'
|
|
||||||
|
|
||||||
class Abort(CommandException):
|
|
||||||
'Abort execution'
|
|
||||||
|
|
||||||
class FOError(CommandException):
|
|
||||||
'Raised on trouble with opster configuration'
|
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
# Copyright (c) 2009 Raymond Hettinger
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person
|
||||||
|
# obtaining a copy of this software and associated documentation files
|
||||||
|
# (the "Software"), to deal in the Software without restriction,
|
||||||
|
# including without limitation the rights to use, copy, modify, merge,
|
||||||
|
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||||
|
# and to permit persons to whom the Software is furnished to do so,
|
||||||
|
# subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be
|
||||||
|
# included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
# OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
from UserDict import DictMixin
|
||||||
|
|
||||||
|
class OrderedDict(dict, DictMixin):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwds):
|
||||||
|
if len(args) > 1:
|
||||||
|
raise TypeError('expected at most 1 arguments, got %d' % len(args))
|
||||||
|
try:
|
||||||
|
self.__end
|
||||||
|
except AttributeError:
|
||||||
|
self.clear()
|
||||||
|
self.update(*args, **kwds)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self.__end = end = []
|
||||||
|
end += [None, end, end] # sentinel node for doubly linked list
|
||||||
|
self.__map = {} # key --> [key, prev, next]
|
||||||
|
dict.clear(self)
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
if key not in self:
|
||||||
|
end = self.__end
|
||||||
|
curr = end[1]
|
||||||
|
curr[2] = end[1] = self.__map[key] = [key, curr, end]
|
||||||
|
dict.__setitem__(self, key, value)
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
dict.__delitem__(self, key)
|
||||||
|
key, prev, next = self.__map.pop(key)
|
||||||
|
prev[2] = next
|
||||||
|
next[1] = prev
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
end = self.__end
|
||||||
|
curr = end[2]
|
||||||
|
while curr is not end:
|
||||||
|
yield curr[0]
|
||||||
|
curr = curr[2]
|
||||||
|
|
||||||
|
def __reversed__(self):
|
||||||
|
end = self.__end
|
||||||
|
curr = end[1]
|
||||||
|
while curr is not end:
|
||||||
|
yield curr[0]
|
||||||
|
curr = curr[1]
|
||||||
|
|
||||||
|
def popitem(self, last=True):
|
||||||
|
if not self:
|
||||||
|
raise KeyError('dictionary is empty')
|
||||||
|
if last:
|
||||||
|
key = reversed(self).next()
|
||||||
|
else:
|
||||||
|
key = iter(self).next()
|
||||||
|
value = self.pop(key)
|
||||||
|
return key, value
|
||||||
|
|
||||||
|
def __reduce__(self):
|
||||||
|
items = [[k, self[k]] for k in self]
|
||||||
|
tmp = self.__map, self.__end
|
||||||
|
del self.__map, self.__end
|
||||||
|
inst_dict = vars(self).copy()
|
||||||
|
self.__map, self.__end = tmp
|
||||||
|
if inst_dict:
|
||||||
|
return (self.__class__, (items,), inst_dict)
|
||||||
|
return self.__class__, (items,)
|
||||||
|
|
||||||
|
def keys(self):
|
||||||
|
return list(self)
|
||||||
|
|
||||||
|
setdefault = DictMixin.setdefault
|
||||||
|
update = DictMixin.update
|
||||||
|
pop = DictMixin.pop
|
||||||
|
values = DictMixin.values
|
||||||
|
items = DictMixin.items
|
||||||
|
iterkeys = DictMixin.iterkeys
|
||||||
|
itervalues = DictMixin.itervalues
|
||||||
|
iteritems = DictMixin.iteritems
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
if not self:
|
||||||
|
return '%s()' % (self.__class__.__name__,)
|
||||||
|
return '%s(%r)' % (self.__class__.__name__, self.items())
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
return self.__class__(self)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def fromkeys(cls, iterable, value=None):
|
||||||
|
d = cls()
|
||||||
|
for key in iterable:
|
||||||
|
d[key] = value
|
||||||
|
return d
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if isinstance(other, OrderedDict):
|
||||||
|
if len(self) != len(other):
|
||||||
|
return False
|
||||||
|
for p, q in zip(self.items(), other.items()):
|
||||||
|
if p != q:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
return dict.__eq__(self, other)
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return not self == other
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,36 +0,0 @@
|
|||||||
"""
|
|
||||||
This module allows doctest to find typechecked functions.
|
|
||||||
|
|
||||||
Currently, doctest verifies functions to make sure that their
|
|
||||||
globals() dict is the __dict__ of their module. In the case of
|
|
||||||
decorated functions, the globals() dict *is* not the right one.
|
|
||||||
|
|
||||||
To enable support for doctest do:
|
|
||||||
|
|
||||||
import typecheck.doctest_support
|
|
||||||
|
|
||||||
This import must occur before any calls to doctest methods.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __DocTestFinder_from_module(self, module, object):
|
|
||||||
"""
|
|
||||||
Return true if the given object is defined in the given
|
|
||||||
module.
|
|
||||||
"""
|
|
||||||
import inspect
|
|
||||||
|
|
||||||
if module is None:
|
|
||||||
return True
|
|
||||||
elif inspect.isfunction(object) or inspect.isclass(object):
|
|
||||||
return module.__name__ == object.__module__
|
|
||||||
elif inspect.getmodule(object) is not None:
|
|
||||||
return module is inspect.getmodule(object)
|
|
||||||
elif hasattr(object, '__module__'):
|
|
||||||
return module.__name__ == object.__module__
|
|
||||||
elif isinstance(object, property):
|
|
||||||
return True # [XX] no way not be sure.
|
|
||||||
else:
|
|
||||||
raise ValueError("object must be a class or function")
|
|
||||||
|
|
||||||
import doctest as __doctest
|
|
||||||
__doctest.DocTestFinder._from_module = __DocTestFinder_from_module
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
from typecheck import _TC_NestedError, _TC_TypeError, check_type, Or
|
|
||||||
from typecheck import register_type, _TC_Exception
|
|
||||||
|
|
||||||
class _TC_IterationError(_TC_NestedError):
|
|
||||||
def __init__(self, iteration, value, inner_exception):
|
|
||||||
_TC_NestedError.__init__(self, inner_exception)
|
|
||||||
|
|
||||||
self.iteration = iteration
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
def error_message(self):
|
|
||||||
return ("at iteration %d (value: %s)" % (self.iteration, repr(self.value))) + _TC_NestedError.error_message(self)
|
|
||||||
|
|
||||||
### This is the shadow class behind UnorderedIteratorMixin.
|
|
||||||
### Again, it tries to pretend it doesn't exist by mimicing
|
|
||||||
### the class of <obj> as much as possible.
|
|
||||||
###
|
|
||||||
### This mixin provides typechecking for iterator classes
|
|
||||||
### where you don't care about the order of the types (ie,
|
|
||||||
### you simply Or() the types together, as opposed to patterned
|
|
||||||
### lists, which would be ordered mixins)
|
|
||||||
class _UnorderedIteratorMixin(object):
|
|
||||||
def __init__(self, class_name, obj):
|
|
||||||
vals = [o for o in obj]
|
|
||||||
|
|
||||||
self.type = self
|
|
||||||
self._type = Or(*vals)
|
|
||||||
self.__cls = obj.__class__
|
|
||||||
self.__vals = vals
|
|
||||||
# This is necessary because it's a huge pain in the ass
|
|
||||||
# to get the "raw" name of the class once it's created
|
|
||||||
self.__cls_name = class_name
|
|
||||||
|
|
||||||
def __typecheck__(self, func, to_check):
|
|
||||||
if not isinstance(to_check, self.__cls):
|
|
||||||
raise _TC_TypeError(to_check, self)
|
|
||||||
|
|
||||||
for i, item in enumerate(to_check):
|
|
||||||
try:
|
|
||||||
check_type(self._type, func, item)
|
|
||||||
except _TC_Exception, e:
|
|
||||||
raise _TC_IterationError(i, item, e)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def __typesig__(cls, obj):
|
|
||||||
if isinstance(obj, cls):
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "%s(%s)" % (self.__cls_name, str(self._type))
|
|
||||||
|
|
||||||
__repr__ = __str__
|
|
||||||
|
|
||||||
### This is included in a class's parent-class section like so:
|
|
||||||
### class MyClass(UnorderedIteratorMixin("MyClass")):
|
|
||||||
### blah blah blah
|
|
||||||
###
|
|
||||||
### This serves as a class factory, whose produced classes
|
|
||||||
### attempt to mask the fact they exist. Their purpose
|
|
||||||
### is to redirect __typesig__ calls to appropriate
|
|
||||||
### instances of _UnorderedIteratorMixin
|
|
||||||
def UnorderedIteratorMixin(class_name):
|
|
||||||
class UIM(object):
|
|
||||||
@classmethod
|
|
||||||
def __typesig__(cls, obj):
|
|
||||||
if isinstance(obj, cls):
|
|
||||||
return _UnorderedIteratorMixin(class_name, obj)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "%s%s" % (class_name, str(tuple(e for e in self)))
|
|
||||||
|
|
||||||
# We register each produced class anew
|
|
||||||
# If someone needs to unregister these classes, they should
|
|
||||||
# save a copy of it before including it in the class-definition:
|
|
||||||
#
|
|
||||||
# my_UIM = UnorderedIteratorMixin("FooClass")
|
|
||||||
# class FooClass(my_UIM):
|
|
||||||
# ...
|
|
||||||
#
|
|
||||||
# Alternatively, you could just look in FooClass.__bases__ later; whatever
|
|
||||||
register_type(UIM)
|
|
||||||
return UIM
|
|
||||||
|
|
||||||
register_type(_UnorderedIteratorMixin)
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
from typecheck import CheckType, _TC_TypeError, check_type, Type
|
|
||||||
from typecheck import register_type, Or, _TC_Exception, _TC_KeyError
|
|
||||||
from typecheck import _TC_LengthError
|
|
||||||
|
|
||||||
### Provide typechecking for the built-in set() class
|
|
||||||
###
|
|
||||||
### XXX: Investigate rewriting this in terms of
|
|
||||||
### UnorderedIteratorMixin or Or()
|
|
||||||
class Set(CheckType):
|
|
||||||
def __init__(self, set_list):
|
|
||||||
self.type = set(set_list)
|
|
||||||
self._types = [Type(t) for t in self.type]
|
|
||||||
|
|
||||||
# self._type is used to build _TC_TypeError
|
|
||||||
if len(self._types) > 1:
|
|
||||||
self._type = Or(*self.type)
|
|
||||||
elif len(self._types) == 1:
|
|
||||||
# XXX Is there an easier way to get this?
|
|
||||||
t = self.type.pop()
|
|
||||||
self._type = t
|
|
||||||
self.type.add(t)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "Set(" + str([e for e in self.type]) + ")"
|
|
||||||
|
|
||||||
__repr__ = __str__
|
|
||||||
|
|
||||||
def __typecheck__(self, func, to_check):
|
|
||||||
if not isinstance(to_check, set):
|
|
||||||
raise _TC_TypeError(to_check, self.type)
|
|
||||||
|
|
||||||
if len(self._types) == 0 and len(to_check) > 0:
|
|
||||||
raise _TC_LengthError(len(to_check), 0)
|
|
||||||
|
|
||||||
for obj in to_check:
|
|
||||||
error = False
|
|
||||||
for type in self._types:
|
|
||||||
try:
|
|
||||||
check_type(type, func, obj)
|
|
||||||
except _TC_Exception:
|
|
||||||
error = True
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
error = False
|
|
||||||
break
|
|
||||||
if error:
|
|
||||||
raise _TC_KeyError(obj, _TC_TypeError(obj, self._type))
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
if self.__class__ is not other.__class__:
|
|
||||||
return False
|
|
||||||
return self.type == other.type
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return hash(str(hash(self.__class__)) + str(hash(frozenset(self.type))))
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def __typesig__(self, obj):
|
|
||||||
if isinstance(obj, set):
|
|
||||||
return Set(obj)
|
|
||||||
|
|
||||||
register_type(Set)
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
from typecheck import Typeclass
|
|
||||||
|
|
||||||
### Number
|
|
||||||
####################################################
|
|
||||||
|
|
||||||
_numbers = [int, float, complex, long, bool]
|
|
||||||
try:
|
|
||||||
from decimal import Decimal
|
|
||||||
_numbers.append(Decimal)
|
|
||||||
del Decimal
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
Number = Typeclass(*_numbers)
|
|
||||||
del _numbers
|
|
||||||
|
|
||||||
### String -- subinstance of ImSequence
|
|
||||||
####################################################
|
|
||||||
|
|
||||||
String = Typeclass(str, unicode)
|
|
||||||
|
|
||||||
### ImSequence -- immutable sequences
|
|
||||||
####################################################
|
|
||||||
|
|
||||||
ImSequence = Typeclass(tuple, xrange, String)
|
|
||||||
|
|
||||||
### MSequence -- mutable sequences
|
|
||||||
####################################################
|
|
||||||
|
|
||||||
MSequence = Typeclass(list)
|
|
||||||
|
|
||||||
### Mapping
|
|
||||||
####################################################
|
|
||||||
|
|
||||||
Mapping = Typeclass(dict)
|
|
||||||
Binary file not shown.
@@ -1,60 +1,113 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: ascii -*-
|
# -*- coding: windows-1251 -*-
|
||||||
# Portions Copyright (C) 2005 Kiseliov Roman
|
# Copyright (C) 2005 Kiseliov Roman
|
||||||
|
|
||||||
import xlwt
|
from xlwt import *
|
||||||
|
|
||||||
style = xlwt.easyxf(
|
fnt = Font()
|
||||||
"font: name Arial, colour_index blue, bold on;"
|
fnt.name = 'Arial'
|
||||||
"borders: top double, bottom double, left double, right double;"
|
fnt.colour_index = 4
|
||||||
)
|
fnt.bold = True
|
||||||
|
|
||||||
def write_data_cells(ws):
|
borders = Borders()
|
||||||
ws.write_merge(1, 1, 1, 5, 'test 1', style)
|
borders.left = 6
|
||||||
ws.write_merge(2, 2, 1, 4, 'test 1', style)
|
borders.right = 6
|
||||||
ws.write_merge(3, 3, 1, 3, 'test 2', style)
|
borders.top = 6
|
||||||
ws.write_merge(4, 4, 1, 4, 'test 1', style)
|
borders.bottom = 6
|
||||||
ws.write_merge(5, 5, 1, 4, 'test 3', style)
|
|
||||||
ws.write_merge(6, 6, 1, 5, 'test 1', style)
|
|
||||||
ws.write_merge(7, 7, 1, 5, 'test 4', style)
|
|
||||||
ws.write_merge(8, 8, 1, 4, 'test 1', style)
|
|
||||||
ws.write_merge(9, 9, 1, 3, 'test 5', style)
|
|
||||||
|
|
||||||
def write_row_outline_levels(ws):
|
style = XFStyle()
|
||||||
ws.row(1).level = 1
|
style.font = fnt
|
||||||
ws.row(2).level = 1
|
style.borders = borders
|
||||||
ws.row(3).level = 2
|
|
||||||
ws.row(4).level = 2
|
|
||||||
ws.row(5).level = 2
|
|
||||||
ws.row(6).level = 2
|
|
||||||
ws.row(7).level = 2
|
|
||||||
ws.row(8).level = 1
|
|
||||||
ws.row(9).level = 1
|
|
||||||
|
|
||||||
def write_column_outline_levels(ws):
|
wb = Workbook()
|
||||||
ws.col(1).level = 1
|
|
||||||
ws.col(2).level = 1
|
|
||||||
ws.col(3).level = 2
|
|
||||||
ws.col(4).level = 2
|
|
||||||
ws.col(5).level = 2
|
|
||||||
ws.col(6).level = 2
|
|
||||||
ws.col(7).level = 2
|
|
||||||
ws.col(8).level = 1
|
|
||||||
ws.col(9).level = 1
|
|
||||||
|
|
||||||
wb = xlwt.Workbook()
|
|
||||||
|
|
||||||
ws0 = wb.add_sheet('Rows Outline')
|
ws0 = wb.add_sheet('Rows Outline')
|
||||||
write_data_cells(ws0)
|
|
||||||
write_row_outline_levels(ws0)
|
ws0.write_merge(1, 1, 1, 5, 'test 1', style)
|
||||||
|
ws0.write_merge(2, 2, 1, 4, 'test 1', style)
|
||||||
|
ws0.write_merge(3, 3, 1, 3, 'test 2', style)
|
||||||
|
ws0.write_merge(4, 4, 1, 4, 'test 1', style)
|
||||||
|
ws0.write_merge(5, 5, 1, 4, 'test 3', style)
|
||||||
|
ws0.write_merge(6, 6, 1, 5, 'test 1', style)
|
||||||
|
ws0.write_merge(7, 7, 1, 5, 'test 4', style)
|
||||||
|
ws0.write_merge(8, 8, 1, 4, 'test 1', style)
|
||||||
|
ws0.write_merge(9, 9, 1, 3, 'test 5', style)
|
||||||
|
|
||||||
|
ws0.row(1).level = 1
|
||||||
|
ws0.row(2).level = 1
|
||||||
|
ws0.row(3).level = 2
|
||||||
|
ws0.row(4).level = 2
|
||||||
|
ws0.row(5).level = 2
|
||||||
|
ws0.row(6).level = 2
|
||||||
|
ws0.row(7).level = 2
|
||||||
|
ws0.row(8).level = 1
|
||||||
|
ws0.row(9).level = 1
|
||||||
|
|
||||||
|
|
||||||
ws1 = wb.add_sheet('Columns Outline')
|
ws1 = wb.add_sheet('Columns Outline')
|
||||||
write_data_cells(ws1)
|
|
||||||
write_column_outline_levels(ws1)
|
ws1.write_merge(1, 1, 1, 5, 'test 1', style)
|
||||||
|
ws1.write_merge(2, 2, 1, 4, 'test 1', style)
|
||||||
|
ws1.write_merge(3, 3, 1, 3, 'test 2', style)
|
||||||
|
ws1.write_merge(4, 4, 1, 4, 'test 1', style)
|
||||||
|
ws1.write_merge(5, 5, 1, 4, 'test 3', style)
|
||||||
|
ws1.write_merge(6, 6, 1, 5, 'test 1', style)
|
||||||
|
ws1.write_merge(7, 7, 1, 5, 'test 4', style)
|
||||||
|
ws1.write_merge(8, 8, 1, 4, 'test 1', style)
|
||||||
|
ws1.write_merge(9, 9, 1, 3, 'test 5', style)
|
||||||
|
|
||||||
|
ws1.col(1).level = 1
|
||||||
|
ws1.col(2).level = 1
|
||||||
|
ws1.col(3).level = 2
|
||||||
|
ws1.col(4).level = 2
|
||||||
|
ws1.col(5).level = 2
|
||||||
|
ws1.col(6).level = 2
|
||||||
|
ws1.col(7).level = 2
|
||||||
|
ws1.col(8).level = 1
|
||||||
|
ws1.col(9).level = 1
|
||||||
|
|
||||||
|
|
||||||
ws2 = wb.add_sheet('Rows and Columns Outline')
|
ws2 = wb.add_sheet('Rows and Columns Outline')
|
||||||
write_data_cells(ws2)
|
|
||||||
write_row_outline_levels(ws2)
|
ws2.write_merge(1, 1, 1, 5, 'test 1', style)
|
||||||
write_column_outline_levels(ws2)
|
ws2.write_merge(2, 2, 1, 4, 'test 1', style)
|
||||||
|
ws2.write_merge(3, 3, 1, 3, 'test 2', style)
|
||||||
|
ws2.write_merge(4, 4, 1, 4, 'test 1', style)
|
||||||
|
ws2.write_merge(5, 5, 1, 4, 'test 3', style)
|
||||||
|
ws2.write_merge(6, 6, 1, 5, 'test 1', style)
|
||||||
|
ws2.write_merge(7, 7, 1, 5, 'test 4', style)
|
||||||
|
ws2.write_merge(8, 8, 1, 4, 'test 1', style)
|
||||||
|
ws2.write_merge(9, 9, 1, 3, 'test 5', style)
|
||||||
|
|
||||||
|
ws2.row(1).level = 1
|
||||||
|
ws2.row(2).level = 1
|
||||||
|
ws2.row(3).level = 2
|
||||||
|
ws2.row(4).level = 2
|
||||||
|
ws2.row(5).level = 2
|
||||||
|
ws2.row(6).level = 2
|
||||||
|
ws2.row(7).level = 2
|
||||||
|
ws2.row(8).level = 1
|
||||||
|
ws2.row(9).level = 1
|
||||||
|
|
||||||
|
ws2.write_merge(1, 1, 1, 5, 'test 1', style)
|
||||||
|
ws2.write_merge(2, 2, 1, 4, 'test 1', style)
|
||||||
|
ws2.write_merge(3, 3, 1, 3, 'test 2', style)
|
||||||
|
ws2.write_merge(4, 4, 1, 4, 'test 1', style)
|
||||||
|
ws2.write_merge(5, 5, 1, 4, 'test 3', style)
|
||||||
|
ws2.write_merge(6, 6, 1, 5, 'test 1', style)
|
||||||
|
ws2.write_merge(7, 7, 1, 5, 'test 4', style)
|
||||||
|
ws2.write_merge(8, 8, 1, 4, 'test 1', style)
|
||||||
|
ws2.write_merge(9, 9, 1, 3, 'test 5', style)
|
||||||
|
|
||||||
|
ws2.col(1).level = 1
|
||||||
|
ws2.col(2).level = 1
|
||||||
|
ws2.col(3).level = 2
|
||||||
|
ws2.col(4).level = 2
|
||||||
|
ws2.col(5).level = 2
|
||||||
|
ws2.col(6).level = 2
|
||||||
|
ws2.col(7).level = 2
|
||||||
|
ws2.col(8).level = 1
|
||||||
|
ws2.col(9).level = 1
|
||||||
|
|
||||||
|
|
||||||
wb.save('outline.xls')
|
wb.save('outline.xls')
|
||||||
|
|||||||
@@ -87,18 +87,6 @@ ws2.row(7).level = 2
|
|||||||
ws2.row(8).level = 1
|
ws2.row(8).level = 1
|
||||||
ws2.row(9).level = 1
|
ws2.row(9).level = 1
|
||||||
|
|
||||||
ws2.write_merge(3, 3, 1, 3, 'test 2', style)
|
|
||||||
if 0:
|
|
||||||
ws2.write_merge(1, 1, 1, 5, 'test 1', style)
|
|
||||||
ws2.write_merge(2, 2, 1, 4, 'test 1', style)
|
|
||||||
ws2.write_merge(3, 3, 1, 3, 'test 2', style)
|
|
||||||
ws2.write_merge(4, 4, 1, 4, 'test 1', style)
|
|
||||||
ws2.write_merge(5, 5, 1, 4, 'test 3', style)
|
|
||||||
ws2.write_merge(6, 6, 1, 5, 'test 1', style)
|
|
||||||
ws2.write_merge(7, 7, 1, 5, 'test 4', style)
|
|
||||||
ws2.write_merge(8, 8, 1, 4, 'test 1', style)
|
|
||||||
ws2.write_merge(9, 9, 1, 3, 'test 5', style)
|
|
||||||
|
|
||||||
ws2.col(1).level = 1
|
ws2.col(1).level = 1
|
||||||
ws2.col(2).level = 1
|
ws2.col(2).level = 1
|
||||||
ws2.col(3).level = 2
|
ws2.col(3).level = 2
|
||||||
@@ -109,26 +97,26 @@ ws2.col(7).level = 2
|
|||||||
ws2.col(8).level = 1
|
ws2.col(8).level = 1
|
||||||
ws2.col(9).level = 1
|
ws2.col(9).level = 1
|
||||||
|
|
||||||
if 0:
|
|
||||||
ws0.protect = True
|
|
||||||
ws0.wnd_protect = True
|
|
||||||
ws0.obj_protect = True
|
|
||||||
ws0.scen_protect = True
|
|
||||||
ws0.password = "123456"
|
|
||||||
|
|
||||||
ws1.protect = True
|
ws0.protect = True
|
||||||
ws1.wnd_protect = True
|
ws0.wnd_protect = True
|
||||||
ws1.obj_protect = True
|
ws0.obj_protect = True
|
||||||
ws1.scen_protect = True
|
ws0.scen_protect = True
|
||||||
ws1.password = "abcdefghij"
|
ws0.password = "123456"
|
||||||
|
|
||||||
ws2.protect = True
|
ws1.protect = True
|
||||||
ws2.wnd_protect = True
|
ws1.wnd_protect = True
|
||||||
ws2.obj_protect = True
|
ws1.obj_protect = True
|
||||||
ws2.scen_protect = True
|
ws1.scen_protect = True
|
||||||
ws2.password = "ok"
|
ws1.password = "abcdefghij"
|
||||||
|
|
||||||
wb.protect = True
|
ws2.protect = True
|
||||||
wb.wnd_protect = True
|
ws2.wnd_protect = True
|
||||||
wb.obj_protect = True
|
ws2.obj_protect = True
|
||||||
|
ws2.scen_protect = True
|
||||||
|
ws2.password = "ok"
|
||||||
|
|
||||||
|
wb.protect = True
|
||||||
|
wb.wnd_protect = True
|
||||||
|
wb.obj_protect = True
|
||||||
wb.save('protection.xls')
|
wb.save('protection.xls')
|
||||||
|
|||||||
@@ -0,0 +1,288 @@
|
|||||||
|
|
||||||
|
from error import *
|
||||||
|
|
||||||
|
from tokens import *
|
||||||
|
from events import *
|
||||||
|
from nodes import *
|
||||||
|
|
||||||
|
from loader import *
|
||||||
|
from dumper import *
|
||||||
|
|
||||||
|
__version__ = '3.09'
|
||||||
|
|
||||||
|
try:
|
||||||
|
from cyaml import *
|
||||||
|
__with_libyaml__ = True
|
||||||
|
except ImportError:
|
||||||
|
__with_libyaml__ = False
|
||||||
|
|
||||||
|
def scan(stream, Loader=Loader):
|
||||||
|
"""
|
||||||
|
Scan a YAML stream and produce scanning tokens.
|
||||||
|
"""
|
||||||
|
loader = Loader(stream)
|
||||||
|
while loader.check_token():
|
||||||
|
yield loader.get_token()
|
||||||
|
|
||||||
|
def parse(stream, Loader=Loader):
|
||||||
|
"""
|
||||||
|
Parse a YAML stream and produce parsing events.
|
||||||
|
"""
|
||||||
|
loader = Loader(stream)
|
||||||
|
while loader.check_event():
|
||||||
|
yield loader.get_event()
|
||||||
|
|
||||||
|
def compose(stream, Loader=Loader):
|
||||||
|
"""
|
||||||
|
Parse the first YAML document in a stream
|
||||||
|
and produce the corresponding representation tree.
|
||||||
|
"""
|
||||||
|
loader = Loader(stream)
|
||||||
|
return loader.get_single_node()
|
||||||
|
|
||||||
|
def compose_all(stream, Loader=Loader):
|
||||||
|
"""
|
||||||
|
Parse all YAML documents in a stream
|
||||||
|
and produce corresponding representation trees.
|
||||||
|
"""
|
||||||
|
loader = Loader(stream)
|
||||||
|
while loader.check_node():
|
||||||
|
yield loader.get_node()
|
||||||
|
|
||||||
|
def load(stream, Loader=Loader):
|
||||||
|
"""
|
||||||
|
Parse the first YAML document in a stream
|
||||||
|
and produce the corresponding Python object.
|
||||||
|
"""
|
||||||
|
loader = Loader(stream)
|
||||||
|
return loader.get_single_data()
|
||||||
|
|
||||||
|
def load_all(stream, Loader=Loader):
|
||||||
|
"""
|
||||||
|
Parse all YAML documents in a stream
|
||||||
|
and produce corresponding Python objects.
|
||||||
|
"""
|
||||||
|
loader = Loader(stream)
|
||||||
|
while loader.check_data():
|
||||||
|
yield loader.get_data()
|
||||||
|
|
||||||
|
def safe_load(stream):
|
||||||
|
"""
|
||||||
|
Parse the first YAML document in a stream
|
||||||
|
and produce the corresponding Python object.
|
||||||
|
Resolve only basic YAML tags.
|
||||||
|
"""
|
||||||
|
return load(stream, SafeLoader)
|
||||||
|
|
||||||
|
def safe_load_all(stream):
|
||||||
|
"""
|
||||||
|
Parse all YAML documents in a stream
|
||||||
|
and produce corresponding Python objects.
|
||||||
|
Resolve only basic YAML tags.
|
||||||
|
"""
|
||||||
|
return load_all(stream, SafeLoader)
|
||||||
|
|
||||||
|
def emit(events, stream=None, Dumper=Dumper,
|
||||||
|
canonical=None, indent=None, width=None,
|
||||||
|
allow_unicode=None, line_break=None):
|
||||||
|
"""
|
||||||
|
Emit YAML parsing events into a stream.
|
||||||
|
If stream is None, return the produced string instead.
|
||||||
|
"""
|
||||||
|
getvalue = None
|
||||||
|
if stream is None:
|
||||||
|
from StringIO import StringIO
|
||||||
|
stream = StringIO()
|
||||||
|
getvalue = stream.getvalue
|
||||||
|
dumper = Dumper(stream, canonical=canonical, indent=indent, width=width,
|
||||||
|
allow_unicode=allow_unicode, line_break=line_break)
|
||||||
|
for event in events:
|
||||||
|
dumper.emit(event)
|
||||||
|
if getvalue:
|
||||||
|
return getvalue()
|
||||||
|
|
||||||
|
def serialize_all(nodes, stream=None, Dumper=Dumper,
|
||||||
|
canonical=None, indent=None, width=None,
|
||||||
|
allow_unicode=None, line_break=None,
|
||||||
|
encoding='utf-8', explicit_start=None, explicit_end=None,
|
||||||
|
version=None, tags=None):
|
||||||
|
"""
|
||||||
|
Serialize a sequence of representation trees into a YAML stream.
|
||||||
|
If stream is None, return the produced string instead.
|
||||||
|
"""
|
||||||
|
getvalue = None
|
||||||
|
if stream is None:
|
||||||
|
if encoding is None:
|
||||||
|
from StringIO import StringIO
|
||||||
|
else:
|
||||||
|
from cStringIO import StringIO
|
||||||
|
stream = StringIO()
|
||||||
|
getvalue = stream.getvalue
|
||||||
|
dumper = Dumper(stream, canonical=canonical, indent=indent, width=width,
|
||||||
|
allow_unicode=allow_unicode, line_break=line_break,
|
||||||
|
encoding=encoding, version=version, tags=tags,
|
||||||
|
explicit_start=explicit_start, explicit_end=explicit_end)
|
||||||
|
dumper.open()
|
||||||
|
for node in nodes:
|
||||||
|
dumper.serialize(node)
|
||||||
|
dumper.close()
|
||||||
|
if getvalue:
|
||||||
|
return getvalue()
|
||||||
|
|
||||||
|
def serialize(node, stream=None, Dumper=Dumper, **kwds):
|
||||||
|
"""
|
||||||
|
Serialize a representation tree into a YAML stream.
|
||||||
|
If stream is None, return the produced string instead.
|
||||||
|
"""
|
||||||
|
return serialize_all([node], stream, Dumper=Dumper, **kwds)
|
||||||
|
|
||||||
|
def dump_all(documents, stream=None, Dumper=Dumper,
|
||||||
|
default_style=None, default_flow_style=None,
|
||||||
|
canonical=None, indent=None, width=None,
|
||||||
|
allow_unicode=None, line_break=None,
|
||||||
|
encoding='utf-8', explicit_start=None, explicit_end=None,
|
||||||
|
version=None, tags=None):
|
||||||
|
"""
|
||||||
|
Serialize a sequence of Python objects into a YAML stream.
|
||||||
|
If stream is None, return the produced string instead.
|
||||||
|
"""
|
||||||
|
getvalue = None
|
||||||
|
if stream is None:
|
||||||
|
if encoding is None:
|
||||||
|
from StringIO import StringIO
|
||||||
|
else:
|
||||||
|
from cStringIO import StringIO
|
||||||
|
stream = StringIO()
|
||||||
|
getvalue = stream.getvalue
|
||||||
|
dumper = Dumper(stream, default_style=default_style,
|
||||||
|
default_flow_style=default_flow_style,
|
||||||
|
canonical=canonical, indent=indent, width=width,
|
||||||
|
allow_unicode=allow_unicode, line_break=line_break,
|
||||||
|
encoding=encoding, version=version, tags=tags,
|
||||||
|
explicit_start=explicit_start, explicit_end=explicit_end)
|
||||||
|
dumper.open()
|
||||||
|
for data in documents:
|
||||||
|
dumper.represent(data)
|
||||||
|
dumper.close()
|
||||||
|
if getvalue:
|
||||||
|
return getvalue()
|
||||||
|
|
||||||
|
def dump(data, stream=None, Dumper=Dumper, **kwds):
|
||||||
|
"""
|
||||||
|
Serialize a Python object into a YAML stream.
|
||||||
|
If stream is None, return the produced string instead.
|
||||||
|
"""
|
||||||
|
return dump_all([data], stream, Dumper=Dumper, **kwds)
|
||||||
|
|
||||||
|
def safe_dump_all(documents, stream=None, **kwds):
|
||||||
|
"""
|
||||||
|
Serialize a sequence of Python objects into a YAML stream.
|
||||||
|
Produce only basic YAML tags.
|
||||||
|
If stream is None, return the produced string instead.
|
||||||
|
"""
|
||||||
|
return dump_all(documents, stream, Dumper=SafeDumper, **kwds)
|
||||||
|
|
||||||
|
def safe_dump(data, stream=None, **kwds):
|
||||||
|
"""
|
||||||
|
Serialize a Python object into a YAML stream.
|
||||||
|
Produce only basic YAML tags.
|
||||||
|
If stream is None, return the produced string instead.
|
||||||
|
"""
|
||||||
|
return dump_all([data], stream, Dumper=SafeDumper, **kwds)
|
||||||
|
|
||||||
|
def add_implicit_resolver(tag, regexp, first=None,
|
||||||
|
Loader=Loader, Dumper=Dumper):
|
||||||
|
"""
|
||||||
|
Add an implicit scalar detector.
|
||||||
|
If an implicit scalar value matches the given regexp,
|
||||||
|
the corresponding tag is assigned to the scalar.
|
||||||
|
first is a sequence of possible initial characters or None.
|
||||||
|
"""
|
||||||
|
Loader.add_implicit_resolver(tag, regexp, first)
|
||||||
|
Dumper.add_implicit_resolver(tag, regexp, first)
|
||||||
|
|
||||||
|
def add_path_resolver(tag, path, kind=None, Loader=Loader, Dumper=Dumper):
|
||||||
|
"""
|
||||||
|
Add a path based resolver for the given tag.
|
||||||
|
A path is a list of keys that forms a path
|
||||||
|
to a node in the representation tree.
|
||||||
|
Keys can be string values, integers, or None.
|
||||||
|
"""
|
||||||
|
Loader.add_path_resolver(tag, path, kind)
|
||||||
|
Dumper.add_path_resolver(tag, path, kind)
|
||||||
|
|
||||||
|
def add_constructor(tag, constructor, Loader=Loader):
|
||||||
|
"""
|
||||||
|
Add a constructor for the given tag.
|
||||||
|
Constructor is a function that accepts a Loader instance
|
||||||
|
and a node object and produces the corresponding Python object.
|
||||||
|
"""
|
||||||
|
Loader.add_constructor(tag, constructor)
|
||||||
|
|
||||||
|
def add_multi_constructor(tag_prefix, multi_constructor, Loader=Loader):
|
||||||
|
"""
|
||||||
|
Add a multi-constructor for the given tag prefix.
|
||||||
|
Multi-constructor is called for a node if its tag starts with tag_prefix.
|
||||||
|
Multi-constructor accepts a Loader instance, a tag suffix,
|
||||||
|
and a node object and produces the corresponding Python object.
|
||||||
|
"""
|
||||||
|
Loader.add_multi_constructor(tag_prefix, multi_constructor)
|
||||||
|
|
||||||
|
def add_representer(data_type, representer, Dumper=Dumper):
|
||||||
|
"""
|
||||||
|
Add a representer for the given type.
|
||||||
|
Representer is a function accepting a Dumper instance
|
||||||
|
and an instance of the given data type
|
||||||
|
and producing the corresponding representation node.
|
||||||
|
"""
|
||||||
|
Dumper.add_representer(data_type, representer)
|
||||||
|
|
||||||
|
def add_multi_representer(data_type, multi_representer, Dumper=Dumper):
|
||||||
|
"""
|
||||||
|
Add a representer for the given type.
|
||||||
|
Multi-representer is a function accepting a Dumper instance
|
||||||
|
and an instance of the given data type or subtype
|
||||||
|
and producing the corresponding representation node.
|
||||||
|
"""
|
||||||
|
Dumper.add_multi_representer(data_type, multi_representer)
|
||||||
|
|
||||||
|
class YAMLObjectMetaclass(type):
|
||||||
|
"""
|
||||||
|
The metaclass for YAMLObject.
|
||||||
|
"""
|
||||||
|
def __init__(cls, name, bases, kwds):
|
||||||
|
super(YAMLObjectMetaclass, cls).__init__(name, bases, kwds)
|
||||||
|
if 'yaml_tag' in kwds and kwds['yaml_tag'] is not None:
|
||||||
|
cls.yaml_loader.add_constructor(cls.yaml_tag, cls.from_yaml)
|
||||||
|
cls.yaml_dumper.add_representer(cls, cls.to_yaml)
|
||||||
|
|
||||||
|
class YAMLObject(object):
|
||||||
|
"""
|
||||||
|
An object that can dump itself to a YAML stream
|
||||||
|
and load itself from a YAML stream.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__metaclass__ = YAMLObjectMetaclass
|
||||||
|
__slots__ = () # no direct instantiation, so allow immutable subclasses
|
||||||
|
|
||||||
|
yaml_loader = Loader
|
||||||
|
yaml_dumper = Dumper
|
||||||
|
|
||||||
|
yaml_tag = None
|
||||||
|
yaml_flow_style = None
|
||||||
|
|
||||||
|
def from_yaml(cls, loader, node):
|
||||||
|
"""
|
||||||
|
Convert a representation node to a Python object.
|
||||||
|
"""
|
||||||
|
return loader.construct_yaml_object(node, cls)
|
||||||
|
from_yaml = classmethod(from_yaml)
|
||||||
|
|
||||||
|
def to_yaml(cls, dumper, data):
|
||||||
|
"""
|
||||||
|
Convert a Python object to a representation node.
|
||||||
|
"""
|
||||||
|
return dumper.represent_yaml_object(cls.yaml_tag, data, cls,
|
||||||
|
flow_style=cls.yaml_flow_style)
|
||||||
|
to_yaml = classmethod(to_yaml)
|
||||||
|
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
|
||||||
|
__all__ = ['Composer', 'ComposerError']
|
||||||
|
|
||||||
|
from error import MarkedYAMLError
|
||||||
|
from events import *
|
||||||
|
from nodes import *
|
||||||
|
|
||||||
|
class ComposerError(MarkedYAMLError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Composer(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.anchors = {}
|
||||||
|
|
||||||
|
def check_node(self):
|
||||||
|
# Drop the STREAM-START event.
|
||||||
|
if self.check_event(StreamStartEvent):
|
||||||
|
self.get_event()
|
||||||
|
|
||||||
|
# If there are more documents available?
|
||||||
|
return not self.check_event(StreamEndEvent)
|
||||||
|
|
||||||
|
def get_node(self):
|
||||||
|
# Get the root node of the next document.
|
||||||
|
if not self.check_event(StreamEndEvent):
|
||||||
|
return self.compose_document()
|
||||||
|
|
||||||
|
def get_single_node(self):
|
||||||
|
# Drop the STREAM-START event.
|
||||||
|
self.get_event()
|
||||||
|
|
||||||
|
# Compose a document if the stream is not empty.
|
||||||
|
document = None
|
||||||
|
if not self.check_event(StreamEndEvent):
|
||||||
|
document = self.compose_document()
|
||||||
|
|
||||||
|
# Ensure that the stream contains no more documents.
|
||||||
|
if not self.check_event(StreamEndEvent):
|
||||||
|
event = self.get_event()
|
||||||
|
raise ComposerError("expected a single document in the stream",
|
||||||
|
document.start_mark, "but found another document",
|
||||||
|
event.start_mark)
|
||||||
|
|
||||||
|
# Drop the STREAM-END event.
|
||||||
|
self.get_event()
|
||||||
|
|
||||||
|
return document
|
||||||
|
|
||||||
|
def compose_document(self):
|
||||||
|
# Drop the DOCUMENT-START event.
|
||||||
|
self.get_event()
|
||||||
|
|
||||||
|
# Compose the root node.
|
||||||
|
node = self.compose_node(None, None)
|
||||||
|
|
||||||
|
# Drop the DOCUMENT-END event.
|
||||||
|
self.get_event()
|
||||||
|
|
||||||
|
self.anchors = {}
|
||||||
|
return node
|
||||||
|
|
||||||
|
def compose_node(self, parent, index):
|
||||||
|
if self.check_event(AliasEvent):
|
||||||
|
event = self.get_event()
|
||||||
|
anchor = event.anchor
|
||||||
|
if anchor not in self.anchors:
|
||||||
|
raise ComposerError(None, None, "found undefined alias %r"
|
||||||
|
% anchor.encode('utf-8'), event.start_mark)
|
||||||
|
return self.anchors[anchor]
|
||||||
|
event = self.peek_event()
|
||||||
|
anchor = event.anchor
|
||||||
|
if anchor is not None:
|
||||||
|
if anchor in self.anchors:
|
||||||
|
raise ComposerError("found duplicate anchor %r; first occurence"
|
||||||
|
% anchor.encode('utf-8'), self.anchors[anchor].start_mark,
|
||||||
|
"second occurence", event.start_mark)
|
||||||
|
self.descend_resolver(parent, index)
|
||||||
|
if self.check_event(ScalarEvent):
|
||||||
|
node = self.compose_scalar_node(anchor)
|
||||||
|
elif self.check_event(SequenceStartEvent):
|
||||||
|
node = self.compose_sequence_node(anchor)
|
||||||
|
elif self.check_event(MappingStartEvent):
|
||||||
|
node = self.compose_mapping_node(anchor)
|
||||||
|
self.ascend_resolver()
|
||||||
|
return node
|
||||||
|
|
||||||
|
def compose_scalar_node(self, anchor):
|
||||||
|
event = self.get_event()
|
||||||
|
tag = event.tag
|
||||||
|
if tag is None or tag == u'!':
|
||||||
|
tag = self.resolve(ScalarNode, event.value, event.implicit)
|
||||||
|
node = ScalarNode(tag, event.value,
|
||||||
|
event.start_mark, event.end_mark, style=event.style)
|
||||||
|
if anchor is not None:
|
||||||
|
self.anchors[anchor] = node
|
||||||
|
return node
|
||||||
|
|
||||||
|
def compose_sequence_node(self, anchor):
|
||||||
|
start_event = self.get_event()
|
||||||
|
tag = start_event.tag
|
||||||
|
if tag is None or tag == u'!':
|
||||||
|
tag = self.resolve(SequenceNode, None, start_event.implicit)
|
||||||
|
node = SequenceNode(tag, [],
|
||||||
|
start_event.start_mark, None,
|
||||||
|
flow_style=start_event.flow_style)
|
||||||
|
if anchor is not None:
|
||||||
|
self.anchors[anchor] = node
|
||||||
|
index = 0
|
||||||
|
while not self.check_event(SequenceEndEvent):
|
||||||
|
node.value.append(self.compose_node(node, index))
|
||||||
|
index += 1
|
||||||
|
end_event = self.get_event()
|
||||||
|
node.end_mark = end_event.end_mark
|
||||||
|
return node
|
||||||
|
|
||||||
|
def compose_mapping_node(self, anchor):
|
||||||
|
start_event = self.get_event()
|
||||||
|
tag = start_event.tag
|
||||||
|
if tag is None or tag == u'!':
|
||||||
|
tag = self.resolve(MappingNode, None, start_event.implicit)
|
||||||
|
node = MappingNode(tag, [],
|
||||||
|
start_event.start_mark, None,
|
||||||
|
flow_style=start_event.flow_style)
|
||||||
|
if anchor is not None:
|
||||||
|
self.anchors[anchor] = node
|
||||||
|
while not self.check_event(MappingEndEvent):
|
||||||
|
#key_event = self.peek_event()
|
||||||
|
item_key = self.compose_node(node, None)
|
||||||
|
#if item_key in node.value:
|
||||||
|
# raise ComposerError("while composing a mapping", start_event.start_mark,
|
||||||
|
# "found duplicate key", key_event.start_mark)
|
||||||
|
item_value = self.compose_node(node, item_key)
|
||||||
|
#node.value[item_key] = item_value
|
||||||
|
node.value.append((item_key, item_value))
|
||||||
|
end_event = self.get_event()
|
||||||
|
node.end_mark = end_event.end_mark
|
||||||
|
return node
|
||||||
|
|
||||||
@@ -0,0 +1,684 @@
|
|||||||
|
|
||||||
|
__all__ = ['BaseConstructor', 'SafeConstructor', 'Constructor',
|
||||||
|
'ConstructorError']
|
||||||
|
|
||||||
|
from error import *
|
||||||
|
from nodes import *
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
try:
|
||||||
|
set
|
||||||
|
except NameError:
|
||||||
|
from sets import Set as set
|
||||||
|
|
||||||
|
import binascii, re, sys, types
|
||||||
|
|
||||||
|
class ConstructorError(MarkedYAMLError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class BaseConstructor(object):
|
||||||
|
|
||||||
|
yaml_constructors = {}
|
||||||
|
yaml_multi_constructors = {}
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.constructed_objects = {}
|
||||||
|
self.recursive_objects = {}
|
||||||
|
self.state_generators = []
|
||||||
|
self.deep_construct = False
|
||||||
|
|
||||||
|
def check_data(self):
|
||||||
|
# If there are more documents available?
|
||||||
|
return self.check_node()
|
||||||
|
|
||||||
|
def get_data(self):
|
||||||
|
# Construct and return the next document.
|
||||||
|
if self.check_node():
|
||||||
|
return self.construct_document(self.get_node())
|
||||||
|
|
||||||
|
def get_single_data(self):
|
||||||
|
# Ensure that the stream contains a single document and construct it.
|
||||||
|
node = self.get_single_node()
|
||||||
|
if node is not None:
|
||||||
|
return self.construct_document(node)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def construct_document(self, node):
|
||||||
|
data = self.construct_object(node)
|
||||||
|
while self.state_generators:
|
||||||
|
state_generators = self.state_generators
|
||||||
|
self.state_generators = []
|
||||||
|
for generator in state_generators:
|
||||||
|
for dummy in generator:
|
||||||
|
pass
|
||||||
|
self.constructed_objects = {}
|
||||||
|
self.recursive_objects = {}
|
||||||
|
self.deep_construct = False
|
||||||
|
return data
|
||||||
|
|
||||||
|
def construct_object(self, node, deep=False):
|
||||||
|
if deep:
|
||||||
|
old_deep = self.deep_construct
|
||||||
|
self.deep_construct = True
|
||||||
|
if node in self.constructed_objects:
|
||||||
|
return self.constructed_objects[node]
|
||||||
|
if node in self.recursive_objects:
|
||||||
|
raise ConstructorError(None, None,
|
||||||
|
"found unconstructable recursive node", node.start_mark)
|
||||||
|
self.recursive_objects[node] = None
|
||||||
|
constructor = None
|
||||||
|
tag_suffix = None
|
||||||
|
if node.tag in self.yaml_constructors:
|
||||||
|
constructor = self.yaml_constructors[node.tag]
|
||||||
|
else:
|
||||||
|
for tag_prefix in self.yaml_multi_constructors:
|
||||||
|
if node.tag.startswith(tag_prefix):
|
||||||
|
tag_suffix = node.tag[len(tag_prefix):]
|
||||||
|
constructor = self.yaml_multi_constructors[tag_prefix]
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
if None in self.yaml_multi_constructors:
|
||||||
|
tag_suffix = node.tag
|
||||||
|
constructor = self.yaml_multi_constructors[None]
|
||||||
|
elif None in self.yaml_constructors:
|
||||||
|
constructor = self.yaml_constructors[None]
|
||||||
|
elif isinstance(node, ScalarNode):
|
||||||
|
constructor = self.__class__.construct_scalar
|
||||||
|
elif isinstance(node, SequenceNode):
|
||||||
|
constructor = self.__class__.construct_sequence
|
||||||
|
elif isinstance(node, MappingNode):
|
||||||
|
constructor = self.__class__.construct_mapping
|
||||||
|
if tag_suffix is None:
|
||||||
|
data = constructor(self, node)
|
||||||
|
else:
|
||||||
|
data = constructor(self, tag_suffix, node)
|
||||||
|
if isinstance(data, types.GeneratorType):
|
||||||
|
generator = data
|
||||||
|
data = generator.next()
|
||||||
|
if self.deep_construct:
|
||||||
|
for dummy in generator:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.state_generators.append(generator)
|
||||||
|
self.constructed_objects[node] = data
|
||||||
|
del self.recursive_objects[node]
|
||||||
|
if deep:
|
||||||
|
self.deep_construct = old_deep
|
||||||
|
return data
|
||||||
|
|
||||||
|
def construct_scalar(self, node):
|
||||||
|
if not isinstance(node, ScalarNode):
|
||||||
|
raise ConstructorError(None, None,
|
||||||
|
"expected a scalar node, but found %s" % node.id,
|
||||||
|
node.start_mark)
|
||||||
|
return node.value
|
||||||
|
|
||||||
|
def construct_sequence(self, node, deep=False):
|
||||||
|
if not isinstance(node, SequenceNode):
|
||||||
|
raise ConstructorError(None, None,
|
||||||
|
"expected a sequence node, but found %s" % node.id,
|
||||||
|
node.start_mark)
|
||||||
|
return [self.construct_object(child, deep=deep)
|
||||||
|
for child in node.value]
|
||||||
|
|
||||||
|
def construct_mapping(self, node, deep=False):
|
||||||
|
if not isinstance(node, MappingNode):
|
||||||
|
raise ConstructorError(None, None,
|
||||||
|
"expected a mapping node, but found %s" % node.id,
|
||||||
|
node.start_mark)
|
||||||
|
mapping = {}
|
||||||
|
for key_node, value_node in node.value:
|
||||||
|
key = self.construct_object(key_node, deep=deep)
|
||||||
|
try:
|
||||||
|
hash(key)
|
||||||
|
except TypeError, exc:
|
||||||
|
raise ConstructorError("while constructing a mapping", node.start_mark,
|
||||||
|
"found unacceptable key (%s)" % exc, key_node.start_mark)
|
||||||
|
value = self.construct_object(value_node, deep=deep)
|
||||||
|
mapping[key] = value
|
||||||
|
return mapping
|
||||||
|
|
||||||
|
def construct_pairs(self, node, deep=False):
|
||||||
|
if not isinstance(node, MappingNode):
|
||||||
|
raise ConstructorError(None, None,
|
||||||
|
"expected a mapping node, but found %s" % node.id,
|
||||||
|
node.start_mark)
|
||||||
|
pairs = []
|
||||||
|
for key_node, value_node in node.value:
|
||||||
|
key = self.construct_object(key_node, deep=deep)
|
||||||
|
value = self.construct_object(value_node, deep=deep)
|
||||||
|
pairs.append((key, value))
|
||||||
|
return pairs
|
||||||
|
|
||||||
|
def add_constructor(cls, tag, constructor):
|
||||||
|
if not 'yaml_constructors' in cls.__dict__:
|
||||||
|
cls.yaml_constructors = cls.yaml_constructors.copy()
|
||||||
|
cls.yaml_constructors[tag] = constructor
|
||||||
|
add_constructor = classmethod(add_constructor)
|
||||||
|
|
||||||
|
def add_multi_constructor(cls, tag_prefix, multi_constructor):
|
||||||
|
if not 'yaml_multi_constructors' in cls.__dict__:
|
||||||
|
cls.yaml_multi_constructors = cls.yaml_multi_constructors.copy()
|
||||||
|
cls.yaml_multi_constructors[tag_prefix] = multi_constructor
|
||||||
|
add_multi_constructor = classmethod(add_multi_constructor)
|
||||||
|
|
||||||
|
class SafeConstructor(BaseConstructor):
|
||||||
|
|
||||||
|
def construct_scalar(self, node):
|
||||||
|
if isinstance(node, MappingNode):
|
||||||
|
for key_node, value_node in node.value:
|
||||||
|
if key_node.tag == u'tag:yaml.org,2002:value':
|
||||||
|
return self.construct_scalar(value_node)
|
||||||
|
return BaseConstructor.construct_scalar(self, node)
|
||||||
|
|
||||||
|
def flatten_mapping(self, node):
|
||||||
|
merge = []
|
||||||
|
index = 0
|
||||||
|
while index < len(node.value):
|
||||||
|
key_node, value_node = node.value[index]
|
||||||
|
if key_node.tag == u'tag:yaml.org,2002:merge':
|
||||||
|
del node.value[index]
|
||||||
|
if isinstance(value_node, MappingNode):
|
||||||
|
self.flatten_mapping(value_node)
|
||||||
|
merge.extend(value_node.value)
|
||||||
|
elif isinstance(value_node, SequenceNode):
|
||||||
|
submerge = []
|
||||||
|
for subnode in value_node.value:
|
||||||
|
if not isinstance(subnode, MappingNode):
|
||||||
|
raise ConstructorError("while constructing a mapping",
|
||||||
|
node.start_mark,
|
||||||
|
"expected a mapping for merging, but found %s"
|
||||||
|
% subnode.id, subnode.start_mark)
|
||||||
|
self.flatten_mapping(subnode)
|
||||||
|
submerge.append(subnode.value)
|
||||||
|
submerge.reverse()
|
||||||
|
for value in submerge:
|
||||||
|
merge.extend(value)
|
||||||
|
else:
|
||||||
|
raise ConstructorError("while constructing a mapping", node.start_mark,
|
||||||
|
"expected a mapping or list of mappings for merging, but found %s"
|
||||||
|
% value_node.id, value_node.start_mark)
|
||||||
|
elif key_node.tag == u'tag:yaml.org,2002:value':
|
||||||
|
key_node.tag = u'tag:yaml.org,2002:str'
|
||||||
|
index += 1
|
||||||
|
else:
|
||||||
|
index += 1
|
||||||
|
if merge:
|
||||||
|
node.value = merge + node.value
|
||||||
|
|
||||||
|
def construct_mapping(self, node, deep=False):
|
||||||
|
if isinstance(node, MappingNode):
|
||||||
|
self.flatten_mapping(node)
|
||||||
|
return BaseConstructor.construct_mapping(self, node, deep=deep)
|
||||||
|
|
||||||
|
def construct_yaml_null(self, node):
|
||||||
|
self.construct_scalar(node)
|
||||||
|
return None
|
||||||
|
|
||||||
|
bool_values = {
|
||||||
|
u'yes': True,
|
||||||
|
u'no': False,
|
||||||
|
u'true': True,
|
||||||
|
u'false': False,
|
||||||
|
u'on': True,
|
||||||
|
u'off': False,
|
||||||
|
}
|
||||||
|
|
||||||
|
def construct_yaml_bool(self, node):
|
||||||
|
value = self.construct_scalar(node)
|
||||||
|
return self.bool_values[value.lower()]
|
||||||
|
|
||||||
|
def construct_yaml_int(self, node):
|
||||||
|
value = str(self.construct_scalar(node))
|
||||||
|
value = value.replace('_', '')
|
||||||
|
sign = +1
|
||||||
|
if value[0] == '-':
|
||||||
|
sign = -1
|
||||||
|
if value[0] in '+-':
|
||||||
|
value = value[1:]
|
||||||
|
if value == '0':
|
||||||
|
return 0
|
||||||
|
elif value.startswith('0b'):
|
||||||
|
return sign*int(value[2:], 2)
|
||||||
|
elif value.startswith('0x'):
|
||||||
|
return sign*int(value[2:], 16)
|
||||||
|
elif value[0] == '0':
|
||||||
|
return sign*int(value, 8)
|
||||||
|
elif ':' in value:
|
||||||
|
digits = [int(part) for part in value.split(':')]
|
||||||
|
digits.reverse()
|
||||||
|
base = 1
|
||||||
|
value = 0
|
||||||
|
for digit in digits:
|
||||||
|
value += digit*base
|
||||||
|
base *= 60
|
||||||
|
return sign*value
|
||||||
|
else:
|
||||||
|
return sign*int(value)
|
||||||
|
|
||||||
|
inf_value = 1e300
|
||||||
|
while inf_value != inf_value*inf_value:
|
||||||
|
inf_value *= inf_value
|
||||||
|
nan_value = -inf_value/inf_value # Trying to make a quiet NaN (like C99).
|
||||||
|
|
||||||
|
def construct_yaml_float(self, node):
|
||||||
|
value = str(self.construct_scalar(node))
|
||||||
|
value = value.replace('_', '').lower()
|
||||||
|
sign = +1
|
||||||
|
if value[0] == '-':
|
||||||
|
sign = -1
|
||||||
|
if value[0] in '+-':
|
||||||
|
value = value[1:]
|
||||||
|
if value == '.inf':
|
||||||
|
return sign*self.inf_value
|
||||||
|
elif value == '.nan':
|
||||||
|
return self.nan_value
|
||||||
|
elif ':' in value:
|
||||||
|
digits = [float(part) for part in value.split(':')]
|
||||||
|
digits.reverse()
|
||||||
|
base = 1
|
||||||
|
value = 0.0
|
||||||
|
for digit in digits:
|
||||||
|
value += digit*base
|
||||||
|
base *= 60
|
||||||
|
return sign*value
|
||||||
|
else:
|
||||||
|
return sign*float(value)
|
||||||
|
|
||||||
|
def construct_yaml_binary(self, node):
|
||||||
|
value = self.construct_scalar(node)
|
||||||
|
try:
|
||||||
|
return str(value).decode('base64')
|
||||||
|
except (binascii.Error, UnicodeEncodeError), exc:
|
||||||
|
raise ConstructorError(None, None,
|
||||||
|
"failed to decode base64 data: %s" % exc, node.start_mark)
|
||||||
|
|
||||||
|
timestamp_regexp = re.compile(
|
||||||
|
ur'''^(?P<year>[0-9][0-9][0-9][0-9])
|
||||||
|
-(?P<month>[0-9][0-9]?)
|
||||||
|
-(?P<day>[0-9][0-9]?)
|
||||||
|
(?:(?:[Tt]|[ \t]+)
|
||||||
|
(?P<hour>[0-9][0-9]?)
|
||||||
|
:(?P<minute>[0-9][0-9])
|
||||||
|
:(?P<second>[0-9][0-9])
|
||||||
|
(?:\.(?P<fraction>[0-9]*))?
|
||||||
|
(?:[ \t]*(?P<tz>Z|(?P<tz_sign>[-+])(?P<tz_hour>[0-9][0-9]?)
|
||||||
|
(?::(?P<tz_minute>[0-9][0-9]))?))?)?$''', re.X)
|
||||||
|
|
||||||
|
def construct_yaml_timestamp(self, node):
|
||||||
|
value = self.construct_scalar(node)
|
||||||
|
match = self.timestamp_regexp.match(node.value)
|
||||||
|
values = match.groupdict()
|
||||||
|
year = int(values['year'])
|
||||||
|
month = int(values['month'])
|
||||||
|
day = int(values['day'])
|
||||||
|
if not values['hour']:
|
||||||
|
return datetime.date(year, month, day)
|
||||||
|
hour = int(values['hour'])
|
||||||
|
minute = int(values['minute'])
|
||||||
|
second = int(values['second'])
|
||||||
|
fraction = 0
|
||||||
|
if values['fraction']:
|
||||||
|
fraction = values['fraction'][:6]
|
||||||
|
while len(fraction) < 6:
|
||||||
|
fraction += '0'
|
||||||
|
fraction = int(fraction)
|
||||||
|
delta = None
|
||||||
|
if values['tz_sign']:
|
||||||
|
tz_hour = int(values['tz_hour'])
|
||||||
|
tz_minute = int(values['tz_minute'] or 0)
|
||||||
|
delta = datetime.timedelta(hours=tz_hour, minutes=tz_minute)
|
||||||
|
if values['tz_sign'] == '-':
|
||||||
|
delta = -delta
|
||||||
|
data = datetime.datetime(year, month, day, hour, minute, second, fraction)
|
||||||
|
if delta:
|
||||||
|
data -= delta
|
||||||
|
return data
|
||||||
|
|
||||||
|
def construct_yaml_omap(self, node):
|
||||||
|
# Note: we do not check for duplicate keys, because it's too
|
||||||
|
# CPU-expensive.
|
||||||
|
omap = []
|
||||||
|
yield omap
|
||||||
|
if not isinstance(node, SequenceNode):
|
||||||
|
raise ConstructorError("while constructing an ordered map", node.start_mark,
|
||||||
|
"expected a sequence, but found %s" % node.id, node.start_mark)
|
||||||
|
for subnode in node.value:
|
||||||
|
if not isinstance(subnode, MappingNode):
|
||||||
|
raise ConstructorError("while constructing an ordered map", node.start_mark,
|
||||||
|
"expected a mapping of length 1, but found %s" % subnode.id,
|
||||||
|
subnode.start_mark)
|
||||||
|
if len(subnode.value) != 1:
|
||||||
|
raise ConstructorError("while constructing an ordered map", node.start_mark,
|
||||||
|
"expected a single mapping item, but found %d items" % len(subnode.value),
|
||||||
|
subnode.start_mark)
|
||||||
|
key_node, value_node = subnode.value[0]
|
||||||
|
key = self.construct_object(key_node)
|
||||||
|
value = self.construct_object(value_node)
|
||||||
|
omap.append((key, value))
|
||||||
|
|
||||||
|
def construct_yaml_pairs(self, node):
|
||||||
|
# Note: the same code as `construct_yaml_omap`.
|
||||||
|
pairs = []
|
||||||
|
yield pairs
|
||||||
|
if not isinstance(node, SequenceNode):
|
||||||
|
raise ConstructorError("while constructing pairs", node.start_mark,
|
||||||
|
"expected a sequence, but found %s" % node.id, node.start_mark)
|
||||||
|
for subnode in node.value:
|
||||||
|
if not isinstance(subnode, MappingNode):
|
||||||
|
raise ConstructorError("while constructing pairs", node.start_mark,
|
||||||
|
"expected a mapping of length 1, but found %s" % subnode.id,
|
||||||
|
subnode.start_mark)
|
||||||
|
if len(subnode.value) != 1:
|
||||||
|
raise ConstructorError("while constructing pairs", node.start_mark,
|
||||||
|
"expected a single mapping item, but found %d items" % len(subnode.value),
|
||||||
|
subnode.start_mark)
|
||||||
|
key_node, value_node = subnode.value[0]
|
||||||
|
key = self.construct_object(key_node)
|
||||||
|
value = self.construct_object(value_node)
|
||||||
|
pairs.append((key, value))
|
||||||
|
|
||||||
|
def construct_yaml_set(self, node):
|
||||||
|
data = set()
|
||||||
|
yield data
|
||||||
|
value = self.construct_mapping(node)
|
||||||
|
data.update(value)
|
||||||
|
|
||||||
|
def construct_yaml_str(self, node):
|
||||||
|
value = self.construct_scalar(node)
|
||||||
|
try:
|
||||||
|
return value.encode('ascii')
|
||||||
|
except UnicodeEncodeError:
|
||||||
|
return value
|
||||||
|
|
||||||
|
def construct_yaml_seq(self, node):
|
||||||
|
data = []
|
||||||
|
yield data
|
||||||
|
data.extend(self.construct_sequence(node))
|
||||||
|
|
||||||
|
def construct_yaml_map(self, node):
|
||||||
|
data = {}
|
||||||
|
yield data
|
||||||
|
value = self.construct_mapping(node)
|
||||||
|
data.update(value)
|
||||||
|
|
||||||
|
def construct_yaml_object(self, node, cls):
|
||||||
|
data = cls.__new__(cls)
|
||||||
|
yield data
|
||||||
|
if hasattr(data, '__setstate__'):
|
||||||
|
state = self.construct_mapping(node, deep=True)
|
||||||
|
data.__setstate__(state)
|
||||||
|
else:
|
||||||
|
state = self.construct_mapping(node)
|
||||||
|
data.__dict__.update(state)
|
||||||
|
|
||||||
|
def construct_undefined(self, node):
|
||||||
|
raise ConstructorError(None, None,
|
||||||
|
"could not determine a constructor for the tag %r" % node.tag.encode('utf-8'),
|
||||||
|
node.start_mark)
|
||||||
|
|
||||||
|
SafeConstructor.add_constructor(
|
||||||
|
u'tag:yaml.org,2002:null',
|
||||||
|
SafeConstructor.construct_yaml_null)
|
||||||
|
|
||||||
|
SafeConstructor.add_constructor(
|
||||||
|
u'tag:yaml.org,2002:bool',
|
||||||
|
SafeConstructor.construct_yaml_bool)
|
||||||
|
|
||||||
|
SafeConstructor.add_constructor(
|
||||||
|
u'tag:yaml.org,2002:int',
|
||||||
|
SafeConstructor.construct_yaml_int)
|
||||||
|
|
||||||
|
SafeConstructor.add_constructor(
|
||||||
|
u'tag:yaml.org,2002:float',
|
||||||
|
SafeConstructor.construct_yaml_float)
|
||||||
|
|
||||||
|
SafeConstructor.add_constructor(
|
||||||
|
u'tag:yaml.org,2002:binary',
|
||||||
|
SafeConstructor.construct_yaml_binary)
|
||||||
|
|
||||||
|
SafeConstructor.add_constructor(
|
||||||
|
u'tag:yaml.org,2002:timestamp',
|
||||||
|
SafeConstructor.construct_yaml_timestamp)
|
||||||
|
|
||||||
|
SafeConstructor.add_constructor(
|
||||||
|
u'tag:yaml.org,2002:omap',
|
||||||
|
SafeConstructor.construct_yaml_omap)
|
||||||
|
|
||||||
|
SafeConstructor.add_constructor(
|
||||||
|
u'tag:yaml.org,2002:pairs',
|
||||||
|
SafeConstructor.construct_yaml_pairs)
|
||||||
|
|
||||||
|
SafeConstructor.add_constructor(
|
||||||
|
u'tag:yaml.org,2002:set',
|
||||||
|
SafeConstructor.construct_yaml_set)
|
||||||
|
|
||||||
|
SafeConstructor.add_constructor(
|
||||||
|
u'tag:yaml.org,2002:str',
|
||||||
|
SafeConstructor.construct_yaml_str)
|
||||||
|
|
||||||
|
SafeConstructor.add_constructor(
|
||||||
|
u'tag:yaml.org,2002:seq',
|
||||||
|
SafeConstructor.construct_yaml_seq)
|
||||||
|
|
||||||
|
SafeConstructor.add_constructor(
|
||||||
|
u'tag:yaml.org,2002:map',
|
||||||
|
SafeConstructor.construct_yaml_map)
|
||||||
|
|
||||||
|
SafeConstructor.add_constructor(None,
|
||||||
|
SafeConstructor.construct_undefined)
|
||||||
|
|
||||||
|
class Constructor(SafeConstructor):
|
||||||
|
|
||||||
|
def construct_python_str(self, node):
|
||||||
|
return self.construct_scalar(node).encode('utf-8')
|
||||||
|
|
||||||
|
def construct_python_unicode(self, node):
|
||||||
|
return self.construct_scalar(node)
|
||||||
|
|
||||||
|
def construct_python_long(self, node):
|
||||||
|
return long(self.construct_yaml_int(node))
|
||||||
|
|
||||||
|
def construct_python_complex(self, node):
|
||||||
|
return complex(self.construct_scalar(node))
|
||||||
|
|
||||||
|
def construct_python_tuple(self, node):
|
||||||
|
return tuple(self.construct_sequence(node))
|
||||||
|
|
||||||
|
def find_python_module(self, name, mark):
|
||||||
|
if not name:
|
||||||
|
raise ConstructorError("while constructing a Python module", mark,
|
||||||
|
"expected non-empty name appended to the tag", mark)
|
||||||
|
try:
|
||||||
|
__import__(name)
|
||||||
|
except ImportError, exc:
|
||||||
|
raise ConstructorError("while constructing a Python module", mark,
|
||||||
|
"cannot find module %r (%s)" % (name.encode('utf-8'), exc), mark)
|
||||||
|
return sys.modules[name]
|
||||||
|
|
||||||
|
def find_python_name(self, name, mark):
|
||||||
|
if not name:
|
||||||
|
raise ConstructorError("while constructing a Python object", mark,
|
||||||
|
"expected non-empty name appended to the tag", mark)
|
||||||
|
if u'.' in name:
|
||||||
|
# Python 2.4 only
|
||||||
|
#module_name, object_name = name.rsplit('.', 1)
|
||||||
|
items = name.split('.')
|
||||||
|
object_name = items.pop()
|
||||||
|
module_name = '.'.join(items)
|
||||||
|
else:
|
||||||
|
module_name = '__builtin__'
|
||||||
|
object_name = name
|
||||||
|
try:
|
||||||
|
__import__(module_name)
|
||||||
|
except ImportError, exc:
|
||||||
|
raise ConstructorError("while constructing a Python object", mark,
|
||||||
|
"cannot find module %r (%s)" % (module_name.encode('utf-8'), exc), mark)
|
||||||
|
module = sys.modules[module_name]
|
||||||
|
if not hasattr(module, object_name):
|
||||||
|
raise ConstructorError("while constructing a Python object", mark,
|
||||||
|
"cannot find %r in the module %r" % (object_name.encode('utf-8'),
|
||||||
|
module.__name__), mark)
|
||||||
|
return getattr(module, object_name)
|
||||||
|
|
||||||
|
def construct_python_name(self, suffix, node):
|
||||||
|
value = self.construct_scalar(node)
|
||||||
|
if value:
|
||||||
|
raise ConstructorError("while constructing a Python name", node.start_mark,
|
||||||
|
"expected the empty value, but found %r" % value.encode('utf-8'),
|
||||||
|
node.start_mark)
|
||||||
|
return self.find_python_name(suffix, node.start_mark)
|
||||||
|
|
||||||
|
def construct_python_module(self, suffix, node):
|
||||||
|
value = self.construct_scalar(node)
|
||||||
|
if value:
|
||||||
|
raise ConstructorError("while constructing a Python module", node.start_mark,
|
||||||
|
"expected the empty value, but found %r" % value.encode('utf-8'),
|
||||||
|
node.start_mark)
|
||||||
|
return self.find_python_module(suffix, node.start_mark)
|
||||||
|
|
||||||
|
class classobj: pass
|
||||||
|
|
||||||
|
def make_python_instance(self, suffix, node,
|
||||||
|
args=None, kwds=None, newobj=False):
|
||||||
|
if not args:
|
||||||
|
args = []
|
||||||
|
if not kwds:
|
||||||
|
kwds = {}
|
||||||
|
cls = self.find_python_name(suffix, node.start_mark)
|
||||||
|
if newobj and isinstance(cls, type(self.classobj)) \
|
||||||
|
and not args and not kwds:
|
||||||
|
instance = self.classobj()
|
||||||
|
instance.__class__ = cls
|
||||||
|
return instance
|
||||||
|
elif newobj and isinstance(cls, type):
|
||||||
|
return cls.__new__(cls, *args, **kwds)
|
||||||
|
else:
|
||||||
|
return cls(*args, **kwds)
|
||||||
|
|
||||||
|
def set_python_instance_state(self, instance, state):
|
||||||
|
if hasattr(instance, '__setstate__'):
|
||||||
|
instance.__setstate__(state)
|
||||||
|
else:
|
||||||
|
slotstate = {}
|
||||||
|
if isinstance(state, tuple) and len(state) == 2:
|
||||||
|
state, slotstate = state
|
||||||
|
if hasattr(instance, '__dict__'):
|
||||||
|
instance.__dict__.update(state)
|
||||||
|
elif state:
|
||||||
|
slotstate.update(state)
|
||||||
|
for key, value in slotstate.items():
|
||||||
|
setattr(object, key, value)
|
||||||
|
|
||||||
|
def construct_python_object(self, suffix, node):
|
||||||
|
# Format:
|
||||||
|
# !!python/object:module.name { ... state ... }
|
||||||
|
instance = self.make_python_instance(suffix, node, newobj=True)
|
||||||
|
yield instance
|
||||||
|
deep = hasattr(instance, '__setstate__')
|
||||||
|
state = self.construct_mapping(node, deep=deep)
|
||||||
|
self.set_python_instance_state(instance, state)
|
||||||
|
|
||||||
|
def construct_python_object_apply(self, suffix, node, newobj=False):
|
||||||
|
# Format:
|
||||||
|
# !!python/object/apply # (or !!python/object/new)
|
||||||
|
# args: [ ... arguments ... ]
|
||||||
|
# kwds: { ... keywords ... }
|
||||||
|
# state: ... state ...
|
||||||
|
# listitems: [ ... listitems ... ]
|
||||||
|
# dictitems: { ... dictitems ... }
|
||||||
|
# or short format:
|
||||||
|
# !!python/object/apply [ ... arguments ... ]
|
||||||
|
# The difference between !!python/object/apply and !!python/object/new
|
||||||
|
# is how an object is created, check make_python_instance for details.
|
||||||
|
if isinstance(node, SequenceNode):
|
||||||
|
args = self.construct_sequence(node, deep=True)
|
||||||
|
kwds = {}
|
||||||
|
state = {}
|
||||||
|
listitems = []
|
||||||
|
dictitems = {}
|
||||||
|
else:
|
||||||
|
value = self.construct_mapping(node, deep=True)
|
||||||
|
args = value.get('args', [])
|
||||||
|
kwds = value.get('kwds', {})
|
||||||
|
state = value.get('state', {})
|
||||||
|
listitems = value.get('listitems', [])
|
||||||
|
dictitems = value.get('dictitems', {})
|
||||||
|
instance = self.make_python_instance(suffix, node, args, kwds, newobj)
|
||||||
|
if state:
|
||||||
|
self.set_python_instance_state(instance, state)
|
||||||
|
if listitems:
|
||||||
|
instance.extend(listitems)
|
||||||
|
if dictitems:
|
||||||
|
for key in dictitems:
|
||||||
|
instance[key] = dictitems[key]
|
||||||
|
return instance
|
||||||
|
|
||||||
|
def construct_python_object_new(self, suffix, node):
|
||||||
|
return self.construct_python_object_apply(suffix, node, newobj=True)
|
||||||
|
|
||||||
|
Constructor.add_constructor(
|
||||||
|
u'tag:yaml.org,2002:python/none',
|
||||||
|
Constructor.construct_yaml_null)
|
||||||
|
|
||||||
|
Constructor.add_constructor(
|
||||||
|
u'tag:yaml.org,2002:python/bool',
|
||||||
|
Constructor.construct_yaml_bool)
|
||||||
|
|
||||||
|
Constructor.add_constructor(
|
||||||
|
u'tag:yaml.org,2002:python/str',
|
||||||
|
Constructor.construct_python_str)
|
||||||
|
|
||||||
|
Constructor.add_constructor(
|
||||||
|
u'tag:yaml.org,2002:python/unicode',
|
||||||
|
Constructor.construct_python_unicode)
|
||||||
|
|
||||||
|
Constructor.add_constructor(
|
||||||
|
u'tag:yaml.org,2002:python/int',
|
||||||
|
Constructor.construct_yaml_int)
|
||||||
|
|
||||||
|
Constructor.add_constructor(
|
||||||
|
u'tag:yaml.org,2002:python/long',
|
||||||
|
Constructor.construct_python_long)
|
||||||
|
|
||||||
|
Constructor.add_constructor(
|
||||||
|
u'tag:yaml.org,2002:python/float',
|
||||||
|
Constructor.construct_yaml_float)
|
||||||
|
|
||||||
|
Constructor.add_constructor(
|
||||||
|
u'tag:yaml.org,2002:python/complex',
|
||||||
|
Constructor.construct_python_complex)
|
||||||
|
|
||||||
|
Constructor.add_constructor(
|
||||||
|
u'tag:yaml.org,2002:python/list',
|
||||||
|
Constructor.construct_yaml_seq)
|
||||||
|
|
||||||
|
Constructor.add_constructor(
|
||||||
|
u'tag:yaml.org,2002:python/tuple',
|
||||||
|
Constructor.construct_python_tuple)
|
||||||
|
|
||||||
|
Constructor.add_constructor(
|
||||||
|
u'tag:yaml.org,2002:python/dict',
|
||||||
|
Constructor.construct_yaml_map)
|
||||||
|
|
||||||
|
Constructor.add_multi_constructor(
|
||||||
|
u'tag:yaml.org,2002:python/name:',
|
||||||
|
Constructor.construct_python_name)
|
||||||
|
|
||||||
|
Constructor.add_multi_constructor(
|
||||||
|
u'tag:yaml.org,2002:python/module:',
|
||||||
|
Constructor.construct_python_module)
|
||||||
|
|
||||||
|
Constructor.add_multi_constructor(
|
||||||
|
u'tag:yaml.org,2002:python/object:',
|
||||||
|
Constructor.construct_python_object)
|
||||||
|
|
||||||
|
Constructor.add_multi_constructor(
|
||||||
|
u'tag:yaml.org,2002:python/object/apply:',
|
||||||
|
Constructor.construct_python_object_apply)
|
||||||
|
|
||||||
|
Constructor.add_multi_constructor(
|
||||||
|
u'tag:yaml.org,2002:python/object/new:',
|
||||||
|
Constructor.construct_python_object_new)
|
||||||
|
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
|
||||||
|
__all__ = ['CBaseLoader', 'CSafeLoader', 'CLoader',
|
||||||
|
'CBaseDumper', 'CSafeDumper', 'CDumper']
|
||||||
|
|
||||||
|
from _yaml import CParser, CEmitter
|
||||||
|
|
||||||
|
from constructor import *
|
||||||
|
|
||||||
|
from serializer import *
|
||||||
|
from representer import *
|
||||||
|
|
||||||
|
from resolver import *
|
||||||
|
|
||||||
|
class CBaseLoader(CParser, BaseConstructor, BaseResolver):
|
||||||
|
|
||||||
|
def __init__(self, stream):
|
||||||
|
CParser.__init__(self, stream)
|
||||||
|
BaseConstructor.__init__(self)
|
||||||
|
BaseResolver.__init__(self)
|
||||||
|
|
||||||
|
class CSafeLoader(CParser, SafeConstructor, Resolver):
|
||||||
|
|
||||||
|
def __init__(self, stream):
|
||||||
|
CParser.__init__(self, stream)
|
||||||
|
SafeConstructor.__init__(self)
|
||||||
|
Resolver.__init__(self)
|
||||||
|
|
||||||
|
class CLoader(CParser, Constructor, Resolver):
|
||||||
|
|
||||||
|
def __init__(self, stream):
|
||||||
|
CParser.__init__(self, stream)
|
||||||
|
Constructor.__init__(self)
|
||||||
|
Resolver.__init__(self)
|
||||||
|
|
||||||
|
class CBaseDumper(CEmitter, BaseRepresenter, BaseResolver):
|
||||||
|
|
||||||
|
def __init__(self, stream,
|
||||||
|
default_style=None, default_flow_style=None,
|
||||||
|
canonical=None, indent=None, width=None,
|
||||||
|
allow_unicode=None, line_break=None,
|
||||||
|
encoding=None, explicit_start=None, explicit_end=None,
|
||||||
|
version=None, tags=None):
|
||||||
|
CEmitter.__init__(self, stream, canonical=canonical,
|
||||||
|
indent=indent, width=width, encoding=encoding,
|
||||||
|
allow_unicode=allow_unicode, line_break=line_break,
|
||||||
|
explicit_start=explicit_start, explicit_end=explicit_end,
|
||||||
|
version=version, tags=tags)
|
||||||
|
Representer.__init__(self, default_style=default_style,
|
||||||
|
default_flow_style=default_flow_style)
|
||||||
|
Resolver.__init__(self)
|
||||||
|
|
||||||
|
class CSafeDumper(CEmitter, SafeRepresenter, Resolver):
|
||||||
|
|
||||||
|
def __init__(self, stream,
|
||||||
|
default_style=None, default_flow_style=None,
|
||||||
|
canonical=None, indent=None, width=None,
|
||||||
|
allow_unicode=None, line_break=None,
|
||||||
|
encoding=None, explicit_start=None, explicit_end=None,
|
||||||
|
version=None, tags=None):
|
||||||
|
CEmitter.__init__(self, stream, canonical=canonical,
|
||||||
|
indent=indent, width=width, encoding=encoding,
|
||||||
|
allow_unicode=allow_unicode, line_break=line_break,
|
||||||
|
explicit_start=explicit_start, explicit_end=explicit_end,
|
||||||
|
version=version, tags=tags)
|
||||||
|
SafeRepresenter.__init__(self, default_style=default_style,
|
||||||
|
default_flow_style=default_flow_style)
|
||||||
|
Resolver.__init__(self)
|
||||||
|
|
||||||
|
class CDumper(CEmitter, Serializer, Representer, Resolver):
|
||||||
|
|
||||||
|
def __init__(self, stream,
|
||||||
|
default_style=None, default_flow_style=None,
|
||||||
|
canonical=None, indent=None, width=None,
|
||||||
|
allow_unicode=None, line_break=None,
|
||||||
|
encoding=None, explicit_start=None, explicit_end=None,
|
||||||
|
version=None, tags=None):
|
||||||
|
CEmitter.__init__(self, stream, canonical=canonical,
|
||||||
|
indent=indent, width=width, encoding=encoding,
|
||||||
|
allow_unicode=allow_unicode, line_break=line_break,
|
||||||
|
explicit_start=explicit_start, explicit_end=explicit_end,
|
||||||
|
version=version, tags=tags)
|
||||||
|
Representer.__init__(self, default_style=default_style,
|
||||||
|
default_flow_style=default_flow_style)
|
||||||
|
Resolver.__init__(self)
|
||||||
|
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
|
||||||
|
__all__ = ['BaseDumper', 'SafeDumper', 'Dumper']
|
||||||
|
|
||||||
|
from emitter import *
|
||||||
|
from serializer import *
|
||||||
|
from representer import *
|
||||||
|
from resolver import *
|
||||||
|
|
||||||
|
class BaseDumper(Emitter, Serializer, BaseRepresenter, BaseResolver):
|
||||||
|
|
||||||
|
def __init__(self, stream,
|
||||||
|
default_style=None, default_flow_style=None,
|
||||||
|
canonical=None, indent=None, width=None,
|
||||||
|
allow_unicode=None, line_break=None,
|
||||||
|
encoding=None, explicit_start=None, explicit_end=None,
|
||||||
|
version=None, tags=None):
|
||||||
|
Emitter.__init__(self, stream, canonical=canonical,
|
||||||
|
indent=indent, width=width,
|
||||||
|
allow_unicode=allow_unicode, line_break=line_break)
|
||||||
|
Serializer.__init__(self, encoding=encoding,
|
||||||
|
explicit_start=explicit_start, explicit_end=explicit_end,
|
||||||
|
version=version, tags=tags)
|
||||||
|
Representer.__init__(self, default_style=default_style,
|
||||||
|
default_flow_style=default_flow_style)
|
||||||
|
Resolver.__init__(self)
|
||||||
|
|
||||||
|
class SafeDumper(Emitter, Serializer, SafeRepresenter, Resolver):
|
||||||
|
|
||||||
|
def __init__(self, stream,
|
||||||
|
default_style=None, default_flow_style=None,
|
||||||
|
canonical=None, indent=None, width=None,
|
||||||
|
allow_unicode=None, line_break=None,
|
||||||
|
encoding=None, explicit_start=None, explicit_end=None,
|
||||||
|
version=None, tags=None):
|
||||||
|
Emitter.__init__(self, stream, canonical=canonical,
|
||||||
|
indent=indent, width=width,
|
||||||
|
allow_unicode=allow_unicode, line_break=line_break)
|
||||||
|
Serializer.__init__(self, encoding=encoding,
|
||||||
|
explicit_start=explicit_start, explicit_end=explicit_end,
|
||||||
|
version=version, tags=tags)
|
||||||
|
SafeRepresenter.__init__(self, default_style=default_style,
|
||||||
|
default_flow_style=default_flow_style)
|
||||||
|
Resolver.__init__(self)
|
||||||
|
|
||||||
|
class Dumper(Emitter, Serializer, Representer, Resolver):
|
||||||
|
|
||||||
|
def __init__(self, stream,
|
||||||
|
default_style=None, default_flow_style=None,
|
||||||
|
canonical=None, indent=None, width=None,
|
||||||
|
allow_unicode=None, line_break=None,
|
||||||
|
encoding=None, explicit_start=None, explicit_end=None,
|
||||||
|
version=None, tags=None):
|
||||||
|
Emitter.__init__(self, stream, canonical=canonical,
|
||||||
|
indent=indent, width=width,
|
||||||
|
allow_unicode=allow_unicode, line_break=line_break)
|
||||||
|
Serializer.__init__(self, encoding=encoding,
|
||||||
|
explicit_start=explicit_start, explicit_end=explicit_end,
|
||||||
|
version=version, tags=tags)
|
||||||
|
Representer.__init__(self, default_style=default_style,
|
||||||
|
default_flow_style=default_flow_style)
|
||||||
|
Resolver.__init__(self)
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,75 @@
|
|||||||
|
|
||||||
|
__all__ = ['Mark', 'YAMLError', 'MarkedYAMLError']
|
||||||
|
|
||||||
|
class Mark(object):
|
||||||
|
|
||||||
|
def __init__(self, name, index, line, column, buffer, pointer):
|
||||||
|
self.name = name
|
||||||
|
self.index = index
|
||||||
|
self.line = line
|
||||||
|
self.column = column
|
||||||
|
self.buffer = buffer
|
||||||
|
self.pointer = pointer
|
||||||
|
|
||||||
|
def get_snippet(self, indent=4, max_length=75):
|
||||||
|
if self.buffer is None:
|
||||||
|
return None
|
||||||
|
head = ''
|
||||||
|
start = self.pointer
|
||||||
|
while start > 0 and self.buffer[start-1] not in u'\0\r\n\x85\u2028\u2029':
|
||||||
|
start -= 1
|
||||||
|
if self.pointer-start > max_length/2-1:
|
||||||
|
head = ' ... '
|
||||||
|
start += 5
|
||||||
|
break
|
||||||
|
tail = ''
|
||||||
|
end = self.pointer
|
||||||
|
while end < len(self.buffer) and self.buffer[end] not in u'\0\r\n\x85\u2028\u2029':
|
||||||
|
end += 1
|
||||||
|
if end-self.pointer > max_length/2-1:
|
||||||
|
tail = ' ... '
|
||||||
|
end -= 5
|
||||||
|
break
|
||||||
|
snippet = self.buffer[start:end].encode('utf-8')
|
||||||
|
return ' '*indent + head + snippet + tail + '\n' \
|
||||||
|
+ ' '*(indent+self.pointer-start+len(head)) + '^'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
snippet = self.get_snippet()
|
||||||
|
where = " in \"%s\", line %d, column %d" \
|
||||||
|
% (self.name, self.line+1, self.column+1)
|
||||||
|
if snippet is not None:
|
||||||
|
where += ":\n"+snippet
|
||||||
|
return where
|
||||||
|
|
||||||
|
class YAMLError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class MarkedYAMLError(YAMLError):
|
||||||
|
|
||||||
|
def __init__(self, context=None, context_mark=None,
|
||||||
|
problem=None, problem_mark=None, note=None):
|
||||||
|
self.context = context
|
||||||
|
self.context_mark = context_mark
|
||||||
|
self.problem = problem
|
||||||
|
self.problem_mark = problem_mark
|
||||||
|
self.note = note
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
lines = []
|
||||||
|
if self.context is not None:
|
||||||
|
lines.append(self.context)
|
||||||
|
if self.context_mark is not None \
|
||||||
|
and (self.problem is None or self.problem_mark is None
|
||||||
|
or self.context_mark.name != self.problem_mark.name
|
||||||
|
or self.context_mark.line != self.problem_mark.line
|
||||||
|
or self.context_mark.column != self.problem_mark.column):
|
||||||
|
lines.append(str(self.context_mark))
|
||||||
|
if self.problem is not None:
|
||||||
|
lines.append(self.problem)
|
||||||
|
if self.problem_mark is not None:
|
||||||
|
lines.append(str(self.problem_mark))
|
||||||
|
if self.note is not None:
|
||||||
|
lines.append(self.note)
|
||||||
|
return '\n'.join(lines)
|
||||||
|
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
|
||||||
|
# Abstract classes.
|
||||||
|
|
||||||
|
class Event(object):
|
||||||
|
def __init__(self, start_mark=None, end_mark=None):
|
||||||
|
self.start_mark = start_mark
|
||||||
|
self.end_mark = end_mark
|
||||||
|
def __repr__(self):
|
||||||
|
attributes = [key for key in ['anchor', 'tag', 'implicit', 'value']
|
||||||
|
if hasattr(self, key)]
|
||||||
|
arguments = ', '.join(['%s=%r' % (key, getattr(self, key))
|
||||||
|
for key in attributes])
|
||||||
|
return '%s(%s)' % (self.__class__.__name__, arguments)
|
||||||
|
|
||||||
|
class NodeEvent(Event):
|
||||||
|
def __init__(self, anchor, start_mark=None, end_mark=None):
|
||||||
|
self.anchor = anchor
|
||||||
|
self.start_mark = start_mark
|
||||||
|
self.end_mark = end_mark
|
||||||
|
|
||||||
|
class CollectionStartEvent(NodeEvent):
|
||||||
|
def __init__(self, anchor, tag, implicit, start_mark=None, end_mark=None,
|
||||||
|
flow_style=None):
|
||||||
|
self.anchor = anchor
|
||||||
|
self.tag = tag
|
||||||
|
self.implicit = implicit
|
||||||
|
self.start_mark = start_mark
|
||||||
|
self.end_mark = end_mark
|
||||||
|
self.flow_style = flow_style
|
||||||
|
|
||||||
|
class CollectionEndEvent(Event):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Implementations.
|
||||||
|
|
||||||
|
class StreamStartEvent(Event):
|
||||||
|
def __init__(self, start_mark=None, end_mark=None, encoding=None):
|
||||||
|
self.start_mark = start_mark
|
||||||
|
self.end_mark = end_mark
|
||||||
|
self.encoding = encoding
|
||||||
|
|
||||||
|
class StreamEndEvent(Event):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class DocumentStartEvent(Event):
|
||||||
|
def __init__(self, start_mark=None, end_mark=None,
|
||||||
|
explicit=None, version=None, tags=None):
|
||||||
|
self.start_mark = start_mark
|
||||||
|
self.end_mark = end_mark
|
||||||
|
self.explicit = explicit
|
||||||
|
self.version = version
|
||||||
|
self.tags = tags
|
||||||
|
|
||||||
|
class DocumentEndEvent(Event):
|
||||||
|
def __init__(self, start_mark=None, end_mark=None,
|
||||||
|
explicit=None):
|
||||||
|
self.start_mark = start_mark
|
||||||
|
self.end_mark = end_mark
|
||||||
|
self.explicit = explicit
|
||||||
|
|
||||||
|
class AliasEvent(NodeEvent):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ScalarEvent(NodeEvent):
|
||||||
|
def __init__(self, anchor, tag, implicit, value,
|
||||||
|
start_mark=None, end_mark=None, style=None):
|
||||||
|
self.anchor = anchor
|
||||||
|
self.tag = tag
|
||||||
|
self.implicit = implicit
|
||||||
|
self.value = value
|
||||||
|
self.start_mark = start_mark
|
||||||
|
self.end_mark = end_mark
|
||||||
|
self.style = style
|
||||||
|
|
||||||
|
class SequenceStartEvent(CollectionStartEvent):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class SequenceEndEvent(CollectionEndEvent):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class MappingStartEvent(CollectionStartEvent):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class MappingEndEvent(CollectionEndEvent):
|
||||||
|
pass
|
||||||
|
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
|
||||||
|
__all__ = ['BaseLoader', 'SafeLoader', 'Loader']
|
||||||
|
|
||||||
|
from reader import *
|
||||||
|
from scanner import *
|
||||||
|
from parser import *
|
||||||
|
from composer import *
|
||||||
|
from constructor import *
|
||||||
|
from resolver import *
|
||||||
|
|
||||||
|
class BaseLoader(Reader, Scanner, Parser, Composer, BaseConstructor, BaseResolver):
|
||||||
|
|
||||||
|
def __init__(self, stream):
|
||||||
|
Reader.__init__(self, stream)
|
||||||
|
Scanner.__init__(self)
|
||||||
|
Parser.__init__(self)
|
||||||
|
Composer.__init__(self)
|
||||||
|
BaseConstructor.__init__(self)
|
||||||
|
BaseResolver.__init__(self)
|
||||||
|
|
||||||
|
class SafeLoader(Reader, Scanner, Parser, Composer, SafeConstructor, Resolver):
|
||||||
|
|
||||||
|
def __init__(self, stream):
|
||||||
|
Reader.__init__(self, stream)
|
||||||
|
Scanner.__init__(self)
|
||||||
|
Parser.__init__(self)
|
||||||
|
Composer.__init__(self)
|
||||||
|
SafeConstructor.__init__(self)
|
||||||
|
Resolver.__init__(self)
|
||||||
|
|
||||||
|
class Loader(Reader, Scanner, Parser, Composer, Constructor, Resolver):
|
||||||
|
|
||||||
|
def __init__(self, stream):
|
||||||
|
Reader.__init__(self, stream)
|
||||||
|
Scanner.__init__(self)
|
||||||
|
Parser.__init__(self)
|
||||||
|
Composer.__init__(self)
|
||||||
|
Constructor.__init__(self)
|
||||||
|
Resolver.__init__(self)
|
||||||
|
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
|
||||||
|
class Node(object):
|
||||||
|
def __init__(self, tag, value, start_mark, end_mark):
|
||||||
|
self.tag = tag
|
||||||
|
self.value = value
|
||||||
|
self.start_mark = start_mark
|
||||||
|
self.end_mark = end_mark
|
||||||
|
def __repr__(self):
|
||||||
|
value = self.value
|
||||||
|
#if isinstance(value, list):
|
||||||
|
# if len(value) == 0:
|
||||||
|
# value = '<empty>'
|
||||||
|
# elif len(value) == 1:
|
||||||
|
# value = '<1 item>'
|
||||||
|
# else:
|
||||||
|
# value = '<%d items>' % len(value)
|
||||||
|
#else:
|
||||||
|
# if len(value) > 75:
|
||||||
|
# value = repr(value[:70]+u' ... ')
|
||||||
|
# else:
|
||||||
|
# value = repr(value)
|
||||||
|
value = repr(value)
|
||||||
|
return '%s(tag=%r, value=%s)' % (self.__class__.__name__, self.tag, value)
|
||||||
|
|
||||||
|
class ScalarNode(Node):
|
||||||
|
id = 'scalar'
|
||||||
|
def __init__(self, tag, value,
|
||||||
|
start_mark=None, end_mark=None, style=None):
|
||||||
|
self.tag = tag
|
||||||
|
self.value = value
|
||||||
|
self.start_mark = start_mark
|
||||||
|
self.end_mark = end_mark
|
||||||
|
self.style = style
|
||||||
|
|
||||||
|
class CollectionNode(Node):
|
||||||
|
def __init__(self, tag, value,
|
||||||
|
start_mark=None, end_mark=None, flow_style=None):
|
||||||
|
self.tag = tag
|
||||||
|
self.value = value
|
||||||
|
self.start_mark = start_mark
|
||||||
|
self.end_mark = end_mark
|
||||||
|
self.flow_style = flow_style
|
||||||
|
|
||||||
|
class SequenceNode(CollectionNode):
|
||||||
|
id = 'sequence'
|
||||||
|
|
||||||
|
class MappingNode(CollectionNode):
|
||||||
|
id = 'mapping'
|
||||||
|
|
||||||
@@ -0,0 +1,584 @@
|
|||||||
|
|
||||||
|
# The following YAML grammar is LL(1) and is parsed by a recursive descent
|
||||||
|
# parser.
|
||||||
|
#
|
||||||
|
# stream ::= STREAM-START implicit_document? explicit_document* STREAM-END
|
||||||
|
# implicit_document ::= block_node DOCUMENT-END*
|
||||||
|
# explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END*
|
||||||
|
# block_node_or_indentless_sequence ::=
|
||||||
|
# ALIAS
|
||||||
|
# | properties (block_content | indentless_block_sequence)?
|
||||||
|
# | block_content
|
||||||
|
# | indentless_block_sequence
|
||||||
|
# block_node ::= ALIAS
|
||||||
|
# | properties block_content?
|
||||||
|
# | block_content
|
||||||
|
# flow_node ::= ALIAS
|
||||||
|
# | properties flow_content?
|
||||||
|
# | flow_content
|
||||||
|
# properties ::= TAG ANCHOR? | ANCHOR TAG?
|
||||||
|
# block_content ::= block_collection | flow_collection | SCALAR
|
||||||
|
# flow_content ::= flow_collection | SCALAR
|
||||||
|
# block_collection ::= block_sequence | block_mapping
|
||||||
|
# flow_collection ::= flow_sequence | flow_mapping
|
||||||
|
# block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END
|
||||||
|
# indentless_sequence ::= (BLOCK-ENTRY block_node?)+
|
||||||
|
# block_mapping ::= BLOCK-MAPPING_START
|
||||||
|
# ((KEY block_node_or_indentless_sequence?)?
|
||||||
|
# (VALUE block_node_or_indentless_sequence?)?)*
|
||||||
|
# BLOCK-END
|
||||||
|
# flow_sequence ::= FLOW-SEQUENCE-START
|
||||||
|
# (flow_sequence_entry FLOW-ENTRY)*
|
||||||
|
# flow_sequence_entry?
|
||||||
|
# FLOW-SEQUENCE-END
|
||||||
|
# flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)?
|
||||||
|
# flow_mapping ::= FLOW-MAPPING-START
|
||||||
|
# (flow_mapping_entry FLOW-ENTRY)*
|
||||||
|
# flow_mapping_entry?
|
||||||
|
# FLOW-MAPPING-END
|
||||||
|
# flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)?
|
||||||
|
#
|
||||||
|
# FIRST sets:
|
||||||
|
#
|
||||||
|
# stream: { STREAM-START }
|
||||||
|
# explicit_document: { DIRECTIVE DOCUMENT-START }
|
||||||
|
# implicit_document: FIRST(block_node)
|
||||||
|
# block_node: { ALIAS TAG ANCHOR SCALAR BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START }
|
||||||
|
# flow_node: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START }
|
||||||
|
# block_content: { BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START SCALAR }
|
||||||
|
# flow_content: { FLOW-SEQUENCE-START FLOW-MAPPING-START SCALAR }
|
||||||
|
# block_collection: { BLOCK-SEQUENCE-START BLOCK-MAPPING-START }
|
||||||
|
# flow_collection: { FLOW-SEQUENCE-START FLOW-MAPPING-START }
|
||||||
|
# block_sequence: { BLOCK-SEQUENCE-START }
|
||||||
|
# block_mapping: { BLOCK-MAPPING-START }
|
||||||
|
# block_node_or_indentless_sequence: { ALIAS ANCHOR TAG SCALAR BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START BLOCK-ENTRY }
|
||||||
|
# indentless_sequence: { ENTRY }
|
||||||
|
# flow_collection: { FLOW-SEQUENCE-START FLOW-MAPPING-START }
|
||||||
|
# flow_sequence: { FLOW-SEQUENCE-START }
|
||||||
|
# flow_mapping: { FLOW-MAPPING-START }
|
||||||
|
# flow_sequence_entry: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START KEY }
|
||||||
|
# flow_mapping_entry: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START KEY }
|
||||||
|
|
||||||
|
__all__ = ['Parser', 'ParserError']
|
||||||
|
|
||||||
|
from error import MarkedYAMLError
|
||||||
|
from tokens import *
|
||||||
|
from events import *
|
||||||
|
from scanner import *
|
||||||
|
|
||||||
|
class ParserError(MarkedYAMLError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Parser(object):
|
||||||
|
# Since writing a recursive-descendant parser is a straightforward task, we
|
||||||
|
# do not give many comments here.
|
||||||
|
|
||||||
|
DEFAULT_TAGS = {
|
||||||
|
u'!': u'!',
|
||||||
|
u'!!': u'tag:yaml.org,2002:',
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.current_event = None
|
||||||
|
self.yaml_version = None
|
||||||
|
self.tag_handles = {}
|
||||||
|
self.states = []
|
||||||
|
self.marks = []
|
||||||
|
self.state = self.parse_stream_start
|
||||||
|
|
||||||
|
def check_event(self, *choices):
|
||||||
|
# Check the type of the next event.
|
||||||
|
if self.current_event is None:
|
||||||
|
if self.state:
|
||||||
|
self.current_event = self.state()
|
||||||
|
if self.current_event is not None:
|
||||||
|
if not choices:
|
||||||
|
return True
|
||||||
|
for choice in choices:
|
||||||
|
if isinstance(self.current_event, choice):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def peek_event(self):
|
||||||
|
# Get the next event.
|
||||||
|
if self.current_event is None:
|
||||||
|
if self.state:
|
||||||
|
self.current_event = self.state()
|
||||||
|
return self.current_event
|
||||||
|
|
||||||
|
def get_event(self):
|
||||||
|
# Get the next event and proceed further.
|
||||||
|
if self.current_event is None:
|
||||||
|
if self.state:
|
||||||
|
self.current_event = self.state()
|
||||||
|
value = self.current_event
|
||||||
|
self.current_event = None
|
||||||
|
return value
|
||||||
|
|
||||||
|
# stream ::= STREAM-START implicit_document? explicit_document* STREAM-END
|
||||||
|
# implicit_document ::= block_node DOCUMENT-END*
|
||||||
|
# explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END*
|
||||||
|
|
||||||
|
def parse_stream_start(self):
|
||||||
|
|
||||||
|
# Parse the stream start.
|
||||||
|
token = self.get_token()
|
||||||
|
event = StreamStartEvent(token.start_mark, token.end_mark,
|
||||||
|
encoding=token.encoding)
|
||||||
|
|
||||||
|
# Prepare the next state.
|
||||||
|
self.state = self.parse_implicit_document_start
|
||||||
|
|
||||||
|
return event
|
||||||
|
|
||||||
|
def parse_implicit_document_start(self):
|
||||||
|
|
||||||
|
# Parse an implicit document.
|
||||||
|
if not self.check_token(DirectiveToken, DocumentStartToken,
|
||||||
|
StreamEndToken):
|
||||||
|
self.tag_handles = self.DEFAULT_TAGS
|
||||||
|
token = self.peek_token()
|
||||||
|
start_mark = end_mark = token.start_mark
|
||||||
|
event = DocumentStartEvent(start_mark, end_mark,
|
||||||
|
explicit=False)
|
||||||
|
|
||||||
|
# Prepare the next state.
|
||||||
|
self.states.append(self.parse_document_end)
|
||||||
|
self.state = self.parse_block_node
|
||||||
|
|
||||||
|
return event
|
||||||
|
|
||||||
|
else:
|
||||||
|
return self.parse_document_start()
|
||||||
|
|
||||||
|
def parse_document_start(self):
|
||||||
|
|
||||||
|
# Parse any extra document end indicators.
|
||||||
|
while self.check_token(DocumentEndToken):
|
||||||
|
self.get_token()
|
||||||
|
|
||||||
|
# Parse an explicit document.
|
||||||
|
if not self.check_token(StreamEndToken):
|
||||||
|
token = self.peek_token()
|
||||||
|
start_mark = token.start_mark
|
||||||
|
version, tags = self.process_directives()
|
||||||
|
if not self.check_token(DocumentStartToken):
|
||||||
|
raise ParserError(None, None,
|
||||||
|
"expected '<document start>', but found %r"
|
||||||
|
% self.peek_token().id,
|
||||||
|
self.peek_token().start_mark)
|
||||||
|
token = self.get_token()
|
||||||
|
end_mark = token.end_mark
|
||||||
|
event = DocumentStartEvent(start_mark, end_mark,
|
||||||
|
explicit=True, version=version, tags=tags)
|
||||||
|
self.states.append(self.parse_document_end)
|
||||||
|
self.state = self.parse_document_content
|
||||||
|
else:
|
||||||
|
# Parse the end of the stream.
|
||||||
|
token = self.get_token()
|
||||||
|
event = StreamEndEvent(token.start_mark, token.end_mark)
|
||||||
|
assert not self.states
|
||||||
|
assert not self.marks
|
||||||
|
self.state = None
|
||||||
|
return event
|
||||||
|
|
||||||
|
def parse_document_end(self):
|
||||||
|
|
||||||
|
# Parse the document end.
|
||||||
|
token = self.peek_token()
|
||||||
|
start_mark = end_mark = token.start_mark
|
||||||
|
explicit = False
|
||||||
|
if self.check_token(DocumentEndToken):
|
||||||
|
token = self.get_token()
|
||||||
|
end_mark = token.end_mark
|
||||||
|
explicit = True
|
||||||
|
event = DocumentEndEvent(start_mark, end_mark,
|
||||||
|
explicit=explicit)
|
||||||
|
|
||||||
|
# Prepare the next state.
|
||||||
|
self.state = self.parse_document_start
|
||||||
|
|
||||||
|
return event
|
||||||
|
|
||||||
|
def parse_document_content(self):
|
||||||
|
if self.check_token(DirectiveToken,
|
||||||
|
DocumentStartToken, DocumentEndToken, StreamEndToken):
|
||||||
|
event = self.process_empty_scalar(self.peek_token().start_mark)
|
||||||
|
self.state = self.states.pop()
|
||||||
|
return event
|
||||||
|
else:
|
||||||
|
return self.parse_block_node()
|
||||||
|
|
||||||
|
def process_directives(self):
|
||||||
|
self.yaml_version = None
|
||||||
|
self.tag_handles = {}
|
||||||
|
while self.check_token(DirectiveToken):
|
||||||
|
token = self.get_token()
|
||||||
|
if token.name == u'YAML':
|
||||||
|
if self.yaml_version is not None:
|
||||||
|
raise ParserError(None, None,
|
||||||
|
"found duplicate YAML directive", token.start_mark)
|
||||||
|
major, minor = token.value
|
||||||
|
if major != 1:
|
||||||
|
raise ParserError(None, None,
|
||||||
|
"found incompatible YAML document (version 1.* is required)",
|
||||||
|
token.start_mark)
|
||||||
|
self.yaml_version = token.value
|
||||||
|
elif token.name == u'TAG':
|
||||||
|
handle, prefix = token.value
|
||||||
|
if handle in self.tag_handles:
|
||||||
|
raise ParserError(None, None,
|
||||||
|
"duplicate tag handle %r" % handle.encode('utf-8'),
|
||||||
|
token.start_mark)
|
||||||
|
self.tag_handles[handle] = prefix
|
||||||
|
if self.tag_handles:
|
||||||
|
value = self.yaml_version, self.tag_handles.copy()
|
||||||
|
else:
|
||||||
|
value = self.yaml_version, None
|
||||||
|
for key in self.DEFAULT_TAGS:
|
||||||
|
if key not in self.tag_handles:
|
||||||
|
self.tag_handles[key] = self.DEFAULT_TAGS[key]
|
||||||
|
return value
|
||||||
|
|
||||||
|
# block_node_or_indentless_sequence ::= ALIAS
|
||||||
|
# | properties (block_content | indentless_block_sequence)?
|
||||||
|
# | block_content
|
||||||
|
# | indentless_block_sequence
|
||||||
|
# block_node ::= ALIAS
|
||||||
|
# | properties block_content?
|
||||||
|
# | block_content
|
||||||
|
# flow_node ::= ALIAS
|
||||||
|
# | properties flow_content?
|
||||||
|
# | flow_content
|
||||||
|
# properties ::= TAG ANCHOR? | ANCHOR TAG?
|
||||||
|
# block_content ::= block_collection | flow_collection | SCALAR
|
||||||
|
# flow_content ::= flow_collection | SCALAR
|
||||||
|
# block_collection ::= block_sequence | block_mapping
|
||||||
|
# flow_collection ::= flow_sequence | flow_mapping
|
||||||
|
|
||||||
|
def parse_block_node(self):
|
||||||
|
return self.parse_node(block=True)
|
||||||
|
|
||||||
|
def parse_flow_node(self):
|
||||||
|
return self.parse_node()
|
||||||
|
|
||||||
|
def parse_block_node_or_indentless_sequence(self):
|
||||||
|
return self.parse_node(block=True, indentless_sequence=True)
|
||||||
|
|
||||||
|
def parse_node(self, block=False, indentless_sequence=False):
|
||||||
|
if self.check_token(AliasToken):
|
||||||
|
token = self.get_token()
|
||||||
|
event = AliasEvent(token.value, token.start_mark, token.end_mark)
|
||||||
|
self.state = self.states.pop()
|
||||||
|
else:
|
||||||
|
anchor = None
|
||||||
|
tag = None
|
||||||
|
start_mark = end_mark = tag_mark = None
|
||||||
|
if self.check_token(AnchorToken):
|
||||||
|
token = self.get_token()
|
||||||
|
start_mark = token.start_mark
|
||||||
|
end_mark = token.end_mark
|
||||||
|
anchor = token.value
|
||||||
|
if self.check_token(TagToken):
|
||||||
|
token = self.get_token()
|
||||||
|
tag_mark = token.start_mark
|
||||||
|
end_mark = token.end_mark
|
||||||
|
tag = token.value
|
||||||
|
elif self.check_token(TagToken):
|
||||||
|
token = self.get_token()
|
||||||
|
start_mark = tag_mark = token.start_mark
|
||||||
|
end_mark = token.end_mark
|
||||||
|
tag = token.value
|
||||||
|
if self.check_token(AnchorToken):
|
||||||
|
token = self.get_token()
|
||||||
|
end_mark = token.end_mark
|
||||||
|
anchor = token.value
|
||||||
|
if tag is not None:
|
||||||
|
handle, suffix = tag
|
||||||
|
if handle is not None:
|
||||||
|
if handle not in self.tag_handles:
|
||||||
|
raise ParserError("while parsing a node", start_mark,
|
||||||
|
"found undefined tag handle %r" % handle.encode('utf-8'),
|
||||||
|
tag_mark)
|
||||||
|
tag = self.tag_handles[handle]+suffix
|
||||||
|
else:
|
||||||
|
tag = suffix
|
||||||
|
#if tag == u'!':
|
||||||
|
# raise ParserError("while parsing a node", start_mark,
|
||||||
|
# "found non-specific tag '!'", tag_mark,
|
||||||
|
# "Please check 'http://pyyaml.org/wiki/YAMLNonSpecificTag' and share your opinion.")
|
||||||
|
if start_mark is None:
|
||||||
|
start_mark = end_mark = self.peek_token().start_mark
|
||||||
|
event = None
|
||||||
|
implicit = (tag is None or tag == u'!')
|
||||||
|
if indentless_sequence and self.check_token(BlockEntryToken):
|
||||||
|
end_mark = self.peek_token().end_mark
|
||||||
|
event = SequenceStartEvent(anchor, tag, implicit,
|
||||||
|
start_mark, end_mark)
|
||||||
|
self.state = self.parse_indentless_sequence_entry
|
||||||
|
else:
|
||||||
|
if self.check_token(ScalarToken):
|
||||||
|
token = self.get_token()
|
||||||
|
end_mark = token.end_mark
|
||||||
|
if (token.plain and tag is None) or tag == u'!':
|
||||||
|
implicit = (True, False)
|
||||||
|
elif tag is None:
|
||||||
|
implicit = (False, True)
|
||||||
|
else:
|
||||||
|
implicit = (False, False)
|
||||||
|
event = ScalarEvent(anchor, tag, implicit, token.value,
|
||||||
|
start_mark, end_mark, style=token.style)
|
||||||
|
self.state = self.states.pop()
|
||||||
|
elif self.check_token(FlowSequenceStartToken):
|
||||||
|
end_mark = self.peek_token().end_mark
|
||||||
|
event = SequenceStartEvent(anchor, tag, implicit,
|
||||||
|
start_mark, end_mark, flow_style=True)
|
||||||
|
self.state = self.parse_flow_sequence_first_entry
|
||||||
|
elif self.check_token(FlowMappingStartToken):
|
||||||
|
end_mark = self.peek_token().end_mark
|
||||||
|
event = MappingStartEvent(anchor, tag, implicit,
|
||||||
|
start_mark, end_mark, flow_style=True)
|
||||||
|
self.state = self.parse_flow_mapping_first_key
|
||||||
|
elif block and self.check_token(BlockSequenceStartToken):
|
||||||
|
end_mark = self.peek_token().start_mark
|
||||||
|
event = SequenceStartEvent(anchor, tag, implicit,
|
||||||
|
start_mark, end_mark, flow_style=False)
|
||||||
|
self.state = self.parse_block_sequence_first_entry
|
||||||
|
elif block and self.check_token(BlockMappingStartToken):
|
||||||
|
end_mark = self.peek_token().start_mark
|
||||||
|
event = MappingStartEvent(anchor, tag, implicit,
|
||||||
|
start_mark, end_mark, flow_style=False)
|
||||||
|
self.state = self.parse_block_mapping_first_key
|
||||||
|
elif anchor is not None or tag is not None:
|
||||||
|
# Empty scalars are allowed even if a tag or an anchor is
|
||||||
|
# specified.
|
||||||
|
event = ScalarEvent(anchor, tag, (implicit, False), u'',
|
||||||
|
start_mark, end_mark)
|
||||||
|
self.state = self.states.pop()
|
||||||
|
else:
|
||||||
|
if block:
|
||||||
|
node = 'block'
|
||||||
|
else:
|
||||||
|
node = 'flow'
|
||||||
|
token = self.peek_token()
|
||||||
|
raise ParserError("while parsing a %s node" % node, start_mark,
|
||||||
|
"expected the node content, but found %r" % token.id,
|
||||||
|
token.start_mark)
|
||||||
|
return event
|
||||||
|
|
||||||
|
# block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END
|
||||||
|
|
||||||
|
def parse_block_sequence_first_entry(self):
|
||||||
|
token = self.get_token()
|
||||||
|
self.marks.append(token.start_mark)
|
||||||
|
return self.parse_block_sequence_entry()
|
||||||
|
|
||||||
|
def parse_block_sequence_entry(self):
|
||||||
|
if self.check_token(BlockEntryToken):
|
||||||
|
token = self.get_token()
|
||||||
|
if not self.check_token(BlockEntryToken, BlockEndToken):
|
||||||
|
self.states.append(self.parse_block_sequence_entry)
|
||||||
|
return self.parse_block_node()
|
||||||
|
else:
|
||||||
|
self.state = self.parse_block_sequence_entry
|
||||||
|
return self.process_empty_scalar(token.end_mark)
|
||||||
|
if not self.check_token(BlockEndToken):
|
||||||
|
token = self.peek_token()
|
||||||
|
raise ParserError("while parsing a block collection", self.marks[-1],
|
||||||
|
"expected <block end>, but found %r" % token.id, token.start_mark)
|
||||||
|
token = self.get_token()
|
||||||
|
event = SequenceEndEvent(token.start_mark, token.end_mark)
|
||||||
|
self.state = self.states.pop()
|
||||||
|
self.marks.pop()
|
||||||
|
return event
|
||||||
|
|
||||||
|
# indentless_sequence ::= (BLOCK-ENTRY block_node?)+
|
||||||
|
|
||||||
|
def parse_indentless_sequence_entry(self):
|
||||||
|
if self.check_token(BlockEntryToken):
|
||||||
|
token = self.get_token()
|
||||||
|
if not self.check_token(BlockEntryToken,
|
||||||
|
KeyToken, ValueToken, BlockEndToken):
|
||||||
|
self.states.append(self.parse_indentless_sequence_entry)
|
||||||
|
return self.parse_block_node()
|
||||||
|
else:
|
||||||
|
self.state = self.parse_indentless_sequence_entry
|
||||||
|
return self.process_empty_scalar(token.end_mark)
|
||||||
|
token = self.peek_token()
|
||||||
|
event = SequenceEndEvent(token.start_mark, token.start_mark)
|
||||||
|
self.state = self.states.pop()
|
||||||
|
return event
|
||||||
|
|
||||||
|
# block_mapping ::= BLOCK-MAPPING_START
|
||||||
|
# ((KEY block_node_or_indentless_sequence?)?
|
||||||
|
# (VALUE block_node_or_indentless_sequence?)?)*
|
||||||
|
# BLOCK-END
|
||||||
|
|
||||||
|
def parse_block_mapping_first_key(self):
|
||||||
|
token = self.get_token()
|
||||||
|
self.marks.append(token.start_mark)
|
||||||
|
return self.parse_block_mapping_key()
|
||||||
|
|
||||||
|
def parse_block_mapping_key(self):
|
||||||
|
if self.check_token(KeyToken):
|
||||||
|
token = self.get_token()
|
||||||
|
if not self.check_token(KeyToken, ValueToken, BlockEndToken):
|
||||||
|
self.states.append(self.parse_block_mapping_value)
|
||||||
|
return self.parse_block_node_or_indentless_sequence()
|
||||||
|
else:
|
||||||
|
self.state = self.parse_block_mapping_value
|
||||||
|
return self.process_empty_scalar(token.end_mark)
|
||||||
|
if not self.check_token(BlockEndToken):
|
||||||
|
token = self.peek_token()
|
||||||
|
raise ParserError("while parsing a block mapping", self.marks[-1],
|
||||||
|
"expected <block end>, but found %r" % token.id, token.start_mark)
|
||||||
|
token = self.get_token()
|
||||||
|
event = MappingEndEvent(token.start_mark, token.end_mark)
|
||||||
|
self.state = self.states.pop()
|
||||||
|
self.marks.pop()
|
||||||
|
return event
|
||||||
|
|
||||||
|
def parse_block_mapping_value(self):
|
||||||
|
if self.check_token(ValueToken):
|
||||||
|
token = self.get_token()
|
||||||
|
if not self.check_token(KeyToken, ValueToken, BlockEndToken):
|
||||||
|
self.states.append(self.parse_block_mapping_key)
|
||||||
|
return self.parse_block_node_or_indentless_sequence()
|
||||||
|
else:
|
||||||
|
self.state = self.parse_block_mapping_key
|
||||||
|
return self.process_empty_scalar(token.end_mark)
|
||||||
|
else:
|
||||||
|
self.state = self.parse_block_mapping_key
|
||||||
|
token = self.peek_token()
|
||||||
|
return self.process_empty_scalar(token.start_mark)
|
||||||
|
|
||||||
|
# flow_sequence ::= FLOW-SEQUENCE-START
|
||||||
|
# (flow_sequence_entry FLOW-ENTRY)*
|
||||||
|
# flow_sequence_entry?
|
||||||
|
# FLOW-SEQUENCE-END
|
||||||
|
# flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)?
|
||||||
|
#
|
||||||
|
# Note that while production rules for both flow_sequence_entry and
|
||||||
|
# flow_mapping_entry are equal, their interpretations are different.
|
||||||
|
# For `flow_sequence_entry`, the part `KEY flow_node? (VALUE flow_node?)?`
|
||||||
|
# generate an inline mapping (set syntax).
|
||||||
|
|
||||||
|
def parse_flow_sequence_first_entry(self):
|
||||||
|
token = self.get_token()
|
||||||
|
self.marks.append(token.start_mark)
|
||||||
|
return self.parse_flow_sequence_entry(first=True)
|
||||||
|
|
||||||
|
def parse_flow_sequence_entry(self, first=False):
|
||||||
|
if not self.check_token(FlowSequenceEndToken):
|
||||||
|
if not first:
|
||||||
|
if self.check_token(FlowEntryToken):
|
||||||
|
self.get_token()
|
||||||
|
else:
|
||||||
|
token = self.peek_token()
|
||||||
|
raise ParserError("while parsing a flow sequence", self.marks[-1],
|
||||||
|
"expected ',' or ']', but got %r" % token.id, token.start_mark)
|
||||||
|
|
||||||
|
if self.check_token(KeyToken):
|
||||||
|
token = self.peek_token()
|
||||||
|
event = MappingStartEvent(None, None, True,
|
||||||
|
token.start_mark, token.end_mark,
|
||||||
|
flow_style=True)
|
||||||
|
self.state = self.parse_flow_sequence_entry_mapping_key
|
||||||
|
return event
|
||||||
|
elif not self.check_token(FlowSequenceEndToken):
|
||||||
|
self.states.append(self.parse_flow_sequence_entry)
|
||||||
|
return self.parse_flow_node()
|
||||||
|
token = self.get_token()
|
||||||
|
event = SequenceEndEvent(token.start_mark, token.end_mark)
|
||||||
|
self.state = self.states.pop()
|
||||||
|
self.marks.pop()
|
||||||
|
return event
|
||||||
|
|
||||||
|
def parse_flow_sequence_entry_mapping_key(self):
|
||||||
|
token = self.get_token()
|
||||||
|
if not self.check_token(ValueToken,
|
||||||
|
FlowEntryToken, FlowSequenceEndToken):
|
||||||
|
self.states.append(self.parse_flow_sequence_entry_mapping_value)
|
||||||
|
return self.parse_flow_node()
|
||||||
|
else:
|
||||||
|
self.state = self.parse_flow_sequence_entry_mapping_value
|
||||||
|
return self.process_empty_scalar(token.end_mark)
|
||||||
|
|
||||||
|
def parse_flow_sequence_entry_mapping_value(self):
|
||||||
|
if self.check_token(ValueToken):
|
||||||
|
token = self.get_token()
|
||||||
|
if not self.check_token(FlowEntryToken, FlowSequenceEndToken):
|
||||||
|
self.states.append(self.parse_flow_sequence_entry_mapping_end)
|
||||||
|
return self.parse_flow_node()
|
||||||
|
else:
|
||||||
|
self.state = self.parse_flow_sequence_entry_mapping_end
|
||||||
|
return self.process_empty_scalar(token.end_mark)
|
||||||
|
else:
|
||||||
|
self.state = self.parse_flow_sequence_entry_mapping_end
|
||||||
|
token = self.peek_token()
|
||||||
|
return self.process_empty_scalar(token.start_mark)
|
||||||
|
|
||||||
|
def parse_flow_sequence_entry_mapping_end(self):
|
||||||
|
self.state = self.parse_flow_sequence_entry
|
||||||
|
token = self.peek_token()
|
||||||
|
return MappingEndEvent(token.start_mark, token.start_mark)
|
||||||
|
|
||||||
|
# flow_mapping ::= FLOW-MAPPING-START
|
||||||
|
# (flow_mapping_entry FLOW-ENTRY)*
|
||||||
|
# flow_mapping_entry?
|
||||||
|
# FLOW-MAPPING-END
|
||||||
|
# flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)?
|
||||||
|
|
||||||
|
def parse_flow_mapping_first_key(self):
|
||||||
|
token = self.get_token()
|
||||||
|
self.marks.append(token.start_mark)
|
||||||
|
return self.parse_flow_mapping_key(first=True)
|
||||||
|
|
||||||
|
def parse_flow_mapping_key(self, first=False):
|
||||||
|
if not self.check_token(FlowMappingEndToken):
|
||||||
|
if not first:
|
||||||
|
if self.check_token(FlowEntryToken):
|
||||||
|
self.get_token()
|
||||||
|
else:
|
||||||
|
token = self.peek_token()
|
||||||
|
raise ParserError("while parsing a flow mapping", self.marks[-1],
|
||||||
|
"expected ',' or '}', but got %r" % token.id, token.start_mark)
|
||||||
|
if self.check_token(KeyToken):
|
||||||
|
token = self.get_token()
|
||||||
|
if not self.check_token(ValueToken,
|
||||||
|
FlowEntryToken, FlowMappingEndToken):
|
||||||
|
self.states.append(self.parse_flow_mapping_value)
|
||||||
|
return self.parse_flow_node()
|
||||||
|
else:
|
||||||
|
self.state = self.parse_flow_mapping_value
|
||||||
|
return self.process_empty_scalar(token.end_mark)
|
||||||
|
elif not self.check_token(FlowMappingEndToken):
|
||||||
|
self.states.append(self.parse_flow_mapping_empty_value)
|
||||||
|
return self.parse_flow_node()
|
||||||
|
token = self.get_token()
|
||||||
|
event = MappingEndEvent(token.start_mark, token.end_mark)
|
||||||
|
self.state = self.states.pop()
|
||||||
|
self.marks.pop()
|
||||||
|
return event
|
||||||
|
|
||||||
|
def parse_flow_mapping_value(self):
|
||||||
|
if self.check_token(ValueToken):
|
||||||
|
token = self.get_token()
|
||||||
|
if not self.check_token(FlowEntryToken, FlowMappingEndToken):
|
||||||
|
self.states.append(self.parse_flow_mapping_key)
|
||||||
|
return self.parse_flow_node()
|
||||||
|
else:
|
||||||
|
self.state = self.parse_flow_mapping_key
|
||||||
|
return self.process_empty_scalar(token.end_mark)
|
||||||
|
else:
|
||||||
|
self.state = self.parse_flow_mapping_key
|
||||||
|
token = self.peek_token()
|
||||||
|
return self.process_empty_scalar(token.start_mark)
|
||||||
|
|
||||||
|
def parse_flow_mapping_empty_value(self):
|
||||||
|
self.state = self.parse_flow_mapping_key
|
||||||
|
return self.process_empty_scalar(self.peek_token().start_mark)
|
||||||
|
|
||||||
|
def process_empty_scalar(self, mark):
|
||||||
|
return ScalarEvent(None, None, (True, False), u'', mark, mark)
|
||||||
|
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
# This module contains abstractions for the input stream. You don't have to
|
||||||
|
# looks further, there are no pretty code.
|
||||||
|
#
|
||||||
|
# We define two classes here.
|
||||||
|
#
|
||||||
|
# Mark(source, line, column)
|
||||||
|
# It's just a record and its only use is producing nice error messages.
|
||||||
|
# Parser does not use it for any other purposes.
|
||||||
|
#
|
||||||
|
# Reader(source, data)
|
||||||
|
# Reader determines the encoding of `data` and converts it to unicode.
|
||||||
|
# Reader provides the following methods and attributes:
|
||||||
|
# reader.peek(length=1) - return the next `length` characters
|
||||||
|
# reader.forward(length=1) - move the current position to `length` characters.
|
||||||
|
# reader.index - the number of the current character.
|
||||||
|
# reader.line, stream.column - the line and the column of the current character.
|
||||||
|
|
||||||
|
__all__ = ['Reader', 'ReaderError']
|
||||||
|
|
||||||
|
from error import YAMLError, Mark
|
||||||
|
|
||||||
|
import codecs, re
|
||||||
|
|
||||||
|
# Unfortunately, codec functions in Python 2.3 does not support the `finish`
|
||||||
|
# arguments, so we have to write our own wrappers.
|
||||||
|
|
||||||
|
try:
|
||||||
|
codecs.utf_8_decode('', 'strict', False)
|
||||||
|
from codecs import utf_8_decode, utf_16_le_decode, utf_16_be_decode
|
||||||
|
|
||||||
|
except TypeError:
|
||||||
|
|
||||||
|
def utf_16_le_decode(data, errors, finish=False):
|
||||||
|
if not finish and len(data) % 2 == 1:
|
||||||
|
data = data[:-1]
|
||||||
|
return codecs.utf_16_le_decode(data, errors)
|
||||||
|
|
||||||
|
def utf_16_be_decode(data, errors, finish=False):
|
||||||
|
if not finish and len(data) % 2 == 1:
|
||||||
|
data = data[:-1]
|
||||||
|
return codecs.utf_16_be_decode(data, errors)
|
||||||
|
|
||||||
|
def utf_8_decode(data, errors, finish=False):
|
||||||
|
if not finish:
|
||||||
|
# We are trying to remove a possible incomplete multibyte character
|
||||||
|
# from the suffix of the data.
|
||||||
|
# The first byte of a multi-byte sequence is in the range 0xc0 to 0xfd.
|
||||||
|
# All further bytes are in the range 0x80 to 0xbf.
|
||||||
|
# UTF-8 encoded UCS characters may be up to six bytes long.
|
||||||
|
count = 0
|
||||||
|
while count < 5 and count < len(data) \
|
||||||
|
and '\x80' <= data[-count-1] <= '\xBF':
|
||||||
|
count -= 1
|
||||||
|
if count < 5 and count < len(data) \
|
||||||
|
and '\xC0' <= data[-count-1] <= '\xFD':
|
||||||
|
data = data[:-count-1]
|
||||||
|
return codecs.utf_8_decode(data, errors)
|
||||||
|
|
||||||
|
class ReaderError(YAMLError):
|
||||||
|
|
||||||
|
def __init__(self, name, position, character, encoding, reason):
|
||||||
|
self.name = name
|
||||||
|
self.character = character
|
||||||
|
self.position = position
|
||||||
|
self.encoding = encoding
|
||||||
|
self.reason = reason
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if isinstance(self.character, str):
|
||||||
|
return "'%s' codec can't decode byte #x%02x: %s\n" \
|
||||||
|
" in \"%s\", position %d" \
|
||||||
|
% (self.encoding, ord(self.character), self.reason,
|
||||||
|
self.name, self.position)
|
||||||
|
else:
|
||||||
|
return "unacceptable character #x%04x: %s\n" \
|
||||||
|
" in \"%s\", position %d" \
|
||||||
|
% (self.character, self.reason,
|
||||||
|
self.name, self.position)
|
||||||
|
|
||||||
|
class Reader(object):
|
||||||
|
# Reader:
|
||||||
|
# - determines the data encoding and converts it to unicode,
|
||||||
|
# - checks if characters are in allowed range,
|
||||||
|
# - adds '\0' to the end.
|
||||||
|
|
||||||
|
# Reader accepts
|
||||||
|
# - a `str` object,
|
||||||
|
# - a `unicode` object,
|
||||||
|
# - a file-like object with its `read` method returning `str`,
|
||||||
|
# - a file-like object with its `read` method returning `unicode`.
|
||||||
|
|
||||||
|
# Yeah, it's ugly and slow.
|
||||||
|
|
||||||
|
def __init__(self, stream):
|
||||||
|
self.name = None
|
||||||
|
self.stream = None
|
||||||
|
self.stream_pointer = 0
|
||||||
|
self.eof = True
|
||||||
|
self.buffer = u''
|
||||||
|
self.pointer = 0
|
||||||
|
self.raw_buffer = None
|
||||||
|
self.raw_decode = None
|
||||||
|
self.encoding = None
|
||||||
|
self.index = 0
|
||||||
|
self.line = 0
|
||||||
|
self.column = 0
|
||||||
|
if isinstance(stream, unicode):
|
||||||
|
self.name = "<unicode string>"
|
||||||
|
self.check_printable(stream)
|
||||||
|
self.buffer = stream+u'\0'
|
||||||
|
elif isinstance(stream, str):
|
||||||
|
self.name = "<string>"
|
||||||
|
self.raw_buffer = stream
|
||||||
|
self.determine_encoding()
|
||||||
|
else:
|
||||||
|
self.stream = stream
|
||||||
|
self.name = getattr(stream, 'name', "<file>")
|
||||||
|
self.eof = False
|
||||||
|
self.raw_buffer = ''
|
||||||
|
self.determine_encoding()
|
||||||
|
|
||||||
|
def peek(self, index=0):
|
||||||
|
try:
|
||||||
|
return self.buffer[self.pointer+index]
|
||||||
|
except IndexError:
|
||||||
|
self.update(index+1)
|
||||||
|
return self.buffer[self.pointer+index]
|
||||||
|
|
||||||
|
def prefix(self, length=1):
|
||||||
|
if self.pointer+length >= len(self.buffer):
|
||||||
|
self.update(length)
|
||||||
|
return self.buffer[self.pointer:self.pointer+length]
|
||||||
|
|
||||||
|
def forward(self, length=1):
|
||||||
|
if self.pointer+length+1 >= len(self.buffer):
|
||||||
|
self.update(length+1)
|
||||||
|
while length:
|
||||||
|
ch = self.buffer[self.pointer]
|
||||||
|
self.pointer += 1
|
||||||
|
self.index += 1
|
||||||
|
if ch in u'\n\x85\u2028\u2029' \
|
||||||
|
or (ch == u'\r' and self.buffer[self.pointer] != u'\n'):
|
||||||
|
self.line += 1
|
||||||
|
self.column = 0
|
||||||
|
elif ch != u'\uFEFF':
|
||||||
|
self.column += 1
|
||||||
|
length -= 1
|
||||||
|
|
||||||
|
def get_mark(self):
|
||||||
|
if self.stream is None:
|
||||||
|
return Mark(self.name, self.index, self.line, self.column,
|
||||||
|
self.buffer, self.pointer)
|
||||||
|
else:
|
||||||
|
return Mark(self.name, self.index, self.line, self.column,
|
||||||
|
None, None)
|
||||||
|
|
||||||
|
def determine_encoding(self):
|
||||||
|
while not self.eof and len(self.raw_buffer) < 2:
|
||||||
|
self.update_raw()
|
||||||
|
if not isinstance(self.raw_buffer, unicode):
|
||||||
|
if self.raw_buffer.startswith(codecs.BOM_UTF16_LE):
|
||||||
|
self.raw_decode = utf_16_le_decode
|
||||||
|
self.encoding = 'utf-16-le'
|
||||||
|
elif self.raw_buffer.startswith(codecs.BOM_UTF16_BE):
|
||||||
|
self.raw_decode = utf_16_be_decode
|
||||||
|
self.encoding = 'utf-16-be'
|
||||||
|
else:
|
||||||
|
self.raw_decode = utf_8_decode
|
||||||
|
self.encoding = 'utf-8'
|
||||||
|
self.update(1)
|
||||||
|
|
||||||
|
NON_PRINTABLE = re.compile(u'[^\x09\x0A\x0D\x20-\x7E\x85\xA0-\uD7FF\uE000-\uFFFD]')
|
||||||
|
def check_printable(self, data):
|
||||||
|
match = self.NON_PRINTABLE.search(data)
|
||||||
|
if match:
|
||||||
|
character = match.group()
|
||||||
|
position = self.index+(len(self.buffer)-self.pointer)+match.start()
|
||||||
|
raise ReaderError(self.name, position, ord(character),
|
||||||
|
'unicode', "special characters are not allowed")
|
||||||
|
|
||||||
|
def update(self, length):
|
||||||
|
if self.raw_buffer is None:
|
||||||
|
return
|
||||||
|
self.buffer = self.buffer[self.pointer:]
|
||||||
|
self.pointer = 0
|
||||||
|
while len(self.buffer) < length:
|
||||||
|
if not self.eof:
|
||||||
|
self.update_raw()
|
||||||
|
if self.raw_decode is not None:
|
||||||
|
try:
|
||||||
|
data, converted = self.raw_decode(self.raw_buffer,
|
||||||
|
'strict', self.eof)
|
||||||
|
except UnicodeDecodeError, exc:
|
||||||
|
character = exc.object[exc.start]
|
||||||
|
if self.stream is not None:
|
||||||
|
position = self.stream_pointer-len(self.raw_buffer)+exc.start
|
||||||
|
else:
|
||||||
|
position = exc.start
|
||||||
|
raise ReaderError(self.name, position, character,
|
||||||
|
exc.encoding, exc.reason)
|
||||||
|
else:
|
||||||
|
data = self.raw_buffer
|
||||||
|
converted = len(data)
|
||||||
|
self.check_printable(data)
|
||||||
|
self.buffer += data
|
||||||
|
self.raw_buffer = self.raw_buffer[converted:]
|
||||||
|
if self.eof:
|
||||||
|
self.buffer += u'\0'
|
||||||
|
self.raw_buffer = None
|
||||||
|
break
|
||||||
|
|
||||||
|
def update_raw(self, size=1024):
|
||||||
|
data = self.stream.read(size)
|
||||||
|
if data:
|
||||||
|
self.raw_buffer += data
|
||||||
|
self.stream_pointer += len(data)
|
||||||
|
else:
|
||||||
|
self.eof = True
|
||||||
|
|
||||||
|
#try:
|
||||||
|
# import psyco
|
||||||
|
# psyco.bind(Reader)
|
||||||
|
#except ImportError:
|
||||||
|
# pass
|
||||||
|
|
||||||
@@ -0,0 +1,489 @@
|
|||||||
|
|
||||||
|
__all__ = ['BaseRepresenter', 'SafeRepresenter', 'Representer',
|
||||||
|
'RepresenterError']
|
||||||
|
|
||||||
|
from error import *
|
||||||
|
from nodes import *
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
try:
|
||||||
|
set
|
||||||
|
except NameError:
|
||||||
|
from sets import Set as set
|
||||||
|
|
||||||
|
import sys, copy_reg, types
|
||||||
|
|
||||||
|
class RepresenterError(YAMLError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class BaseRepresenter(object):
|
||||||
|
|
||||||
|
yaml_representers = {}
|
||||||
|
yaml_multi_representers = {}
|
||||||
|
|
||||||
|
def __init__(self, default_style=None, default_flow_style=None):
|
||||||
|
self.default_style = default_style
|
||||||
|
self.default_flow_style = default_flow_style
|
||||||
|
self.represented_objects = {}
|
||||||
|
self.object_keeper = []
|
||||||
|
self.alias_key = None
|
||||||
|
|
||||||
|
def represent(self, data):
|
||||||
|
node = self.represent_data(data)
|
||||||
|
self.serialize(node)
|
||||||
|
self.represented_objects = {}
|
||||||
|
self.object_keeper = []
|
||||||
|
self.alias_key = None
|
||||||
|
|
||||||
|
def get_classobj_bases(self, cls):
|
||||||
|
bases = [cls]
|
||||||
|
for base in cls.__bases__:
|
||||||
|
bases.extend(self.get_classobj_bases(base))
|
||||||
|
return bases
|
||||||
|
|
||||||
|
def represent_data(self, data):
|
||||||
|
if self.ignore_aliases(data):
|
||||||
|
self.alias_key = None
|
||||||
|
else:
|
||||||
|
self.alias_key = id(data)
|
||||||
|
if self.alias_key is not None:
|
||||||
|
if self.alias_key in self.represented_objects:
|
||||||
|
node = self.represented_objects[self.alias_key]
|
||||||
|
#if node is None:
|
||||||
|
# raise RepresenterError("recursive objects are not allowed: %r" % data)
|
||||||
|
return node
|
||||||
|
#self.represented_objects[alias_key] = None
|
||||||
|
self.object_keeper.append(data)
|
||||||
|
data_types = type(data).__mro__
|
||||||
|
if type(data) is types.InstanceType:
|
||||||
|
data_types = self.get_classobj_bases(data.__class__)+list(data_types)
|
||||||
|
if data_types[0] in self.yaml_representers:
|
||||||
|
node = self.yaml_representers[data_types[0]](self, data)
|
||||||
|
else:
|
||||||
|
for data_type in data_types:
|
||||||
|
if data_type in self.yaml_multi_representers:
|
||||||
|
node = self.yaml_multi_representers[data_type](self, data)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
if None in self.yaml_multi_representers:
|
||||||
|
node = self.yaml_multi_representers[None](self, data)
|
||||||
|
elif None in self.yaml_representers:
|
||||||
|
node = self.yaml_representers[None](self, data)
|
||||||
|
else:
|
||||||
|
node = ScalarNode(None, unicode(data))
|
||||||
|
#if alias_key is not None:
|
||||||
|
# self.represented_objects[alias_key] = node
|
||||||
|
return node
|
||||||
|
|
||||||
|
def add_representer(cls, data_type, representer):
|
||||||
|
if not 'yaml_representers' in cls.__dict__:
|
||||||
|
cls.yaml_representers = cls.yaml_representers.copy()
|
||||||
|
cls.yaml_representers[data_type] = representer
|
||||||
|
add_representer = classmethod(add_representer)
|
||||||
|
|
||||||
|
def add_multi_representer(cls, data_type, representer):
|
||||||
|
if not 'yaml_multi_representers' in cls.__dict__:
|
||||||
|
cls.yaml_multi_representers = cls.yaml_multi_representers.copy()
|
||||||
|
cls.yaml_multi_representers[data_type] = representer
|
||||||
|
add_multi_representer = classmethod(add_multi_representer)
|
||||||
|
|
||||||
|
def represent_scalar(self, tag, value, style=None):
|
||||||
|
if style is None:
|
||||||
|
style = self.default_style
|
||||||
|
node = ScalarNode(tag, value, style=style)
|
||||||
|
if self.alias_key is not None:
|
||||||
|
self.represented_objects[self.alias_key] = node
|
||||||
|
return node
|
||||||
|
|
||||||
|
def represent_sequence(self, tag, sequence, flow_style=None):
|
||||||
|
value = []
|
||||||
|
node = SequenceNode(tag, value, flow_style=flow_style)
|
||||||
|
if self.alias_key is not None:
|
||||||
|
self.represented_objects[self.alias_key] = node
|
||||||
|
best_style = True
|
||||||
|
for item in sequence:
|
||||||
|
node_item = self.represent_data(item)
|
||||||
|
if not (isinstance(node_item, ScalarNode) and not node_item.style):
|
||||||
|
best_style = False
|
||||||
|
value.append(node_item)
|
||||||
|
if flow_style is None:
|
||||||
|
if self.default_flow_style is not None:
|
||||||
|
node.flow_style = self.default_flow_style
|
||||||
|
else:
|
||||||
|
node.flow_style = best_style
|
||||||
|
return node
|
||||||
|
|
||||||
|
def represent_mapping(self, tag, mapping, flow_style=None):
|
||||||
|
value = []
|
||||||
|
node = MappingNode(tag, value, flow_style=flow_style)
|
||||||
|
if self.alias_key is not None:
|
||||||
|
self.represented_objects[self.alias_key] = node
|
||||||
|
best_style = True
|
||||||
|
if hasattr(mapping, 'items'):
|
||||||
|
mapping = mapping.items()
|
||||||
|
mapping.sort()
|
||||||
|
for item_key, item_value in mapping:
|
||||||
|
node_key = self.represent_data(item_key)
|
||||||
|
node_value = self.represent_data(item_value)
|
||||||
|
if not (isinstance(node_key, ScalarNode) and not node_key.style):
|
||||||
|
best_style = False
|
||||||
|
if not (isinstance(node_value, ScalarNode) and not node_value.style):
|
||||||
|
best_style = False
|
||||||
|
value.append((node_key, node_value))
|
||||||
|
if flow_style is None:
|
||||||
|
if self.default_flow_style is not None:
|
||||||
|
node.flow_style = self.default_flow_style
|
||||||
|
else:
|
||||||
|
node.flow_style = best_style
|
||||||
|
return node
|
||||||
|
|
||||||
|
def ignore_aliases(self, data):
|
||||||
|
return False
|
||||||
|
|
||||||
|
class SafeRepresenter(BaseRepresenter):
|
||||||
|
|
||||||
|
def ignore_aliases(self, data):
|
||||||
|
if data in [None, ()]:
|
||||||
|
return True
|
||||||
|
if isinstance(data, (str, unicode, bool, int, float)):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def represent_none(self, data):
|
||||||
|
return self.represent_scalar(u'tag:yaml.org,2002:null',
|
||||||
|
u'null')
|
||||||
|
|
||||||
|
def represent_str(self, data):
|
||||||
|
tag = None
|
||||||
|
style = None
|
||||||
|
try:
|
||||||
|
data = unicode(data, 'ascii')
|
||||||
|
tag = u'tag:yaml.org,2002:str'
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
try:
|
||||||
|
data = unicode(data, 'utf-8')
|
||||||
|
tag = u'tag:yaml.org,2002:str'
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
data = data.encode('base64')
|
||||||
|
tag = u'tag:yaml.org,2002:binary'
|
||||||
|
style = '|'
|
||||||
|
return self.represent_scalar(tag, data, style=style)
|
||||||
|
|
||||||
|
def represent_unicode(self, data):
|
||||||
|
return self.represent_scalar(u'tag:yaml.org,2002:str', data)
|
||||||
|
|
||||||
|
def represent_bool(self, data):
|
||||||
|
if data:
|
||||||
|
value = u'true'
|
||||||
|
else:
|
||||||
|
value = u'false'
|
||||||
|
return self.represent_scalar(u'tag:yaml.org,2002:bool', value)
|
||||||
|
|
||||||
|
def represent_int(self, data):
|
||||||
|
return self.represent_scalar(u'tag:yaml.org,2002:int', unicode(data))
|
||||||
|
|
||||||
|
def represent_long(self, data):
|
||||||
|
return self.represent_scalar(u'tag:yaml.org,2002:int', unicode(data))
|
||||||
|
|
||||||
|
inf_value = 1e300
|
||||||
|
while repr(inf_value) != repr(inf_value*inf_value):
|
||||||
|
inf_value *= inf_value
|
||||||
|
|
||||||
|
def represent_float(self, data):
|
||||||
|
if data != data or (data == 0.0 and data == 1.0):
|
||||||
|
value = u'.nan'
|
||||||
|
elif data == self.inf_value:
|
||||||
|
value = u'.inf'
|
||||||
|
elif data == -self.inf_value:
|
||||||
|
value = u'-.inf'
|
||||||
|
else:
|
||||||
|
value = unicode(repr(data)).lower()
|
||||||
|
# Note that in some cases `repr(data)` represents a float number
|
||||||
|
# without the decimal parts. For instance:
|
||||||
|
# >>> repr(1e17)
|
||||||
|
# '1e17'
|
||||||
|
# Unfortunately, this is not a valid float representation according
|
||||||
|
# to the definition of the `!!float` tag. We fix this by adding
|
||||||
|
# '.0' before the 'e' symbol.
|
||||||
|
if u'.' not in value and u'e' in value:
|
||||||
|
value = value.replace(u'e', u'.0e', 1)
|
||||||
|
return self.represent_scalar(u'tag:yaml.org,2002:float', value)
|
||||||
|
|
||||||
|
def represent_list(self, data):
|
||||||
|
#pairs = (len(data) > 0 and isinstance(data, list))
|
||||||
|
#if pairs:
|
||||||
|
# for item in data:
|
||||||
|
# if not isinstance(item, tuple) or len(item) != 2:
|
||||||
|
# pairs = False
|
||||||
|
# break
|
||||||
|
#if not pairs:
|
||||||
|
return self.represent_sequence(u'tag:yaml.org,2002:seq', data)
|
||||||
|
#value = []
|
||||||
|
#for item_key, item_value in data:
|
||||||
|
# value.append(self.represent_mapping(u'tag:yaml.org,2002:map',
|
||||||
|
# [(item_key, item_value)]))
|
||||||
|
#return SequenceNode(u'tag:yaml.org,2002:pairs', value)
|
||||||
|
|
||||||
|
def represent_dict(self, data):
|
||||||
|
return self.represent_mapping(u'tag:yaml.org,2002:map', data)
|
||||||
|
|
||||||
|
def represent_set(self, data):
|
||||||
|
value = {}
|
||||||
|
for key in data:
|
||||||
|
value[key] = None
|
||||||
|
return self.represent_mapping(u'tag:yaml.org,2002:set', value)
|
||||||
|
|
||||||
|
def represent_date(self, data):
|
||||||
|
value = unicode(data.isoformat())
|
||||||
|
return self.represent_scalar(u'tag:yaml.org,2002:timestamp', value)
|
||||||
|
|
||||||
|
def represent_datetime(self, data):
|
||||||
|
value = unicode(data.isoformat(' '))
|
||||||
|
return self.represent_scalar(u'tag:yaml.org,2002:timestamp', value)
|
||||||
|
|
||||||
|
def represent_yaml_object(self, tag, data, cls, flow_style=None):
|
||||||
|
if hasattr(data, '__getstate__'):
|
||||||
|
state = data.__getstate__()
|
||||||
|
else:
|
||||||
|
state = data.__dict__.copy()
|
||||||
|
return self.represent_mapping(tag, state, flow_style=flow_style)
|
||||||
|
|
||||||
|
def represent_undefined(self, data):
|
||||||
|
raise RepresenterError("cannot represent an object: %s" % data)
|
||||||
|
|
||||||
|
SafeRepresenter.add_representer(type(None),
|
||||||
|
SafeRepresenter.represent_none)
|
||||||
|
|
||||||
|
SafeRepresenter.add_representer(str,
|
||||||
|
SafeRepresenter.represent_str)
|
||||||
|
|
||||||
|
SafeRepresenter.add_representer(unicode,
|
||||||
|
SafeRepresenter.represent_unicode)
|
||||||
|
|
||||||
|
SafeRepresenter.add_representer(bool,
|
||||||
|
SafeRepresenter.represent_bool)
|
||||||
|
|
||||||
|
SafeRepresenter.add_representer(int,
|
||||||
|
SafeRepresenter.represent_int)
|
||||||
|
|
||||||
|
SafeRepresenter.add_representer(long,
|
||||||
|
SafeRepresenter.represent_long)
|
||||||
|
|
||||||
|
SafeRepresenter.add_representer(float,
|
||||||
|
SafeRepresenter.represent_float)
|
||||||
|
|
||||||
|
SafeRepresenter.add_representer(list,
|
||||||
|
SafeRepresenter.represent_list)
|
||||||
|
|
||||||
|
SafeRepresenter.add_representer(tuple,
|
||||||
|
SafeRepresenter.represent_list)
|
||||||
|
|
||||||
|
SafeRepresenter.add_representer(dict,
|
||||||
|
SafeRepresenter.represent_dict)
|
||||||
|
|
||||||
|
SafeRepresenter.add_representer(set,
|
||||||
|
SafeRepresenter.represent_set)
|
||||||
|
|
||||||
|
SafeRepresenter.add_representer(datetime.date,
|
||||||
|
SafeRepresenter.represent_date)
|
||||||
|
|
||||||
|
SafeRepresenter.add_representer(datetime.datetime,
|
||||||
|
SafeRepresenter.represent_datetime)
|
||||||
|
|
||||||
|
SafeRepresenter.add_representer(None,
|
||||||
|
SafeRepresenter.represent_undefined)
|
||||||
|
|
||||||
|
class Representer(SafeRepresenter):
|
||||||
|
|
||||||
|
def represent_str(self, data):
|
||||||
|
tag = None
|
||||||
|
style = None
|
||||||
|
try:
|
||||||
|
data = unicode(data, 'ascii')
|
||||||
|
tag = u'tag:yaml.org,2002:str'
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
try:
|
||||||
|
data = unicode(data, 'utf-8')
|
||||||
|
tag = u'tag:yaml.org,2002:python/str'
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
data = data.encode('base64')
|
||||||
|
tag = u'tag:yaml.org,2002:binary'
|
||||||
|
style = '|'
|
||||||
|
return self.represent_scalar(tag, data, style=style)
|
||||||
|
|
||||||
|
def represent_unicode(self, data):
|
||||||
|
tag = None
|
||||||
|
try:
|
||||||
|
data.encode('ascii')
|
||||||
|
tag = u'tag:yaml.org,2002:python/unicode'
|
||||||
|
except UnicodeEncodeError:
|
||||||
|
tag = u'tag:yaml.org,2002:str'
|
||||||
|
return self.represent_scalar(tag, data)
|
||||||
|
|
||||||
|
def represent_long(self, data):
|
||||||
|
tag = u'tag:yaml.org,2002:int'
|
||||||
|
if int(data) is not data:
|
||||||
|
tag = u'tag:yaml.org,2002:python/long'
|
||||||
|
return self.represent_scalar(tag, unicode(data))
|
||||||
|
|
||||||
|
def represent_complex(self, data):
|
||||||
|
if data.imag == 0.0:
|
||||||
|
data = u'%r' % data.real
|
||||||
|
elif data.real == 0.0:
|
||||||
|
data = u'%rj' % data.imag
|
||||||
|
elif data.imag > 0:
|
||||||
|
data = u'%r+%rj' % (data.real, data.imag)
|
||||||
|
else:
|
||||||
|
data = u'%r%rj' % (data.real, data.imag)
|
||||||
|
return self.represent_scalar(u'tag:yaml.org,2002:python/complex', data)
|
||||||
|
|
||||||
|
def represent_tuple(self, data):
|
||||||
|
return self.represent_sequence(u'tag:yaml.org,2002:python/tuple', data)
|
||||||
|
|
||||||
|
def represent_name(self, data):
|
||||||
|
name = u'%s.%s' % (data.__module__, data.__name__)
|
||||||
|
return self.represent_scalar(u'tag:yaml.org,2002:python/name:'+name, u'')
|
||||||
|
|
||||||
|
def represent_module(self, data):
|
||||||
|
return self.represent_scalar(
|
||||||
|
u'tag:yaml.org,2002:python/module:'+data.__name__, u'')
|
||||||
|
|
||||||
|
def represent_instance(self, data):
|
||||||
|
# For instances of classic classes, we use __getinitargs__ and
|
||||||
|
# __getstate__ to serialize the data.
|
||||||
|
|
||||||
|
# If data.__getinitargs__ exists, the object must be reconstructed by
|
||||||
|
# calling cls(**args), where args is a tuple returned by
|
||||||
|
# __getinitargs__. Otherwise, the cls.__init__ method should never be
|
||||||
|
# called and the class instance is created by instantiating a trivial
|
||||||
|
# class and assigning to the instance's __class__ variable.
|
||||||
|
|
||||||
|
# If data.__getstate__ exists, it returns the state of the object.
|
||||||
|
# Otherwise, the state of the object is data.__dict__.
|
||||||
|
|
||||||
|
# We produce either a !!python/object or !!python/object/new node.
|
||||||
|
# If data.__getinitargs__ does not exist and state is a dictionary, we
|
||||||
|
# produce a !!python/object node . Otherwise we produce a
|
||||||
|
# !!python/object/new node.
|
||||||
|
|
||||||
|
cls = data.__class__
|
||||||
|
class_name = u'%s.%s' % (cls.__module__, cls.__name__)
|
||||||
|
args = None
|
||||||
|
state = None
|
||||||
|
if hasattr(data, '__getinitargs__'):
|
||||||
|
args = list(data.__getinitargs__())
|
||||||
|
if hasattr(data, '__getstate__'):
|
||||||
|
state = data.__getstate__()
|
||||||
|
else:
|
||||||
|
state = data.__dict__
|
||||||
|
if args is None and isinstance(state, dict):
|
||||||
|
return self.represent_mapping(
|
||||||
|
u'tag:yaml.org,2002:python/object:'+class_name, state)
|
||||||
|
if isinstance(state, dict) and not state:
|
||||||
|
return self.represent_sequence(
|
||||||
|
u'tag:yaml.org,2002:python/object/new:'+class_name, args)
|
||||||
|
value = {}
|
||||||
|
if args:
|
||||||
|
value['args'] = args
|
||||||
|
value['state'] = state
|
||||||
|
return self.represent_mapping(
|
||||||
|
u'tag:yaml.org,2002:python/object/new:'+class_name, value)
|
||||||
|
|
||||||
|
def represent_object(self, data):
|
||||||
|
# We use __reduce__ API to save the data. data.__reduce__ returns
|
||||||
|
# a tuple of length 2-5:
|
||||||
|
# (function, args, state, listitems, dictitems)
|
||||||
|
|
||||||
|
# For reconstructing, we calls function(*args), then set its state,
|
||||||
|
# listitems, and dictitems if they are not None.
|
||||||
|
|
||||||
|
# A special case is when function.__name__ == '__newobj__'. In this
|
||||||
|
# case we create the object with args[0].__new__(*args).
|
||||||
|
|
||||||
|
# Another special case is when __reduce__ returns a string - we don't
|
||||||
|
# support it.
|
||||||
|
|
||||||
|
# We produce a !!python/object, !!python/object/new or
|
||||||
|
# !!python/object/apply node.
|
||||||
|
|
||||||
|
cls = type(data)
|
||||||
|
if cls in copy_reg.dispatch_table:
|
||||||
|
reduce = copy_reg.dispatch_table[cls](data)
|
||||||
|
elif hasattr(data, '__reduce_ex__'):
|
||||||
|
reduce = data.__reduce_ex__(2)
|
||||||
|
elif hasattr(data, '__reduce__'):
|
||||||
|
reduce = data.__reduce__()
|
||||||
|
else:
|
||||||
|
raise RepresenterError("cannot represent object: %r" % data)
|
||||||
|
reduce = (list(reduce)+[None]*5)[:5]
|
||||||
|
function, args, state, listitems, dictitems = reduce
|
||||||
|
args = list(args)
|
||||||
|
if state is None:
|
||||||
|
state = {}
|
||||||
|
if listitems is not None:
|
||||||
|
listitems = list(listitems)
|
||||||
|
if dictitems is not None:
|
||||||
|
dictitems = dict(dictitems)
|
||||||
|
if function.__name__ == '__newobj__':
|
||||||
|
function = args[0]
|
||||||
|
args = args[1:]
|
||||||
|
tag = u'tag:yaml.org,2002:python/object/new:'
|
||||||
|
newobj = True
|
||||||
|
else:
|
||||||
|
tag = u'tag:yaml.org,2002:python/object/apply:'
|
||||||
|
newobj = False
|
||||||
|
function_name = u'%s.%s' % (function.__module__, function.__name__)
|
||||||
|
if not args and not listitems and not dictitems \
|
||||||
|
and isinstance(state, dict) and newobj:
|
||||||
|
return self.represent_mapping(
|
||||||
|
u'tag:yaml.org,2002:python/object:'+function_name, state)
|
||||||
|
if not listitems and not dictitems \
|
||||||
|
and isinstance(state, dict) and not state:
|
||||||
|
return self.represent_sequence(tag+function_name, args)
|
||||||
|
value = {}
|
||||||
|
if args:
|
||||||
|
value['args'] = args
|
||||||
|
if state or not isinstance(state, dict):
|
||||||
|
value['state'] = state
|
||||||
|
if listitems:
|
||||||
|
value['listitems'] = listitems
|
||||||
|
if dictitems:
|
||||||
|
value['dictitems'] = dictitems
|
||||||
|
return self.represent_mapping(tag+function_name, value)
|
||||||
|
|
||||||
|
Representer.add_representer(str,
|
||||||
|
Representer.represent_str)
|
||||||
|
|
||||||
|
Representer.add_representer(unicode,
|
||||||
|
Representer.represent_unicode)
|
||||||
|
|
||||||
|
Representer.add_representer(long,
|
||||||
|
Representer.represent_long)
|
||||||
|
|
||||||
|
Representer.add_representer(complex,
|
||||||
|
Representer.represent_complex)
|
||||||
|
|
||||||
|
Representer.add_representer(tuple,
|
||||||
|
Representer.represent_tuple)
|
||||||
|
|
||||||
|
Representer.add_representer(type,
|
||||||
|
Representer.represent_name)
|
||||||
|
|
||||||
|
Representer.add_representer(types.ClassType,
|
||||||
|
Representer.represent_name)
|
||||||
|
|
||||||
|
Representer.add_representer(types.FunctionType,
|
||||||
|
Representer.represent_name)
|
||||||
|
|
||||||
|
Representer.add_representer(types.BuiltinFunctionType,
|
||||||
|
Representer.represent_name)
|
||||||
|
|
||||||
|
Representer.add_representer(types.ModuleType,
|
||||||
|
Representer.represent_module)
|
||||||
|
|
||||||
|
Representer.add_multi_representer(types.InstanceType,
|
||||||
|
Representer.represent_instance)
|
||||||
|
|
||||||
|
Representer.add_multi_representer(object,
|
||||||
|
Representer.represent_object)
|
||||||
|
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
|
||||||
|
__all__ = ['BaseResolver', 'Resolver']
|
||||||
|
|
||||||
|
from error import *
|
||||||
|
from nodes import *
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
class ResolverError(YAMLError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class BaseResolver(object):
|
||||||
|
|
||||||
|
DEFAULT_SCALAR_TAG = u'tag:yaml.org,2002:str'
|
||||||
|
DEFAULT_SEQUENCE_TAG = u'tag:yaml.org,2002:seq'
|
||||||
|
DEFAULT_MAPPING_TAG = u'tag:yaml.org,2002:map'
|
||||||
|
|
||||||
|
yaml_implicit_resolvers = {}
|
||||||
|
yaml_path_resolvers = {}
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.resolver_exact_paths = []
|
||||||
|
self.resolver_prefix_paths = []
|
||||||
|
|
||||||
|
def add_implicit_resolver(cls, tag, regexp, first):
|
||||||
|
if not 'yaml_implicit_resolvers' in cls.__dict__:
|
||||||
|
cls.yaml_implicit_resolvers = cls.yaml_implicit_resolvers.copy()
|
||||||
|
if first is None:
|
||||||
|
first = [None]
|
||||||
|
for ch in first:
|
||||||
|
cls.yaml_implicit_resolvers.setdefault(ch, []).append((tag, regexp))
|
||||||
|
add_implicit_resolver = classmethod(add_implicit_resolver)
|
||||||
|
|
||||||
|
def add_path_resolver(cls, tag, path, kind=None):
|
||||||
|
# Note: `add_path_resolver` is experimental. The API could be changed.
|
||||||
|
# `new_path` is a pattern that is matched against the path from the
|
||||||
|
# root to the node that is being considered. `node_path` elements are
|
||||||
|
# tuples `(node_check, index_check)`. `node_check` is a node class:
|
||||||
|
# `ScalarNode`, `SequenceNode`, `MappingNode` or `None`. `None`
|
||||||
|
# matches any kind of a node. `index_check` could be `None`, a boolean
|
||||||
|
# value, a string value, or a number. `None` and `False` match against
|
||||||
|
# any _value_ of sequence and mapping nodes. `True` matches against
|
||||||
|
# any _key_ of a mapping node. A string `index_check` matches against
|
||||||
|
# a mapping value that corresponds to a scalar key which content is
|
||||||
|
# equal to the `index_check` value. An integer `index_check` matches
|
||||||
|
# against a sequence value with the index equal to `index_check`.
|
||||||
|
if not 'yaml_path_resolvers' in cls.__dict__:
|
||||||
|
cls.yaml_path_resolvers = cls.yaml_path_resolvers.copy()
|
||||||
|
new_path = []
|
||||||
|
for element in path:
|
||||||
|
if isinstance(element, (list, tuple)):
|
||||||
|
if len(element) == 2:
|
||||||
|
node_check, index_check = element
|
||||||
|
elif len(element) == 1:
|
||||||
|
node_check = element[0]
|
||||||
|
index_check = True
|
||||||
|
else:
|
||||||
|
raise ResolverError("Invalid path element: %s" % element)
|
||||||
|
else:
|
||||||
|
node_check = None
|
||||||
|
index_check = element
|
||||||
|
if node_check is str:
|
||||||
|
node_check = ScalarNode
|
||||||
|
elif node_check is list:
|
||||||
|
node_check = SequenceNode
|
||||||
|
elif node_check is dict:
|
||||||
|
node_check = MappingNode
|
||||||
|
elif node_check not in [ScalarNode, SequenceNode, MappingNode] \
|
||||||
|
and not isinstance(node_check, basestring) \
|
||||||
|
and node_check is not None:
|
||||||
|
raise ResolverError("Invalid node checker: %s" % node_check)
|
||||||
|
if not isinstance(index_check, (basestring, int)) \
|
||||||
|
and index_check is not None:
|
||||||
|
raise ResolverError("Invalid index checker: %s" % index_check)
|
||||||
|
new_path.append((node_check, index_check))
|
||||||
|
if kind is str:
|
||||||
|
kind = ScalarNode
|
||||||
|
elif kind is list:
|
||||||
|
kind = SequenceNode
|
||||||
|
elif kind is dict:
|
||||||
|
kind = MappingNode
|
||||||
|
elif kind not in [ScalarNode, SequenceNode, MappingNode] \
|
||||||
|
and kind is not None:
|
||||||
|
raise ResolverError("Invalid node kind: %s" % kind)
|
||||||
|
cls.yaml_path_resolvers[tuple(new_path), kind] = tag
|
||||||
|
add_path_resolver = classmethod(add_path_resolver)
|
||||||
|
|
||||||
|
def descend_resolver(self, current_node, current_index):
|
||||||
|
if not self.yaml_path_resolvers:
|
||||||
|
return
|
||||||
|
exact_paths = {}
|
||||||
|
prefix_paths = []
|
||||||
|
if current_node:
|
||||||
|
depth = len(self.resolver_prefix_paths)
|
||||||
|
for path, kind in self.resolver_prefix_paths[-1]:
|
||||||
|
if self.check_resolver_prefix(depth, path, kind,
|
||||||
|
current_node, current_index):
|
||||||
|
if len(path) > depth:
|
||||||
|
prefix_paths.append((path, kind))
|
||||||
|
else:
|
||||||
|
exact_paths[kind] = self.yaml_path_resolvers[path, kind]
|
||||||
|
else:
|
||||||
|
for path, kind in self.yaml_path_resolvers:
|
||||||
|
if not path:
|
||||||
|
exact_paths[kind] = self.yaml_path_resolvers[path, kind]
|
||||||
|
else:
|
||||||
|
prefix_paths.append((path, kind))
|
||||||
|
self.resolver_exact_paths.append(exact_paths)
|
||||||
|
self.resolver_prefix_paths.append(prefix_paths)
|
||||||
|
|
||||||
|
def ascend_resolver(self):
|
||||||
|
if not self.yaml_path_resolvers:
|
||||||
|
return
|
||||||
|
self.resolver_exact_paths.pop()
|
||||||
|
self.resolver_prefix_paths.pop()
|
||||||
|
|
||||||
|
def check_resolver_prefix(self, depth, path, kind,
|
||||||
|
current_node, current_index):
|
||||||
|
node_check, index_check = path[depth-1]
|
||||||
|
if isinstance(node_check, basestring):
|
||||||
|
if current_node.tag != node_check:
|
||||||
|
return
|
||||||
|
elif node_check is not None:
|
||||||
|
if not isinstance(current_node, node_check):
|
||||||
|
return
|
||||||
|
if index_check is True and current_index is not None:
|
||||||
|
return
|
||||||
|
if (index_check is False or index_check is None) \
|
||||||
|
and current_index is None:
|
||||||
|
return
|
||||||
|
if isinstance(index_check, basestring):
|
||||||
|
if not (isinstance(current_index, ScalarNode)
|
||||||
|
and index_check == current_index.value):
|
||||||
|
return
|
||||||
|
elif isinstance(index_check, int) and not isinstance(index_check, bool):
|
||||||
|
if index_check != current_index:
|
||||||
|
return
|
||||||
|
return True
|
||||||
|
|
||||||
|
def resolve(self, kind, value, implicit):
|
||||||
|
if kind is ScalarNode and implicit[0]:
|
||||||
|
if value == u'':
|
||||||
|
resolvers = self.yaml_implicit_resolvers.get(u'', [])
|
||||||
|
else:
|
||||||
|
resolvers = self.yaml_implicit_resolvers.get(value[0], [])
|
||||||
|
resolvers += self.yaml_implicit_resolvers.get(None, [])
|
||||||
|
for tag, regexp in resolvers:
|
||||||
|
if regexp.match(value):
|
||||||
|
return tag
|
||||||
|
implicit = implicit[1]
|
||||||
|
if self.yaml_path_resolvers:
|
||||||
|
exact_paths = self.resolver_exact_paths[-1]
|
||||||
|
if kind in exact_paths:
|
||||||
|
return exact_paths[kind]
|
||||||
|
if None in exact_paths:
|
||||||
|
return exact_paths[None]
|
||||||
|
if kind is ScalarNode:
|
||||||
|
return self.DEFAULT_SCALAR_TAG
|
||||||
|
elif kind is SequenceNode:
|
||||||
|
return self.DEFAULT_SEQUENCE_TAG
|
||||||
|
elif kind is MappingNode:
|
||||||
|
return self.DEFAULT_MAPPING_TAG
|
||||||
|
|
||||||
|
class Resolver(BaseResolver):
|
||||||
|
pass
|
||||||
|
|
||||||
|
Resolver.add_implicit_resolver(
|
||||||
|
u'tag:yaml.org,2002:bool',
|
||||||
|
re.compile(ur'''^(?:yes|Yes|YES|no|No|NO
|
||||||
|
|true|True|TRUE|false|False|FALSE
|
||||||
|
|on|On|ON|off|Off|OFF)$''', re.X),
|
||||||
|
list(u'yYnNtTfFoO'))
|
||||||
|
|
||||||
|
Resolver.add_implicit_resolver(
|
||||||
|
u'tag:yaml.org,2002:float',
|
||||||
|
re.compile(ur'''^(?:[-+]?(?:[0-9][0-9_]*)\.[0-9_]*(?:[eE][-+][0-9]+)?
|
||||||
|
|\.[0-9_]+(?:[eE][-+][0-9]+)?
|
||||||
|
|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]*
|
||||||
|
|[-+]?\.(?:inf|Inf|INF)
|
||||||
|
|\.(?:nan|NaN|NAN))$''', re.X),
|
||||||
|
list(u'-+0123456789.'))
|
||||||
|
|
||||||
|
Resolver.add_implicit_resolver(
|
||||||
|
u'tag:yaml.org,2002:int',
|
||||||
|
re.compile(ur'''^(?:[-+]?0b[0-1_]+
|
||||||
|
|[-+]?0[0-7_]+
|
||||||
|
|[-+]?(?:0|[1-9][0-9_]*)
|
||||||
|
|[-+]?0x[0-9a-fA-F_]+
|
||||||
|
|[-+]?[1-9][0-9_]*(?::[0-5]?[0-9])+)$''', re.X),
|
||||||
|
list(u'-+0123456789'))
|
||||||
|
|
||||||
|
Resolver.add_implicit_resolver(
|
||||||
|
u'tag:yaml.org,2002:merge',
|
||||||
|
re.compile(ur'^(?:<<)$'),
|
||||||
|
[u'<'])
|
||||||
|
|
||||||
|
Resolver.add_implicit_resolver(
|
||||||
|
u'tag:yaml.org,2002:null',
|
||||||
|
re.compile(ur'''^(?: ~
|
||||||
|
|null|Null|NULL
|
||||||
|
| )$''', re.X),
|
||||||
|
[u'~', u'n', u'N', u''])
|
||||||
|
|
||||||
|
Resolver.add_implicit_resolver(
|
||||||
|
u'tag:yaml.org,2002:timestamp',
|
||||||
|
re.compile(ur'''^(?:[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]
|
||||||
|
|[0-9][0-9][0-9][0-9] -[0-9][0-9]? -[0-9][0-9]?
|
||||||
|
(?:[Tt]|[ \t]+)[0-9][0-9]?
|
||||||
|
:[0-9][0-9] :[0-9][0-9] (?:\.[0-9]*)?
|
||||||
|
(?:[ \t]*(?:Z|[-+][0-9][0-9]?(?::[0-9][0-9])?))?)$''', re.X),
|
||||||
|
list(u'0123456789'))
|
||||||
|
|
||||||
|
Resolver.add_implicit_resolver(
|
||||||
|
u'tag:yaml.org,2002:value',
|
||||||
|
re.compile(ur'^(?:=)$'),
|
||||||
|
[u'='])
|
||||||
|
|
||||||
|
# The following resolver is only for documentation purposes. It cannot work
|
||||||
|
# because plain scalars cannot start with '!', '&', or '*'.
|
||||||
|
Resolver.add_implicit_resolver(
|
||||||
|
u'tag:yaml.org,2002:yaml',
|
||||||
|
re.compile(ur'^(?:!|&|\*)$'),
|
||||||
|
list(u'!&*'))
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,111 @@
|
|||||||
|
|
||||||
|
__all__ = ['Serializer', 'SerializerError']
|
||||||
|
|
||||||
|
from error import YAMLError
|
||||||
|
from events import *
|
||||||
|
from nodes import *
|
||||||
|
|
||||||
|
class SerializerError(YAMLError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Serializer(object):
|
||||||
|
|
||||||
|
ANCHOR_TEMPLATE = u'id%03d'
|
||||||
|
|
||||||
|
def __init__(self, encoding=None,
|
||||||
|
explicit_start=None, explicit_end=None, version=None, tags=None):
|
||||||
|
self.use_encoding = encoding
|
||||||
|
self.use_explicit_start = explicit_start
|
||||||
|
self.use_explicit_end = explicit_end
|
||||||
|
self.use_version = version
|
||||||
|
self.use_tags = tags
|
||||||
|
self.serialized_nodes = {}
|
||||||
|
self.anchors = {}
|
||||||
|
self.last_anchor_id = 0
|
||||||
|
self.closed = None
|
||||||
|
|
||||||
|
def open(self):
|
||||||
|
if self.closed is None:
|
||||||
|
self.emit(StreamStartEvent(encoding=self.use_encoding))
|
||||||
|
self.closed = False
|
||||||
|
elif self.closed:
|
||||||
|
raise SerializerError("serializer is closed")
|
||||||
|
else:
|
||||||
|
raise SerializerError("serializer is already opened")
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
if self.closed is None:
|
||||||
|
raise SerializerError("serializer is not opened")
|
||||||
|
elif not self.closed:
|
||||||
|
self.emit(StreamEndEvent())
|
||||||
|
self.closed = True
|
||||||
|
|
||||||
|
#def __del__(self):
|
||||||
|
# self.close()
|
||||||
|
|
||||||
|
def serialize(self, node):
|
||||||
|
if self.closed is None:
|
||||||
|
raise SerializerError("serializer is not opened")
|
||||||
|
elif self.closed:
|
||||||
|
raise SerializerError("serializer is closed")
|
||||||
|
self.emit(DocumentStartEvent(explicit=self.use_explicit_start,
|
||||||
|
version=self.use_version, tags=self.use_tags))
|
||||||
|
self.anchor_node(node)
|
||||||
|
self.serialize_node(node, None, None)
|
||||||
|
self.emit(DocumentEndEvent(explicit=self.use_explicit_end))
|
||||||
|
self.serialized_nodes = {}
|
||||||
|
self.anchors = {}
|
||||||
|
self.last_anchor_id = 0
|
||||||
|
|
||||||
|
def anchor_node(self, node):
|
||||||
|
if node in self.anchors:
|
||||||
|
if self.anchors[node] is None:
|
||||||
|
self.anchors[node] = self.generate_anchor(node)
|
||||||
|
else:
|
||||||
|
self.anchors[node] = None
|
||||||
|
if isinstance(node, SequenceNode):
|
||||||
|
for item in node.value:
|
||||||
|
self.anchor_node(item)
|
||||||
|
elif isinstance(node, MappingNode):
|
||||||
|
for key, value in node.value:
|
||||||
|
self.anchor_node(key)
|
||||||
|
self.anchor_node(value)
|
||||||
|
|
||||||
|
def generate_anchor(self, node):
|
||||||
|
self.last_anchor_id += 1
|
||||||
|
return self.ANCHOR_TEMPLATE % self.last_anchor_id
|
||||||
|
|
||||||
|
def serialize_node(self, node, parent, index):
|
||||||
|
alias = self.anchors[node]
|
||||||
|
if node in self.serialized_nodes:
|
||||||
|
self.emit(AliasEvent(alias))
|
||||||
|
else:
|
||||||
|
self.serialized_nodes[node] = True
|
||||||
|
self.descend_resolver(parent, index)
|
||||||
|
if isinstance(node, ScalarNode):
|
||||||
|
detected_tag = self.resolve(ScalarNode, node.value, (True, False))
|
||||||
|
default_tag = self.resolve(ScalarNode, node.value, (False, True))
|
||||||
|
implicit = (node.tag == detected_tag), (node.tag == default_tag)
|
||||||
|
self.emit(ScalarEvent(alias, node.tag, implicit, node.value,
|
||||||
|
style=node.style))
|
||||||
|
elif isinstance(node, SequenceNode):
|
||||||
|
implicit = (node.tag
|
||||||
|
== self.resolve(SequenceNode, node.value, True))
|
||||||
|
self.emit(SequenceStartEvent(alias, node.tag, implicit,
|
||||||
|
flow_style=node.flow_style))
|
||||||
|
index = 0
|
||||||
|
for item in node.value:
|
||||||
|
self.serialize_node(item, node, index)
|
||||||
|
index += 1
|
||||||
|
self.emit(SequenceEndEvent())
|
||||||
|
elif isinstance(node, MappingNode):
|
||||||
|
implicit = (node.tag
|
||||||
|
== self.resolve(MappingNode, node.value, True))
|
||||||
|
self.emit(MappingStartEvent(alias, node.tag, implicit,
|
||||||
|
flow_style=node.flow_style))
|
||||||
|
for key, value in node.value:
|
||||||
|
self.serialize_node(key, node, None)
|
||||||
|
self.serialize_node(value, node, key)
|
||||||
|
self.emit(MappingEndEvent())
|
||||||
|
self.ascend_resolver()
|
||||||
|
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
|
||||||
|
class Token(object):
|
||||||
|
def __init__(self, start_mark, end_mark):
|
||||||
|
self.start_mark = start_mark
|
||||||
|
self.end_mark = end_mark
|
||||||
|
def __repr__(self):
|
||||||
|
attributes = [key for key in self.__dict__
|
||||||
|
if not key.endswith('_mark')]
|
||||||
|
attributes.sort()
|
||||||
|
arguments = ', '.join(['%s=%r' % (key, getattr(self, key))
|
||||||
|
for key in attributes])
|
||||||
|
return '%s(%s)' % (self.__class__.__name__, arguments)
|
||||||
|
|
||||||
|
#class BOMToken(Token):
|
||||||
|
# id = '<byte order mark>'
|
||||||
|
|
||||||
|
class DirectiveToken(Token):
|
||||||
|
id = '<directive>'
|
||||||
|
def __init__(self, name, value, start_mark, end_mark):
|
||||||
|
self.name = name
|
||||||
|
self.value = value
|
||||||
|
self.start_mark = start_mark
|
||||||
|
self.end_mark = end_mark
|
||||||
|
|
||||||
|
class DocumentStartToken(Token):
|
||||||
|
id = '<document start>'
|
||||||
|
|
||||||
|
class DocumentEndToken(Token):
|
||||||
|
id = '<document end>'
|
||||||
|
|
||||||
|
class StreamStartToken(Token):
|
||||||
|
id = '<stream start>'
|
||||||
|
def __init__(self, start_mark=None, end_mark=None,
|
||||||
|
encoding=None):
|
||||||
|
self.start_mark = start_mark
|
||||||
|
self.end_mark = end_mark
|
||||||
|
self.encoding = encoding
|
||||||
|
|
||||||
|
class StreamEndToken(Token):
|
||||||
|
id = '<stream end>'
|
||||||
|
|
||||||
|
class BlockSequenceStartToken(Token):
|
||||||
|
id = '<block sequence start>'
|
||||||
|
|
||||||
|
class BlockMappingStartToken(Token):
|
||||||
|
id = '<block mapping start>'
|
||||||
|
|
||||||
|
class BlockEndToken(Token):
|
||||||
|
id = '<block end>'
|
||||||
|
|
||||||
|
class FlowSequenceStartToken(Token):
|
||||||
|
id = '['
|
||||||
|
|
||||||
|
class FlowMappingStartToken(Token):
|
||||||
|
id = '{'
|
||||||
|
|
||||||
|
class FlowSequenceEndToken(Token):
|
||||||
|
id = ']'
|
||||||
|
|
||||||
|
class FlowMappingEndToken(Token):
|
||||||
|
id = '}'
|
||||||
|
|
||||||
|
class KeyToken(Token):
|
||||||
|
id = '?'
|
||||||
|
|
||||||
|
class ValueToken(Token):
|
||||||
|
id = ':'
|
||||||
|
|
||||||
|
class BlockEntryToken(Token):
|
||||||
|
id = '-'
|
||||||
|
|
||||||
|
class FlowEntryToken(Token):
|
||||||
|
id = ','
|
||||||
|
|
||||||
|
class AliasToken(Token):
|
||||||
|
id = '<alias>'
|
||||||
|
def __init__(self, value, start_mark, end_mark):
|
||||||
|
self.value = value
|
||||||
|
self.start_mark = start_mark
|
||||||
|
self.end_mark = end_mark
|
||||||
|
|
||||||
|
class AnchorToken(Token):
|
||||||
|
id = '<anchor>'
|
||||||
|
def __init__(self, value, start_mark, end_mark):
|
||||||
|
self.value = value
|
||||||
|
self.start_mark = start_mark
|
||||||
|
self.end_mark = end_mark
|
||||||
|
|
||||||
|
class TagToken(Token):
|
||||||
|
id = '<tag>'
|
||||||
|
def __init__(self, value, start_mark, end_mark):
|
||||||
|
self.value = value
|
||||||
|
self.start_mark = start_mark
|
||||||
|
self.end_mark = end_mark
|
||||||
|
|
||||||
|
class ScalarToken(Token):
|
||||||
|
id = '<scalar>'
|
||||||
|
def __init__(self, value, plain, start_mark, end_mark, style=None):
|
||||||
|
self.value = value
|
||||||
|
self.plain = plain
|
||||||
|
self.start_mark = start_mark
|
||||||
|
self.end_mark = end_mark
|
||||||
|
self.style = style
|
||||||
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
# example: md5(csv) == md5(Data(csv).export(to='json'))
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
import tablib
|
|
||||||
Executable
+4
@@ -0,0 +1,4 @@
|
|||||||
|
nosetests test_tablib.py --with-xunit --with-coverage
|
||||||
|
coverage xml
|
||||||
|
rm -fr pylint.txt
|
||||||
|
pylint -d W0312 -d W0212 -d E1101 -d E0202 -d W0102 -d E0102 -f parseable ./tablib > pylint.txt || true
|
||||||
Executable
+424
@@ -0,0 +1,424 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""Tests for Tablib."""
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import tablib
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TablibTestCase(unittest.TestCase):
|
||||||
|
"""Tablib test cases."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""Create simple data set with headers."""
|
||||||
|
|
||||||
|
global data, book
|
||||||
|
|
||||||
|
data = tablib.Dataset()
|
||||||
|
book = tablib.Databook()
|
||||||
|
|
||||||
|
self.headers = ('first_name', 'last_name', 'gpa')
|
||||||
|
self.john = ('John', 'Adams', 90)
|
||||||
|
self.george = ('George', 'Washington', 67)
|
||||||
|
self.tom = ('Thomas', 'Jefferson', 50)
|
||||||
|
|
||||||
|
self.founders = tablib.Dataset(headers=self.headers)
|
||||||
|
self.founders.append(self.john)
|
||||||
|
self.founders.append(self.george)
|
||||||
|
self.founders.append(self.tom)
|
||||||
|
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
"""Teardown."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_empty_append(self):
|
||||||
|
"""Verify append() correctly adds tuple with no headers."""
|
||||||
|
new_row = (1, 2, 3)
|
||||||
|
data.append(new_row)
|
||||||
|
|
||||||
|
# Verify width/data
|
||||||
|
self.assertTrue(data.width == len(new_row))
|
||||||
|
self.assertTrue(data[0] == new_row)
|
||||||
|
|
||||||
|
|
||||||
|
def test_empty_append_with_headers(self):
|
||||||
|
"""Verify append() correctly detects mismatch of number of
|
||||||
|
headers and data.
|
||||||
|
"""
|
||||||
|
data.headers = ['first', 'second']
|
||||||
|
new_row = (1, 2, 3, 4)
|
||||||
|
|
||||||
|
self.assertRaises(tablib.InvalidDimensions, data.append, new_row)
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_column(self):
|
||||||
|
"""Verify adding column works with/without headers."""
|
||||||
|
|
||||||
|
data.append(['kenneth'])
|
||||||
|
data.append(['bessie'])
|
||||||
|
|
||||||
|
new_col = ['reitz', 'monke']
|
||||||
|
|
||||||
|
data.append(col=new_col)
|
||||||
|
|
||||||
|
self.assertEquals(data[0], ('kenneth', 'reitz'))
|
||||||
|
self.assertEquals(data.width, 2)
|
||||||
|
|
||||||
|
# With Headers
|
||||||
|
data.headers = ('fname', 'lname')
|
||||||
|
new_col = [21, 22]
|
||||||
|
data.append(col=new_col, header='age')
|
||||||
|
|
||||||
|
self.assertEquals(data['age'], new_col)
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_column_no_data_no_headers(self):
|
||||||
|
"""Verify adding new column with no headers."""
|
||||||
|
|
||||||
|
new_col = ('reitz', 'monke')
|
||||||
|
|
||||||
|
data.append(col=new_col)
|
||||||
|
|
||||||
|
self.assertEquals(data[0], tuple([new_col[0]]))
|
||||||
|
self.assertEquals(data.width, 1)
|
||||||
|
self.assertEquals(data.height, len(new_col))
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_callable_column(self):
|
||||||
|
"""Verify adding column with values specified as callable."""
|
||||||
|
new_col = [lambda x: x[0]]
|
||||||
|
self.founders.append(col=new_col, header='first_again')
|
||||||
|
#
|
||||||
|
# self.assertTrue(map(lambda x: x[0] == x[-1], self.founders))
|
||||||
|
|
||||||
|
|
||||||
|
def test_header_slicing(self):
|
||||||
|
"""Verify slicing by headers."""
|
||||||
|
|
||||||
|
self.assertEqual(self.founders['first_name'],
|
||||||
|
[self.john[0], self.george[0], self.tom[0]])
|
||||||
|
self.assertEqual(self.founders['last_name'],
|
||||||
|
[self.john[1], self.george[1], self.tom[1]])
|
||||||
|
self.assertEqual(self.founders['gpa'],
|
||||||
|
[self.john[2], self.george[2], self.tom[2]])
|
||||||
|
|
||||||
|
|
||||||
|
def test_data_slicing(self):
|
||||||
|
"""Verify slicing by data."""
|
||||||
|
|
||||||
|
# Slice individual rows
|
||||||
|
self.assertEqual(self.founders[0], self.john)
|
||||||
|
self.assertEqual(self.founders[:1], [self.john])
|
||||||
|
self.assertEqual(self.founders[1:2], [self.george])
|
||||||
|
self.assertEqual(self.founders[-1], self.tom)
|
||||||
|
self.assertEqual(self.founders[3:], [])
|
||||||
|
|
||||||
|
# Slice multiple rows
|
||||||
|
self.assertEqual(self.founders[:], [self.john, self.george, self.tom])
|
||||||
|
self.assertEqual(self.founders[0:2], [self.john, self.george])
|
||||||
|
self.assertEqual(self.founders[1:3], [self.george, self.tom])
|
||||||
|
self.assertEqual(self.founders[2:], [self.tom])
|
||||||
|
|
||||||
|
|
||||||
|
def test_delete(self):
|
||||||
|
"""Verify deleting from dataset works."""
|
||||||
|
|
||||||
|
# Delete from front of object
|
||||||
|
del self.founders[0]
|
||||||
|
self.assertEqual(self.founders[:], [self.george, self.tom])
|
||||||
|
|
||||||
|
# Verify dimensions, width should NOT change
|
||||||
|
self.assertEqual(self.founders.height, 2)
|
||||||
|
self.assertEqual(self.founders.width, 3)
|
||||||
|
|
||||||
|
# Delete from back of object
|
||||||
|
del self.founders[1]
|
||||||
|
self.assertEqual(self.founders[:], [self.george])
|
||||||
|
|
||||||
|
# Verify dimensions, width should NOT change
|
||||||
|
self.assertEqual(self.founders.height, 1)
|
||||||
|
self.assertEqual(self.founders.width, 3)
|
||||||
|
|
||||||
|
# Delete from invalid index
|
||||||
|
self.assertRaises(IndexError, self.founders.__delitem__, 3)
|
||||||
|
|
||||||
|
|
||||||
|
def test_csv_export(self):
|
||||||
|
"""Verify exporting dataset object as CSV."""
|
||||||
|
|
||||||
|
# Build up the csv string with headers first, followed by each row
|
||||||
|
csv = ''
|
||||||
|
for col in self.headers:
|
||||||
|
csv += col + ','
|
||||||
|
|
||||||
|
csv = csv.strip(',') + '\r\n'
|
||||||
|
|
||||||
|
for founder in self.founders:
|
||||||
|
for col in founder:
|
||||||
|
csv += str(col) + ','
|
||||||
|
csv = csv.strip(',') + '\r\n'
|
||||||
|
|
||||||
|
self.assertEqual(csv, self.founders.csv)
|
||||||
|
|
||||||
|
def test_tsv_export(self):
|
||||||
|
"""Verify exporting dataset object as CSV."""
|
||||||
|
|
||||||
|
# Build up the csv string with headers first, followed by each row
|
||||||
|
tsv = ''
|
||||||
|
for col in self.headers:
|
||||||
|
tsv += col + '\t'
|
||||||
|
|
||||||
|
tsv = tsv.strip('\t') + '\r\n'
|
||||||
|
|
||||||
|
for founder in self.founders:
|
||||||
|
for col in founder:
|
||||||
|
tsv += str(col) + '\t'
|
||||||
|
tsv = tsv.strip('\t') + '\r\n'
|
||||||
|
|
||||||
|
self.assertEqual(tsv, self.founders.tsv)
|
||||||
|
|
||||||
|
def test_unicode_append(self):
|
||||||
|
"""Passes in a single unicode charecter and exports."""
|
||||||
|
|
||||||
|
new_row = ('å', 'é')
|
||||||
|
data.append(new_row)
|
||||||
|
|
||||||
|
data.json
|
||||||
|
data.yaml
|
||||||
|
data.csv
|
||||||
|
data.tsv
|
||||||
|
data.xls
|
||||||
|
|
||||||
|
|
||||||
|
def test_book_export_no_exceptions(self):
|
||||||
|
"""Test that varoius exports don't error out."""
|
||||||
|
|
||||||
|
book = tablib.Databook()
|
||||||
|
book.add_sheet(data)
|
||||||
|
|
||||||
|
book.json
|
||||||
|
book.yaml
|
||||||
|
book.xls
|
||||||
|
|
||||||
|
|
||||||
|
def test_json_import_set(self):
|
||||||
|
"""Generate and import JSON set serialization."""
|
||||||
|
data.append(self.john)
|
||||||
|
data.append(self.george)
|
||||||
|
data.headers = self.headers
|
||||||
|
|
||||||
|
_json = data.json
|
||||||
|
|
||||||
|
data.json = _json
|
||||||
|
|
||||||
|
self.assertEqual(_json, data.json)
|
||||||
|
|
||||||
|
|
||||||
|
def test_json_import_book(self):
|
||||||
|
"""Generate and import JSON book serialization."""
|
||||||
|
data.append(self.john)
|
||||||
|
data.append(self.george)
|
||||||
|
data.headers = self.headers
|
||||||
|
|
||||||
|
book.add_sheet(data)
|
||||||
|
_json = book.json
|
||||||
|
|
||||||
|
book.json = _json
|
||||||
|
|
||||||
|
self.assertEqual(_json, book.json)
|
||||||
|
|
||||||
|
|
||||||
|
def test_yaml_import_set(self):
|
||||||
|
"""Generate and import YAML set serialization."""
|
||||||
|
data.append(self.john)
|
||||||
|
data.append(self.george)
|
||||||
|
data.headers = self.headers
|
||||||
|
|
||||||
|
_yaml = data.yaml
|
||||||
|
|
||||||
|
data.yaml = _yaml
|
||||||
|
|
||||||
|
self.assertEqual(_yaml, data.yaml)
|
||||||
|
|
||||||
|
|
||||||
|
def test_yaml_import_book(self):
|
||||||
|
"""Generate and import YAML book serialization."""
|
||||||
|
data.append(self.john)
|
||||||
|
data.append(self.george)
|
||||||
|
data.headers = self.headers
|
||||||
|
|
||||||
|
book.add_sheet(data)
|
||||||
|
_yaml = book.yaml
|
||||||
|
|
||||||
|
book.yaml = _yaml
|
||||||
|
|
||||||
|
self.assertEqual(_yaml, book.yaml)
|
||||||
|
|
||||||
|
|
||||||
|
def test_csv_import_set(self):
|
||||||
|
"""Generate and import CSV set serialization."""
|
||||||
|
data.append(self.john)
|
||||||
|
data.append(self.george)
|
||||||
|
data.headers = self.headers
|
||||||
|
|
||||||
|
_csv = data.csv
|
||||||
|
|
||||||
|
data.csv = _csv
|
||||||
|
|
||||||
|
self.assertEqual(_csv, data.csv)
|
||||||
|
|
||||||
|
def test_tsv_import_set(self):
|
||||||
|
"""Generate and import TSV set serialization."""
|
||||||
|
data.append(self.john)
|
||||||
|
data.append(self.george)
|
||||||
|
data.headers = self.headers
|
||||||
|
|
||||||
|
_tsv = data.tsv
|
||||||
|
|
||||||
|
data.tsv = _tsv
|
||||||
|
|
||||||
|
self.assertEqual(_tsv, data.tsv)
|
||||||
|
|
||||||
|
def test_csv_format_detect(self):
|
||||||
|
"""Test CSV format detection."""
|
||||||
|
|
||||||
|
_csv = (
|
||||||
|
'1,2,3\n'
|
||||||
|
'4,5,6\n'
|
||||||
|
'7,8,9\n'
|
||||||
|
)
|
||||||
|
_bunk = (
|
||||||
|
'¡¡¡¡¡¡¡¡£™∞¢£§∞§¶•¶ª∞¶•ªº••ª–º§•†•§º¶•†¥ª–º•§ƒø¥¨©πƒø†ˆ¥ç©¨√øˆ¥≈†ƒ¥ç©ø¨çˆ¥ƒçø¶'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertTrue(tablib.formats.csv.detect(_csv))
|
||||||
|
self.assertFalse(tablib.formats.csv.detect(_bunk))
|
||||||
|
|
||||||
|
def test_tsv_format_detect(self):
|
||||||
|
"""Test TSV format detection."""
|
||||||
|
|
||||||
|
_tsv = (
|
||||||
|
'1\t2\t3\n'
|
||||||
|
'4\t5\t6\n'
|
||||||
|
'7\t8\t9\n'
|
||||||
|
)
|
||||||
|
_bunk = (
|
||||||
|
'¡¡¡¡¡¡¡¡£™∞¢£§∞§¶•¶ª∞¶•ªº••ª–º§•†•§º¶•†¥ª–º•§ƒø¥¨©πƒø†ˆ¥ç©¨√øˆ¥≈†ƒ¥ç©ø¨çˆ¥ƒçø¶'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertTrue(tablib.formats.tsv.detect(_tsv))
|
||||||
|
self.assertFalse(tablib.formats.tsv.detect(_bunk))
|
||||||
|
|
||||||
|
def test_json_format_detect(self):
|
||||||
|
"""Test JSON format detection."""
|
||||||
|
|
||||||
|
_json = '[{"last_name": "Adams","age": 90,"first_name": "John"}]'
|
||||||
|
_bunk = (
|
||||||
|
'¡¡¡¡¡¡¡¡£™∞¢£§∞§¶•¶ª∞¶•ªº••ª–º§•†•§º¶•†¥ª–º•§ƒø¥¨©πƒø†ˆ¥ç©¨√øˆ¥≈†ƒ¥ç©ø¨çˆ¥ƒçø¶'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertTrue(tablib.formats.json.detect(_json))
|
||||||
|
self.assertFalse(tablib.formats.json.detect(_bunk))
|
||||||
|
|
||||||
|
|
||||||
|
def test_yaml_format_detect(self):
|
||||||
|
"""Test YAML format detection."""
|
||||||
|
|
||||||
|
_yaml = '- {age: 90, first_name: John, last_name: Adams}'
|
||||||
|
_bunk = (
|
||||||
|
'¡¡¡¡¡¡---///\n\n\n¡¡£™∞¢£§∞§¶•¶ª∞¶•ªº••ª–º§•†•§º¶•†¥ª–º•§ƒø¥¨©πƒø†ˆ¥ç©¨√øˆ¥≈†ƒ¥ç©ø¨çˆ¥ƒçø¶'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertTrue(tablib.formats.yaml.detect(_yaml))
|
||||||
|
self.assertFalse(tablib.formats.yaml.detect(_bunk))
|
||||||
|
|
||||||
|
|
||||||
|
def test_auto_format_detect(self):
|
||||||
|
"""Test auto format detection."""
|
||||||
|
|
||||||
|
_yaml = '- {age: 90, first_name: John, last_name: Adams}'
|
||||||
|
_json = '[{"last_name": "Adams","age": 90,"first_name": "John"}]'
|
||||||
|
_csv = '1,2,3\n4,5,6\n7,8,9\n'
|
||||||
|
_bunk = '¡¡¡¡¡¡---///\n\n\n¡¡£™∞¢£§∞§¶•¶ª∞¶•ªº••ª–º§•†•§º¶•†¥ª–º•§ƒø¥¨©πƒø†ˆ¥ç©¨√øˆ¥≈†ƒ¥ç©ø¨çˆ¥ƒçø¶'
|
||||||
|
|
||||||
|
self.assertEqual(tablib.detect(_yaml)[0], tablib.formats.yaml)
|
||||||
|
self.assertEqual(tablib.detect(_csv)[0], tablib.formats.csv)
|
||||||
|
self.assertEqual(tablib.detect(_json)[0], tablib.formats.json)
|
||||||
|
self.assertEqual(tablib.detect(_bunk)[0], None)
|
||||||
|
|
||||||
|
def test_transpose(self):
|
||||||
|
"""Transpose a dataset."""
|
||||||
|
|
||||||
|
transposed_founders = self.founders.transpose()
|
||||||
|
first_row = transposed_founders[0]
|
||||||
|
second_row = transposed_founders[1]
|
||||||
|
|
||||||
|
self.assertEqual(transposed_founders.headers,
|
||||||
|
["first_name","John", "George", "Thomas"])
|
||||||
|
self.assertEqual(first_row,
|
||||||
|
("last_name","Adams", "Washington", "Jefferson"))
|
||||||
|
self.assertEqual(second_row,
|
||||||
|
("gpa",90, 67, 50))
|
||||||
|
|
||||||
|
def test_row_stacking(self):
|
||||||
|
|
||||||
|
"""Row stacking."""
|
||||||
|
|
||||||
|
to_join = tablib.Dataset(headers=self.founders.headers)
|
||||||
|
|
||||||
|
for row in self.founders:
|
||||||
|
to_join.append(row=row)
|
||||||
|
|
||||||
|
row_stacked = self.founders.stack_rows(to_join)
|
||||||
|
|
||||||
|
for column in row_stacked.headers:
|
||||||
|
|
||||||
|
original_data = self.founders[column]
|
||||||
|
expected_data = original_data + original_data
|
||||||
|
self.assertEqual(row_stacked[column], expected_data)
|
||||||
|
|
||||||
|
def test_column_stacking(self):
|
||||||
|
|
||||||
|
"""Column stacking"""
|
||||||
|
|
||||||
|
to_join = tablib.Dataset(headers=self.founders.headers)
|
||||||
|
|
||||||
|
for row in self.founders:
|
||||||
|
to_join.append(row=row)
|
||||||
|
|
||||||
|
column_stacked = self.founders.stack_columns(to_join)
|
||||||
|
|
||||||
|
for index, row in enumerate(column_stacked):
|
||||||
|
|
||||||
|
original_data = self.founders[index]
|
||||||
|
expected_data = original_data + original_data
|
||||||
|
self.assertEqual(row, expected_data)
|
||||||
|
|
||||||
|
self.assertEqual(column_stacked[0],
|
||||||
|
("John", "Adams", 90, "John", "Adams", 90))
|
||||||
|
|
||||||
|
|
||||||
|
def test_wipe(self):
|
||||||
|
"""Purge a dataset."""
|
||||||
|
|
||||||
|
new_row = (1, 2, 3)
|
||||||
|
data.append(new_row)
|
||||||
|
|
||||||
|
# Verify width/data
|
||||||
|
self.assertTrue(data.width == len(new_row))
|
||||||
|
self.assertTrue(data[0] == new_row)
|
||||||
|
|
||||||
|
data.wipe()
|
||||||
|
new_row = (1, 2, 3, 4)
|
||||||
|
data.append(new_row)
|
||||||
|
self.assertTrue(data.width == len(new_row))
|
||||||
|
self.assertTrue(data[0] == new_row)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user