1# https://pyrocko.org - GPLv3
2#
3# The Pyrocko Developers, 21st Century
4# ---|P------/S----------~Lg----------
6from __future__ import absolute_import, print_function, division
8import difflib
10from pyrocko.guts import Object, List, clone
11from weakref import ref
14class listdict(dict):
15 def __missing__(self, k):
16 self[k] = []
17 return self[k]
20def root_and_path(obj, name=None):
21 root = obj
22 path = []
23 if name is not None:
24 path.append(name)
26 while True:
27 try:
28 root, name_at_parent = root._talkie_parent
29 path.append(name_at_parent)
30 except AttributeError:
31 break
33 return root, '.'.join(path[::-1])
36def lclone(xs):
37 [clone(x) for x in xs]
40g_uid = 0
43def new_uid():
44 global g_uid
45 g_uid += 1
46 return '#%i' % g_uid
49class Talkie(Object):
51 def __setattr__(self, name, value):
52 try:
53 t = self.T.get_property(name)
54 except ValueError:
55 Object.__setattr__(self, name, value)
56 return
58 if isinstance(t, List.T):
59 value = TalkieList(value)
61 oldvalue = getattr(self, name, None)
62 if oldvalue:
63 if isinstance(oldvalue, (Talkie, TalkieList)):
64 oldvalue.unset_parent()
66 if isinstance(value, (Talkie, TalkieList)):
67 value.set_parent(self, name)
69 Object.__setattr__(self, name, value)
70 self.fire([name], value)
72 def fire(self, path, value):
73 self.fire_event(path, value)
74 if hasattr(self, '_talkie_parent'):
75 root, name_at_parent = self._talkie_parent
76 path.append(name_at_parent)
77 root.fire(path, value)
79 def set_parent(self, parent, name):
80 Object.__setattr__(self, '_talkie_parent', (parent, name))
82 def unset_parent(self):
83 Object.__delattr__(self, '_talkie_parent')
85 def fire_event(self, path, value):
86 pass
88 def diff(self, other, path=()):
89 assert type(self) is type(other), '%s %s' % (type(self), type(other))
91 for (s_prop, s_val), (o_prop, o_val) in zip(
92 self.T.ipropvals(self), other.T.ipropvals(other)):
94 if not s_prop.multivalued:
95 if isinstance(s_val, Talkie) \
96 and type(s_val) is type(o_val):
98 for x in s_val.diff(o_val, path + (s_prop.name,)):
99 yield x
100 else:
101 if not equal(s_val, o_val):
102 yield 'set', path + (s_prop.name,), clone(o_val)
103 else:
104 if issubclass(s_prop.content_t.cls, Talkie):
105 sm = difflib.SequenceMatcher(
106 None,
107 type_eq_proxy_seq(s_val),
108 type_eq_proxy_seq(o_val))
109 mode = 1
111 else:
112 sm = difflib.SequenceMatcher(
113 None,
114 eq_proxy_seq(s_val),
115 eq_proxy_seq(o_val))
116 mode = 2
118 for tag, i1, i2, j1, j2 in list(sm.get_opcodes()):
119 if tag == 'equal' and mode == 1:
120 for koff, (s_element, o_element) in enumerate(zip(
121 s_val[i1:i2], o_val[j1:j2])):
123 for x in s_element.diff(
124 o_element,
125 path + ((s_prop.name, i1+koff),)):
126 yield x
128 if tag == 'replace':
129 yield (
130 'replace',
131 path + ((s_prop.name, i1, i2),),
132 lclone(o_val[j1:j2]))
134 elif tag == 'delete':
135 yield (
136 'delete',
137 path + ((s_prop.name, i1, i2),),
138 None)
140 elif tag == 'insert':
141 yield (
142 'insert',
143 path + ((s_prop.name, i1, i2)),
144 lclone(o_val[j1:j2]))
146 def diff_update(self, other, path=()):
147 assert type(self) is type(other), '%s %s' % (type(self), type(other))
149 for (s_prop, s_val), (o_prop, o_val) in zip(
150 self.T.ipropvals(self), other.T.ipropvals(other)):
152 if not s_prop.multivalued:
153 if isinstance(s_val, Talkie) \
154 and type(s_val) is type(o_val):
156 s_val.diff_update(o_val, path + (s_prop.name,))
157 else:
158 if not equal(s_val, o_val):
159 setattr(self, s_prop.name, clone(o_val))
160 else:
161 if issubclass(s_prop.content_t.cls, Talkie):
162 sm = difflib.SequenceMatcher(
163 None,
164 type_eq_proxy_seq(s_val),
165 type_eq_proxy_seq(o_val))
166 mode = 1
168 else:
169 sm = difflib.SequenceMatcher(
170 None,
171 eq_proxy_seq(s_val),
172 eq_proxy_seq(o_val))
173 mode = 2
175 ioff = 0
176 for tag, i1, i2, j1, j2 in list(sm.get_opcodes()):
177 if tag == 'equal' and mode == 1:
178 for koff, (s_element, o_element) in enumerate(zip(
179 s_val[i1+ioff:i2+ioff], o_val[j1:j2])):
181 s_element.diff_update(
182 o_element,
183 path + ((s_prop.name, i1+ioff+koff),))
185 elif tag == 'replace':
186 for _ in range(i1, i2):
187 s_val.pop(i1+ioff)
189 for j in range(j1, j2):
190 s_val.insert(i1+ioff, clone(o_val[j]))
192 ioff += (j2 - j1) - (i2 - i1)
194 elif tag == 'delete':
195 for _ in range(i1, i2):
196 s_val.pop(i1 + ioff)
198 ioff -= (i2 - i1)
200 elif tag == 'insert':
201 for j in range(j1, j2):
202 s_val.insert(i1+ioff, clone(o_val[j]))
204 ioff += (j2 - j1)
207def equal(a, b):
208 return str(a) == str(b) # to be replaced by recursive guts.equal
211def ghash(obj):
212 return hash(str(obj))
215class GutsEqProxy(object):
216 def __init__(self, obj):
217 self._obj = obj
219 def __eq__(self, other):
220 return equal(self._obj, other._obj)
222 def __hash__(self):
223 return ghash(self._obj)
226class TypeEqProxy(object):
227 def __init__(self, obj):
228 self._obj = obj
230 def __eq__(self, other):
231 return type(self._obj) is type(other._obj) # noqa
233 def __hash__(self):
234 return hash(type(self._obj))
237def eq_proxy_seq(seq):
238 return list(GutsEqProxy(x) for x in seq)
241def type_eq_proxy_seq(seq):
242 return list(TypeEqProxy(x) for x in seq)
245class ListenerRef(object):
246 def __init__(self, talkie_root, listener, path, ref_listener):
247 self._talkie_root = talkie_root
248 self._listener = listener
249 self._path = path
250 self._ref_listener = ref_listener
252 def release(self):
253 self._talkie_root.remove_listener(self)
256class TalkieRoot(Talkie):
258 def __init__(self, **kwargs):
259 self._listeners = listdict()
260 Talkie.__init__(self, **kwargs)
262 def add_listener(self, listener, path=''):
263 ref_listener = ref(listener)
264 self._listeners[path].append(ref_listener)
265 return ListenerRef(self, listener, path, ref_listener)
267 def remove_listener(self, listener_ref):
268 try:
269 self._listeners[listener_ref._path].remove(
270 listener_ref._ref_listener)
271 except ValueError:
272 pass
274 def fire_event(self, path, value):
275 path = '.'.join(path[::-1])
276 # print('fire_event:', path, value)
277 parts = path.split('.')
278 for i in range(len(parts)+1):
279 subpath = '.'.join(parts[:i])
280 target_refs = self._listeners[subpath]
281 delete = []
282 for target_ref in target_refs:
283 target = target_ref()
284 if target:
285 target(path, value)
286 else:
287 delete.append(target_ref)
289 for target_ref in delete:
290 target_refs.remove(target_ref)
292 def get(self, path):
293 x = self
294 for s in path.split('.'):
295 x = getattr(x, s)
297 return x
299 def set(self, path, value):
300 x = self
301 p = path.split('.')
302 for s in p[:-1]:
303 x = getattr(x, s)
305 setattr(x, p[-1], value)
308class TalkieList(list):
310 def fire(self, path, value):
311 if self._talkie_parent:
312 root, name_at_parent = self._talkie_parent
313 path.append(name_at_parent)
314 root.fire(path, value)
316 def set_parent(self, parent, name):
317 list.__setattr__(self, '_talkie_parent', (parent, name))
319 def unset_parent(self):
320 list.__delattr__(self, '_talkie_parent')
322 def append(self, element):
323 retval = list.append(self, element)
324 name = new_uid()
325 if isinstance(element, (Talkie, TalkieList)):
326 element.set_parent(self, name)
328 self.fire([], self)
329 return retval
331 def insert(self, index, element):
332 retval = list.insert(self, index, element)
333 name = new_uid()
334 if isinstance(element, (Talkie, TalkieList)):
335 element.set_parent(self, name)
337 self.fire([], self)
338 return retval
340 def remove(self, element):
341 list.remove(self, element)
342 if isinstance(element, (Talkie, TalkieList)):
343 element.unset_parent()
345 self.fire([], self)
347 def pop(self, index=-1):
348 element = list.pop(self, index)
349 if isinstance(element, (Talkie, TalkieList)):
350 element.unset_parent()
352 self.fire([], self)
353 return element
355 def extend(self, elements):
356 for element in elements:
357 self.append(element)
359 self.fire([], self)
361 def __setitem__(self, key, value):
362 try:
363 element = self[key]
364 if isinstance(element, (Talkie, TalkieList)):
365 element.unset_parent()
367 except IndexError:
368 pass
370 list.__setitem__(self, key, value)
371 self.fire([], self)
373 def __setslice__(self, *args, **kwargs):
374 raise Exception('not implemented')
376 def __iadd__(self, *args, **kwargs):
377 raise Exception('not implemented')
379 def __imul__(self, *args, **kwargs):
380 raise Exception('not implemented')
383for method_name in ['reverse', 'sort']:
385 def x():
386 list_meth = getattr(list, method_name)
388 def meth(self, *args, **kwargs):
389 retval = list_meth(self, *args, **kwargs)
390 self.fire([], self)
391 return retval
393 return meth
395 try:
396 setattr(TalkieList, method_name, x())
397 except AttributeError:
398 pass