1 #=======================================================================
3 __version__
= '''0.0.01'''
4 __sub_version__
= '''20080307022726'''
5 __copyright__
= '''(c) Alex A. Naanou 2008'''
8 #-----------------------------------------------------------------------
10 import pli
.pattern
.mixin
.mapping
as mapping
11 import pli
.pattern
.proxy
.utils
as putils
14 #-----------------------------------------------------------------------
15 # target general features:
16 # - node must be referencable from several places with proper context
17 # handling from each location (no explicit references or
19 # - ability to reference any node in the graph (node path).
20 # - context referencing.
23 # - live template updates to influence all usage (update
24 # template, keep the slots).
25 # - template expansion.
27 # more specific features:
28 # - node type library.
30 # - render/editor backends:
41 #-----------------------------------------------------------------------
42 #--------------------------------------------------------AbstractNode---
43 class AbstractNode(object):
49 #-----------------------------------------------AbstractNodeContainer---
50 class AbstractNodeContainer(AbstractNode
):
56 #-----------------------------------------------------AbstractContext---
57 class AbstractContext(object):
64 #-----------------------------------------------------------------------
65 #----------------------------------------------------------------Node---
66 class Node(AbstractNode
):
72 #-------------------------------------------------------NodeContainer---
73 # must be something like a sorted mapping...
74 # XXX make this a generic mixin and move to an apropriate place...
75 class NodeContainer(AbstractNodeContainer
, Node
, mapping
.Mapping
):
85 def __getitem__(self
, name
):
89 if type(name
) is slice:
90 # support the slice protocol...
91 start
, stop
, step
= name
.start
, name
.stop
, name
.step
92 # normalize the metric...
93 if start
!= None and type(start
) not in (int, long):
94 start
= self
.index(start
)
95 if stop
!= None and type(stop
) not in (int, long):
96 stop
= self
.index(stop
)
97 if step
!= None and type(step
) not in (int, long):
98 raise TypeError, 'slice step bust be either an int or a long.'
100 keys
= self
._order
[slice(start
, stop
, step
)]
102 return [ data
[k
] for k
in keys
]
105 def __setitem__(self
, name
, value
):
108 self
._data
[name
] = value
109 if name
not in self
._order
:
110 # by default add to the end...
111 self
._order
+= [name
]
112 def __delitem__(self
, name
):
116 self
._order
.remove(name
)
120 for n
in self
._order
:
122 # list-like operations...
123 putils
.proxymethods((
129 # specific methods...
130 def insertafter(self
, key
, nkey
, value
):
132 insert an item after key.
134 NOTE: if nkey exists, then move the element.
137 if nkey
in self
._order
:
138 self
._order
.remove(nkey
)
139 self
._order
.insert(i
+1, nkey
)
140 self
._data
[nkey
] = value
141 def insertbefore(self
, key
, nkey
, value
):
145 if nkey
in self
._order
:
146 self
._order
.remove(nkey
)
147 self
._order
.insert(i
, nkey
)
148 self
._data
[nkey
] = value
149 def shift(self
, offset
, key
):
151 move key up or down offset.
156 i
= self
._order
.index(key
)
160 self
._order
.insert(i
+offset
, key
)
161 def keyat(self
, index
):
163 return key at position index.
165 return self
._order
[index
]
166 def valueat(self
, index
):
168 return value at position index.
170 return self
[self
.keyat(index
)]
171 def itemat(self
, index
):
173 return item at position index.
175 k
= self
.keyat(index
)
180 #---------------------------------------------------------NodeContext---
181 # operations to support:
182 # browsing/selecting:
192 # new <node type> <args>
193 # unwrap contents (move contennts up and remove container)
196 # move to <context> (before/after/into)
199 # XXX might be good to add more specific editor interface...
200 # XXX may be a good idea to make this into a mixin compatible with
201 # pli.pattern.proxy.InheritAndOverrideProxy...
202 class NodeContext(AbstractContext
):
204 represents a specific path to a node.
206 supports structural operations as well as node editing and conversion.
208 # basic internal data...
215 node name. this is the key in the container it was accessed from.
217 on write, the new name will be cached in the context and will replace
218 the old key the object was accessed by.
220 fget
=lambda self
: self
._key
,
221 fset
=lambda self
, val
: self
.rename(val
))
222 # context navigation...
223 # XXX should this return a new context or reuse the old one??
233 next context on the same level.
235 parent_node
= self
._parent
._node
236 i
= parent_node
.index(self
._key
)
237 if i
== len(parent_node
) - 1:
238 ##!!! is this correct behaviour?? (return None or raise StopIteration)
239 raise IndexError, 'at last element.'
240 return self
.__create
_context
__(self
._parent
, *parent_node
.itemat(i
+1))
244 previous context on the same level.
246 parent_node
= self
._parent
._node
247 i
= parent_node
.index(self
._key
)
249 ##!!! is this correct behaviour?? (return None or raise StopIteration)
250 raise IndexError, 'at first element.'
251 return self
.__create
_context
__(self
._parent
, *parent_node
.itemat(i
-1))
252 # XXX should this support root ('/')
259 while node
._parent
!= None:
267 return self
._node
.__class
__.__name
__
271 def __init__(self
, parent_context
, key
, node
):
274 self
._parent
= parent_context
277 # XXX do we need this to proxy anything else?
278 # XXX add constructors... (code from testsys/tags?)
279 def __getattr__(self
, name
):
283 return self
.__create
_context
__(self
, name
, self
._node
[name
])
284 __getitem__
= __getattr__
285 # specific interface...
286 def __create_context__(self
, context
, key
, node
):
289 return self
.__class
__(context
, key
, node
)
292 def rename(self
, name
):
295 NOTE: the name is relative to the context. if several containers
296 reference this node, only the one directly above in the
297 current context will be changed.
299 p_node
= self
._parent
._node
300 # XXX possible race condition...
301 p_node
.insertbefore(self
._key
, name
, self
._node
)
302 del p_node
[self
._key
]
304 def shift(self
, offset
):
307 self
._parent
._node
.shift(offset
, self
._key
)
311 del self
._parent
._node
[self
._key
]
315 replace node with it's contents.
317 # XXX check if self is a container...
320 ## def wraprangewith(self, start, end, container):
325 def moveto(self
, context
, key
):
330 def copyto(self
, context
, key
):
335 # proxy functionality to the node...
339 #-----------------------------------------------------------------------
342 # templates are comprised of two parts:
343 # - the template itself.
344 # a structure used by the renderer. defines structure, fields and
345 # rules.... (XXX anything else?)
346 # - the reference or usage of a template.
347 # a template reference or usage is the filled fields for a
348 # template and a reference to it.
349 # * the context sees the expanded tree (but templated nodes are marked).
350 # * templates can be expanded (e.g. turned into normal nodes).
351 class Template(Node
):
358 #-----------------------------------------------------------------------
359 if __name__
== '__main__':
362 context
= NodeContext(None, None, nc
)
367 nc
['text'] = 'some text...'
372 print nc
['c'], nc
.index('c'), nc
.itemat(nc
.index('c'))
380 # and we even support slices! :))))
385 print context
.c
.prev
.name
386 print context
.c
.next
.name
387 print context
.c
.parent
.name
388 print context
.c
.a
.text
.name
389 print context
.c
.a
.text
.parent
.name
392 context
.c
.name
= 'aaa'
393 print context
.aaa
.name
395 context
.aaa
.shift(-2)
402 pages
= NodeContainer()
403 root
= NodeContext(None, None, pages
)
405 # XXX use constructors...
406 pages
['first page'] = NodeContainer()
407 pages
['second page'] = NodeContainer()
408 pages
['third page'] = NodeContainer()
410 pages
['first page']['001'] = NodeContainer()
411 pages
['first page']['001'][001] = NodeContainer()
412 pages
['first page']['002'] = NodeContainer()
413 pages
['first page']['002'][001] = NodeContainer()
414 pages
['first page']['003'] = NodeContainer()
415 pages
['first page']['003'][001] = NodeContainer()
416 pages
['first page']['003'][002] = pages
['second page']
420 #=======================================================================
421 # vim:set ts=4 sw=4 nowrap :