Commit 2599deb2 authored by Mark Florisson's avatar Mark Florisson

Modify utility code loader as per discussion + tests

parent 3f8fefb0
...@@ -44,8 +44,11 @@ uncachable_builtins = [ ...@@ -44,8 +44,11 @@ uncachable_builtins = [
'WindowsError', 'WindowsError',
] ]
Cython_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) def get_utility_dir():
Utility_dir = os.path.join(Cython_dir, "Utility") # make this a function and not global variables:
# http://trac.cython.org/cython_trac/ticket/475
Cython_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
return os.path.join(Cython_dir, "Utility")
class UtilityCodeBase(object): class UtilityCodeBase(object):
...@@ -53,7 +56,7 @@ class UtilityCodeBase(object): ...@@ -53,7 +56,7 @@ class UtilityCodeBase(object):
_utility_cache = {} _utility_cache = {}
@classmethod # @classmethod
def _add_utility(cls, utility, type, lines, begin_lineno): def _add_utility(cls, utility, type, lines, begin_lineno):
if utility: if utility:
if cls.is_cython_utility: if cls.is_cython_utility:
...@@ -68,40 +71,52 @@ class UtilityCodeBase(object): ...@@ -68,40 +71,52 @@ class UtilityCodeBase(object):
else: else:
utility[1] = code utility[1] = code
@classmethod _add_utility = classmethod(_add_utility)
# @classmethod
def load_utilities_from_file(cls, path): def load_utilities_from_file(cls, path):
utilities = cls._utility_cache.get(path) utilities = cls._utility_cache.get(path)
if utilities: if utilities:
return utilities return utilities
filename = os.path.join(Utility_dir, path) filename = os.path.join(get_utility_dir(), path)
f = codecs.open(filename, encoding='UTF-8')
_, ext = os.path.splitext(path) _, ext = os.path.splitext(path)
if ext in ('.pyx', '.py', '.pxd', '.pxi'): if ext in ('.pyx', '.py', '.pxd', '.pxi'):
comment = '#' comment = '#'
else: else:
comment = '//' comment = '/'
regex = r'%s\s*Utility(Proto|Code)\s*:\s*((\w|\.)+)\s*' % comment regex = r'%s{5,20}\s*((\w|\.)+)\s*%s{5,20}' % (comment, comment)
utilities = {} utilities = {}
lines = [] lines = []
utility = type = None utility = type = None
begin_lineno = 0 begin_lineno = 0
for lineno, line in enumerate(f):
m = re.search(regex, line)
if m:
cls._add_utility(utility, type, lines, begin_lineno)
begin_lineno = lineno + 1
type, name = m.group(1), m.group(2)
utility = utilities.setdefault(name, [None, None])
utilities[name] = utility
lines = [] f = Utils.open_source_file(filename, encoding='UTF-8')
else: try:
lines.append(line) for lineno, line in enumerate(f):
m = re.search(regex, line)
if m:
cls._add_utility(utility, type, lines, begin_lineno)
begin_lineno = lineno + 1
name = m.group(1)
if name.endswith(".proto"):
name = name[:-6]
type = 'Proto'
else:
type = 'Code'
utility = utilities.setdefault(name, [None, None])
utilities[name] = utility
lines = []
else:
lines.append(line)
finally:
f.close()
if not utility: if not utility:
raise ValueError("Empty utility code file") raise ValueError("Empty utility code file")
...@@ -114,12 +129,15 @@ class UtilityCodeBase(object): ...@@ -114,12 +129,15 @@ class UtilityCodeBase(object):
cls._utility_cache[path] = utilities cls._utility_cache[path] = utilities
return utilities return utilities
@classmethod load_utilities_from_file = classmethod(load_utilities_from_file)
def load_utility_from_file(cls, path, util_code_name,
context=None, **kwargs): # @classmethod
def load(cls, util_code_name, from_file=None, context=None, **kwargs):
""" """
Load a utility code from a file specified by path (relative to Load a utility code from a file specified by from_file (relative to
Cython/Utility) and name util_code_name. Cython/Utility) and name util_code_name. If from_file is not given,
load it from the file util_code_name.*. There should be only one file
matched by this pattern.
Utilities in the file can be specified as follows: Utilities in the file can be specified as follows:
...@@ -137,7 +155,7 @@ class UtilityCodeBase(object): ...@@ -137,7 +155,7 @@ class UtilityCodeBase(object):
one should pass in the 'name' keyword argument to be used for name one should pass in the 'name' keyword argument to be used for name
mangling of such entries. mangling of such entries.
""" """
proto, impl = cls.load_utility_as_string(path, util_code_name, context) proto, impl = cls.load_as_string(util_code_name, from_file, context)
if proto is not None: if proto is not None:
kwargs['proto'] = proto kwargs['proto'] = proto
...@@ -145,36 +163,40 @@ class UtilityCodeBase(object): ...@@ -145,36 +163,40 @@ class UtilityCodeBase(object):
kwargs['impl'] = impl kwargs['impl'] = impl
if 'name' not in kwargs: if 'name' not in kwargs:
kwargs['name'] = os.path.splitext(path)[0] if from_file:
kwargs['name'] = os.path.splitext(from_file)[0]
else:
kwargs['name'] = util_code_name
return cls(**kwargs) return cls(**kwargs)
@classmethod load = classmethod(load)
def load_utility_as_string(cls, path, util_code_name, context=None):
# @classmethod
def load_as_string(cls, util_code_name, from_file=None, context=None):
""" """
Load a utility code as a string. Returns (proto, implementation) Load a utility code as a string. Returns (proto, implementation)
""" """
utilities = cls.load_utilities_from_file(path) if from_file is None:
files = glob.glob(os.path.join(get_utility_dir(),
util_code_name + '.*'))
if len(files) != 1:
raise ValueError("Need exactly one utility file")
from_file, = files
utilities = cls.load_utilities_from_file(from_file)
proto, impl = utilities[util_code_name] proto, impl = utilities[util_code_name]
if proto: if context is not None:
if context is not None: if proto:
proto = tempita.sub(proto, **context) proto = tempita.sub(proto, **context)
if impl:
if impl:
if context is not None:
impl = tempita.sub(impl, **context) impl = tempita.sub(impl, **context)
return proto, impl return proto, impl
@classmethod load_as_string = classmethod(load_as_string)
def load_utility(cls, name, context=None, **kwargs):
"Load utility name with context from a utility file name.suffix"
files = glob.glob(os.path.join(Utility_dir, name + '.*'))
if len(files) != 1:
raise ValueError("Need exactly one utility file")
return cls.load_utilities_from_file(files[0], name, context, **kwargs)
def __str__(self): def __str__(self):
return "<%s(%s)" % (type(self).__name__, self.name) return "<%s(%s)" % (type(self).__name__, self.name)
......
...@@ -103,9 +103,8 @@ def create_cython_scope(context, create_testscope): ...@@ -103,9 +103,8 @@ def create_cython_scope(context, create_testscope):
# Load test utilities for the cython scope # Load test utilities for the cython scope
def load_testscope_utility(cython_util_name, *args, **kwargs): def load_testscope_utility(cy_util_name, **kwargs):
return CythonUtilityCode.load_utility_from_file( return CythonUtilityCode.load(cy_util_name, "TestCythonScope.pyx", **kwargs)
"TestCythonScope.pyx", cython_util_name, *args, **kwargs)
undecorated_methods_protos = UtilityCode(proto=u""" undecorated_methods_protos = UtilityCode(proto=u"""
......
...@@ -382,8 +382,9 @@ class CopyFuncUtilCode(object): ...@@ -382,8 +382,9 @@ class CopyFuncUtilCode(object):
copy_contents_name=copy_contents_name copy_contents_name=copy_contents_name
) )
_, copy_code = UtilityCode.load_utility_as_string( _, copy_code = UtilityCode.load_as_string("MemviewSliceCopyTemplate",
"MemoryView_C.c", "MemviewSliceCopyTemplate", context) from_file="MemoryView_C.c",
context=context)
code.put(copy_code) code.put(copy_code)
...@@ -737,17 +738,11 @@ class MemoryViewSliceTransform(CythonTransform): ...@@ -737,17 +738,11 @@ class MemoryViewSliceTransform(CythonTransform):
return node return node
def load_memview_cy_utility(name, *args, **kwargs): def load_memview_cy_utility(util_code_name, **kwargs):
return CythonUtilityCode.load_utility_from_file( return CythonUtilityCode.load(util_code_name, "MemoryView.pyx", **kwargs)
"MemoryView.pyx", name, *args, **kwargs)
def load_memview_c_utility(name, *args, **kwargs):
return UtilityCode.load_utility_from_file(
"MemoryView_C.c", name, *args, **kwargs)
def load_memview_c_string(name):
return UtilityCode.load_utility_as_string("MemoryView_C.c", name)
def load_memview_c_utility(util_code_name, **kwargs):
return UtilityCode.load(util_code_name, "MemoryView_C.c", **kwargs)
context = { context = {
'memview_struct_name': memview_objstruct_cname, 'memview_struct_name': memview_objstruct_cname,
...@@ -755,12 +750,12 @@ context = { ...@@ -755,12 +750,12 @@ context = {
'memviewslice_name': memviewslice_cname, 'memviewslice_name': memviewslice_cname,
} }
memviewslice_declare_code = load_memview_c_utility( memviewslice_declare_code = load_memview_c_utility(
name="MemviewSliceStruct", "MemviewSliceStruct",
proto_block='utility_code_proto_before_types', proto_block='utility_code_proto_before_types',
context=context) context=context)
memviewslice_init_code = load_memview_c_utility( memviewslice_init_code = load_memview_c_utility(
name="MemviewSliceInit", "MemviewSliceInit",
context={'BUF_MAX_NDIMS': Options.buffer_max_dims}, context={'BUF_MAX_NDIMS': Options.buffer_max_dims},
requires=[memviewslice_declare_code], requires=[memviewslice_declare_code],
) )
import unittest
from Cython.Compiler import Code, UtilityCode
def strip_2tup(tup):
return tup[0] and tup[0].strip(), tup[1] and tup[1].strip()
class TestUtilityLoader(unittest.TestCase):
"""
Test loading UtilityCodes
"""
expected = "test {{loader}} prototype", "test {{loader}} impl"
expected_tempita = (expected[0].replace('{{loader}}', 'Loader'),
expected[1].replace('{{loader}}', 'Loader'))
required = "I am a dependency proto", "I am a dependency impl"
context = dict(loader='Loader')
name = "TestUtilityLoader"
filename = "TestUtilityLoader.c"
cls = Code.UtilityCode
def test_load_as_string(self):
got = strip_2tup(self.cls.load_as_string(self.name))
self.assertEquals(got, self.expected)
got = strip_2tup(self.cls.load_as_string(self.name, self.filename))
self.assertEquals(got, self.expected)
got = strip_2tup(self.cls.load_as_string(self.name, context=self.context))
self.assertEquals(got, self.expected_tempita)
def test_load(self):
utility = self.cls.load(self.name)
got = strip_2tup((utility.proto, utility.impl))
self.assertEquals(got, self.expected)
# Not implemented yet
#required, = utility.requires
#self.assertEquals((required.proto, required.impl), self.required)
utility = self.cls.load(self.name, from_file=self.filename)
got = strip_2tup((utility.proto, utility.impl))
self.assertEquals(got, self.expected)
class TestCythonUtilityLoader(TestUtilityLoader):
"""
Test loading CythonUtilityCodes
"""
# Just change the attributes and run the same tests
expected = None, "test {{cy_loader}} impl"
expected_tempita = None, "test CyLoader impl"
required = None, "I am a Cython dependency impl"
context = dict(cy_loader='CyLoader')
name = "TestCyUtilityLoader"
filename = "TestCyUtilityLoader.pyx"
cls = UtilityCode.CythonUtilityCode
# Small hack to pass our tests above
cls.proto = None
\ No newline at end of file
...@@ -70,7 +70,7 @@ class CythonUtilityCode(Code.UtilityCodeBase): ...@@ -70,7 +70,7 @@ class CythonUtilityCode(Code.UtilityCodeBase):
# while the generated node trees can be altered in the compilation of a # while the generated node trees can be altered in the compilation of a
# single file. # single file.
# Hence, delay any processing until later. # Hence, delay any processing until later.
self.pyx = impl self.impl = impl
self.name = name self.name = name
self.prefix = prefix self.prefix = prefix
self.requires = requires or [] self.requires = requires or []
...@@ -86,7 +86,7 @@ class CythonUtilityCode(Code.UtilityCodeBase): ...@@ -86,7 +86,7 @@ class CythonUtilityCode(Code.UtilityCodeBase):
context = CythonUtilityCodeContext(self.name) context = CythonUtilityCodeContext(self.name)
context.prefix = self.prefix context.prefix = self.prefix
#context = StringParseContext(self.name) #context = StringParseContext(self.name)
tree = parse_from_strings(self.name, self.pyx, context=context, tree = parse_from_strings(self.name, self.impl, context=context,
allow_struct_enum_decorator=True) allow_struct_enum_decorator=True)
pipeline = Pipeline.create_pipeline(context, 'pyx', exclude_classes=excludes) pipeline = Pipeline.create_pipeline(context, 'pyx', exclude_classes=excludes)
......
# UtilityCode: CythonArray ########## CythonArray ##########
cdef extern from "stdlib.h": cdef extern from "stdlib.h":
void *malloc(size_t) void *malloc(size_t)
...@@ -134,7 +134,7 @@ cdef class array: ...@@ -134,7 +134,7 @@ cdef class array:
cdef array array_cwrapper(tuple shape, Py_ssize_t itemsize, char *format, char *mode): cdef array array_cwrapper(tuple shape, Py_ssize_t itemsize, char *format, char *mode):
return array(shape, itemsize, format, mode.decode('ASCII')) return array(shape, itemsize, format, mode.decode('ASCII'))
# UtilityCode: MemoryView ########## MemoryView ##########
# from cpython cimport ... # from cpython cimport ...
cdef extern from "pythread.h": cdef extern from "pythread.h":
......
// UtilityProto: MemviewSliceStruct ////////// MemviewSliceStruct.proto //////////
/* memoryview slice struct */ /* memoryview slice struct */
...@@ -11,7 +11,7 @@ typedef struct { ...@@ -11,7 +11,7 @@ typedef struct {
Py_ssize_t suboffsets[{{max_dims}}]; Py_ssize_t suboffsets[{{max_dims}}];
} {{memviewslice_name}}; } {{memviewslice_name}};
// UtilityProto: MemviewSliceInit ////////// MemviewSliceInit.proto //////////
#define __Pyx_BUF_MAX_NDIMS %(BUF_MAX_NDIMS)d #define __Pyx_BUF_MAX_NDIMS %(BUF_MAX_NDIMS)d
...@@ -36,7 +36,7 @@ static int __Pyx_init_memviewslice( ...@@ -36,7 +36,7 @@ static int __Pyx_init_memviewslice(
int ndim, int ndim,
__Pyx_memviewslice *memviewslice); __Pyx_memviewslice *memviewslice);
// UtilityCode: MemviewSliceInit ////////// MemviewSliceInit //////////
static int __Pyx_ValidateAndInit_memviewslice( static int __Pyx_ValidateAndInit_memviewslice(
struct __pyx_memoryview_obj *memview, struct __pyx_memoryview_obj *memview,
...@@ -218,7 +218,7 @@ no_fail: ...@@ -218,7 +218,7 @@ no_fail:
return retval; return retval;
} }
// UtilityCode: MemviewSliceCopyTemplate ////////// MemviewSliceCopyTemplate //////////
static __Pyx_memviewslice {{copy_name}}(const __Pyx_memviewslice from_mvs) { static __Pyx_memviewslice {{copy_name}}(const __Pyx_memviewslice from_mvs) {
......
########## TestCyUtilityLoader ##########
test {{cy_loader}} impl
# UtilityCode: TestClass ########## TestClass ##########
# These utilities are for testing purposes # These utilities are for testing purposes
cdef extern from *: cdef extern from *:
...@@ -44,19 +44,19 @@ cdef test_call(obj): ...@@ -44,19 +44,19 @@ cdef test_call(obj):
cdef _testclass_new(int value): cdef _testclass_new(int value):
return TestClass(value) return TestClass(value)
# UtilityCode: TestDep ########### TestDep ##########
@cname('__pyx_test_dep') @cname('__pyx_test_dep')
cdef test_dep(obj): cdef test_dep(obj):
print 'test_dep', obj print 'test_dep', obj
# UtilityCode: TestScope ########## TestScope ##########
@cname('__pyx_testscope') @cname('__pyx_testscope')
cdef object _testscope(int value): cdef object _testscope(int value):
return "hello from cython scope, value=%d" % value return "hello from cython scope, value=%d" % value
# UtilityCode: View.TestScope ########## View.TestScope ##########
@cname('__pyx_view_testscope') @cname('__pyx_view_testscope')
cdef object _testscope(int value): cdef object _testscope(int value):
......
////////// TestUtilityLoader.proto //////////
test {{loader}} prototype
////////// TestUtilityLoader //////////
test {{loader}} impl
...@@ -18,7 +18,7 @@ include Demos/freeze/* ...@@ -18,7 +18,7 @@ include Demos/freeze/*
include Demos/libraries/* include Demos/libraries/*
include Demos/Makefile* include Demos/Makefile*
recursive-include Cython/Debugger/Tests *.pyx *.pxd *.c *.h recursive-include Cython/Debugger/Tests *.pyx *.pxd *.c *.h
recursive-include Cython/Utility * recursive-include Cython/Utility *.pyx *.pxd *.c *.h
recursive-include Tools * recursive-include Tools *
recursive-include tests *.pyx *.pxd *.pxi *.py *.h *.BROKEN bugs.txt recursive-include tests *.pyx *.pxd *.pxi *.py *.h *.BROKEN bugs.txt
recursive-include tests *_lib.cpp *.srctree recursive-include tests *_lib.cpp *.srctree
......
...@@ -1156,7 +1156,11 @@ class TagsSelector: ...@@ -1156,7 +1156,11 @@ class TagsSelector:
class RegExSelector: class RegExSelector:
def __init__(self, pattern_string): def __init__(self, pattern_string):
self.pattern = re.compile(pattern_string, re.I|re.U) try:
self.pattern = re.compile(pattern_string, re.I|re.U)
except re.error:
print('Invalid pattern: %r' % pattern_string)
raise
def __call__(self, testname, tags=None): def __call__(self, testname, tags=None):
return self.pattern.search(testname) return self.pattern.search(testname)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment