1. Jonathan Fine
  2. python-jfine-new

Commits

Jonathan Fine  committed 5de3c1b Draft Merge

$ hg merge jfine.2012-11-17 # Not finished - architectural problems.

  • Participants
  • Parent commits b0da20c, 762fd23
  • Branches jfine

Comments (0)

Files changed (5)

File py2/jfine/operator.py

View file
+'''Module docstring.
+
+Sometimes it's useful to have a transform that does nothing.
+>>> obj = object()
+>>> obj is identity(obj)
+True
+
+We already have itemgetter and attrgetter.  Their interface is a bit
+odd.
+>>> from operator import itemgetter
+>>> itemgetter(2)('abc')
+'c'
+>>> itemgetter(2, 0)('abc')
+('c', 'a')
+
+>>> argv_call('{0} {1} {2}'.format)('abc')
+'a b c'
+
+>>> kwargs_call('{a} {b}'.format)(dict(a=0, b=1))
+'0 1'
+
+>>> argv_kwargs_call('{0} {b}'.format)([['a'], dict(b=1)])
+'a 1'
+
+>>> call_method('upper')('abc')
+'ABC'
+
+>>> call_method('format', 'a', 'b', 'c')('{0} {1} {2}')
+'a b c'
+
+>>> call_method('format', a=1, b=2, c=3)('{a} {b} {c}')
+'1 2 3'
+
+>>> call_method('format', 'a',  b=2)('{0} {b}')
+'a 2'
+
+'''
+
+# Future imports that work in Python 2.5 and later.
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import with_statement
+
+# Future imports that work in Python 2.6 and later.
+# from __future__ import print_function
+# from __future__ import unicode_literals
+
+# Prevent use of old-style classes.
+__metaclass__ = type
+
+from functools import wraps
+
+
+def identity(obj):
+    '''obj -> obj'''
+    return obj
+
+
+def argv_call(fn):
+    '''argv_call(fn)(argv) -> fn(*argv)
+
+    Useful for existing functions, such as '{0} {1}'.format.
+    '''
+
+    @wraps(fn)
+    def tmp(argv):
+        return fn(*argv)
+    return tmp
+
+
+def kwargs_call(fn):
+    '''kwargs_call(fn)(kwargs) -> fn(**kwargs)
+
+    Useful for existing functions, such as '{a} {b}'.format.
+    '''
+
+    @wraps(fn)
+    def tmp(kwargs):
+        return fn(**kwargs)
+    return tmp
+
+
+def argv_kwargs_call(fn):
+    '''argv_kwargs_call(fn)((argv, kwargs)) -> fn(*argv, **kwargs)
+
+    Useful for existing functions, such as '{0} {b}'.format.
+    '''
+
+    @wraps(fn)
+    def tmp(argv_kwargs):
+        return fn(*argv_kwargs[0], **argv_kwargs[1])
+    return tmp
+
+
+def call_method(*argv, **kwargs):
+
+    # ('a', name='b') breaks signature (name, *argv, **kwargs).
+    name = argv[0]
+    argv = argv[1:]
+
+    if not (argv or kwargs):
+        # Common special case.
+        def fn(obj):
+            return getattr(obj, name)()
+        fn.__doc__ = 'obj -> obj.%s()' % name
+        return fn
+
+    if not kwargs:
+        def fn(obj):
+            return getattr(obj, name)(*argv)
+        fn.__doc__ = 'obj -> obj.%s(*argv)' % name
+        return fn
+
+    if not argv:
+        def fn(obj):
+            return getattr(obj, name)(**kwargs)
+        fn.__doc__ = 'obj -> obj.%s(**kwargs)' % name
+        return fn
+
+
+    def fn(obj):
+        return getattr(obj, name)(*argv, **kwargs)
+    fn.__doc__ = 'obj -> obj.%s(*argv, **kwargs)' % name
+    return fn
+
+

File py2/jfine/template.py

View file
-'''Module docstring.'''
+'''Module docstring.
+
+Flattening is a key concept.  Anything that does not have a flatten
+method is left unchanged.  So nothing happens here - each item in the
+seq is in turn appended to the out list.
+
+>>> out = []
+>>> seq = [0, 1, [2, 3, [4, 5]], [6, 7]]
+>>> flatten(seq, out.extend)
+>>> out
+[0, 1, [2, 3, [4, 5]], [6, 7]]
+
+A flatlist has a flatten method, which extends the out list with its
+content.  Note the change produced by using a flatlist.
+
+>>> out = []
+>>> seq = [0, 1, flatlist([2, 3, [4, 5]]), [6, 7]]
+>>> flatten(seq, out.extend)
+>>> out
+[0, 1, 2, 3, [4, 5], [6, 7]]
+
+'''
 
 # Future imports that work in Python 2.5 and later.
 from __future__ import division
 # Prevent use of old-style classes.
 __metaclass__ = type
 
-
+from functools import partial
 from itertools import imap
 
-class Base(list):
+
+# TODO: Write tests.
+def as_mapget(obj):
+
+    if isinstance(obj, dict):
+        return partial(imap, obj.get)
+
+    return ValueError(obj)
+
+
+class flatlist(list):
+    '''A list where flatten extend by whole of self.'''
+    # TODO: Needed?  Why not just modify an ordinary list?
+    def flatten(self, extend):
+        extend(self)
+
+
+def flatten(iterable, extend):
+    '''Simple recursive flatten of template.
+    '''
+    for item in iterable:
+        try:
+            fn = item.flatten
+        except AttributeError:
+            extend([item])
+        else:
+            fn(extend)
+
+
+class Changeable:
+    '''For items in prototype that should be changed on instantiation.'''
+
+
+class ChangeableType(type, Changeable):
+    '''Classes of this type are recognized as changeable.'''
+
+
+class ListShared(list):
+    '''Routines that are shared by all LIST template classes.
+    '''
+
+    __metaclass__ = ChangeableType
 
     keys = property()           # Implemented by subclass.
     proto = property()          # Implemented by subclass.
 
 
     def __init__(self, mapget):
-        values = tuple(mapget(self.keys))
-        self.populate(values)
 
+        # For now only key access.  Might want to change this.
+        tmp = tuple(mapget(self.keys))
+        values = dict()
+        if 0:                              # Don't provide index access.
+            values.update(enumerate(tmp))
+        values.update(zip(self.keys, tmp)) # Provide key access.
+        self.values = values               # For use by subtemplate.
+        self.populate()
 
-    def flatten(self, extend):
-        '''Simple recursive flatten of template.
-        '''
-        for item in self:
-            try:
-                fn = item.flatten
-            except AttributeError:
-                extend([item])
-            else:
-                fn(extend)
 
+    def populate(self):
+        '''Use self.populate_script to populate template.'''
+        for j, fn in self.populate_script:
+            self[j] = fn(self.values)
 
-    def populate(self, values):
-        '''Use self.populate_script to populate template.'''
-
-        for fn, argv in self.populate_script:
-            fn(self, values, *argv)
+    flatten = flatten
 
 
 def dummy_mapget(keys):
     In Python3, map returns an iterator, not a list.
     '''
     return ('<<' + key + '>>' for key in keys)
-
-

File py2/jfine/test_template.py

View file
 from collections import defaultdict
 
 from jfine.template import *          # Allow this shortcut here.
+from jfine.operator import *          # OK for now.
 
 
 # TODO: is if ... else ... nicer than (a, b)[...]?
 # format_spec and conversion will be None.
 
 
-class _Field(unicode):
+class VALUE(Changeable):
 
-    def __new__(cls, s, format=None):
-        return unicode.__new__(cls, s)
+    def __init__(self, key, transform=identity):
 
-    # As in string.Formatter.parse.
-    def __init__(self, s, format=None):
-        self.format = format
+        self.key = key
+        self.transform = transform
 
     @property
-    def key(self):
-        return '' + self
+    def keys(self):
+        return (self.key,)
 
+    def evaluate(self, values):
+        return self.transform(values[self.key])
 
-def field_factory(fn):
 
-    class aaa(_Field):
-        __slots__ = ('format',)
-        def factory(self, src, tgt):
-            return(fn, (src, tgt, self.format))
-
-    return aaa
-
-
-@field_factory
-def Field(self, values, src, tgt, format):
-    self[tgt] = values[src]
-
-
-@field_factory
-def NewField(self, values, src, tgt, format):
-    self[tgt] = format(values[src])
-
-
-@field_factory
-def NewField2(self, values, src, tgt, format):
-    self[tgt] = format(*values[src])
-
-
-@field_factory
-def MethodCallField(self, values, src, tgt, format):
-    self[tgt] = getattr(values[src], format)()
-
-
-def template_factory(src):
+def LIST(src):
+    '''Return list-like template class, for inclusion in template.
+    '''
 
     src = tuple(src)
 
+    # Get the prototype.
     proto = tuple(
-        (s, None)[isinstance(s, _Field)]
+        (s, None)[isinstance(s, Changeable)]
         for s in src
         )
 
-    fields_data = defaultdict(list)
-    for i, field in enumerate(src):
-        if isinstance(field, _Field):
-            # Store index and field.
-            fields_data[field.key].append((i, field))
-
-    keys = tuple(sorted(fields_data))
+    # Find the keys.
+    keys = set()
+    for item in src:
+        if isinstance(item, Changeable):
+            keys.update(item.keys)
+    keys = tuple(sorted(keys))
 
     populate_script = tuple(
-        field.factory(src, tgt)
-        for src, key in enumerate(keys)
-        for tgt, field in fields_data[key]
+        (i, obj.evaluate,)
+        for i, obj in enumerate(src)
+        if isinstance(obj, Changeable)
         )
 
-    return type(
-        'Bbb',
-        (Base,),
-        dict(populate_script=populate_script,
+    @classmethod
+    def evaluate(cls, values):
+        tmp = flatlist()
+        cls(as_mapget(values)).flatten(tmp.extend)
+        return tmp
+
+    this_class = type(
+        'ListSharedSubclass',
+        (ListShared,),
+        dict(
              keys = keys,
-             proto = proto
+             proto = proto,
+             populate_script = populate_script,
+             evaluate = evaluate,
              ))
 
+    return this_class
 
-Aaa = template_factory([
+
+Aaa = LIST([
         'aaaa',
-        MethodCallField('a', format='upper'),
+        VALUE('a', call_method('upper')),
         'bbbb',
-        Field('b'),
+        VALUE('b'),
         'cccc'
         ])
 
 tmp =  Aaa.render(dummy_mapget)
 assert tmp == 'aaaa<<A>>bbbb<<b>>cccc', tmp
 
-tmp = Aaa.render(partial(imap, dict(
+tmp = Aaa.render(as_mapget(dict(
             a = '((aa))',
             b = aaa,
-            ).get))
+            )))
 
 assert tmp == 'aaaa((AA))bbbbaaaa<<A>>bbbb<<b>>cccccccc', tmp
 
 
-Aaa = template_factory([
+Aaa = LIST([
         '<<<',
-        NewField2('a', '{0}..{1}'.format),
+        VALUE('a', argv_call('{0}..{1}'.format)),
         '>>>',
         ])
 
-tmp = Aaa.render(partial(imap, dict(
+tmp = Aaa.render(as_mapget(dict(
                 a = [123, 456],
-                ).get))
+                )))
+
 
 assert tmp == '<<<123..456>>>', tmp
+
+
+# Example of subtemplates.
+Bbb = LIST([
+        '(((',
+        VALUE('b'),
+        '---',
+        LIST([' ', VALUE('x'), VALUE('y'), ' ']),
+        ')))'
+        ])
+
+assert Bbb.keys == ('b', 'x', 'y')
+
+tmp = Bbb.render(dummy_mapget)
+assert tmp == '(((<<b>>--- <<x>><<y>> )))', tmp

File py2/jfine/work.py

View file
+'''Module docstring.'''
+
+# Future imports that work in Python 2.5 and later.
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import with_statement
+
+# Future imports that work in Python 2.6 and later.
+# from __future__ import print_function
+# from __future__ import unicode_literals
+
+# Prevent use of old-style classes.
+__metaclass__ = type
+
+from jfine.test_template import * # OK for now.
+
+
+# Make a start on a CHOICE item.
+class CHOICE(Changeable):
+
+    def __init__(self, fn, items):
+
+        self.fn = fn
+        self.items = items
+
+        # Get the keys.
+        code = fn.func_code
+        varnames = code.co_varnames
+        argcount = code.co_argcount
+        keys = code.co_varnames[:argcount]
+        self.keys = keys
+
+    @staticmethod
+    def evaluate(values):
+        return 'hi'
+
+    def render(self, mapget):
+
+        return LIST([self]).render(mapget)
+
+
+choice = CHOICE(lambda a, b, c: None, None)
+Ccc = LIST([
+        '<< ',
+        choice,
+        ' >>',
+        ])
+
+
+tmp = Ccc.render(dummy_mapget)
+assert tmp == '<< hi >>', tmp
+
+tmp = Ccc.keys
+assert tmp == ('a', 'b', 'c'), tmp
+
+tmp = choice.render(dummy_mapget)
+assert tmp == 'hi', repr(tmp)

File py2/run_tests.py

View file
 import jfine.classtools
 import jfine.fake_nonlocal
 import jfine.functools
-import jfine.testtools
-import jfine.test_streamtools
-import jfine.test_template
+import jfine.operator
 import jfine.proxy
 import jfine.specialmethods
 import jfine.streamtools
 import jfine.template
+import jfine.testtools
+import jfine.test_streamtools
+import jfine.test_template
 import jfine.treetools
-
+import jfine.work
 
 modules_to_test = [
     jfine,
     jfine.classtools,
     jfine.fake_nonlocal,
     jfine.functools,
-    jfine.testtools,
-    jfine.test_streamtools,
-    jfine.test_template,
+    jfine.operator,
     jfine.proxy,
     jfine.specialmethods,
     jfine.streamtools,
     jfine.template,
+    jfine.testtools,
+    jfine.test_streamtools,
+    jfine.test_template,
     jfine.treetools,
+    jfine.work,
     ]
 
 if __name__ == '__main__':