diff --git a/cybertools/composer/README.txt b/cybertools/composer/README.txt
index d4d4fea..4976b63 100644
--- a/cybertools/composer/README.txt
+++ b/cybertools/composer/README.txt
@@ -2,8 +2,6 @@
Composer - Building Complex Structures with Templates or Schemas
================================================================
- ($Id$)
-
>>> from cybertools.composer.base import Element, Compound, Template
>>> from cybertools.composer.instance import Instance
@@ -46,7 +44,7 @@ with the template.
>>> class ConfigurationAdapter(Instance):
... def applyTemplate(self):
... for c in self.template.components:
- ... print c, self.context.parts.get(c.name, '-')
+ ... print(c, self.context.parts.get(c.name, '-'))
>>> inst = ConfigurationAdapter(c001)
>>> inst.template = desktop
diff --git a/cybertools/composer/instance.py b/cybertools/composer/instance.py
index 039f769..a2c998c 100644
--- a/cybertools/composer/instance.py
+++ b/cybertools/composer/instance.py
@@ -1,36 +1,16 @@
-#
-# Copyright (c) 2007 Helmut Merz helmutm@cy55.de
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-#
+# cybertools.composer.instance
-"""
-Base classes to be used for client adapters.
-
-$Id$
+""" Base classes to be used for client adapters.
"""
-from zope.interface import implements
+from zope.interface import implementer
from cybertools.composer.interfaces import IInstance
+@implementer(IInstance)
class Instance(object):
- implements(IInstance)
-
templateFactory = dict
templateAttributeName = '__ctc_template__'
diff --git a/cybertools/text/tests.py b/cybertools/text/tests.py
index 79aa0b6..83451aa 100755
--- a/cybertools/text/tests.py
+++ b/cybertools/text/tests.py
@@ -3,13 +3,13 @@
"""
Tests for the 'cybertools.text' package.
"""
-import sys
-#sys.path = [p for p in sys.path if p != '']
-sys.path = sys.path[2:] # avoid import cycle with bs4 when importing html
-#print(sys.path)
import unittest, doctest
+import sys
import warnings
+#print('***', sys.path)
+sys.path = sys.path[1:] # avoid import cycle with bs4 when importing html
+
from cybertools.text import pdf
from cybertools.text.html import htmlToText
diff --git a/cybertools/util/adapter.py b/cybertools/util/adapter.py
index d34a54e..65342d2 100644
--- a/cybertools/util/adapter.py
+++ b/cybertools/util/adapter.py
@@ -1,25 +1,6 @@
-#
-# Copyright (c) 2007 Helmut Merz helmutm@cy55.de
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-#
+# cybertools.util.adapter
-"""
-A simple adapter framework.
-
-$Id$
+""" A simple adapter framework.
"""
@@ -78,7 +59,7 @@ class AdapterType(type):
factory.register(cls, adapted, name)
-class AdapterBase:
+class AdapterBase(object, metaclass=AdapterType):
- __metaclass__ = AdapterType
+ pass
diff --git a/cybertools/util/cache.py b/cybertools/util/cache.py
index 14268a3..5b19ed2 100644
--- a/cybertools/util/cache.py
+++ b/cybertools/util/cache.py
@@ -1,25 +1,6 @@
-#
-# Copyright (c) 2010 Helmut Merz helmutm@cy55.de
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-#
+# cybertools.util.cache
-"""
-A simple caching mechanism.
-
-$Id$
+""" A simple caching mechanism.
"""
from zope import component
diff --git a/cybertools/util/cache.txt b/cybertools/util/cache.txt
index 4982842..e56d6b3 100644
--- a/cybertools/util/cache.txt
+++ b/cybertools/util/cache.txt
@@ -2,14 +2,12 @@
Data Caching
============
-$Id$
-
>>> from cybertools.util import cache
>>> cache = cache.internalCache
>>> @cache(lambda *args: 'calc')
... def calculate():
- ... print 'calculating'
+ ... print('calculating')
... return 42
>>> calculate()
@@ -24,7 +22,7 @@ $Id$
... return self.id
... @cache(getId)
... def calculate(self):
- ... print 'calculating'
+ ... print('calculating')
... return 42
>>> demo = Demo()
diff --git a/cybertools/util/config.py b/cybertools/util/config.py
index ec99d3f..262cd90 100644
--- a/cybertools/util/config.py
+++ b/cybertools/util/config.py
@@ -1,30 +1,10 @@
-#
-# Copyright (c) 2007 Helmut Merz helmutm@cy55.de
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-#
+# cybertools.util.config
-"""
-Formulating configuration options.
-
-$Id$
+""" Formulating configuration options.
"""
import os
-from zope.interface import implements
from cybertools.util.jeep import Jeep
@@ -58,7 +38,7 @@ class Configurator(dict):
f.close()
if p is None:
return
- exec p in self
+ exec(p, self)
def save(self, filename=None):
fn = self.getConfigFile(filename)
diff --git a/cybertools/util/config.txt b/cybertools/util/config.txt
index 1d04e5b..b878ce6 100644
--- a/cybertools/util/config.txt
+++ b/cybertools/util/config.txt
@@ -49,7 +49,7 @@ it with a default if not found, in one statement.
We can output a configuration in a form that is ready for loading
just by converting it to a string representation.
- >>> print config
+ >>> print(config)
crawl[0].directory = 'documents/projects'
crawl[0].type = 'filesystem'
transport.serverURL = 'http://demo.cy55.de'
@@ -69,7 +69,7 @@ for storage; normally it would be stored in the users home directory.
>>> fn
'....cybertools.cfg'
- >>> print open(fn).read()
+ >>> print(open(fn).read())
crawl[0].directory = 'documents/projects'
crawl[0].type = 'filesystem'
transport.serverURL = 'http://demo.cy55.de'
diff --git a/cybertools/util/defer.txt b/cybertools/util/defer.txt
index 54bcee9..67025e9 100644
--- a/cybertools/util/defer.txt
+++ b/cybertools/util/defer.txt
@@ -2,8 +2,6 @@
Deferred Execution
==================
-$Id$
-
>>> from cybertools.util.defer import Deferred
To show what deferreds are about we need two classes.
@@ -21,7 +19,7 @@ method will be called, thus notifying the client.
...
... def work(self):
... self.deferred = Deferred()
- ... print 'Worker: work started'
+ ... print('Worker: work started')
... return self.deferred
...
... def nowItsTime(self):
@@ -37,10 +35,10 @@ callback method with the deferred object coming back from the
... def run(self, worker):
... deferred = worker.work()
... deferred.addCallback(self.showResult)
- ... print 'Client: The worker seems to be working now...'
+ ... print('Client: The worker seems to be working now...')
...
... def showResult(self, result):
- ... print 'Result:', result
+ ... print('Result:', result)
So we now create a worker and a client, and let the client run:
diff --git a/cybertools/util/format.txt b/cybertools/util/format.txt
index cfab90f..c7881bb 100644
--- a/cybertools/util/format.txt
+++ b/cybertools/util/format.txt
@@ -8,12 +8,12 @@ Basic Formatting Functions
>>> time = datetime(2006, 8, 21, 17, 37, 13)
>>> format.formatDate(time, type='time', variant='medium')
- u'17:37:13'
+ '17:37:13'
>>> format.formatDate(time, type='dateTime', variant='medium')
- u'21.08.2006 17:37:13'
+ '21.08.2006 17:37:13'
>>> format.formatNumber(17.2)
- u'17,20'
+ '17,20'
>>> format.formatNumber(13399.99999997)
- u'13.400,00'
+ '13.400,00'
diff --git a/cybertools/util/html.py b/cybertools/util/html.py
index 485e2b1..4e18bee 100644
--- a/cybertools/util/html.py
+++ b/cybertools/util/html.py
@@ -1,29 +1,13 @@
-#
-# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-#
+# cybertools.util.html
-"""
-Strip HTML tags and other HTML-related utilities.
+""" Strip HTML tags and other HTML-related utilities.
"""
import re
-from cybertools.text.lib.BeautifulSoup import BeautifulSoup, Comment
-from cybertools.text.lib.BeautifulSoup import Declaration, NavigableString
+#from cybertools.text.lib.BeautifulSoup import BeautifulSoup, Comment
+#from cybertools.text.lib.BeautifulSoup import Declaration, NavigableString
+from bs4 import BeautifulSoup, Comment, Declaration, NavigableString
validTags = ('a b br div em font h1 h2 h3 i img li ol p pre span strong '
'table td tr u ul').split()
@@ -40,26 +24,26 @@ sentencePattern = re.compile(r'[:.\?\!]')
def sanitize(value, validTags=validTags, validAttrs=validAttrs,
validStyles=validStyles, stripEscapedComments=True):
- soup = BeautifulSoup(value)
- for comment in soup.findAll(text=lambda text: isinstance(text, Comment)):
+ soup = BeautifulSoup(value, features='lxml')
+ for comment in soup.findAll(string=lambda text: isinstance(text, Comment)):
comment.extract()
for tag in soup.findAll(True):
if tag.name not in validTags:
tag.hidden = True
- attrs = []
- for attr, val in tag.attrs:
+ attrs = {}
+ for attr, val in tag.attrs.items():
attr = attr.lower()
if attr not in validAttrs:
continue
if attr == 'style':
val = sanitizeStyle(val, validStyles)
if val:
- attrs.append((attr, val))
+ attrs[attr] = val
tag.attrs = attrs
- result = soup.renderContents()
+ result = soup.renderContents().decode('UTF-8')
if stripEscapedComments:
- result = escCommPattern.sub(u'', result)
- return result.decode('utf8')
+ result = escCommPattern.sub('', result)
+ return result
def sanitizeStyle(value, validStyles=validStyles):
@@ -85,8 +69,8 @@ def checkStyle(k, validStyles=validStyles):
def stripComments(value):
- soup = BeautifulSoup(value)
- for comment in soup.findAll(text=lambda text: isinstance(text, Comment)):
+ soup = BeautifulSoup(value, features='lxml')
+ for comment in soup.findAll(string=lambda text: isinstance(text, Comment)):
comment.extract()
return soup.renderContents().decode('utf8')
@@ -100,14 +84,14 @@ def stripAll(value):
elif tag is not None and type(tag) is not Declaration:
collectText(tag.contents)
data = []
- soup = BeautifulSoup(value)
+ soup = BeautifulSoup(value, features='lxml')
collectText(soup.contents)
- text = u''.join(data).replace(u'\n', u'').replace(u' ', u' ')
+ text = ''.join(data).replace('\n', '').replace(' ', ' ')
return text
def extractFirstPart(value):
- soup = BeautifulSoup(value)
+ soup = BeautifulSoup(value, features='lxml')
for tag in soup.findAll(True):
if tag.name in ('p',):
part = tag.renderContents()
@@ -115,6 +99,8 @@ def extractFirstPart(value):
else:
text = stripAll(value)
part = sentencePattern.split(text)[0]
- if isinstance(part, unicode):
- part = part.encode('UTF-8')
- return ('
%s
' % part).decode('utf8')
+ #if isinstance(part, str):
+ # part = part.encode('UTF-8')
+ if isinstance(part, bytes):
+ part = part.decode('UTF-8')
+ return ('%s
' % part) #.decode('utf8')
diff --git a/cybertools/util/html.txt b/cybertools/util/html.txt
index fd1980c..ead14d9 100644
--- a/cybertools/util/html.txt
+++ b/cybertools/util/html.txt
@@ -14,10 +14,10 @@ Sanitize HTML
-------------
>>> sanitize(input, validAttrs=['style'])
- u'\n\nText, and more\n
\n'
+ '\n\nText, and more\n
\n'
>>> sanitize(input, ['p', 'b'], ['class'])
- u'\n\nText, and more\n
\n'
+ '\n\nText, and more\n
\n'
All comments are stripped from the HTML input.
@@ -27,18 +27,18 @@ All comments are stripped from the HTML input.
... text
"""
>>> sanitize(input2)
- u'\ntext
\n\ntext
'
+ '\ntext
\n\ntext
'
It's also possible to remove only the comments from the HTML input.
>>> stripComments(input2)
- u'\ntext
\n\ntext
'
+ '\ntext
\n\ntext
'
It is also possible to strip all HTML tags from the input string.
>>> from cybertools.util.html import stripAll
>>> stripAll(input)
- u'Text, and more'
+ 'Text, and more'
Extract first part of an HTML text
----------------------------------
@@ -46,7 +46,7 @@ Extract first part of an HTML text
>>> from cybertools.util.html import extractFirstPart
>>> extractFirstPart(input)
- u'\nText, and more\n
'
+ '\nText, and more\n
'
>>> extractFirstPart(input2)
- u'text
'
+ 'text
'
diff --git a/cybertools/util/iterate.py b/cybertools/util/iterate.py
index 6df52a6..09dbf32 100644
--- a/cybertools/util/iterate.py
+++ b/cybertools/util/iterate.py
@@ -1,23 +1,6 @@
-#
-# Copyright (c) 2012 Helmut Merz helmutm@cy55.de
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-#
+# cybertools.util.iterate
-"""
-Iterator and generator utilities.
+""" Iterator and generator utilities.
"""
from itertools import islice
@@ -36,7 +19,7 @@ class BatchIterator(object):
def __iter__(self):
return self
- def next(self):
+ def __next__(self):
if self.count >= (self.batch + 1) * self.limit:
raise StopIteration
if self.start:
@@ -45,7 +28,7 @@ class BatchIterator(object):
self.start = 0
self.count += 1
try:
- return self.data.next()
+ return self.data.__next__()
except StopIteration:
self.exhausted = True
raise
diff --git a/cybertools/util/iterate.txt b/cybertools/util/iterate.txt
index 4acacb8..b3f2381 100644
--- a/cybertools/util/iterate.txt
+++ b/cybertools/util/iterate.txt
@@ -15,7 +15,7 @@ We create a BatchIterator upon a base iterator. The BatchIterator
only gives us a limited portion of the values provided by the base
iterator.
- >>> it = BatchIterator(xrange(30))
+ >>> it = BatchIterator(range(30))
>>> list(it)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
>>> list(it)
@@ -39,13 +39,13 @@ Advancing would not help if the base iterator is exhausted.
We can also immediately start at the second batch by providing the ``start``
argument to the BatchIterator constructor.
- >>> it = BatchIterator(xrange(30), start=1)
+ >>> it = BatchIterator(range(30), start=1)
>>> list(it)
[20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
We can use another limit (i.e. the batch size) via the BatchIterator constructor.
- >>> it = BatchIterator(xrange(30), start=1, limit=8)
+ >>> it = BatchIterator(range(30), start=1, limit=8)
>>> list(it)
[8, 9, 10, 11, 12, 13, 14, 15]
>>> it.advance()
diff --git a/cybertools/util/jeep.py b/cybertools/util/jeep.py
index f48cd15..9dcfd4b 100644
--- a/cybertools/util/jeep.py
+++ b/cybertools/util/jeep.py
@@ -1,27 +1,8 @@
-#
-# Copyright (c) 2010 Helmut Merz helmutm@cy55.de
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-#
+# cybertools.util.jeep
-"""
-A general purpose (thus 'Jeep') class that provides most of the interfaces
+""" A general purpose (thus 'Jeep') class that provides most of the interfaces
of sequences and dictionaries and in addition allows attribute access to
the dictionary entries.
-
-$Id$
"""
_notfound = object()
@@ -66,7 +47,7 @@ class Jeep(object):
super(Jeep, self).__delattr__(attr)
def __getitem__(self, key):
- if type(key) in (int, long):
+ if isinstance(key, int):
return getattr(self, self._sequence[key])
value = getattr(self, key, _notfound)
if value is _notfound:
@@ -123,7 +104,7 @@ class Jeep(object):
self[key] = value
def pop(self, key=-1, default=_undefined):
- if type(key) in (int, long):
+ if isinstance(key, int):
key = self._sequence[key]
if default is _undefined:
value = self[key]
@@ -133,7 +114,7 @@ class Jeep(object):
return value
def find(self, obj):
- if isinstance(obj, basestring):
+ if isinstance(obj, str):
key = obj
else:
key = getattr(obj, '__name__', getattr(obj, 'name', _notfound))
@@ -189,7 +170,8 @@ def moveByDelta(objs, toMove, delta):
if delta < 0:
objs = list(reversed(objs))
result.reverse()
- toMove = sorted(toMove, lambda x,y: cmp(objs.index(x), objs.index(y)))
+ #toMove = sorted(toMove, lambda x,y: cmp(objs.index(x), objs.index(y)))
+ toMove = sorted(toMove, key=lambda x: objs.index(x))
for element in toMove:
newPos = min(len(result), objs.index(element) + abs(delta))
result.insert(newPos, element)
diff --git a/cybertools/util/jeep.txt b/cybertools/util/jeep.txt
index 3506753..d06b603 100644
--- a/cybertools/util/jeep.txt
+++ b/cybertools/util/jeep.txt
@@ -2,8 +2,6 @@
Jeep - a General Purpose Class
==============================
-$Id$
-
>>> from cybertools.util.jeep import Jeep
>>> jeep = Jeep()
@@ -76,7 +74,7 @@ More Dictionary Methods
KeyError: 'fourth'
>>> dict(jeep)
- {'second': 'new second value', 'third': 'third value', 'first': 'first value'}
+ {'first': 'first value', 'second': 'new second value', 'third': 'third value'}
>>> jeep.setdefault('first', 'new first value')
'first value'
diff --git a/cybertools/util/json.py b/cybertools/util/json.py
deleted file mode 100644
index 489548f..0000000
--- a/cybertools/util/json.py
+++ /dev/null
@@ -1,666 +0,0 @@
-"""
-A simple, fast, extensible JSON encoder
-
-JSON (JavaScript Object Notation) is a subset of
-JavaScript syntax (ECMA-262 3rd edition) used as a lightweight data
-interchange format."""
-
-"""
-This is a stripped-down version of simplejson
-by Bob Ippolito, http://undefined.org/python/
-
-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.
-"""
-
-import re, sys
-
-ESCAPE = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t]')
-ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])')
-HAS_UTF8 = re.compile(r'[\x80-\xff]')
-ESCAPE_DCT = {
- '\\': '\\\\',
- '"': '\\"',
- '\b': '\\b',
- '\f': '\\f',
- '\n': '\\n',
- '\r': '\\r',
- '\t': '\\t',
-}
-for i in range(0x20):
- ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,))
-
-# Assume this produces an infinity on all machines (probably not guaranteed)
-INFINITY = float('1e66666')
-FLOAT_REPR = repr
-
-
-def floatstr(o, allow_nan=True):
- if o != o:
- text = 'NaN'
- elif o == INFINITY:
- text = 'Infinity'
- elif o == -INFINITY:
- text = '-Infinity'
- else:
- return FLOAT_REPR(o)
- if not allow_nan:
- raise ValueError("Out of range float values are not JSON compliant: %r"
- % (o,))
- return text
-
-
-def encode_basestring(s):
- def replace(match):
- return ESCAPE_DCT[match.group(0)]
- return '"' + ESCAPE.sub(replace, s) + '"'
-
-
-def py_encode_basestring_ascii(s):
- if isinstance(s, str) and HAS_UTF8.search(s) is not None:
- s = s.decode('utf-8')
- def replace(match):
- s = match.group(0)
- try:
- return ESCAPE_DCT[s]
- except KeyError:
- n = ord(s)
- if n < 0x10000:
- return '\\u%04x' % (n,)
- else:
- # surrogate pair
- n -= 0x10000
- s1 = 0xd800 | ((n >> 10) & 0x3ff)
- s2 = 0xdc00 | (n & 0x3ff)
- return '\\u%04x\\u%04x' % (s1, s2)
- return '"' + str(ESCAPE_ASCII.sub(replace, s)) + '"'
-
-encode_basestring_ascii = py_encode_basestring_ascii
-
-
-class JSONEncoder(object):
-
- item_separator = ', '
- key_separator = ': '
-
- def __init__(self, skipkeys=False, ensure_ascii=True,
- check_circular=True, allow_nan=True, sort_keys=False,
- indent=None, separators=None, encoding='utf-8', default=None):
- self.skipkeys = skipkeys
- self.ensure_ascii = ensure_ascii
- self.check_circular = check_circular
- self.allow_nan = allow_nan
- self.sort_keys = sort_keys
- self.indent = indent
- self.current_indent_level = 0
- if separators is not None:
- self.item_separator, self.key_separator = separators
- if default is not None:
- self.default = default
- self.encoding = encoding
-
- def _newline_indent(self):
- return '\n' + (' ' * (self.indent * self.current_indent_level))
-
- def _iterencode_list(self, lst, markers=None):
- if not lst:
- yield '[]'
- return
- if markers is not None:
- markerid = id(lst)
- if markerid in markers:
- raise ValueError("Circular reference detected")
- markers[markerid] = lst
- yield '['
- if self.indent is not None:
- self.current_indent_level += 1
- newline_indent = self._newline_indent()
- separator = self.item_separator + newline_indent
- yield newline_indent
- else:
- newline_indent = None
- separator = self.item_separator
- first = True
- for value in lst:
- if first:
- first = False
- else:
- yield separator
- for chunk in self._iterencode(value, markers):
- yield chunk
- if newline_indent is not None:
- self.current_indent_level -= 1
- yield self._newline_indent()
- yield ']'
- if markers is not None:
- del markers[markerid]
-
- def _iterencode_dict(self, dct, markers=None):
- if not dct:
- yield '{}'
- return
- if markers is not None:
- markerid = id(dct)
- if markerid in markers:
- raise ValueError("Circular reference detected")
- markers[markerid] = dct
- yield '{'
- key_separator = self.key_separator
- if self.indent is not None:
- self.current_indent_level += 1
- newline_indent = self._newline_indent()
- item_separator = self.item_separator + newline_indent
- yield newline_indent
- else:
- newline_indent = None
- item_separator = self.item_separator
- first = True
- if self.ensure_ascii:
- encoder = encode_basestring_ascii
- else:
- encoder = encode_basestring
- allow_nan = self.allow_nan
- if self.sort_keys:
- keys = dct.keys()
- keys.sort()
- items = [(k, dct[k]) for k in keys]
- else:
- items = dct.iteritems()
- _encoding = self.encoding
- _do_decode = (_encoding is not None
- and not (_encoding == 'utf-8'))
- for key, value in items:
- if isinstance(key, str):
- if _do_decode:
- key = key.decode(_encoding)
- elif isinstance(key, basestring):
- pass
- # JavaScript is weakly typed for these, so it makes sense to
- # also allow them. Many encoders seem to do something like this.
- elif isinstance(key, float):
- key = floatstr(key, allow_nan)
- elif isinstance(key, (int, long)):
- key = str(key)
- elif key is True:
- key = 'true'
- elif key is False:
- key = 'false'
- elif key is None:
- key = 'null'
- elif self.skipkeys:
- continue
- else:
- raise TypeError("key %r is not a string" % (key,))
- if first:
- first = False
- else:
- yield item_separator
- yield encoder(key)
- yield key_separator
- for chunk in self._iterencode(value, markers):
- yield chunk
- if newline_indent is not None:
- self.current_indent_level -= 1
- yield self._newline_indent()
- yield '}'
- if markers is not None:
- del markers[markerid]
-
- def _iterencode(self, o, markers=None):
- if isinstance(o, basestring):
- if self.ensure_ascii:
- encoder = encode_basestring_ascii
- else:
- encoder = encode_basestring
- _encoding = self.encoding
- if (_encoding is not None and isinstance(o, str)
- and not (_encoding == 'utf-8')):
- o = o.decode(_encoding)
- yield encoder(o)
- elif o is None:
- yield 'null'
- elif o is True:
- yield 'true'
- elif o is False:
- yield 'false'
- elif isinstance(o, (int, long)):
- yield str(o)
- elif isinstance(o, float):
- yield floatstr(o, self.allow_nan)
- elif isinstance(o, (list, tuple)):
- for chunk in self._iterencode_list(o, markers):
- yield chunk
- elif isinstance(o, dict):
- for chunk in self._iterencode_dict(o, markers):
- yield chunk
- else:
- if markers is not None:
- markerid = id(o)
- if markerid in markers:
- raise ValueError("Circular reference detected")
- markers[markerid] = o
- for chunk in self._iterencode_default(o, markers):
- yield chunk
- if markers is not None:
- del markers[markerid]
-
- def _iterencode_default(self, o, markers=None):
- newobj = self.default(o)
- return self._iterencode(newobj, markers)
-
- def default(self, o):
- raise TypeError("%r is not JSON serializable" % (o,))
-
- def encode(self, o):
- if isinstance(o, basestring):
- if isinstance(o, str):
- _encoding = self.encoding
- if (_encoding is not None
- and not (_encoding == 'utf-8')):
- o = o.decode(_encoding)
- if self.ensure_ascii:
- return encode_basestring_ascii(o)
- else:
- return encode_basestring(o)
- chunks = list(self.iterencode(o))
- return ''.join(chunks)
-
- def iterencode(self, o):
- if self.check_circular:
- markers = {}
- else:
- markers = None
- return self._iterencode(o, markers)
-
-
-_default_encoder = JSONEncoder(
- skipkeys=False,
- ensure_ascii=True,
- check_circular=True,
- allow_nan=True,
- indent=None,
- separators=None,
- encoding='utf-8',
- default=None,
-)
-
-
-"""
-Implementation of JSONDecoder
-"""
-
-import sre_parse
-import sre_compile
-import sre_constants
-from sre_constants import BRANCH, SUBPATTERN
-
-FLAGS = (re.VERBOSE | re.MULTILINE | re.DOTALL)
-
-class Scanner(object):
- def __init__(self, lexicon, flags=FLAGS):
- self.actions = [None]
- # Combine phrases into a compound pattern
- s = sre_parse.Pattern()
- s.flags = flags
- p = []
- for idx, token in enumerate(lexicon):
- phrase = token.pattern
- try:
- subpattern = sre_parse.SubPattern(s,
- [(SUBPATTERN, (idx + 1, sre_parse.parse(phrase, flags)))])
- except sre_constants.error:
- raise
- p.append(subpattern)
- self.actions.append(token)
-
- s.groups = len(p) + 1 # NOTE(guido): Added to make SRE validation work
- p = sre_parse.SubPattern(s, [(BRANCH, (None, p))])
- self.scanner = sre_compile.compile(p)
-
- def iterscan(self, string, idx=0, context=None):
- """
- Yield match, end_idx for each match
- """
- match = self.scanner.scanner(string, idx).match
- actions = self.actions
- lastend = idx
- end = len(string)
- while True:
- m = match()
- if m is None:
- break
- matchbegin, matchend = m.span()
- if lastend == matchend:
- break
- action = actions[m.lastindex]
- if action is not None:
- rval, next_pos = action(m, context)
- if next_pos is not None and next_pos != matchend:
- # "fast forward" the scanner
- matchend = next_pos
- match = self.scanner.scanner(string, matchend).match
- yield rval, matchend
- lastend = matchend
-
-
-def pattern(pattern, flags=FLAGS):
- def decorator(fn):
- fn.pattern = pattern
- fn.regex = re.compile(pattern, flags)
- return fn
- return decorator
-
-
-def _floatconstants():
- import struct
- import sys
- _BYTES = '7FF80000000000007FF0000000000000'.decode('hex')
- if sys.byteorder != 'big':
- _BYTES = _BYTES[:8][::-1] + _BYTES[8:][::-1]
- nan, inf = struct.unpack('dd', _BYTES)
- return nan, inf, -inf
-
-NaN, PosInf, NegInf = _floatconstants()
-
-
-def linecol(doc, pos):
- lineno = doc.count('\n', 0, pos) + 1
- if lineno == 1:
- colno = pos
- else:
- colno = pos - doc.rindex('\n', 0, pos)
- return lineno, colno
-
-
-def errmsg(msg, doc, pos, end=None):
- lineno, colno = linecol(doc, pos)
- if end is None:
- return '%s: line %d column %d (char %d)' % (msg, lineno, colno, pos)
- endlineno, endcolno = linecol(doc, end)
- return '%s: line %d column %d - line %d column %d (char %d - %d)' % (
- msg, lineno, colno, endlineno, endcolno, pos, end)
-
-
-_CONSTANTS = {
- '-Infinity': NegInf,
- 'Infinity': PosInf,
- 'NaN': NaN,
- 'true': True,
- 'false': False,
- 'null': None,
-}
-
-def JSONConstant(match, context, c=_CONSTANTS):
- s = match.group(0)
- fn = getattr(context, 'parse_constant', None)
- if fn is None:
- rval = c[s]
- else:
- rval = fn(s)
- return rval, None
-pattern('(-?Infinity|NaN|true|false|null)')(JSONConstant)
-
-
-def JSONNumber(match, context):
- match = JSONNumber.regex.match(match.string, *match.span())
- integer, frac, exp = match.groups()
- if frac or exp:
- fn = getattr(context, 'parse_float', None) or float
- res = fn(integer + (frac or '') + (exp or ''))
- else:
- fn = getattr(context, 'parse_int', None) or int
- res = fn(integer)
- return res, None
-pattern(r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?')(JSONNumber)
-
-
-STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS)
-BACKSLASH = {
- '"': u'"', '\\': u'\\', '/': u'/',
- 'b': u'\b', 'f': u'\f', 'n': u'\n', 'r': u'\r', 't': u'\t',
-}
-
-DEFAULT_ENCODING = "utf-8"
-
-def py_scanstring(s, end, encoding=None, strict=True, _b=BACKSLASH, _m=STRINGCHUNK.match):
- if encoding is None:
- encoding = DEFAULT_ENCODING
- chunks = []
- _append = chunks.append
- begin = end - 1
- while 1:
- chunk = _m(s, end)
- if chunk is None:
- raise ValueError(
- errmsg("Unterminated string starting at", s, begin))
- end = chunk.end()
- content, terminator = chunk.groups()
- if content:
- if not isinstance(content, unicode):
- content = unicode(content, encoding)
- _append(content)
- if terminator == '"':
- break
- elif terminator != '\\':
- if strict:
- raise ValueError(errmsg("Invalid control character %r at", s, end))
- else:
- _append(terminator)
- continue
- try:
- esc = s[end]
- except IndexError:
- raise ValueError(
- errmsg("Unterminated string starting at", s, begin))
- if esc != 'u':
- try:
- m = _b[esc]
- except KeyError:
- raise ValueError(
- errmsg("Invalid \\escape: %r" % (esc,), s, end))
- end += 1
- else:
- esc = s[end + 1:end + 5]
- next_end = end + 5
- msg = "Invalid \\uXXXX escape"
- try:
- if len(esc) != 4:
- raise ValueError
- uni = int(esc, 16)
- if 0xd800 <= uni <= 0xdbff and sys.maxunicode > 65535:
- msg = "Invalid \\uXXXX\\uXXXX surrogate pair"
- if not s[end + 5:end + 7] == '\\u':
- raise ValueError
- esc2 = s[end + 7:end + 11]
- if len(esc2) != 4:
- raise ValueError
- uni2 = int(esc2, 16)
- uni = 0x10000 + (((uni - 0xd800) << 10) | (uni2 - 0xdc00))
- next_end += 6
- m = unichr(uni)
- except ValueError:
- raise ValueError(errmsg(msg, s, end))
- end = next_end
- _append(m)
- return u''.join(chunks), end
-
-
-scanstring = py_scanstring
-
-def JSONString(match, context):
- encoding = getattr(context, 'encoding', None)
- strict = getattr(context, 'strict', True)
- return scanstring(match.string, match.end(), encoding, strict)
-pattern(r'"')(JSONString)
-
-
-WHITESPACE = re.compile(r'\s*', FLAGS)
-
-def JSONObject(match, context, _w=WHITESPACE.match):
- pairs = {}
- s = match.string
- end = _w(s, match.end()).end()
- nextchar = s[end:end + 1]
- # Trivial empty object
- if nextchar == '}':
- return pairs, end + 1
- if nextchar != '"':
- raise ValueError(errmsg("Expecting property name", s, end))
- end += 1
- encoding = getattr(context, 'encoding', None)
- strict = getattr(context, 'strict', True)
- iterscan = JSONScanner.iterscan
- while True:
- key, end = scanstring(s, end, encoding, strict)
- end = _w(s, end).end()
- if s[end:end + 1] != ':':
- raise ValueError(errmsg("Expecting : delimiter", s, end))
- end = _w(s, end + 1).end()
- try:
- value, end = iterscan(s, idx=end, context=context).next()
- except StopIteration:
- raise ValueError(errmsg("Expecting object", s, end))
- pairs[key] = value
- end = _w(s, end).end()
- nextchar = s[end:end + 1]
- end += 1
- if nextchar == '}':
- break
- if nextchar != ',':
- raise ValueError(errmsg("Expecting , delimiter", s, end - 1))
- end = _w(s, end).end()
- nextchar = s[end:end + 1]
- end += 1
- if nextchar != '"':
- raise ValueError(errmsg("Expecting property name", s, end - 1))
- object_hook = getattr(context, 'object_hook', None)
- if object_hook is not None:
- pairs = object_hook(pairs)
- return pairs, end
-pattern(r'{')(JSONObject)
-
-
-def JSONArray(match, context, _w=WHITESPACE.match):
- values = []
- s = match.string
- end = _w(s, match.end()).end()
- # Look-ahead for trivial empty array
- nextchar = s[end:end + 1]
- if nextchar == ']':
- return values, end + 1
- iterscan = JSONScanner.iterscan
- while True:
- try:
- value, end = iterscan(s, idx=end, context=context).next()
- except StopIteration:
- raise ValueError(errmsg("Expecting object", s, end))
- values.append(value)
- end = _w(s, end).end()
- nextchar = s[end:end + 1]
- end += 1
- if nextchar == ']':
- break
- if nextchar != ',':
- raise ValueError(errmsg("Expecting , delimiter", s, end))
- end = _w(s, end).end()
- return values, end
-pattern(r'\[')(JSONArray)
-
-
-ANYTHING = [
- JSONObject,
- JSONArray,
- JSONString,
- JSONConstant,
- JSONNumber,
-]
-
-JSONScanner = Scanner(ANYTHING)
-
-
-class JSONDecoder(object):
-
- _scanner = Scanner(ANYTHING)
- __all__ = ['__init__', 'decode', 'raw_decode']
-
- def __init__(self, encoding=None, object_hook=None, parse_float=None,
- parse_int=None, parse_constant=None, strict=True):
- self.encoding = encoding
- self.object_hook = object_hook
- self.parse_float = parse_float
- self.parse_int = parse_int
- self.parse_constant = parse_constant
- self.strict = strict
-
- def decode(self, s, _w=WHITESPACE.match):
- obj, end = self.raw_decode(s, idx=_w(s, 0).end())
- end = _w(s, end).end()
- if end != len(s):
- raise ValueError(errmsg("Extra data", s, end, len(s)))
- return obj
-
- def raw_decode(self, s, **kw):
- kw.setdefault('context', self)
- try:
- obj, end = self._scanner.iterscan(s, **kw).next()
- except StopIteration:
- raise ValueError("No JSON object could be decoded")
- return obj, end
-
-
-_default_decoder = JSONDecoder(encoding=None, object_hook=None)
-
-
-# public functions
-
-def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True,
- allow_nan=True, cls=None, indent=None, separators=None,
- encoding='utf-8', default=None, **kw):
- if (skipkeys is False and ensure_ascii is True and
- check_circular is True and allow_nan is True and
- cls is None and indent is None and separators is None and
- encoding == 'utf-8' and default is None and not kw):
- return _default_encoder.encode(obj)
- if cls is None:
- cls = JSONEncoder
- return cls(
- skipkeys=skipkeys, ensure_ascii=ensure_ascii,
- check_circular=check_circular, allow_nan=allow_nan, indent=indent,
- separators=separators, encoding=encoding, default=default,
- **kw).encode(obj)
-
-
-def loads(s, encoding=None, cls=None, object_hook=None, parse_float=None,
- parse_int=None, parse_constant=None, **kw):
- if (cls is None and encoding is None and object_hook is None and
- parse_int is None and parse_float is None and
- parse_constant is None and not kw):
- return _default_decoder.decode(s)
- if cls is None:
- cls = JSONDecoder
- if object_hook is not None:
- kw['object_hook'] = object_hook
- if parse_float is not None:
- kw['parse_float'] = parse_float
- if parse_int is not None:
- kw['parse_int'] = parse_int
- if parse_constant is not None:
- kw['parse_constant'] = parse_constant
- return cls(encoding=encoding, **kw).decode(s)
diff --git a/cybertools/util/json.txt b/cybertools/util/json.txt
deleted file mode 100644
index 156f98f..0000000
--- a/cybertools/util/json.txt
+++ /dev/null
@@ -1,64 +0,0 @@
-==========================
-JSON Encoding and Decoding
-==========================
-
-$Id$
-
-This is a stripped-down version of simplejson
-by Bob Ippolito, http://undefined.org/python/
-
-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.
-
- >>> from cybertools.util.json import dumps, loads
-
-Encoding basic Python object hierarchies::
-
- >>> dumps(['foo', {'bar': ('baz', None, 1.0, 2)}])
- '["foo", {"bar": ["baz", null, 1.0, 2]}]'
- >>> print dumps("\"foo\bar")
- "\"foo\bar"
- >>> print dumps(u'\u1234')
- "\u1234"
- >>> print dumps('\\')
- "\\"
- >>> print dumps({"c": 0, "b": 0, "a": 0}, sort_keys=True)
- {"a": 0, "b": 0, "c": 0}
-
-Compact encoding::
-
- >>> dumps([1,2,3,{'4': 5, '6': 7}], separators=(',',':'))
- '[1,2,3,{"4":5,"6":7}]'
-
-Pretty printing::
-
- >>> print dumps({'4': 5, '6': 7}, sort_keys=True, indent=4)
- {
- "4": 5,
- "6": 7
- }
-
-Decoding JSON::
-
- >>> loads('["foo", {"bar":["baz", null, 1.0, 2]}]')
- [u'foo', {u'bar': [u'baz', None, 1.0, 2]}]
- >>> loads('"\\"foo\\bar"')
- u'"foo\x08ar'
-
diff --git a/cybertools/util/multikey.txt b/cybertools/util/multikey.txt
index c1aaf82..f04d4f9 100644
--- a/cybertools/util/multikey.txt
+++ b/cybertools/util/multikey.txt
@@ -65,9 +65,9 @@ Index entries that are present in the stored dictionary must always match:
If you don't know any more what entries we had added to the registry just
query its items() method:
- >>> sorted(registry.items())
- [(('edit.html', None, None, 'Custom'), 'edit.html for Custom skin'),
- (('edit.html', 'topic', 'zope3', 'Custom'), 'very special edit.html'),
- (('index.html', None, None, None), 'global index.html'),
+ >>> registry.items()
+ ((('index.html', None, None, None), 'global index.html'),
(('index.html', None, None, 'Custom'), 'Global index.html for Custom skin'),
- (('index.html', 'topic', None, None), 'index.html for type "topic"')]
+ (('index.html', 'topic', None, None), 'index.html for type "topic"'),
+ (('edit.html', 'topic', 'zope3', 'Custom'), 'very special edit.html'),
+ (('edit.html', None, None, 'Custom'), 'edit.html for Custom skin'))
diff --git a/cybertools/util/property.txt b/cybertools/util/property.txt
index 039d34b..d3ef650 100644
--- a/cybertools/util/property.txt
+++ b/cybertools/util/property.txt
@@ -2,8 +2,6 @@
Smart Properties
================
-$Id$
-
lzprop
======
@@ -21,7 +19,7 @@ by printing an informative message:
... base = 6
... @lzprop
... def value(self):
- ... print 'calculating'
+ ... print('calculating')
... return self.base * 7
>>> demo = Demo()
diff --git a/cybertools/util/tests.py b/cybertools/util/tests.py
index dcf2605..699ac33 100755
--- a/cybertools/util/tests.py
+++ b/cybertools/util/tests.py
@@ -1,25 +1,28 @@
-# $Id$
+# cybertools.util.tests
-import unittest
-import doctest
-
-import cybertools.util.property
+import unittest, doctest
+import sys
+import warnings
+#print('***', sys.path)
+sys.path = sys.path[1:]
class Test(unittest.TestCase):
"Basic tests for modules in the util package."
def testBasicStuff(self):
+ warnings.filterwarnings('ignore', category=ResourceWarning)
+ warnings.filterwarnings('ignore', category=DeprecationWarning)
pass
def test_suite():
flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
return unittest.TestSuite((
- #unittest.makeSuite(Test), # we don't need this
+ unittest.makeSuite(Test),
#doctest.DocTestSuite(cybertools.util.property, optionflags=flags),
doctest.DocFileSuite('adapter.txt', optionflags=flags),
- doctest.DocFileSuite('aop.txt', optionflags=flags),
+ #doctest.DocFileSuite('aop.txt', optionflags=flags),
doctest.DocFileSuite('cache.txt', optionflags=flags),
doctest.DocFileSuite('config.txt', optionflags=flags),
doctest.DocFileSuite('defer.txt', optionflags=flags),
@@ -28,7 +31,6 @@ def test_suite():
doctest.DocFileSuite('iterate.txt', optionflags=flags),
doctest.DocFileSuite('multikey.txt', optionflags=flags),
doctest.DocFileSuite('property.txt', optionflags=flags),
- doctest.DocFileSuite('json.txt', optionflags=flags),
doctest.DocFileSuite('jeep.txt', optionflags=flags),
doctest.DocFileSuite('randomname.txt', optionflags=flags),
doctest.DocFileSuite('version.txt', optionflags=flags),
diff --git a/cybertools/util/version.txt b/cybertools/util/version.txt
index 758310c..16cb7c2 100644
--- a/cybertools/util/version.txt
+++ b/cybertools/util/version.txt
@@ -2,8 +2,6 @@
Collect Version Information from Different Packages
===================================================
-$Id$
-
>>> from cybertools.util.version import versions
>>> versions.add()
@@ -11,7 +9,7 @@ $Id$
>>> v = versions.get('cybertools.util.version')
>>> v
cybertools.util.version 0.4-3014
- >>> print v
+ >>> print(v)
0.4-3014
>>> v.short
'0.4'