changeset 35:2f9e7f03dbf7

Support predicates in structpath.py
author Thinker K.F. Li <thinker@codemud.net>
date Wed, 15 Jun 2011 12:03:23 +0800
parents fe1ebf0c3d40
children 0b9ac7cef6e5
files paraspace/structpath.py paraspace/tests/structpath_test.py
diffstat 2 files changed, 77 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/paraspace/structpath.py	Wed Jun 15 02:33:54 2011 +0800
+++ b/paraspace/structpath.py	Wed Jun 15 12:03:23 2011 +0800
@@ -1,6 +1,10 @@
 ##
 # Implement a xpath liked query language.
 #
+# \section predicate Predicate
+# Structpath uses syntax of Python for predicate.  That means you must use
+# '==' instead of '='.
+#
 class context(object):
     all_classes = None
     class_instances = None
@@ -49,6 +53,46 @@
     return part == '..'
 
 
+def _split_attr_pred(part):
+    try:
+        lq_idx = part.index('[')
+    except:
+        return part, ''
+    return part[:lq_idx].strip(), part[lq_idx:].strip()
+
+
+def _eval_obj_sub_pred(ctx, obj, sub_pred):
+    ns_global = {}
+    for attr in dir(obj):
+        if attr.startswith('_'):
+            continue
+        v = _obj_attr(obj, attr)
+        ns_global[attr] = v
+        pass
+
+    truth_v = eval(sub_pred, ns_global)
+    return truth_v
+
+
+def _split_pred_into_subs(pred):
+    striped_pred = pred.strip(' []\n\r')
+    subs = striped_pred.split('][')
+    return subs
+
+
+def _eval_obj_pred(ctx, obj, pred):
+    subs = _split_pred_into_subs(pred)
+    
+    for sub in subs:
+        if not sub:
+            continue
+        
+        if not _eval_obj_sub_pred(ctx, obj, sub):
+            return False
+        pass
+    return True
+
+
 def _obj_attr(obj, attrname):
     if isinstance(obj, list):
         idx = int(attrname)
@@ -60,17 +104,22 @@
 
 
 def _handle_path_part_obj(ctx, part, obj):
-    if _is_parent_name(part):
+    attr, pred = _split_attr_pred(part)
+    
+    if _is_parent_name(attr):
         new_objs = [ctx.get_parent(obj)]
-    elif _is_class(part):
-        class_name = _class_name(part)
+    elif _is_class(attr):
+        class_name = _class_name(attr)
         new_objs = ctx.class_instances[class_name]
     else:
         try:
-            new_objs = [_obj_attr(obj, part)]
+            new_objs = [_obj_attr(obj, attr)]
         except AttributeError:
             return []
         pass
+
+    new_objs = filter((lambda x: _eval_obj_pred(ctx, x, pred)), new_objs)
+                   
     return new_objs
 
 
--- a/paraspace/tests/structpath_test.py	Wed Jun 15 02:33:54 2011 +0800
+++ b/paraspace/tests/structpath_test.py	Wed Jun 15 12:03:23 2011 +0800
@@ -6,6 +6,9 @@
 
 
 class wheel(object):
+
+    def num_next(self):
+        return self.num + 1
     pass
 
 
@@ -25,8 +28,11 @@
     root = ctx.root
 
     root.wheels = [wheel(), wheel(), wheel(), wheel()]
+    sn = 1
     for w in root.wheels:
         w.parent = root
+        w.num = sn
+        sn = sn + 1
         pass
     root.handle = handle()
     root.handle.parent = root
@@ -104,3 +110,21 @@
     wheels = structpath.find_objs_path(car_ctx, '.handle/wheels/1')
     assert len(wheels) == 0
     pass
+
+
+def structpath_pred_test():
+    car_ctx = _build_car()
+    root = car_ctx.root
+
+    wheels = structpath.find_objs_path(car_ctx, '.wheel[num == 2]')
+    assert len(wheels) == 1
+    a_wheel = wheels[0]
+    assert a_wheel.num == 2
+    assert a_wheel is root.wheels[1]
+    
+    wheels = structpath.find_objs_path(car_ctx, '.wheel[num_next() == 2]')
+    assert len(wheels) == 1
+    a_wheel = wheels[0]
+    assert a_wheel.num == 1
+    assert a_wheel is root.wheels[0]
+    pass