From jan at codespeak.net Wed Mar 1 00:50:23 2006 From: jan at codespeak.net (jan at codespeak.net) Date: Wed, 1 Mar 2006 00:50:23 +0100 (CET) Subject: [py-svn] r23794 - in py/dist/py/path/local: . testing Message-ID: <20060228235023.73FA11007F@code0.codespeak.net> Author: jan Date: Wed Mar 1 00:49:57 2006 New Revision: 23794 Modified: py/dist/py/path/local/local.py py/dist/py/path/local/posix.py py/dist/py/path/local/testing/test_local.py py/dist/py/path/local/win.py Log: create platform specific version of py.path.local().remove basicly you can't force a remove of readonly files on windows, so we try to make them writeable Modified: py/dist/py/path/local/local.py ============================================================================== --- py/dist/py/path/local/local.py (original) +++ py/dist/py/path/local/local.py Wed Mar 1 00:49:57 2006 @@ -209,16 +209,6 @@ """ return last modification time of the path. """ return self.stat().mtime - def remove(self, rec=1): - """ remove a file or directory (or a directory tree if rec=1). """ - if self.check(dir=1, link=0): - if rec: - self._callex(py.std.shutil.rmtree, self.strpath) - else: - self._callex(os.rmdir, self.strpath) - else: - self._callex(os.remove, self.strpath) - def copy(self, target, archive=False): assert not archive, "XXX archive-mode not supported" if self.check(file=1): Modified: py/dist/py/path/local/posix.py ============================================================================== --- py/dist/py/path/local/posix.py (original) +++ py/dist/py/path/local/posix.py Wed Mar 1 00:49:57 2006 @@ -99,6 +99,18 @@ target = self.sep.join(('..', )*n + (relsource, )) self._callex(os.symlink, target, self.strpath) + + def remove(self, rec=1): + """ remove a file or directory (or a directory tree if rec=1). """ + if self.check(dir=1, link=0): + if rec: + self._callex(py.std.shutil.rmtree, self.strpath) + else: + self._callex(os.rmdir, self.strpath) + else: + self._callex(os.remove, self.strpath) + + def getuserid(user): import pwd if isinstance(user, int): Modified: py/dist/py/path/local/testing/test_local.py ============================================================================== --- py/dist/py/path/local/testing/test_local.py (original) +++ py/dist/py/path/local/testing/test_local.py Wed Mar 1 00:49:57 2006 @@ -15,6 +15,26 @@ self.tmpdir.remove(rec=1) class TestLocalPath(LocalSetup, CommonFSTests): + + def test_remove_removes_readonly_file(self): + readonly_file = self.tmpdir.join('readonly').ensure() + readonly_file.chmod(0) + readonly_file.remove() + assert not readonly_file.check(exists=1) + + def test_remove_removes_readonly_dir(self): + readonly_dir = self.tmpdir.join('readonlydir').ensure(dir=1) + readonly_dir.chmod(0500) + readonly_dir.remove() + assert not readonly_dir.check(exists=1) + + def test_remove_removes_dir_and_readonly_file(self): + readonly_dir = self.tmpdir.join('readonlydir').ensure(dir=1) + readonly_file = readonly_dir.join('readonlyfile').ensure() + readonly_file.chmod(0) + readonly_dir.remove() + assert not readonly_dir.check(exists=1) + def test_initialize_curdir(self): assert str(local()) == py.std.os.getcwd() Modified: py/dist/py/path/local/win.py ============================================================================== --- py/dist/py/path/local/win.py (original) +++ py/dist/py/path/local/win.py Wed Mar 1 00:49:57 2006 @@ -27,3 +27,14 @@ self._callex(os.chmod, str(x), mode) self._callex(os.chmod, str(self), mode) + def remove(self, rec=1): + """ remove a file or directory (or a directory tree if rec=1). """ + if self.check(dir=1, link=0): + if rec: + # force remove of readonly files on windows + self.chmod(0700, rec=1) + self._callex(py.std.shutil.rmtree, self.strpath) + else: + self._callex(os.rmdir, self.strpath) + else: + self._callex(os.remove, self.strpath) From briandorsey at codespeak.net Wed Mar 1 01:29:39 2006 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Wed, 1 Mar 2006 01:29:39 +0100 (CET) Subject: [py-svn] r23797 - py/dist/py/path/local Message-ID: <20060301002939.9551B1007F@code0.codespeak.net> Author: briandorsey Date: Wed Mar 1 01:29:38 2006 New Revision: 23797 Modified: py/dist/py/path/local/win.py Log: Fixing the windows details for py.path.local().remove. Modified: py/dist/py/path/local/win.py ============================================================================== --- py/dist/py/path/local/win.py (original) +++ py/dist/py/path/local/win.py Wed Mar 1 01:29:38 2006 @@ -1,11 +1,11 @@ -import os - """ module for win-specific local path stuff (implementor needed :-) """ +import os +import py from py.__.path.local.common import Stat class WinMixin: @@ -37,4 +37,5 @@ else: self._callex(os.rmdir, self.strpath) else: + self.chmod(0700) self._callex(os.remove, self.strpath) From jan at codespeak.net Wed Mar 1 02:53:23 2006 From: jan at codespeak.net (jan at codespeak.net) Date: Wed, 1 Mar 2006 02:53:23 +0100 (CET) Subject: [py-svn] r23811 - py/dist/py/path/svn Message-ID: <20060301015323.329951007E@code0.codespeak.net> Author: jan Date: Wed Mar 1 02:53:20 2006 New Revision: 23811 Modified: py/dist/py/path/svn/urlcommand.py Log: svn error messages changed but it wasn't noticed because of a incorrect find usage Modified: py/dist/py/path/svn/urlcommand.py ============================================================================== --- py/dist/py/path/svn/urlcommand.py (original) +++ py/dist/py/path/svn/urlcommand.py Wed Mar 1 02:53:20 2006 @@ -56,7 +56,8 @@ try: out = process.cmdexec(string) except py.process.cmdexec.Error, e: - if e.err.find('File Exists'): + if (e.err.find('File Exists') != -1 or + e.err.find('File already exists') != -1): raise py.error.EEXIST(self) raise return out From briandorsey at codespeak.net Wed Mar 1 03:21:33 2006 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Wed, 1 Mar 2006 03:21:33 +0100 (CET) Subject: [py-svn] r23814 - in py/dist/py/path/svn: . testing Message-ID: <20060301022133.47AA210085@code0.codespeak.net> Author: briandorsey Date: Wed Mar 1 03:21:30 2006 New Revision: 23814 Modified: py/dist/py/path/svn/testing/test_wccommand.py py/dist/py/path/svn/wccommand.py Log: This is the first in a series of checkins to fix various windows/path issues with py.path.svn. * my copy of svn has a different error message: added 'file already exists' * don't add '@HEAD' to svn co on windows. * several changes to quoting changing single quotes to double-quotes. * ignore one info.path comparison on windows. It looks like the case is already covered, and the path comparison doesn't always work on windows. Modified: py/dist/py/path/svn/testing/test_wccommand.py ============================================================================== --- py/dist/py/path/svn/testing/test_wccommand.py (original) +++ py/dist/py/path/svn/testing/test_wccommand.py Wed Mar 1 03:21:30 2006 @@ -49,8 +49,10 @@ t1.write('test') t1.add() s = sampledir.status(rec=1) - assert t1 in s.added - assert t2 in s.added + # Comparing just the file names, because paths are unpredictable + # on Windows. (long vs. 8.3 paths) + assert t1.basename in [item.basename for item in s.added] + assert t2.basename in [item.basename for item in s.added] finally: t2.revert(rec=1) t2.localpath.remove(rec=1) @@ -60,23 +62,33 @@ try: t1.write('test') s = self.root.status() - assert t1 in s.unknown + # Comparing just the file names, because paths are unpredictable + # on Windows. (long vs. 8.3 paths) + assert t1.basename in [item.basename for item in s.unknown] finally: t1.localpath.remove() def test_status_unchanged(self): r = self.root s = self.root.status(rec=1) - assert r.join('samplefile') in s.unchanged - assert r.join('sampledir') in s.unchanged - assert r.join('sampledir/otherfile') in s.unchanged + # Comparing just the file names, because paths are unpredictable + # on Windows. (long vs. 8.3 paths) + assert r.join('samplefile').basename in [item.basename + for item in s.unchanged] + assert r.join('sampledir').basename in [item.basename + for item in s.unchanged] + assert r.join('sampledir/otherfile').basename in [item.basename + for item in s.unchanged] def test_status_update(self): r = self.root try: r.update(rev=1) s = r.status(updates=1, rec=1) - assert r.join('anotherfile') in s.update_available + # Comparing just the file names, because paths are unpredictable + # on Windows. (long vs. 8.3 paths) + assert r.join('anotherfile').basename in [item.basename for + item in s.update_available] #assert len(s.update_available) == 1 finally: r.update() @@ -99,7 +111,8 @@ def test_versioned(self): assert self.root.check(versioned=1) - assert self.root.join('.svn').check(versioned=0) + # TODO: Why does my copy of svn think .svn is versioned? + #assert self.root.join('.svn').check(versioned=0) assert self.root.join('samplefile').check(versioned=1) assert not self.root.join('notexisting').check(versioned=1) notexisting = self.root.join('hello').localpath @@ -113,7 +126,10 @@ try: self.root.propset('gaga', 'this') assert self.root.propget('gaga') == 'this' - assert self.root in self.root.status().prop_modified + # Comparing just the file names, because paths are unpredictable + # on Windows. (long vs. 8.3 paths) + assert self.root.basename in [item.basename for item in + self.root.status().prop_modified] assert 'gaga' in self.root.proplist() assert self.root.proplist()['gaga'] == 'this' @@ -125,7 +141,10 @@ s.propset('gugu', 'that') try: p = self.root.proplist(rec=1) - assert self.root / 'samplefile' in p + # Comparing just the file names, because paths are unpredictable + # on Windows. (long vs. 8.3 paths) + assert (self.root / 'samplefile').basename in [item.basename + for item in p] finally: s.propdel('gugu') Modified: py/dist/py/path/svn/wccommand.py ============================================================================== --- py/dist/py/path/svn/wccommand.py (original) +++ py/dist/py/path/svn/wccommand.py Wed Mar 1 03:21:30 2006 @@ -80,7 +80,8 @@ strerr = e.err.lower() if strerr.find('file not found') != -1: raise py.error.ENOENT(self) - if strerr.find('file exists') != -1: + if (strerr.find('file exists') != -1 or + strerr.find('file already exists') != -1): raise py.error.EEXIST(self) raise return out @@ -94,7 +95,8 @@ if url is None: url = self.url if rev is None or rev == -1: - url += "@HEAD" + if py.std.sys.platform != 'win32': + url += "@HEAD" else: url += "@%d" % rev self._svn('co', url) @@ -283,7 +285,7 @@ """commit() returns None if there was nothing to commit and the revision number of the commit otherwise. """ - out = self._svn('commit -m %r' % message) + out = self._svn('commit -m "%s"' % message) try: del cache.info[self] except KeyError: @@ -355,8 +357,12 @@ if output.lower().find('not a versioned resource') != -1: raise py.error.ENOENT(self, output) info = InfoSvnWCCommand(output) - if info.path != self.localpath: - raise py.error.ENOENT(self, "not a versioned resource") + + # Can't reliably compare on Windows without access to win32api + if py.std.sys.platform != 'win32': + if info.path != self.localpath: + raise py.error.ENOENT(self, "not a versioned resource:" + + " %s != %s" % (info.path, self.localpath)) cache.info[self] = info self.rev = info.rev return info From briandorsey at codespeak.net Wed Mar 1 03:27:30 2006 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Wed, 1 Mar 2006 03:27:30 +0100 (CET) Subject: [py-svn] r23815 - py/dist/py/path/svn/testing Message-ID: <20060301022730.CB8921007F@code0.codespeak.net> Author: briandorsey Date: Wed Mar 1 03:27:25 2006 New Revision: 23815 Modified: py/dist/py/path/svn/testing/svntestbase.py Log: Windows fixes to py.path.svn tests. * Switched quoting - single quotes to double quotes. * Several hacks to the repository setup code - make the paths work on windows. Modified: py/dist/py/path/svn/testing/svntestbase.py ============================================================================== --- py/dist/py/path/svn/testing/svntestbase.py (original) +++ py/dist/py/path/svn/testing/svntestbase.py Wed Mar 1 03:27:25 2006 @@ -1,3 +1,4 @@ +import sys import py from py import path, test, process from py.__.path.testing.fscommon import CommonFSTests, setuptestfs @@ -16,14 +17,17 @@ #assert not wcdir.check() repo.ensure(dir=1) try: - py.process.cmdexec('svnadmin create %s' % repo) - py.process.cmdexec('svnadmin load -q %r <%r' % (str(repo), str(repodump))) + py.process.cmdexec('svnadmin create "%s"' % repo) + py.process.cmdexec('svnadmin load -q "%s" <"%s"' % (repo, repodump)) except py.process.cmdexec.Error: + raise repo.remove() raise py.test.skip('could not create temporary svn test repository') print "created svn repository", repo wcdir.ensure(dir=1) wc = py.path.svnwc(wcdir) + if py.std.sys.platform == 'win32': + repo = '/' + str(repo).replace('\\', '/') wc.checkout(url='file://%s' % repo) print "checked out new repo into", wc else: @@ -44,8 +48,13 @@ def restore_repowc((savedrepo, savedwc)): repo, wc = getrepowc() + print repo + print repo[len("file://"):] repo = py.path.local(repo[len("file://"):]) + print repo assert repo.check() + # repositories have read only files on windows + #repo.chmod(0777, rec=True) repo.remove() wc.localpath.remove() savedrepo.move(repo) @@ -59,6 +68,8 @@ except: repo.remove() raise + if sys.platform == 'win32': + repo = '/' + str(repo).replace('\\', '/') return py.path.svnurl("file://%s" % repo) class CommonSvnTests(CommonFSTests): From briandorsey at codespeak.net Wed Mar 1 03:32:28 2006 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Wed, 1 Mar 2006 03:32:28 +0100 (CET) Subject: [py-svn] r23816 - py/dist/py/path/svn Message-ID: <20060301023228.B474B1007E@code0.codespeak.net> Author: briandorsey Date: Wed Mar 1 03:32:26 2006 New Revision: 23816 Modified: py/dist/py/path/svn/urlcommand.py Log: Many fixes to py.path.svn for windows, just quoting issues. * Changed many uses of single quotes to double-quotes for escaping. Modified: py/dist/py/path/svn/urlcommand.py ============================================================================== --- py/dist/py/path/svn/urlcommand.py (original) +++ py/dist/py/path/svn/urlcommand.py Wed Mar 1 03:32:26 2006 @@ -38,7 +38,7 @@ l = ['svn %s -r %d' % (cmd, self.rev)] args = map(lambda x: '"%s"' % str(x), args) l.extend(args) - l.append(repr(self._encodedurl())) + l.append('"%s"' % self._encodedurl()) # fixing the locale because we can't otherwise parse string = svncommon.fixlocale() + " ".join(l) #print "execing", string @@ -49,7 +49,7 @@ l = ['svn %s' % cmd] args = map(lambda x: '"%s"' % str(x), args) l.extend(args) - l.append(repr(self._encodedurl())) + l.append('"%s"' % self._encodedurl()) # fixing the locale because we can't otherwise parse string = svncommon.fixlocale() + " ".join(l) #print "execing", string @@ -57,7 +57,7 @@ out = process.cmdexec(string) except py.process.cmdexec.Error, e: if (e.err.find('File Exists') != -1 or - e.err.find('File already exists') != -1): + e.err.find('File already exists') != -1): raise py.error.EEXIST(self) raise return out @@ -103,21 +103,22 @@ def copy(self, target, msg='copied by py lib invocation'): if getattr(target, 'rev', None) is not None: raise py.error.EINVAL(target, "revisions are immutable") - process.cmdexec("svn copy -m %r %s %s" %(msg, str(self), str(target))) + process.cmdexec('svn copy -m "%s" "%s" "%s"' %(msg, + str(self), str(target))) self._lsnorevcache.delentry(target.dirpath().strpath) def rename(self, target, msg="renamed by py lib invocation"): if getattr(self, 'rev', None) is not None: raise py.error.EINVAL(self, "revisions are immutable") - py.process.cmdexec("svn move -m %r --force %s %s" %( - msg, str(self), str(target))) + py.process.cmdexec('svn move -m "%s" --force "%s" "%s"' %( + msg, self, target)) self._lsnorevcache.delentry(self.dirpath().strpath) self._lsnorevcache.delentry(self.strpath) def remove(self, rec=1, msg='removed by py lib invocation'): if self.rev is not None: raise py.error.EINVAL(self, "revisions are immutable") - process.cmdexec("svn rm -m %r %s" %(msg, str(self))) + process.cmdexec('svn rm -m "%s" "%s"' %(msg, self)) self._lsnorevcache.delentry(self.dirpath().strpath) def ensure(self, *args, **kwargs): @@ -143,9 +144,9 @@ tempdir = py.path.local.mkdtemp() try: tempdir.ensure(tocreate, dir=dir) - cmd = 'svn import -m %r %r %r' % ( + cmd = 'svn import -m "%s" "%s" "%s"' % ( "ensure %s" % tocreate, - str(tempdir.join(basename)), + tempdir.join(basename), x.join(basename)._encodedurl()) process.cmdexec(cmd) self._lsnorevcache.delentry(x.strpath) # !!! From briandorsey at codespeak.net Wed Mar 1 03:35:18 2006 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Wed, 1 Mar 2006 03:35:18 +0100 (CET) Subject: [py-svn] r23817 - py/dist/py/documentation Message-ID: <20060301023518.AD96C1007E@code0.codespeak.net> Author: briandorsey Date: Wed Mar 1 03:35:17 2006 New Revision: 23817 Modified: py/dist/py/documentation/api.txt Log: Fixed a few typos. Modified: py/dist/py/documentation/api.txt ============================================================================== --- py/dist/py/documentation/api.txt (original) +++ py/dist/py/documentation/api.txt Wed Mar 1 03:35:17 2006 @@ -7,11 +7,11 @@ with its tests and examples that are already available at our little `getting started`_ chapter. -Yours tests are your API insurance -================================== +Your tests are your API insurance +================================= Tests are the primary assurance of API compatilibity. If you -like to ensure some behaviour gets preserved across major +would like to ensure some behaviour gets preserved across major releases you have to write a test. Note though, that the documentation may state additional restrictions which take precedence. From pmaupin at codespeak.net Wed Mar 1 17:41:16 2006 From: pmaupin at codespeak.net (pmaupin at codespeak.net) Date: Wed, 1 Mar 2006 17:41:16 +0100 (CET) Subject: [py-svn] r23842 - py/dist/py/documentation Message-ID: <20060301164116.B25211007C@code0.codespeak.net> Author: pmaupin Date: Wed Mar 1 17:40:14 2006 New Revision: 23842 Added: py/dist/py/documentation/code_template.txt Log: Initial version of code_template doc for comment Added: py/dist/py/documentation/code_template.txt ============================================================================== --- (empty file) +++ py/dist/py/documentation/code_template.txt Wed Mar 1 17:40:14 2006 @@ -0,0 +1,640 @@ +=============================================================== +py.code_template: Lightweight and flexible code template system +=============================================================== + +.. contents:: +.. sectnum:: + +Motivation +========== + +There are as many python templating systems as there are web frameworks +(a lot). This is partly because it is so darned easy to write a templating +system in Python. What are the distinguishing characteristics of the +py.code_template templating system? + + * Optimized for generating code (Python, C, bash scripts, etc.), + not XML or HTML + + * Designed for use by Python programmers, not by web artists + + + Aesthetic sensibilities are different + + + The templates should be an organic part of a module -- just more code + + + Templates do not need to be incredibly full-featured, because + programmers are perfectly capable of escaping to Python for + advanced features. + + - No requirement to support inheritance + - No requirement to support exec + + * Designed so that templates can be coded in the most natural way + for the task at hand + + + Generation of code and scripts often does not follow MVC paradigm! + + + Small template fragments are typically coded *inside* Python modules + + + Sometimes it is natural to put strings inside code; sometimes it is + natural to put code inside strings. Both should be supported as + reasonably and naturally as possible. + +Imaginary-world examples +======================== + +These would be real-world examples, but, not only is this module not yet +implemented, as of now, PyPy is not incredibly useful to the average +programmer... + +translator/c/genc.py +-------------------- + +The original function:: + + def gen_readable_parts_of_main_c_file(f, database, preimplementationlines=[]): + # + # All declarations + # + structdeflist = database.getstructdeflist() + print >> f + print >> f, '/***********************************************************/' + print >> f, '/*** Structure definitions ***/' + print >> f + for node in structdeflist: + print >> f, 'struct %s;' % node.name + print >> f + for node in structdeflist: + for line in node.definition(): + print >> f, line + print >> f + print >> f, '/***********************************************************/' + print >> f, '/*** Forward declarations ***/' + print >> f + for node in database.globalcontainers(): + for line in node.forward_declaration(): + print >> f, line + + # + # Implementation of functions and global structures and arrays + # + print >> f + print >> f, '/***********************************************************/' + print >> f, '/*** Implementations ***/' + print >> f + for line in preimplementationlines: + print >> f, line + print >> f, '#include "src/g_include.h"' + print >> f + blank = True + for node in database.globalcontainers(): + if blank: + print >> f + blank = False + for line in node.implementation(): + print >> f, line + blank = True + +This could be refactored heavily. An initial starting point +would look something like this, although later, the template +instance could be passed in and reused directly, rather than +passing the file handle around:: + + def gen_readable_parts_of_main_c_file(f, database, preimplementationlines=[]): + def container_implementation(): + # Helper function designed to introduce blank lines + # between container implementations + + blank = True + for node in database.globalcontainers(): + if blank: + yield '' + blank = False + for line in node.implementation(): + yield line + blank = True + + t = code_template.Template() + # + # All declarations + # + structdeflist = database.getstructdeflist() + t.write(dedent=8, text=''' + + /***********************************************************/ + /*** Structure definitions ***/ + + {for node in structdeflist} + struct {node.name}; + {endfor} + + {for node in structdeflist} + {for line in node.definition} + {line} + {endfor} + {endfor} + + /***********************************************************/ + /*** Forward declarations ***/ + + {for node in database.globalcontainers()} + {for line in node.forward_declaration()} + {line} + {endfor} + {endfor} + + {** + ** Implementation of functions and global structures and arrays + **} + + /***********************************************************/ + /*** Implementations ***/ + + {for line in preimplementationlines} + {line} + {endfor} + + #include "src/g_include.h" + + {for line in container_implementation()} + {line} + {endfor} + """) + t.output(f) + +translator/c/genc.py gen_makefile +--------------------------------- + +The original code:: + + MAKEFILE = ''' + CC = gcc + + $(TARGET): $(OBJECTS) + \t$(CC) $(LDFLAGS) -o $@ $(OBJECTS) $(LIBDIRS) $(LIBS) + + %.o: %.c + \t$(CC) $(CFLAGS) -o $@ -c $< $(INCLUDEDIRS) + + clean: + \trm -f $(OBJECTS) + ''' + + def gen_makefile(self, targetdir): + def write_list(lst, prefix): + for i, fn in enumerate(lst): + print >> f, prefix, fn, + if i < len(lst)-1: + print >> f, '\\' + else: + print >> f + prefix = ' ' * len(prefix) + + compiler = self.getccompiler(extra_includes=['.']) + cfiles = [] + ofiles = [] + for fn in compiler.cfilenames: + fn = py.path.local(fn).basename + assert fn.endswith('.c') + cfiles.append(fn) + ofiles.append(fn[:-2] + '.o') + + f = targetdir.join('Makefile').open('w') + print >> f, '# automatically generated Makefile' + print >> f + print >> f, 'TARGET =', py.path.local(compiler.outputfilename).basename + print >> f + write_list(cfiles, 'SOURCES =') + print >> f + write_list(ofiles, 'OBJECTS =') + print >> f + args = ['-l'+libname for libname in compiler.libraries] + print >> f, 'LIBS =', ' '.join(args) + args = ['-L'+path for path in compiler.library_dirs] + print >> f, 'LIBDIRS =', ' '.join(args) + args = ['-I'+path for path in compiler.include_dirs] + write_list(args, 'INCLUDEDIRS =') + print >> f + print >> f, 'CFLAGS =', ' '.join(compiler.compile_extra) + print >> f, 'LDFLAGS =', ' '.join(compiler.link_extra) + print >> f, MAKEFILE.strip() + f.close() + + +Could look something like this:: + + MAKEFILE = ''' + # automatically generated Makefile + + TARGET = {py.path.local(compiler.outputfilename).basename} + + {for line in write_list(cfiles, 'SOURCES =')} + {line} + {endfor} + + {for line in write_list(ofiles, 'OBJECTS =')} + {line} + {endfor} + + LIBS ={for libname in compiler.libraries} -l{libname}{endfor} + LIBDIRS ={for path in compiler.library_dirs} -L{path}{endfor} + INCLUDEDIRS ={for path in compiler.include_dirs} -I{path}{endfor} + + CFLAGS ={for extra in compiler.compile_extra} {extra}{endfor} + LDFLAGS ={for extra in compiler.link_extra} {extra}{endfor} + + CC = gcc + + $(TARGET): $(OBJECTS) + \t$(CC) $(LDFLAGS) -o $@ $(OBJECTS) $(LIBDIRS) $(LIBS) + + %.o: %.c + \t$(CC) $(CFLAGS) -o $@ -c $< $(INCLUDEDIRS) + + clean: + \trm -f $(OBJECTS) + ''' + + def gen_makefile(self, targetdir): + def write_list(lst, prefix): + for i, fn in enumerate(lst): + yield '%s %s %s' % (prefix, fn, i < len(list)-1 and '\\' or '') + prefix = ' ' * len(prefix) + + compiler = self.getccompiler(extra_includes=['.']) + cfiles = [] + ofiles = [] + for fn in compiler.cfilenames: + fn = py.path.local(fn).basename + assert fn.endswith('.c') + cfiles.append(fn) + ofiles.append(fn[:-2] + '.o') + + code_template.Template(MAKEFILE).output(targetdir.join('Makefile')) + + +translator/llvm/module/excsupport.py +------------------------------------ + +The original string:: + + invokeunwind_code = ''' + ccc %(returntype)s%%__entrypoint__%(entrypointname)s { + %%result = invoke %(cconv)s %(returntype)s%%%(entrypointname)s to label %%no_exception except label %%exception + + no_exception: + store %%RPYTHON_EXCEPTION_VTABLE* null, %%RPYTHON_EXCEPTION_VTABLE** %%last_exception_type + ret %(returntype)s %%result + + exception: + ret %(noresult)s + } + + ccc int %%__entrypoint__raised_LLVMException() { + %%tmp = load %%RPYTHON_EXCEPTION_VTABLE** %%last_exception_type + %%result = cast %%RPYTHON_EXCEPTION_VTABLE* %%tmp to int + ret int %%result + } + + internal fastcc void %%unwind() { + unwind + } + ''' + +Could look something like this if it was used in conjunction with a template:: + + invokeunwind_code = ''' + ccc {returntype}%__entrypoint__{entrypointname} { + %result = invoke {cconv} {returntype}%{entrypointname} to label %no_exception except label %exception + + no_exception: + store %RPYTHON_EXCEPTION_VTABLE* null, %RPYTHON_EXCEPTION_VTABLE** %last_exception_type + ret {returntype} %result + + exception: + ret {noresult} + } + + ccc int %__entrypoint__raised_LLVMException() { + %tmp = load %RPYTHON_EXCEPTION_VTABLE** %last_exception_type + %result = cast %RPYTHON_EXCEPTION_VTABLE* %tmp to int + ret int %result + } + + internal fastcc void %unwind() { + unwind + } + ''' + + +Template syntax +=============== + +Design decision +--------------- + +As all programmers must know by now, all the special symbols on the keyboard +are quite heavily overloaded. Often, template systems work around this fact +by having special notation like `<*` ... `*>` or {% ... %}. Some template systems +even have multiple special notations -- one for comments, one for statements, +one for expressions, etc. + +I find these hard to type and ugly. Other markups are either too lightweight, +or use characters which occur so frequently in the target languages that it +becomes hard to distinguish marked-up content from content which should be +rendered as-is. + +The compromise taken by *code_template* is to use braces (**{}**) for markup. + +This immediately raises the question: what about when the marked-up language +is C or C++? The answer is that if the leading brace is immediately followed +by whitespace, it is normal text; if not it is the start of markup. + +To support normal text which has a leading brace immediately followed by +an identifier, if the first whitespace character after the brace is a space +character (e.g. not a newline or tab), it will be removed from the output. + +Examples:: + + { This is normal text and the space between { and This will be removed} + {'this must be a valid Python expression' + ' because it is treated as markup'} + { + This is normal text, but nothing is altered (the newline is kept intact) + } + + {{1:'Any valid Python expression is allowed as markup'}[1].ljust(30)} + +.. _`Code element`: + +Elements +-------- + +Templates consist of normal text and code elements. +(Comments are considered to be code elements.) + +All code elements start with a `left brace`_ which is not followed by +whitespace. + +Keyword element +~~~~~~~~~~~~~~~ + +A keyword element is a `code element`_ which starts with a keyword_. + +For example, *{if foo}* is a keyword element, but *{foo}* is a `substituted expression`_. + +Keyword +~~~~~~~ + +A keyword is a word used in `conditional text`_ or in `repeated text`_, e.g. +one of *if*, *elif*, *else*, *endif*, *for*, or *endfor*. + +Keywords are designed to match their Python equivalents. However, since +templates cannot use spacing to indicate expression nesting, the additional +keywords *endif* and *endfor* are required. + +Left brace +~~~~~~~~~~ + +All elements other than normal text start with a left brace -- the symbol '{', +sometimes known as a 'curly bracket'. A left brace is itself considered +to be normal text if it is followed by whitespace. If the whitespace starts +with a space character, that space character will be stripped from the output. +If the whitespace starts with a tab or linefeed character, the whitespace will +be left in the output. + +Normal Text +~~~~~~~~~~~ + +Normal text remains unsubstituted. Transition from text to the other elements +is effected by use of a `left brace`_ which is not followed by whitespace. + +Comment +~~~~~~~ + +A comment starts with a left brace followed by an asterisk ('{`*`'), and +ends with an asterisk followed by a right brace ('`*`}'):: + + This is a template -- this text will be copied to the output. + {* This is a comment and this text will not be copied to the output *} + + {* + Comments can span lines, + but cannot be nested + *} + +Substituted expression +~~~~~~~~~~~~~~~~~~~~~~ + +Any python expression may be used:: + + Dear {record.name}, + we are sorry to inform you that you did not win {record.contest}. + +The expression must be surrounded by braces, and there must not be any +whitespace between the leftmost brace and the start of the expression. + +The expression will automatically be converted to a string with str(). + +Conditional text +~~~~~~~~~~~~~~~~ + +The following template has text which is included conditionally:: + + This text will always be included in the output + {if foo} + This text will be included if foo is true + {elif bar} + This text will be included if foo is not true but bar is true + {else} + This text will be included if neither foo nor bar is true + {endif} + +The {elif} and {else} elements are optional. + +Repeated text +~~~~~~~~~~~~~ + +The following template shows how to pull multiple items out of a list:: + + {for student, score in sorted(scorelist)} + {student.ljust(20)} {score} + {endfor} + +Whitespace removal or modification +---------------------------------- + +In general, whitespace in `Normal Text`_ is transferred unchanged to the +output. There are three exceptions to this rule: + +Line separators +~~~~~~~~~~~~~~~ + +Each newline is converted to the final output using os.linesep. + +Beginning or end of string +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +py.code_template is designed to allow easy use of templates inside of python +modules. The canonical way to write a template is inside a triple-quoted +string, e.g.:: + + my_template = ''' + This is my template. It can have any text at all in it except + another triple-single-quote. + ''' + +To support this usage, if the first character is a newline, it will be +removed, and if the last line consists solely of whitespace with no +trailing newline, it will also be removed. + +A comment or single keyword element on a line +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Whenever a `keyword element`_ or comment_ is on a line +*by itself*, that line will not be copied to the output. + +This happens when: + - There is nothing on the line before the keyword element + or comment except whitespace (spaces and/or tabs). + + - There is nothing on the line after the keyword element + or comment except a newline. + +Note that even a multi-line comment or keyword element can +have the preceding whitespace and subsequent newline stripped +by this rule. + +The primary purpose of this rule is to allow the Python +programmer to use indentation, **even inside a template**:: + + This is a template + + {if mylist} + List items: + {for item in mylist} + - {item} + {endfor} + {endif} + +Template usage +============== + +Templates are used by importing the Template class from py.code_template, +constructing a template, and then sending data with the write() method. + +In general, there are four methods for getting the formatted data back out +of the template object: + + - read() reads all the data currently in the object + + - output(fobj) outputs the data to a file + + fobj can either be an open file object, or a string. If it is + a string, the file will be opened, written, and closed. + + - open(fobj) (or calling the object constructor with a file object) + + If the open() method is used, or if a file object is passed to + the constructor, each write() will automatically flush the data + out to the file. If the fobj is a string, it is considered to + be *owned*, otherwise it is considered to be *borrowed*. *Owned* + file objects are closed when the class is deleted. + + - write() can be explicitly called with a file object, in which case + it will invoke output() on that object after it generates the data. + +Template instantiation and methods +================================== + +template = code_template.Template(outf=None, cache=None) + +If outf is given, it will be passed to the open() method + +cache may be given as a mapping. If not given, the template will use +the shared default cache. This is not thread safe. + +template.open +------------- + +template.open(outf, borrowed = None) + +The open method closes the internal file object if it was already open, +and then re-opens it on the given file. It is an error to call open() +if there is data in the object left over from previous writes. (Call +output() instead.) + +borrowed defaults to 0 if outf is a string, and 1 if it is a file object. + +borrowed can also be set explicitly if required. + +template.close +-------------- + +close() disassociates the file from the template, and closes the file if +it was not borrowed. close() is automatically called by the destructor. + +template.write +-------------- + +template.write(text='', outf=None, dedent=0, localvars=None, globalvars=None, +framelevel=1) + +The write method has the following parameters: + + - text is the template itself + + - if outf is not None, the output method will be invoked on the object + after the current template is processed. If no outf is given, data + will be accumulated internal to the instance until a write() with outf + is processed, or read() or output() is called, whichever comes first, if + there is no file object. If there is a file object, data will be flushed + to the file after every write. + + - dedent, if given is applied to each line in the template, to "de-indent" + + - localvars and globalvars default to the dictionaries of the caller. A copy + of localvars is made so that the __TrueSpace__ identifier can be added. + + - cache may be given as a mapping. If not given, the template will use + the shared default cache. This is not thread safe. + + - framelevel is used to determine which stackframe to access for globals + and locals if localvars and/or globalvars are not specified. The default + is to use the caller's frame. + +The write method supports the print >> file protocol by deleting the softspace +attribute on every invocation. This allows code like:: + + t = code_template.Template() + print >> t, "Hello, world" + + +template.read +-------------- + +This method reads and flushes all accumulated data in the object. Note that +if a file has been associated with the object, there will never be any data +to read. + +template.output +--------------- + +This method takes one parameter, outf. template.output() first +invokes template.read() to read and flush all accumulated data, +and then outputs the data to the file specified by outf. + +If outf has a write() method, that will be invoked with the +data. If outf has no write() method, it will be treated as +a filename, and that file will be replaced. + +Caching and thread safety +========================= + +The compiled version of every template is cached internal to the +code_template module (unless a separate cache object is specified). + +This allows efficient template reuse, but is not currently thread-safe. +Alternatively, each invocation of a template object can specify a +cache object. This is thread-safe, but not very efficient. A shared +model could be implemented later. + From briandorsey at codespeak.net Wed Mar 1 18:47:36 2006 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Wed, 1 Mar 2006 18:47:36 +0100 (CET) Subject: [py-svn] r23853 - py/dist/py/c-extension/greenlet Message-ID: <20060301174736.48B6F10081@code0.codespeak.net> Author: briandorsey Date: Wed Mar 1 18:47:35 2006 New Revision: 23853 Modified: py/dist/py/c-extension/greenlet/test_generator.py py/dist/py/c-extension/greenlet/test_generator_nested.py py/dist/py/c-extension/greenlet/test_greenlet.py py/dist/py/c-extension/greenlet/test_remote.py Log: Skip greenlet tests when the greenlets cannot be built. Modified: py/dist/py/c-extension/greenlet/test_generator.py ============================================================================== --- py/dist/py/c-extension/greenlet/test_generator.py (original) +++ py/dist/py/c-extension/greenlet/test_generator.py Wed Mar 1 18:47:35 2006 @@ -1,4 +1,9 @@ -from py.magic import greenlet +import py +try: + from py.magic import greenlet +except RuntimeError, e: + py.test.skip(str(e)) + class genlet(greenlet): Modified: py/dist/py/c-extension/greenlet/test_generator_nested.py ============================================================================== --- py/dist/py/c-extension/greenlet/test_generator_nested.py (original) +++ py/dist/py/c-extension/greenlet/test_generator_nested.py Wed Mar 1 18:47:35 2006 @@ -1,4 +1,8 @@ -from py.magic import greenlet +import py +try: + from py.magic import greenlet +except RuntimeError, e: + py.test.skip(str(e)) class genlet(greenlet): Modified: py/dist/py/c-extension/greenlet/test_greenlet.py ============================================================================== --- py/dist/py/c-extension/greenlet/test_greenlet.py (original) +++ py/dist/py/c-extension/greenlet/test_greenlet.py Wed Mar 1 18:47:35 2006 @@ -1,4 +1,9 @@ -from py.magic import greenlet +import py +try: + from py.magic import greenlet +except RuntimeError, e: + py.test.skip(str(e)) + import sys, thread, threading from py.test import raises Modified: py/dist/py/c-extension/greenlet/test_remote.py ============================================================================== --- py/dist/py/c-extension/greenlet/test_remote.py (original) +++ py/dist/py/c-extension/greenlet/test_remote.py Wed Mar 1 18:47:35 2006 @@ -1,5 +1,8 @@ import py -from py.magic import greenlet +try: + from py.magic import greenlet +except RuntimeError, e: + py.test.skip(str(e)) class RGreenletBunch: From briandorsey at codespeak.net Wed Mar 1 19:10:06 2006 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Wed, 1 Mar 2006 19:10:06 +0100 (CET) Subject: [py-svn] r23859 - py/dist/py/path/extpy Message-ID: <20060301181006.5FF8710071@code0.codespeak.net> Author: briandorsey Date: Wed Mar 1 19:09:55 2006 New Revision: 23859 Modified: py/dist/py/path/extpy/extpy.py Log: Added a mode parameter for consistency with the signature of other read() functions. It is ignored in this case. Modified: py/dist/py/path/extpy/extpy.py ============================================================================== --- py/dist/py/path/extpy/extpy.py (original) +++ py/dist/py/path/extpy/extpy.py Wed Mar 1 19:09:55 2006 @@ -194,8 +194,10 @@ if str(self.root) == x: return True - def read(self): - """ return a bytestring from looking at our underlying object. """ + def read(self, mode='ignored'): + """ return a bytestring from looking at our underlying object. + + mode parmeter exists for consistency, but is ignored.""" return str(self.resolve()) class Checkers(common.Checkers): From briandorsey at codespeak.net Wed Mar 1 19:14:51 2006 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Wed, 1 Mar 2006 19:14:51 +0100 (CET) Subject: [py-svn] r23860 - in py/dist/py/log: . testing Message-ID: <20060301181451.3597C1008B@code0.codespeak.net> Author: briandorsey Date: Wed Mar 1 19:14:50 2006 New Revision: 23860 Modified: py/dist/py/log/consumer.py py/dist/py/log/testing/test_log.py Log: Run logging file tests in unbuffered mode so they will pass on Windows. On Windows, reading from a buffered log file produced no output, appearenly it doesn't sync before reading. Modified: py/dist/py/log/consumer.py ============================================================================== --- py/dist/py/log/consumer.py (original) +++ py/dist/py/log/consumer.py Wed Mar 1 19:14:50 2006 @@ -11,15 +11,17 @@ print >>self._file, str(msg) class Path(object): - def __init__(self, filename, append=False, delayed_create=False): + def __init__(self, filename, append=False, delayed_create=False, + buffering=1): self._append = append self._filename = filename + self._buffering = buffering if not delayed_create: self._openfile() def _openfile(self): mode = self._append and 'a' or 'w' - f = open(str(self._filename), mode, buffering=1) + f = open(str(self._filename), mode, buffering=self._buffering) self._file = f def __call__(self, msg): Modified: py/dist/py/log/testing/test_log.py ============================================================================== --- py/dist/py/log/testing/test_log.py (original) +++ py/dist/py/log/testing/test_log.py Wed Mar 1 19:14:50 2006 @@ -112,11 +112,11 @@ def test_log_file(self): custom_log = tempdir.join('log.out') - py.log.setconsumer("default", open(str(custom_log), 'w', buffering=1)) + py.log.setconsumer("default", open(str(custom_log), 'w', buffering=0)) py.log.default("hello world #1") assert custom_log.readlines() == ['[default] hello world #1\n'] - py.log.setconsumer("default", py.log.Path(custom_log)) + py.log.setconsumer("default", py.log.Path(custom_log, buffering=0)) py.log.default("hello world #2") assert custom_log.readlines() == ['[default] hello world #2\n'] # no append by default! @@ -124,12 +124,14 @@ logfilefn = tempdir.join('log_append.out') # The append mode is on by default, so we don't need to specify it for File - py.log.setconsumer("default", py.log.Path(logfilefn, append=True)) + py.log.setconsumer("default", py.log.Path(logfilefn, append=True, + buffering=0)) assert logfilefn.check() py.log.default("hello world #1") lines = logfilefn.readlines() assert lines == ['[default] hello world #1\n'] - py.log.setconsumer("default", py.log.Path(logfilefn, append=True)) + py.log.setconsumer("default", py.log.Path(logfilefn, append=True, + buffering=0)) py.log.default("hello world #1") lines = logfilefn.readlines() assert lines == ['[default] hello world #1\n', @@ -138,7 +140,8 @@ def test_log_file_delayed_create(self): logfilefn = tempdir.join('log_create.out') - py.log.setconsumer("default", py.log.Path(logfilefn, delayed_create=True)) + py.log.setconsumer("default", py.log.Path(logfilefn, + delayed_create=True, buffering=0)) assert not logfilefn.check() py.log.default("hello world #1") lines = logfilefn.readlines() @@ -149,7 +152,7 @@ keywords = 'k1 k2 k3'.split() for key in keywords: path = tempdir.join(key) - py.log.setconsumer(key, py.log.Path(path)) + py.log.setconsumer(key, py.log.Path(path, buffering=0)) py.log.Producer('k1')('1') py.log.Producer('k2')('2') From briandorsey at codespeak.net Wed Mar 1 23:56:45 2006 From: briandorsey at codespeak.net (briandorsey at codespeak.net) Date: Wed, 1 Mar 2006 23:56:45 +0100 (CET) Subject: [py-svn] r23873 - in py/dist/py: misc/testing path/svn/testing rest/testing Message-ID: <20060301225645.1150E1009B@code0.codespeak.net> Author: briandorsey Date: Wed Mar 1 23:56:43 2006 New Revision: 23873 Modified: py/dist/py/misc/testing/test_svnlook.py py/dist/py/path/svn/testing/test_test_repo.py py/dist/py/path/svn/testing/test_urlcommand.py py/dist/py/path/svn/testing/test_wccommand.py py/dist/py/rest/testing/test_directive.py Log: Skip five test modules when svn isn't available. * Users without svn are rare now, but may become more common after release. * Skipping the tests for py.misc.svnlook, py.path.svn and one test of py.rest.directive. * The functionality will still fail on a users system if they try to use it, but the errors should make it clear that svn is missing, and they'll need to install it. Modified: py/dist/py/misc/testing/test_svnlook.py ============================================================================== --- py/dist/py/misc/testing/test_svnlook.py (original) +++ py/dist/py/misc/testing/test_svnlook.py Wed Mar 1 23:56:43 2006 @@ -3,6 +3,12 @@ from py.__.misc import svnlook data = py.magic.autopath().dirpath('data') +try: + py.path.local.sysfind('svnlook') + py.path.local.sysfind('svnadmin') +except py.error.ENOENT: + py.test.skip("cannot test py.misc.svnlook, svn binaries not found") + def test_svnlook(): tempdir = py.test.ensuretemp("svnlook") repo = tempdir.join("repo") Modified: py/dist/py/path/svn/testing/test_test_repo.py ============================================================================== --- py/dist/py/path/svn/testing/test_test_repo.py (original) +++ py/dist/py/path/svn/testing/test_test_repo.py Wed Mar 1 23:56:43 2006 @@ -1,6 +1,11 @@ import py from py.__.path.svn.testing.svntestbase import make_test_repo +try: + py.path.local.sysfind('svn') +except py.error.ENOENT: + py.test.skip("cannot test py.path.svn, 'svn' binary not found") + class TestMakeRepo(object): def setup_class(cls): cls.repo = make_test_repo() Modified: py/dist/py/path/svn/testing/test_urlcommand.py ============================================================================== --- py/dist/py/path/svn/testing/test_urlcommand.py (original) +++ py/dist/py/path/svn/testing/test_urlcommand.py Wed Mar 1 23:56:43 2006 @@ -1,6 +1,11 @@ import py from py.__.path.svn.testing.svntestbase import CommonCommandAndBindingTests, getrepowc +try: + svnversion = py.path.local.sysfind('svn') +except py.error.ENOENT: + py.test.skip("cannot test py.path.svn, 'svn' binary not found") + class TestSvnCommandPath(CommonCommandAndBindingTests): def setup_class(cls): repo, wc = getrepowc() Modified: py/dist/py/path/svn/testing/test_wccommand.py ============================================================================== --- py/dist/py/path/svn/testing/test_wccommand.py (original) +++ py/dist/py/path/svn/testing/test_wccommand.py Wed Mar 1 23:56:43 2006 @@ -1,6 +1,11 @@ import py from py.__.path.svn.testing.svntestbase import CommonSvnTests, getrepowc +try: + svnversion = py.path.local.sysfind('svn') +except py.error.ENOENT: + py.test.skip("cannot test py.path.svn, 'svn' binary not found") + class TestWCSvnCommandPath(CommonSvnTests): def setup_class(cls): Modified: py/dist/py/rest/testing/test_directive.py ============================================================================== --- py/dist/py/rest/testing/test_directive.py (original) +++ py/dist/py/rest/testing/test_directive.py Wed Mar 1 23:56:43 2006 @@ -11,6 +11,8 @@ py.test.skip("graphviz needed") directive.BackendStore("html") #for reasons that elude me rest.process expects svnwcs??? + if not is_on_path("svn"): + py.test.skip("svn needed") txt = py.path.svnwc(datadir.join("graphviz.txt")) html = txt.new(ext="html") png = datadir.join("example1.png") From mwh at codespeak.net Thu Mar 2 00:55:18 2006 From: mwh at codespeak.net (mwh at codespeak.net) Date: Thu, 2 Mar 2006 00:55:18 +0100 (CET) Subject: [py-svn] r23878 - py/dist/py/execnet Message-ID: <20060301235518.8DF121009B@code0.codespeak.net> Author: mwh Date: Thu Mar 2 00:55:16 2006 New Revision: 23878 Modified: py/dist/py/execnet/gateway.py Log: don't hard-code the "let me compile generators" flag in the call to compile() (fixes test failures on python 2.5 where this flag is 0 and 4096 causes an exception) Modified: py/dist/py/execnet/gateway.py ============================================================================== --- py/dist/py/execnet/gateway.py (original) +++ py/dist/py/execnet/gateway.py Thu Mar 2 00:55:16 2006 @@ -4,7 +4,7 @@ import traceback import atexit import weakref - +import __future__ # note that the whole code of this module (as well as some # other modules) execute not only on the local side but @@ -148,7 +148,8 @@ self.trace("execution starts:", repr(source)[:50]) close = self._local_redirect_thread_output(outid, errid) try: - co = compile(source+'\n', '', 'exec', 4096) + co = compile(source+'\n', '', 'exec', + __future__.CO_GENERATOR_ALLOWED) exec co in loc finally: close() From mwh at codespeak.net Thu Mar 2 00:56:39 2006 From: mwh at codespeak.net (mwh at codespeak.net) Date: Thu, 2 Mar 2006 00:56:39 +0100 (CET) Subject: [py-svn] r23879 - in py/dist/py/builtin: . testing Message-ID: <20060301235639.92C341008D@code0.codespeak.net> Author: mwh Date: Thu Mar 2 00:56:36 2006 New Revision: 23879 Modified: py/dist/py/builtin/reversed.py py/dist/py/builtin/testing/test_reversed.py Log: in python2.4 reversed() doesn't have a length, but rather defines __length_hint__(). make the py.test version look like this. XXX possibly a little controversial, otoh all py.lib tests now pass on python 2.5. Modified: py/dist/py/builtin/reversed.py ============================================================================== --- py/dist/py/builtin/reversed.py (original) +++ py/dist/py/builtin/reversed.py Thu Mar 2 00:56:36 2006 @@ -33,5 +33,5 @@ return item raise StopIteration - def __len__(self): + def __length_hint__(self): return self.remaining Modified: py/dist/py/builtin/testing/test_reversed.py ============================================================================== --- py/dist/py/builtin/testing/test_reversed.py (original) +++ py/dist/py/builtin/testing/test_reversed.py Thu Mar 2 00:56:36 2006 @@ -4,15 +4,11 @@ def test_reversed(): r = reversed("hello") assert iter(r) is r - assert len(r) == 5 assert r.next() == "o" assert r.next() == "l" assert r.next() == "l" assert r.next() == "e" - assert len(r) == 1 assert r.next() == "h" - assert len(r) == 0 raises(StopIteration, r.next) - assert len(r) == 0 assert list(reversed(list(reversed("hello")))) == ['h','e','l','l','o'] raises(TypeError, reversed, reversed("hello")) From hpk at codespeak.net Thu Mar 2 22:20:19 2006 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 2 Mar 2006 22:20:19 +0100 (CET) Subject: [py-svn] r23934 - py/dist/py/test/terminal Message-ID: <20060302212019.CB80F100A8@code0.codespeak.net> Author: hpk Date: Thu Mar 2 22:20:17 2006 New Revision: 23934 Modified: py/dist/py/test/terminal/out.py Log: more cleanly handle exceptions Modified: py/dist/py/test/terminal/out.py ============================================================================== --- py/dist/py/test/terminal/out.py (original) +++ py/dist/py/test/terminal/out.py Thu Mar 2 22:20:17 2006 @@ -43,6 +43,8 @@ call = fcntl.ioctl(0,termios.TIOCGWINSZ,"\000"*8) height,width = struct.unpack( "hhhh", call ) [:2] self.fullwidth = width + except (SystemExit, KeyboardInterrupt), e: + raise except: pass From anthon at codespeak.net Sun Mar 5 11:26:41 2006 From: anthon at codespeak.net (anthon at codespeak.net) Date: Sun, 5 Mar 2006 11:26:41 +0100 (CET) Subject: [py-svn] r23979 - py/dist/py/documentation Message-ID: <20060305102641.323FD100AB@code0.codespeak.net> Author: anthon Date: Sun Mar 5 11:26:08 2006 New Revision: 23979 Modified: py/dist/py/documentation/xml.txt Log: minor typo Modified: py/dist/py/documentation/xml.txt ============================================================================== --- py/dist/py/documentation/xml.txt (original) +++ py/dist/py/documentation/xml.txt Sun Mar 5 11:26:08 2006 @@ -63,7 +63,7 @@ keyword-arguments are attributes. On a side note, you'll see that the unicode-serializer -supports a nice indentation style which keep your generated +supports a nice indentation style which keeps your generated html readable, basically through emulating python's white space significance by putting closing-tags rightmost and almost invisible at first glance :-) From nik at codespeak.net Mon Mar 6 15:50:15 2006 From: nik at codespeak.net (nik at codespeak.net) Date: Mon, 6 Mar 2006 15:50:15 +0100 (CET) Subject: [py-svn] r24020 - py/dist/py/magic Message-ID: <20060306145015.9A99B100C1@code0.codespeak.net> Author: nik Date: Mon Mar 6 15:50:14 2006 New Revision: 24020 Modified: py/dist/py/magic/exprinfo.py Log: fix typo Modified: py/dist/py/magic/exprinfo.py ============================================================================== --- py/dist/py/magic/exprinfo.py (original) +++ py/dist/py/magic/exprinfo.py Mon Mar 6 15:50:14 2006 @@ -426,7 +426,7 @@ import traceback traceback.print_exc() if should_fail: - return "(inconsistenty failed then succeeded)" + return "(inconsistently failed then succeeded)" else: return None From anthon at codespeak.net Tue Mar 7 13:39:56 2006 From: anthon at codespeak.net (anthon at codespeak.net) Date: Tue, 7 Mar 2006 13:39:56 +0100 (CET) Subject: [py-svn] r24060 - py/dist/py/documentation Message-ID: <20060307123956.A805910095@code0.codespeak.net> Author: anthon Date: Tue Mar 7 13:39:55 2006 New Revision: 24060 Modified: py/dist/py/documentation/xml.txt Log: to test doc.py Modified: py/dist/py/documentation/xml.txt ============================================================================== --- py/dist/py/documentation/xml.txt (original) +++ py/dist/py/documentation/xml.txt Tue Mar 7 13:39:55 2006 @@ -145,7 +145,7 @@ implementation detail of the unicode serialization code. You can easily modify the serialization to put your styling into the ``html.head`` or in a separate file and autogenerate CSS-class -names or ids. +names or ids. Hey, you could even write tests that you are using correct styles suitable for specific browser requirements. Did i mention From blais at codespeak.net Fri Mar 10 16:31:51 2006 From: blais at codespeak.net (blais at codespeak.net) Date: Fri, 10 Mar 2006 16:31:51 +0100 (CET) Subject: [py-svn] r24231 - py/dist/py/documentation Message-ID: <20060310153151.9A180100D9@code0.codespeak.net> Author: blais Date: Fri Mar 10 16:31:49 2006 New Revision: 24231 Modified: py/dist/py/documentation/execnet.txt Log: Fixed execnet example code in documentation. Modified: py/dist/py/documentation/execnet.txt ============================================================================== --- py/dist/py/documentation/execnet.txt (original) +++ py/dist/py/documentation/execnet.txt Fri Mar 10 16:31:49 2006 @@ -137,12 +137,12 @@ contentserverbootstrap = py.code.Source( """ for fn in channel.receive(): - f = open(f, 'rb') + f = open(fn, 'rb') try: channel.send(f.read()) finally: f.close() - """ + """) # open a gateway to a fresh child process contentgateway = py.execnet.SshGateway('codespeak.net') channel = contentgateway.remote_exec(contentserverbootstrap) From hpk at codespeak.net Sun Mar 12 02:10:48 2006 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 12 Mar 2006 02:10:48 +0100 (CET) Subject: [py-svn] r24243 - py/dist/py/execnet/script Message-ID: <20060312011048.6511B100AB@code0.codespeak.net> Author: hpk Date: Sun Mar 12 02:10:24 2006 New Revision: 24243 Modified: py/dist/py/execnet/script/socketserver.py Log: hopefully fixes a windows issue with sending source text over a socketserver connection Modified: py/dist/py/execnet/script/socketserver.py ============================================================================== --- py/dist/py/execnet/script/socketserver.py (original) +++ py/dist/py/execnet/script/socketserver.py Sun Mar 12 02:10:24 2006 @@ -26,7 +26,7 @@ print progname, 'Entering Accept loop', serversock.getsockname() clientsock,address = serversock.accept() print progname, 'got new connection from %s %s' % address - clientfile = clientsock.makefile('r+b',0) + clientfile = clientsock.makefile('r+',0) print "reading line" # rstrip so that we can use \r\n for telnet testing source = clientfile.readline().rstrip() From jan at codespeak.net Sun Mar 12 02:53:31 2006 From: jan at codespeak.net (jan at codespeak.net) Date: Sun, 12 Mar 2006 02:53:31 +0100 (CET) Subject: [py-svn] r24244 - in py/dist/py/execnet: . testing Message-ID: <20060312015331.D413A100AB@code0.codespeak.net> Author: jan Date: Sun Mar 12 02:53:11 2006 New Revision: 24244 Modified: py/dist/py/execnet/inputoutput.py py/dist/py/execnet/register.py py/dist/py/execnet/testing/test_gateway.py Log: fix 'lineending' bug in nested PopenGateway SoketServer setup enable SocketServer tests on windows Modified: py/dist/py/execnet/inputoutput.py ============================================================================== --- py/dist/py/execnet/inputoutput.py (original) +++ py/dist/py/execnet/inputoutput.py Sun Mar 12 02:53:11 2006 @@ -23,7 +23,7 @@ sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1) sock.setsockopt(socket.SOL_IP, socket.IP_TOS, 0x10) # IPTOS_LOWDELAY except socket.error, e: - print "Cannot set socket option:", str(e) + print "WARNING: Cannot set socket option:", str(e) self.readable = self.writeable = True def read(self, numbytes): Modified: py/dist/py/execnet/register.py ============================================================================== --- py/dist/py/execnet/register.py (original) +++ py/dist/py/execnet/register.py Sun Mar 12 02:53:11 2006 @@ -119,7 +119,7 @@ else: host, port = hostport socketserverbootstrap = py.code.Source( - mypath.dirpath('script', 'socketserver.py').read(), + mypath.dirpath('script', 'socketserver.py').read('rU'), """ import socket sock = bind_and_listen((%r, %r)) Modified: py/dist/py/execnet/testing/test_gateway.py ============================================================================== --- py/dist/py/execnet/testing/test_gateway.py (original) +++ py/dist/py/execnet/testing/test_gateway.py Sun Mar 12 02:53:11 2006 @@ -356,7 +356,6 @@ ## cls.proxygw.exit() class TestSocketGateway(SocketGatewaySetup, BasicRemoteExecution): - disabled = sys.platform == "win32" pass class TestSshGateway(BasicRemoteExecution): From arigo at codespeak.net Sun Mar 12 11:26:55 2006 From: arigo at codespeak.net (arigo at codespeak.net) Date: Sun, 12 Mar 2006 11:26:55 +0100 (CET) Subject: [py-svn] r24255 - in py/dist/py: c-extension/greenlet documentation Message-ID: <20060312102655.DD5C01009A@code0.codespeak.net> Author: arigo Date: Sun Mar 12 11:26:52 2006 New Revision: 24255 Added: py/dist/py/c-extension/greenlet/test_throw.py (contents, props changed) Modified: py/dist/py/c-extension/greenlet/greenlet.c py/dist/py/c-extension/greenlet/test_greenlet.py py/dist/py/documentation/greenlet.txt Log: A patch mostly from Giovanni Bajo: add a throw() method to greenlet objects, similar to the throw() method of Python 2.5's generators. This is necessary because there is otherwise no way to send a GreenletExit exception into a greenlet (all other exceptions can be sent using the trick seen in test_greenlet.send_exception()). Modified: py/dist/py/c-extension/greenlet/greenlet.c ============================================================================== --- py/dist/py/c-extension/greenlet/greenlet.c (original) +++ py/dist/py/c-extension/greenlet/greenlet.c Sun Mar 12 11:26:52 2006 @@ -549,12 +549,102 @@ return results; } +static PyObject * +throw_greenlet(PyGreenlet* self, PyObject* typ, PyObject* val, PyObject* tb) +{ + /* Note: _consumes_ a reference to typ, val, tb */ + PyErr_Restore(typ, val, tb); + return single_result(g_switch(self, NULL)); +} + +PyDoc_STRVAR(switch_doc, +"switch([val]) -> switch execution to greenlet optionally passing a value, " +"return value passed when switching back"); + static PyObject* green_switch(PyGreenlet* self, PyObject* args) { Py_INCREF(args); return single_result(g_switch(self, args)); } +#ifndef PyExceptionClass_Check /* Python < 2.5 */ +# define PyExceptionClass_Check PyClass_Check +#endif +#ifndef PyExceptionInstance_Check /* Python < 2.5 */ +# define PyExceptionInstance_Check PyInstance_Check +#endif +#ifndef PyExceptionInstance_Class /* Python < 2.5 */ +# define PyExceptionInstance_Class(x) \ + ((PyObject*)((PyInstanceObject*)(x))->in_class) +#endif + +PyDoc_STRVAR(throw_doc, +"throw(typ[,val[,tb]]) -> raise exception in greenlet, return value passed " +"when switching back"); + +static PyObject* green_throw(PyGreenlet* self, PyObject* args) +{ + PyObject *typ = PyExc_GreenletExit; + PyObject *val = NULL; + PyObject *tb = NULL; + PyObject *ret = NULL; + + if (!PyArg_ParseTuple(args, "|OOO:throw", &typ, &val, &tb)) + return NULL; + + /* First, check the traceback argument, replacing None with + NULL. */ + if (tb == Py_None) { + Py_DECREF(tb); + tb = NULL; + } + else if (tb != NULL && !PyTraceBack_Check(tb)) { + PyErr_SetString(PyExc_TypeError, + "throw() third argument must be a traceback object"); + return NULL; + } + + Py_INCREF(typ); + Py_XINCREF(val); + Py_XINCREF(tb); + + if (PyExceptionClass_Check(typ)) { + PyErr_NormalizeException(&typ, &val, &tb); + } + + else if (PyExceptionInstance_Check(typ)) { + /* Raising an instance. The value should be a dummy. */ + if (val && val != Py_None) { + PyErr_SetString(PyExc_TypeError, + "instance exception may not have a separate value"); + goto failed_throw; + } + else { + /* Normalize to raise , */ + Py_XDECREF(val); + val = typ; + typ = PyExceptionInstance_Class(typ); + Py_INCREF(typ); + } + } + else { + /* Not something you can raise. throw() fails. */ + PyErr_Format(PyExc_TypeError, + "exceptions must be classes, or instances, not %s", + typ->ob_type->tp_name); + goto failed_throw; + } + + return throw_greenlet(self, typ, val, tb); + +failed_throw: + /* Didn't use our arguments, so restore their original refcounts */ + Py_DECREF(typ); + Py_XDECREF(val); + Py_XDECREF(tb); + return NULL; +} + static int green_nonzero(PyGreenlet* self) { return PyGreen_ACTIVE(self); @@ -686,8 +776,9 @@ static PyMethodDef green_methods[] = { - {"switch", (PyCFunction)green_switch, METH_VARARGS, /*XXXswitch_doc*/ NULL}, - {NULL, NULL} /* sentinel */ + {"switch", (PyCFunction)green_switch, METH_VARARGS, switch_doc}, + {"throw", (PyCFunction)green_throw, METH_VARARGS, throw_doc}, + {NULL, NULL} /* sentinel */ }; static PyGetSetDef green_getsets[] = { Modified: py/dist/py/c-extension/greenlet/test_greenlet.py ============================================================================== --- py/dist/py/c-extension/greenlet/test_greenlet.py (original) +++ py/dist/py/c-extension/greenlet/test_greenlet.py Sun Mar 12 11:26:52 2006 @@ -59,6 +59,8 @@ assert seen == [SomeError] def send_exception(g, exc): + # note: send_exception(g, exc) can be now done with g.throw(exc). + # the purpose of this test is to explicitely check the propagation rules. def crasher(exc): raise exc g1 = greenlet(crasher, parent=g) Added: py/dist/py/c-extension/greenlet/test_throw.py ============================================================================== --- (empty file) +++ py/dist/py/c-extension/greenlet/test_throw.py Sun Mar 12 11:26:52 2006 @@ -0,0 +1,92 @@ +import py +from py.magic import greenlet + +def switch(*args): + return greenlet.getcurrent().parent.switch(*args) + +def test_class(): + def f(): + try: + switch("ok") + except RuntimeError: + switch("ok") + return + switch("fail") + + g = greenlet(f) + res = g.switch() + assert res == "ok" + res = g.throw(RuntimeError) + assert res == "ok" + +def test_val(): + def f(): + try: + switch("ok") + except RuntimeError, val: + if str(val) == "ciao": + switch("ok") + return + switch("fail") + + g = greenlet(f) + res = g.switch() + assert res == "ok" + res = g.throw(RuntimeError("ciao")) + assert res == "ok" + + g = greenlet(f) + res = g.switch() + assert res == "ok" + res = g.throw(RuntimeError, "ciao") + assert res == "ok" + +def test_kill(): + def f(): + switch("ok") + switch("fail") + + g = greenlet(f) + res = g.switch() + assert res == "ok" + res = g.throw() + assert isinstance(res, greenlet.GreenletExit) + assert g.dead + +def test_throw_goes_to_original_parent(): + main = greenlet.getcurrent() + def f1(): + try: + main.switch("f1 ready to catch") + except IndexError: + return "caught" + else: + return "normal exit" + def f2(): + main.switch("from f2") + + g1 = greenlet(f1) + g2 = greenlet(f2, parent=g1) + py.test.raises(IndexError, g2.throw, IndexError) + assert g2.dead + assert g1.dead + + g1 = greenlet(f1) + g2 = greenlet(f2, parent=g1) + res = g1.switch() + assert res == "f1 ready to catch" + res = g2.throw(IndexError) + assert res == "caught" + assert g2.dead + assert g1.dead + + g1 = greenlet(f1) + g2 = greenlet(f2, parent=g1) + res = g1.switch() + assert res == "f1 ready to catch" + res = g2.switch() + assert res == "from f2" + res = g2.throw(IndexError) + assert res == "caught" + assert g2.dead + assert g1.dead Modified: py/dist/py/documentation/greenlet.txt ============================================================================== --- py/dist/py/documentation/greenlet.txt (original) +++ py/dist/py/documentation/greenlet.txt Sun Mar 12 11:26:52 2006 @@ -266,6 +266,22 @@ ``bool(g)`` True if ``g`` is active, False if it is dead or not yet started. +``g.throw([typ, [val, [tb]]])`` + Switches execution to the greenlet ``g``, but immediately raises the + given exception in ``g``. If no argument is provided, the exception + defaults to ``greenlet.GreenletExit``. The normal exception + propagation rules apply, as described above. Note that calling this + method is almost equivalent to the following:: + + def raiser(): + raise typ, val, tb + g_raiser = greenlet(raiser, parent=g) + g_raiser.switch() + + except that this trick does not work for the + ``greenlet.GreenletExit`` exception, which would not propagate + from ``g_raiser`` to ``g``. + Greenlets and Python threads ---------------------------- From hpk at codespeak.net Sun Mar 12 19:03:53 2006 From: hpk at codespeak.net (hpk at codespeak.net) Date: Sun, 12 Mar 2006 19:03:53 +0100 (CET) Subject: [py-svn] r24266 - py/dist/py/execnet/script Message-ID: <20060312180353.89AD4100B9@code0.codespeak.net> Author: hpk Date: Sun Mar 12 19:03:50 2006 New Revision: 24266 Modified: py/dist/py/execnet/script/shell.py Log: fix shell gateway script to allow cross-OS interaction (line endings) Modified: py/dist/py/execnet/script/shell.py ============================================================================== --- py/dist/py/execnet/script/shell.py (original) +++ py/dist/py/execnet/script/shell.py Sun Mar 12 19:03:50 2006 @@ -13,7 +13,7 @@ import sys host, port = sys.argv[1].split(':') port = int(port) - myself = open(os.path.abspath(sys.argv[0])).read() + myself = open(os.path.abspath(sys.argv[0]), 'rU').read() sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((host, port)) sock.sendall(repr(myself)+'\n') From hpk at codespeak.net Mon Mar 13 02:41:25 2006 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 13 Mar 2006 02:41:25 +0100 (CET) Subject: [py-svn] r24273 - py/dist/py/documentation/talk Message-ID: <20060313014125.D464C100BC@code0.codespeak.net> Author: hpk Date: Mon Mar 13 02:41:19 2006 New Revision: 24273 Added: py/dist/py/documentation/talk/ py/dist/py/documentation/talk/_ref.txt (contents, props changed) py/dist/py/documentation/talk/execnet-overview.txt (contents, props changed) py/dist/py/documentation/talk/make.py (contents, props changed) py/dist/py/documentation/talk/makeref.py (contents, props changed) py/dist/py/documentation/talk/pytest-overview.txt (contents, props changed) Log: some initial talk material on pytest and py execnet (work in progress) a script to create references to files (in _ref.txt which can then be included) Added: py/dist/py/documentation/talk/_ref.txt ============================================================================== --- (empty file) +++ py/dist/py/documentation/talk/_ref.txt Mon Mar 13 02:41:19 2006 @@ -0,0 +1 @@ +.. _`py/documentation/conftest.py`: ../../../py/documentation/conftest.py \ No newline at end of file Added: py/dist/py/documentation/talk/execnet-overview.txt ============================================================================== --- (empty file) +++ py/dist/py/documentation/talk/execnet-overview.txt Mon Mar 13 02:41:19 2006 @@ -0,0 +1,64 @@ +.. include:: + +================================================= +py.execnet - simple ad-hoc networking +================================================= + +:Authors: Holger Krekel, merlinux GmbH +:Date: 13th March 2006 + +remote method invocation is cumbersome +======================================== + +- CORBA/RMI/SOAP model is cumbersome +- "infection" with object references throughout your program +- need to define interfaces, generate stubs/skeletons +- need to start server processes ahead of time +- complicates programming + +what you want of ad-hoc networks +==================================== + +- ad hoc **local protocols** +- avoid defining and maintaining global interfaces +- deploy protocols purely from the client side +- zero installation required on server side + +py.execnet model of ad-hoc networks +==================================== + +- *Gateways* can be setup via e.g. SSH logins or via Popen +- *Gateway.remote_exec* allows execution of arbitrary code +- means of communication between the two sides: *Channels* + (with send & receive methods) +- example requirements: ssh login + python installed + +py.execnet.SshGateway example +==================================== + +interactive:: + + gw = py.execnet.SshGateway('codespeak.net') + + channel = gw.remote_exec(""" + for filename in channel: + try: + content = open(filename).read() + except (OSError, IOError): + content = None + channel.send(content) + """) + +next steps / references +==================================== + +- ad-hoc p2p networks +- chaining channels / passing channels around +- ensure it also works nicely on win32 +- btw, py.execnet is part of the py lib + + http://codespeak.net/py/ + +.. |bullet| unicode:: U+02022 +.. footer:: Holger Krekel (merlinux) |bullet| 13th March 2006 + Added: py/dist/py/documentation/talk/make.py ============================================================================== --- (empty file) +++ py/dist/py/documentation/talk/make.py Mon Mar 13 02:41:19 2006 @@ -0,0 +1,11 @@ +#!/usr/bin/python + +import py + +for x in py.path.local(): + if x.ext == '.txt': + cmd = ("python /home/hpk/projects/docutils/tools/rst2s5.py " + "%s %s" %(x, x.new(ext='.html'))) + print "execing", cmd + py.std.os.system(cmd) + Added: py/dist/py/documentation/talk/makeref.py ============================================================================== --- (empty file) +++ py/dist/py/documentation/talk/makeref.py Mon Mar 13 02:41:19 2006 @@ -0,0 +1,54 @@ + +import py +py.magic.autopath() +import py +pydir = py.path.local(py.__file__).dirpath() +distdir = pydir.dirpath() +dist_url = 'http://codespeak.net/svn/py/dist/' +#issue_url = 'http://codespeak.net/issue/py-dev/' + +docdir = pydir.join('documentation') +reffile = docdir / 'talk' / '_ref.txt' + +linkrex = py.std.re.compile('`(\S+)`_') + +name2target = {} +def addlink(linkname, linktarget): + assert linkname and linkname != '/' + if linktarget in name2target: + if linkname in name2target[linktarget]: + return + name2target.setdefault(linktarget, []).append(linkname) + +for textfile in docdir.visit(lambda x: x.ext == '.txt', + lambda x: x.check(dotfile=0)): + for linkname in linkrex.findall(textfile.read()): + if '/' in linkname: + for startloc in ('', 'py'): + cand = distdir.join(startloc, linkname) + if cand.check(): + rel = cand.relto(distdir) + # we are in py/doc/x.txt + count = rel.count("/") + 1 + target = '../' * count + rel + addlink(linkname, target) + break + else: + print "WARNING %s: link %r may be bogus" %(textfile, linkname) + elif linkname.startswith('issue'): + addlink(linkname, issue_url+linkname) + +items = name2target.items() +items.sort() + +lines = [] +for linktarget, linknamelist in items: + linknamelist.sort() + for linkname in linknamelist[:-1]: + lines.append(".. _`%s`:" % linkname) + lines.append(".. _`%s`: %s" %(linknamelist[-1], linktarget)) + +reffile.write("\n".join(lines)) +print "wrote %d references to %r" %(len(lines), reffile) +#print "last ten lines" +#for x in lines[-10:]: print x Added: py/dist/py/documentation/talk/pytest-overview.txt ============================================================================== --- (empty file) +++ py/dist/py/documentation/talk/pytest-overview.txt Mon Mar 13 02:41:19 2006 @@ -0,0 +1,64 @@ +.. include:: _ref.txt +.. include:: + +================================================= +py.test - flexible and powerful automated testing +================================================= + +:Authors: Holger Krekel, merlinux GmbH +:Date: 13th March 2006 + +Current Purposes +=============================== + +- automated cross-project testing tool +- flexible per-project customization +- reusing extensions and test methods across projects +- various iterative test collection methods +- (support for) distributed testing + +Main Features +=============================== + +- simplest possible "assert" approach +- clean setup/teardown semantics +- stdout/stderr capturing per test +- adding command line options +- test selection support +- automated cusotmizable collection of tests +- `more features`_ + +.. _`more features`: ../test.html#features + +Implementation +=============================== + +- `basic picture`_ +- Session objects (Terminal and Tcl-GUI) +- reporting hooks are on session objects +- Collector hierarchy yield iteratively tests +- uses py lib extensively (py.path/py.execnet) +- "conftest.py" per-directory configuration mechanism + +.. _`basic picture`: ../test.html#basicpicture + +Extensions: ReST checking +========================================= + +- `py/documentation/conftest.py`_ provides test + items for checking documentation and link integrity + +- uses its own collector/testitem hierarchy + +- invokes docutils processing, reports errors + +Extensions: Distributed Testing +============================================== + +- using py.execnet to dispatch on different python versions +- using py.execnet to dispatch tests on other hosts/platforms +- currently: Popen, SSH and Socket gateways +- missing: full support for deployment on multiple machines + +.. |bullet| unicode:: U+02022 +.. footer:: Holger Krekel (merlinux) |bullet| |bullet| 13th March 2006 From hpk at codespeak.net Mon Mar 13 02:43:00 2006 From: hpk at codespeak.net (hpk at codespeak.net) Date: Mon, 13 Mar 2006 02:43:00 +0100 (CET) Subject: [py-svn] r24274 - in py/dist/py/documentation/talk/ui: . default Message-ID: <20060313014300.80A6F100BC@code0.codespeak.net> Author: hpk Date: Mon Mar 13 02:42:21 2006 New Revision: 24274 Added: py/dist/py/documentation/talk/ui/ py/dist/py/documentation/talk/ui/default/ py/dist/py/documentation/talk/ui/default/blank.gif (contents, props changed) py/dist/py/documentation/talk/ui/default/framing.css py/dist/py/documentation/talk/ui/default/iepngfix.htc py/dist/py/documentation/talk/ui/default/opera.css py/dist/py/documentation/talk/ui/default/outline.css py/dist/py/documentation/talk/ui/default/pretty.css py/dist/py/documentation/talk/ui/default/print.css py/dist/py/documentation/talk/ui/default/py-web.png (contents, props changed) py/dist/py/documentation/talk/ui/default/s5-core.css py/dist/py/documentation/talk/ui/default/slides.css py/dist/py/documentation/talk/ui/default/slides.js py/dist/py/documentation/talk/ui/merlinux-klein.png (contents, props changed) py/dist/py/documentation/talk/ui/py-web.png (contents, props changed) py/dist/py/documentation/talk/ui/py.css Log: adding the styling Added: py/dist/py/documentation/talk/ui/default/blank.gif ============================================================================== Binary file. No diff available. Added: py/dist/py/documentation/talk/ui/default/framing.css ============================================================================== --- (empty file) +++ py/dist/py/documentation/talk/ui/default/framing.css Mon Mar 13 02:42:21 2006 @@ -0,0 +1,25 @@ +/* This file has been placed in the public domain. */ +/* The following styles size, place, and layer the slide components. + Edit these if you want to change the overall slide layout. + The commented lines can be uncommented (and modified, if necessary) + to help you with the rearrangement process. */ + +/* target = 1024x768 */ + +div#header, div#footer, .slide {width: 100%; top: 0; left: 0;} +div#header {position: fixed; top: 0; height: 3em; z-index: 1;} +div#footer {top: auto; bottom: 0; height: 2.5em; z-index: 5;} +.slide {top: 0; width: 92%; padding: 2.5em 4% 4%; z-index: 2;} +div#controls {left: 50%; bottom: 0; width: 50%; z-index: 100;} +div#controls form {position: absolute; bottom: 0; right: 0; width: 100%; + margin: 0;} +#currentSlide {position: absolute; width: 10%; left: 45%; bottom: 1em; + z-index: 10;} +html>body #currentSlide {position: fixed;} + +/* +div#header {background: #FCC;} +div#footer {background: #CCF;} +div#controls {background: #BBD;} +div#currentSlide {background: #FFC;} +*/ Added: py/dist/py/documentation/talk/ui/default/iepngfix.htc ============================================================================== --- (empty file) +++ py/dist/py/documentation/talk/ui/default/iepngfix.htc Mon Mar 13 02:42:21 2006 @@ -0,0 +1,42 @@ + + + + + \ No newline at end of file Added: py/dist/py/documentation/talk/ui/default/opera.css ============================================================================== --- (empty file) +++ py/dist/py/documentation/talk/ui/default/opera.css Mon Mar 13 02:42:21 2006 @@ -0,0 +1,8 @@ +/* This file has been placed in the public domain. */ +/* DO NOT CHANGE THESE unless you really want to break Opera Show */ +.slide { + visibility: visible !important; + position: static !important; + page-break-before: always; +} +#slide0 {page-break-before: avoid;} Added: py/dist/py/documentation/talk/ui/default/outline.css ============================================================================== --- (empty file) +++ py/dist/py/documentation/talk/ui/default/outline.css Mon Mar 13 02:42:21 2006 @@ -0,0 +1,16 @@ +/* This file has been placed in the public domain. */ +/* Don't change this unless you want the layout stuff to show up in the + outline view! */ + +.layout div, #footer *, #controlForm * {display: none;} +#footer, #controls, #controlForm, #navLinks, #toggle { + display: block; visibility: visible; margin: 0; padding: 0;} +#toggle {float: right; padding: 0.5em;} +html>body #toggle {position: fixed; top: 0; right: 0;} + +/* making the outline look pretty-ish */ + +#slide0 h1, #slide0 h2, #slide0 h3, #slide0 h4 {border: none; margin: 0;} +#toggle {border: 1px solid; border-width: 0 0 1px 1px; background: #FFF;} + +.outline {display: inline ! important;} Added: py/dist/py/documentation/talk/ui/default/pretty.css ============================================================================== --- (empty file) +++ py/dist/py/documentation/talk/ui/default/pretty.css Mon Mar 13 02:42:21 2006 @@ -0,0 +1,121 @@ +/* This file has been placed in the public domain. */ +/* Following are the presentation styles -- edit away! */ + +html, body {margin: 0; padding: 0;} +body {background: #fff color: #222; font-size: 2em;} +/* Replace the background style above with the style below (and again for + div#header) for a graphic: */ +/* background: white url(bodybg.gif) -16px 0 no-repeat; */ +:link, :visited {text-decoration: none; color: #00C;} +#controls :active {color: #88A !important;} +#controls :focus {outline: 1px dotted #227;} +h1, h2, h3, h4 {font-size: 100%; margin: 0; padding: 0; font-weight: inherit;} + +blockquote {padding: 0 2em 0.5em; margin: 0 1.5em 0.5em;} +blockquote p {margin: 0;} + +kbd {font-weight: bold; font-size: 1em;} +sup {font-size: smaller; line-height: 1px;} + +.slide pre {padding: 0; margin-left: 0; margin-right: 0; font-size: 90%;} +.slide ul ul li {list-style: square; } +.slide img.leader {display: block; margin: 0 auto;} +.slide tt {font-size: 90%;} + +div#header, div#footer {background: #005; color: #AAB; font-family: sans-serif;} +/* background: #005 url(bodybg.gif) -16px 0 no-repeat; */ +div#footer {font-size: 0.5em; font-weight: bold; padding: 1em 0;} +#footer h1 {display: block; padding: 0 1em;} +#footer h2 {display: block; padding: 0.8em 1em 0;} + +.slide {font-size: 1.5em;} +.slide li {font-size: 1.0em; padding-bottom: 0.2em;} +.slide h1 {position: absolute; top: 0.45em; z-index: 1; + margin: 0; padding-left: 0.7em; white-space: nowrap; + font: bold 110% sans-serif; color: #DDE; background: #005;} +.slide h2 {font: bold 120%/1em sans-serif; padding-top: 0.5em;} +.slide h3 {font: bold 100% sans-serif; padding-top: 0.5em;} +h1 abbr {font-variant: small-caps;} + +div#controls {position: absolute; left: 50%; bottom: 0; + width: 50%; text-align: right; font: bold 0.9em sans-serif;} +html>body div#controls {position: fixed; padding: 0 0 1em 0; top: auto;} +div#controls form {position: absolute; bottom: 0; right: 0; width: 100%; + margin: 0; padding: 0;} +#controls #navLinks a {padding: 0; margin: 0 0.5em; + background: #005; border: none; color: #779; cursor: pointer;} +#controls #navList {height: 1em;} +#controls #navList #jumplist {position: absolute; bottom: 0; right: 0; + background: #DDD; color: #227;} + +#currentSlide {text-align: center; font-size: 0.5em; color: #449; + font-family: sans-serif; font-weight: bold;} + +#slide0 {padding-top: 1.5em} +#slide0 h1 {position: static; margin: 1em 0 0; padding: 0; color: #000; + font: bold 2em sans-serif; white-space: normal; background: transparent;} +#slide0 h2 {font: bold italic 1em sans-serif; margin: 0.25em;} +#slide0 h3 {margin-top: 1.5em; font-size: 1.5em;} +#slide0 h4 {margin-top: 0; font-size: 1em;} + +ul.urls {list-style: none; display: inline; margin: 0;} +.urls li {display: inline; margin: 0;} +.external {border-bottom: 1px dotted gray;} +html>body .external {border-bottom: none;} +.external:after {content: " \274F"; font-size: smaller; color: #77B;} + +.incremental, .incremental *, .incremental *:after {visibility: visible; + color: white; border: 0;} +img.incremental {visibility: hidden;} +.slide .current {color: green;} + +.slide-display {display: inline ! important;} + +.huge {font-family: sans-serif; font-weight: bold; font-size: 150%;} +.big {font-family: sans-serif; font-weight: bold; font-size: 120%;} +.small {font-size: 75%;} +.tiny {font-size: 50%;} +.huge tt, .big tt, .small tt, .tiny tt {font-size: 115%;} +.huge pre, .big pre, .small pre, .tiny pre {font-size: 115%;} + +.maroon {color: maroon;} +.red {color: red;} +.magenta {color: magenta;} +.fuchsia {color: fuchsia;} +.pink {color: #FAA;} +.orange {color: orange;} +.yellow {color: yellow;} +.lime {color: lime;} +.green {color: green;} +.olive {color: olive;} +.teal {color: teal;} +.cyan {color: cyan;} +.aqua {color: aqua;} +.blue {color: blue;} +.navy {color: navy;} +.purple {color: purple;} +.black {color: black;} +.gray {color: gray;} +.silver {color: silver;} +.white {color: white;} + +.left {text-align: left ! important;} +.center {text-align: center ! important;} +.right {text-align: right ! important;} + +.animation {position: relative; margin: 1em 0; padding: 0;} +.animation img {position: absolute;} + +/* Docutils-specific overrides */ + +.slide table.docinfo {margin: 1em 0 0.5em 2em;} + +pre.literal-block, pre.doctest-block {background-color: white;} + +tt.docutils {background-color: white;} + +/* diagnostics */ +/* +li:after {content: " [" attr(class) "]"; color: #F88;} +div:before {content: "[" attr(class) "]"; color: #F88;} +*/ Added: py/dist/py/documentation/talk/ui/default/print.css ============================================================================== --- (empty file) +++ py/dist/py/documentation/talk/ui/default/print.css Mon Mar 13 02:42:21 2006 @@ -0,0 +1,24 @@ +/* This file has been placed in the public domain. */ +/* The following rule is necessary to have all slides appear in print! + DO NOT REMOVE IT! */ +.slide, ul {page-break-inside: avoid; visibility: visible !important;} +h1 {page-break-after: avoid;} + +body {font-size: 12pt; background: white;} +* {color: black;} + +#slide0 h1 {font-size: 200%; border: none; margin: 0.5em 0 0.25em;} +#slide0 h3 {margin: 0; padding: 0;} +#slide0 h4 {margin: 0 0 0.5em; padding: 0;} +#slide0 {margin-bottom: 3em;} + +#header {display: none;} +#footer h1 {margin: 0; border-bottom: 1px solid; color: gray; + font-style: italic;} +#footer h2, #controls {display: none;} + +.print {display: inline ! important;} + +/* The following rule keeps the layout stuff out of print. + Remove at your own risk! */ +.layout, .layout * {display: none !important;} Added: py/dist/py/documentation/talk/ui/default/py-web.png ============================================================================== Binary file. No diff available. Added: py/dist/py/documentation/talk/ui/default/s5-core.css ============================================================================== --- (empty file) +++ py/dist/py/documentation/talk/ui/default/s5-core.css Mon Mar 13 02:42:21 2006 @@ -0,0 +1,11 @@ +/* This file has been placed in the public domain. */ +/* Do not edit or override these styles! + The system will likely break if you do. */ + +div#header, div#footer, div#controls, .slide {position: absolute;} +html>body div#header, html>body div#footer, + html>body div#controls, html>body .slide {position: fixed;} +.handout {display: none;} +.layout {display: block;} +.slide, .hideme, .incremental {visibility: hidden;} +#slide0 {visibility: visible;} Added: py/dist/py/documentation/talk/ui/default/slides.css ============================================================================== --- (empty file) +++ py/dist/py/documentation/talk/ui/default/slides.css Mon Mar 13 02:42:21 2006 @@ -0,0 +1,13 @@ +/* This file has been placed in the public domain. */ + +/* required to make the slide show run at all */ + at import url(s5-core.css); + +/* sets basic placement and size of slide components */ + at import url(framing.css); + +/* styles that make the slides look good */ + at import url(pretty.css); + +/* pypy override */ + at import url(../py.css); Added: py/dist/py/documentation/talk/ui/default/slides.js ============================================================================== --- (empty file) +++ py/dist/py/documentation/talk/ui/default/slides.js Mon Mar 13 02:42:21 2006 @@ -0,0 +1,558 @@ +// S5 v1.1 slides.js -- released into the Public Domain +// Modified for Docutils (http://docutils.sf.net) by David Goodger +// +// Please see http://www.meyerweb.com/eric/tools/s5/credits.html for +// information about all the wonderful and talented contributors to this code! + +var undef; +var slideCSS = ''; +var snum = 0; +var smax = 1; +var slideIDs = new Array(); +var incpos = 0; +var number = undef; +var s5mode = true; +var defaultView = 'slideshow'; +var controlVis = 'visible'; + +var isIE = navigator.appName == 'Microsoft Internet Explorer' ? 1 : 0; +var isOp = navigator.userAgent.indexOf('Opera') > -1 ? 1 : 0; +var isGe = navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('Safari') < 1 ? 1 : 0; + +function hasClass(object, className) { + if (!object.className) return false; + return (object.className.search('(^|\\s)' + className + '(\\s|$)') != -1); +} + +function hasValue(object, value) { + if (!object) return false; + return (object.search('(^|\\s)' + value + '(\\s|$)') != -1); +} + +function removeClass(object,className) { + if (!object) return; + object.className = object.className.replace(new RegExp('(^|\\s)'+className+'(\\s|$)'), RegExp.$1+RegExp.$2); +} + +function addClass(object,className) { + if (!object || hasClass(object, className)) return; + if (object.className) { + object.className += ' '+className; + } else { + object.className = className; + } +} + +function GetElementsWithClassName(elementName,className) { + var allElements = document.getElementsByTagName(elementName); + var elemColl = new Array(); + for (var i = 0; i< allElements.length; i++) { + if (hasClass(allElements[i], className)) { + elemColl[elemColl.length] = allElements[i]; + } + } + return elemColl; +} + +function isParentOrSelf(element, id) { + if (element == null || element.nodeName=='BODY') return false; + else if (element.id == id) return true; + else return isParentOrSelf(element.parentNode, id); +} + +function nodeValue(node) { + var result = ""; + if (node.nodeType == 1) { + var children = node.childNodes; + for (var i = 0; i < children.length; ++i) { + result += nodeValue(children[i]); + } + } + else if (node.nodeType == 3) { + result = node.nodeValue; + } + return(result); +} + +function slideLabel() { + var slideColl = GetElementsWithClassName('*','slide'); + var list = document.getElementById('jumplist'); + smax = slideColl.length; + for (var n = 0; n < smax; n++) { + var obj = slideColl[n]; + + var did = 'slide' + n.toString(); + if (obj.getAttribute('id')) { + slideIDs[n] = obj.getAttribute('id'); + } + else { + obj.setAttribute('id',did); + slideIDs[n] = did; + } + if (isOp) continue; + + var otext = ''; + var menu = obj.firstChild; + if (!menu) continue; // to cope with empty slides + while (menu && menu.nodeType == 3) { + menu = menu.nextSibling; + } + if (!menu) continue; // to cope with slides with only text nodes + + var menunodes = menu.childNodes; + for (var o = 0; o < menunodes.length; o++) { + otext += nodeValue(menunodes[o]); + } + list.options[list.length] = new Option(n + ' : ' + otext, n); + } +} + +function currentSlide() { + var cs; + var footer_nodes; + var vis = 'visible'; + if (document.getElementById) { + cs = document.getElementById('currentSlide'); + footer_nodes = document.getElementById('footer').childNodes; + } else { + cs = document.currentSlide; + footer = document.footer.childNodes; + } + cs.innerHTML = '' + snum + '<\/span> ' + + '\/<\/span> ' + + '' + (smax-1) + '<\/span>'; + if (snum == 0) { + vis = 'hidden'; + } + cs.style.visibility = vis; + for (var i = 0; i < footer_nodes.length; i++) { + if (footer_nodes[i].nodeType == 1) { + footer_nodes[i].style.visibility = vis; + } + } +} + +function go(step) { + if (document.getElementById('slideProj').disabled || step == 0) return; + var jl = document.getElementById('jumplist'); + var cid = slideIDs[snum]; + var ce = document.getElementById(cid); + if (incrementals[snum].length > 0) { + for (var i = 0; i < incrementals[snum].length; i++) { + removeClass(incrementals[snum][i], 'current'); + removeClass(incrementals[snum][i], 'incremental'); + } + } + if (step != 'j') { + snum += step; + lmax = smax - 1; + if (snum > lmax) snum = lmax; + if (snum < 0) snum = 0; + } else + snum = parseInt(jl.value); + var nid = slideIDs[snum]; + var ne = document.getElementById(nid); + if (!ne) { + ne = document.getElementById(slideIDs[0]); + snum = 0; + } + if (step < 0) {incpos = incrementals[snum].length} else {incpos = 0;} + if (incrementals[snum].length > 0 && incpos == 0) { + for (var i = 0; i < incrementals[snum].length; i++) { + if (hasClass(incrementals[snum][i], 'current')) + incpos = i + 1; + else + addClass(incrementals[snum][i], 'incremental'); + } + } + if (incrementals[snum].length > 0 && incpos > 0) + addClass(incrementals[snum][incpos - 1], 'current'); + ce.style.visibility = 'hidden'; + ne.style.visibility = 'visible'; + jl.selectedIndex = snum; + currentSlide(); + number = 0; +} + +function goTo(target) { + if (target >= smax || target == snum) return; + go(target - snum); +} + +function subgo(step) { + if (step > 0) { + removeClass(incrementals[snum][incpos - 1],'current'); + removeClass(incrementals[snum][incpos], 'incremental'); + addClass(incrementals[snum][incpos],'current'); + incpos++; + } else { + incpos--; + removeClass(incrementals[snum][incpos],'current'); + addClass(incrementals[snum][incpos], 'incremental'); + addClass(incrementals[snum][incpos - 1],'current'); + } +} + +function toggle() { + var slideColl = GetElementsWithClassName('*','slide'); + var slides = document.getElementById('slideProj'); + var outline = document.getElementById('outlineStyle'); + if (!slides.disabled) { + slides.disabled = true; + outline.disabled = false; + s5mode = false; + fontSize('1em'); + for (var n = 0; n < smax; n++) { + var slide = slideColl[n]; + slide.style.visibility = 'visible'; + } + } else { + slides.disabled = false; + outline.disabled = true; + s5mode = true; + fontScale(); + for (var n = 0; n < smax; n++) { + var slide = slideColl[n]; + slide.style.visibility = 'hidden'; + } + slideColl[snum].style.visibility = 'visible'; + } +} + +function showHide(action) { + var obj = GetElementsWithClassName('*','hideme')[0]; + switch (action) { + case 's': obj.style.visibility = 'visible'; break; + case 'h': obj.style.visibility = 'hidden'; break; + case 'k': + if (obj.style.visibility != 'visible') { + obj.style.visibility = 'visible'; + } else { + obj.style.visibility = 'hidden'; + } + break; + } +} + +// 'keys' code adapted from MozPoint (http://mozpoint.mozdev.org/) +function keys(key) { + if (!key) { + key = event; + key.which = key.keyCode; + } + if (key.which == 84) { + toggle(); + return; + } + if (s5mode) { + switch (key.which) { + case 10: // return + case 13: // enter + if (window.event && isParentOrSelf(window.event.srcElement, 'controls')) return; + if (key.target && isParentOrSelf(key.target, 'controls')) return; + if(number != undef) { + goTo(number); + break; + } + case 32: // spacebar + case 34: // page down + case 39: // rightkey + case 40: // downkey + if(number != undef) { + go(number); + } else if (!incrementals[snum] || incpos >= incrementals[snum].length) { + go(1); + } else { + subgo(1); + } + break; + case 33: // page up + case 37: // leftkey + case 38: // upkey + if(number != undef) { + go(-1 * number); + } else if (!incrementals[snum] || incpos <= 0) { + go(-1); + } else { + subgo(-1); + } + break; + case 36: // home + goTo(0); + break; + case 35: // end + goTo(smax-1); + break; + case 67: // c + showHide('k'); + break; + } + if (key.which < 48 || key.which > 57) { + number = undef; + } else { + if (window.event && isParentOrSelf(window.event.srcElement, 'controls')) return; + if (key.target && isParentOrSelf(key.target, 'controls')) return; + number = (((number != undef) ? number : 0) * 10) + (key.which - 48); + } + } + return false; +} + +function clicker(e) { + number = undef; + var target; + if (window.event) { + target = window.event.srcElement; + e = window.event; + } else target = e.target; + if (target.href != null || hasValue(target.rel, 'external') || isParentOrSelf(target, 'controls') || isParentOrSelf(target,'embed') || isParentOrSelf(target, 'object')) return true; + if (!e.which || e.which == 1) { + if (!incrementals[snum] || incpos >= incrementals[snum].length) { + go(1); + } else { + subgo(1); + } + } +} + +function findSlide(hash) { + var target = document.getElementById(hash); + if (target) { + for (var i = 0; i < slideIDs.length; i++) { + if (target.id == slideIDs[i]) return i; + } + } + return null; +} + +function slideJump() { + if (window.location.hash == null || window.location.hash == '') { + currentSlide(); + return; + } + if (window.location.hash == null) return; + var dest = null; + dest = findSlide(window.location.hash.slice(1)); + if (dest == null) { + dest = 0; + } + go(dest - snum); +} + +function fixLinks() { + var thisUri = window.location.href; + thisUri = thisUri.slice(0, thisUri.length - window.location.hash.length); + var aelements = document.getElementsByTagName('A'); + for (var i = 0; i < aelements.length; i++) { + var a = aelements[i].href; + var slideID = a.match('\#.+'); + if ((slideID) && (slideID[0].slice(0,1) == '#')) { + var dest = findSlide(slideID[0].slice(1)); + if (dest != null) { + if (aelements[i].addEventListener) { + aelements[i].addEventListener("click", new Function("e", + "if (document.getElementById('slideProj').disabled) return;" + + "go("+dest+" - snum); " + + "if (e.preventDefault) e.preventDefault();"), true); + } else if (aelements[i].attachEvent) { + aelements[i].attachEvent("onclick", new Function("", + "if (document.getElementById('slideProj').disabled) return;" + + "go("+dest+" - snum); " + + "event.returnValue = false;")); + } + } + } + } +} + +function externalLinks() { + if (!document.getElementsByTagName) return; + var anchors = document.getElementsByTagName('a'); + for (var i=0; i' + + '