1# https://pyrocko.org - GPLv3 

2# 

3# The Pyrocko Developers, 21st Century 

4# ---|P------/S----------~Lg---------- 

5 

6from __future__ import absolute_import, print_function, division 

7 

8import difflib 

9 

10from pyrocko.guts import Object, List, clone 

11from weakref import ref 

12 

13 

14class listdict(dict): 

15 def __missing__(self, k): 

16 self[k] = [] 

17 return self[k] 

18 

19 

20def root_and_path(obj, name=None): 

21 root = obj 

22 path = [] 

23 if name is not None: 

24 path.append(name) 

25 

26 while True: 

27 try: 

28 root, name_at_parent = root._talkie_parent 

29 path.append(name_at_parent) 

30 except AttributeError: 

31 break 

32 

33 return root, '.'.join(path[::-1]) 

34 

35 

36def lclone(xs): 

37 [clone(x) for x in xs] 

38 

39 

40g_uid = 0 

41 

42 

43def new_uid(): 

44 global g_uid 

45 g_uid += 1 

46 return '#%i' % g_uid 

47 

48 

49class Talkie(Object): 

50 

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 

57 

58 if isinstance(t, List.T): 

59 value = TalkieList(value) 

60 

61 oldvalue = getattr(self, name, None) 

62 if oldvalue: 

63 if isinstance(oldvalue, (Talkie, TalkieList)): 

64 oldvalue.unset_parent() 

65 

66 if isinstance(value, (Talkie, TalkieList)): 

67 value.set_parent(self, name) 

68 

69 Object.__setattr__(self, name, value) 

70 self.fire([name], value) 

71 

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) 

78 

79 def set_parent(self, parent, name): 

80 Object.__setattr__(self, '_talkie_parent', (parent, name)) 

81 

82 def unset_parent(self): 

83 Object.__delattr__(self, '_talkie_parent') 

84 

85 def fire_event(self, path, value): 

86 pass 

87 

88 def diff(self, other, path=()): 

89 assert type(self) is type(other), '%s %s' % (type(self), type(other)) 

90 

91 for (s_prop, s_val), (o_prop, o_val) in zip( 

92 self.T.ipropvals(self), other.T.ipropvals(other)): 

93 

94 if not s_prop.multivalued: 

95 if isinstance(s_val, Talkie) \ 

96 and type(s_val) is type(o_val): 

97 

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 

110 

111 else: 

112 sm = difflib.SequenceMatcher( 

113 None, 

114 eq_proxy_seq(s_val), 

115 eq_proxy_seq(o_val)) 

116 mode = 2 

117 

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])): 

122 

123 for x in s_element.diff( 

124 o_element, 

125 path + ((s_prop.name, i1+koff),)): 

126 yield x 

127 

128 if tag == 'replace': 

129 yield ( 

130 'replace', 

131 path + ((s_prop.name, i1, i2),), 

132 lclone(o_val[j1:j2])) 

133 

134 elif tag == 'delete': 

135 yield ( 

136 'delete', 

137 path + ((s_prop.name, i1, i2),), 

138 None) 

139 

140 elif tag == 'insert': 

141 yield ( 

142 'insert', 

143 path + ((s_prop.name, i1, i2)), 

144 lclone(o_val[j1:j2])) 

145 

146 def diff_update(self, other, path=()): 

147 assert type(self) is type(other), '%s %s' % (type(self), type(other)) 

148 

149 for (s_prop, s_val), (o_prop, o_val) in zip( 

150 self.T.ipropvals(self), other.T.ipropvals(other)): 

151 

152 if not s_prop.multivalued: 

153 if isinstance(s_val, Talkie) \ 

154 and type(s_val) is type(o_val): 

155 

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 

167 

168 else: 

169 sm = difflib.SequenceMatcher( 

170 None, 

171 eq_proxy_seq(s_val), 

172 eq_proxy_seq(o_val)) 

173 mode = 2 

174 

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])): 

180 

181 s_element.diff_update( 

182 o_element, 

183 path + ((s_prop.name, i1+ioff+koff),)) 

184 

185 elif tag == 'replace': 

186 for _ in range(i1, i2): 

187 s_val.pop(i1+ioff) 

188 

189 for j in range(j1, j2): 

190 s_val.insert(i1+ioff, clone(o_val[j])) 

191 

192 ioff += (j2 - j1) - (i2 - i1) 

193 

194 elif tag == 'delete': 

195 for _ in range(i1, i2): 

196 s_val.pop(i1 + ioff) 

197 

198 ioff -= (i2 - i1) 

199 

200 elif tag == 'insert': 

201 for j in range(j1, j2): 

202 s_val.insert(i1+ioff, clone(o_val[j])) 

203 

204 ioff += (j2 - j1) 

205 

206 

207def equal(a, b): 

208 return str(a) == str(b) # to be replaced by recursive guts.equal 

209 

210 

211def ghash(obj): 

212 return hash(str(obj)) 

213 

214 

215class GutsEqProxy(object): 

216 def __init__(self, obj): 

217 self._obj = obj 

218 

219 def __eq__(self, other): 

220 return equal(self._obj, other._obj) 

221 

222 def __hash__(self): 

223 return ghash(self._obj) 

224 

225 

226class TypeEqProxy(object): 

227 def __init__(self, obj): 

228 self._obj = obj 

229 

230 def __eq__(self, other): 

231 return type(self._obj) is type(other._obj) # noqa 

232 

233 def __hash__(self): 

234 return hash(type(self._obj)) 

235 

236 

237def eq_proxy_seq(seq): 

238 return list(GutsEqProxy(x) for x in seq) 

239 

240 

241def type_eq_proxy_seq(seq): 

242 return list(TypeEqProxy(x) for x in seq) 

243 

244 

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 

251 

252 def release(self): 

253 self._talkie_root.remove_listener(self) 

254 

255 

256class TalkieRoot(Talkie): 

257 

258 def __init__(self, **kwargs): 

259 self._listeners = listdict() 

260 Talkie.__init__(self, **kwargs) 

261 

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) 

266 

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 

273 

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) 

288 

289 for target_ref in delete: 

290 target_refs.remove(target_ref) 

291 

292 def get(self, path): 

293 x = self 

294 for s in path.split('.'): 

295 x = getattr(x, s) 

296 

297 return x 

298 

299 def set(self, path, value): 

300 x = self 

301 p = path.split('.') 

302 for s in p[:-1]: 

303 x = getattr(x, s) 

304 

305 setattr(x, p[-1], value) 

306 

307 

308class TalkieList(list): 

309 

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) 

315 

316 def set_parent(self, parent, name): 

317 list.__setattr__(self, '_talkie_parent', (parent, name)) 

318 

319 def unset_parent(self): 

320 list.__delattr__(self, '_talkie_parent') 

321 

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) 

327 

328 self.fire([], self) 

329 return retval 

330 

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) 

336 

337 self.fire([], self) 

338 return retval 

339 

340 def remove(self, element): 

341 list.remove(self, element) 

342 if isinstance(element, (Talkie, TalkieList)): 

343 element.unset_parent() 

344 

345 self.fire([], self) 

346 

347 def pop(self, index=-1): 

348 element = list.pop(self, index) 

349 if isinstance(element, (Talkie, TalkieList)): 

350 element.unset_parent() 

351 

352 self.fire([], self) 

353 return element 

354 

355 def extend(self, elements): 

356 for element in elements: 

357 self.append(element) 

358 

359 self.fire([], self) 

360 

361 def __setitem__(self, key, value): 

362 try: 

363 element = self[key] 

364 if isinstance(element, (Talkie, TalkieList)): 

365 element.unset_parent() 

366 

367 except IndexError: 

368 pass 

369 

370 list.__setitem__(self, key, value) 

371 self.fire([], self) 

372 

373 def __setslice__(self, *args, **kwargs): 

374 raise Exception('not implemented') 

375 

376 def __iadd__(self, *args, **kwargs): 

377 raise Exception('not implemented') 

378 

379 def __imul__(self, *args, **kwargs): 

380 raise Exception('not implemented') 

381 

382 

383for method_name in ['reverse', 'sort']: 

384 

385 def x(): 

386 list_meth = getattr(list, method_name) 

387 

388 def meth(self, *args, **kwargs): 

389 retval = list_meth(self, *args, **kwargs) 

390 self.fire([], self) 

391 return retval 

392 

393 return meth 

394 

395 try: 

396 setattr(TalkieList, method_name, x()) 

397 except AttributeError: 

398 pass