added pli.structdoc experimental module for reference and as a starting point.
[p2pB.git] / pli-structdoc / core.py
blobc70b9e4f0715740ebc688aa9a1bdfdd72acf0aa2
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
18 # inclusions).
19 # - ability to reference any node in the graph (node path).
20 # - context referencing.
21 # - root refernecing
22 # - templating:
23 # - live template updates to influence all usage (update
24 # template, keep the slots).
25 # - template expansion.
26 # - editing:
27 # more specific features:
28 # - node type library.
29 # - template library.
30 # - render/editor backends:
31 # - html
32 # - wx
33 # - text/curses
34 # - ...
36 # basic node types:
37 # - container node
38 # - atomic node
39 # - text
41 #-----------------------------------------------------------------------
42 #--------------------------------------------------------AbstractNode---
43 class AbstractNode(object):
44 '''
45 '''
46 pass
49 #-----------------------------------------------AbstractNodeContainer---
50 class AbstractNodeContainer(AbstractNode):
51 '''
52 '''
53 pass
56 #-----------------------------------------------------AbstractContext---
57 class AbstractContext(object):
58 '''
59 '''
60 pass
64 #-----------------------------------------------------------------------
65 #----------------------------------------------------------------Node---
66 class Node(AbstractNode):
67 '''
68 '''
69 pass
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):
76 '''
77 '''
78 _data = None
79 _order = None
80 def __init__(self):
81 '''
82 '''
83 self._data = {}
84 self._order = []
85 def __getitem__(self, name):
86 '''
87 '''
88 data = self._data
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.'
99 # select the keys...
100 keys = self._order[slice(start, stop, step)]
101 # form the result...
102 return [ data[k] for k in keys ]
103 # normal key...
104 return data[name]
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):
115 del self._data[name]
116 self._order.remove(name)
117 def __iter__(self):
120 for n in self._order:
121 yield n
122 # list-like operations...
123 putils.proxymethods((
124 'index',
125 'sort',
126 'reverse'),
127 '_order')
128 # XXX anything more?
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.
136 i = self.index(key)
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):
144 i = self.index(key)
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.
153 if offset == 0:
154 # nothing to do...
155 return
156 i = self._order.index(key)
157 del self._order[i]
158 if offset > 0:
159 offset -= 1
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)
176 return k, self[k]
180 #---------------------------------------------------------NodeContext---
181 # operations to support:
182 # browsing/selecting:
183 # parent
184 # next
185 # prev
186 # get/<context name>
187 # editing:
188 # rename
189 # shift left/right
190 # move up/down
191 # remove
192 # new <node type> <args>
193 # unwrap contents (move contennts up and remove container)
194 # wrap range
195 # clone
196 # move to <context> (before/after/into)
197 # copy to <context>
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...
209 _parent = None
210 _key = None
211 _node = None
212 # introspection...
213 name = property(
214 doc='''\
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.
219 ''',
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??
224 @property
225 def parent(self):
227 parent context.
229 return self._parent
230 @property
231 def next(self):
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))
241 @property
242 def prev(self):
244 previous context on the same level.
246 parent_node = self._parent._node
247 i = parent_node.index(self._key)
248 if i == 0:
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 ('/')
253 @property
254 def path(self):
257 node = self
258 res = []
259 while node._parent != None:
260 res += [node._key]
261 node = node._parent
262 res.reverse()
263 return res
264 # XXX is this good?
265 @property
266 def type(self):
267 return self._node.__class__.__name__
271 def __init__(self, parent_context, key, node):
274 self._parent = parent_context
275 self._key = key
276 self._node = node
277 # XXX do we need this to proxy anything else?
278 # XXX add constructors... (code from testsys/tags?)
279 def __getattr__(self, name):
281 select next context.
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)
291 # editing...
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]
303 self._key = name
304 def shift(self, offset):
307 self._parent._node.shift(offset, self._key)
308 def remove(self):
311 del self._parent._node[self._key]
312 ##!!!
313 def unwrap(self):
315 replace node with it's contents.
317 # XXX check if self is a container...
318 pass
319 ##!!!
320 ## def wraprangewith(self, start, end, container):
321 ## '''
322 ## '''
323 ## pass
324 ##!!!
325 def moveto(self, context, key):
328 pass
329 ##!!!
330 def copyto(self, context, key):
333 pass
335 # proxy functionality to the node...
336 ##!!!
339 #-----------------------------------------------------------------------
340 # tempates...
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):
354 pass
358 #-----------------------------------------------------------------------
359 if __name__ == '__main__':
361 nc = NodeContainer()
362 context = NodeContext(None, None, nc)
364 for k in 'abcdefg':
365 nc[k] = nc
367 nc['text'] = 'some text...'
369 print nc.keys()
370 nc.reverse()
371 print nc.keys()
372 print nc['c'], nc.index('c'), nc.itemat(nc.index('c'))
373 print
374 print nc.keys()
375 nc.shift(10, 'c')
376 print nc.keys()
377 print nc.keyat(4)
378 nc.shift(-2, 'c')
379 print nc.keys()
380 # and we even support slices! :))))
381 print nc['d':'a':2]
382 print
383 print nc.keys()
384 print context.c.name
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
390 print
391 print context.c.name
392 context.c.name = 'aaa'
393 print context.aaa.name
394 print nc.keys()
395 context.aaa.shift(-2)
396 print nc.keys()
397 context.aaa.remove()
398 print nc.keys()
400 print '-'*80
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 :