1# https://pyrocko.org - GPLv3 

2# 

3# The Pyrocko Developers, 21st Century 

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

5 

6import difflib 

7 

8from pyrocko.guts import Object, List, clone 

9from weakref import ref 

10 

11 

12class listdict(dict): 

13 def __missing__(self, k): 

14 self[k] = [] 

15 return self[k] 

16 

17 

18def root_and_path(obj, name=None): 

19 root = obj 

20 path = [] 

21 if name is not None: 

22 path.append(name) 

23 

24 while True: 

25 try: 

26 root, name_at_parent = root._talkie_parent 

27 path.append(name_at_parent) 

28 except AttributeError: 

29 break 

30 

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

32 

33 

34def lclone(xs): 

35 [clone(x) for x in xs] 

36 

37 

38g_uid = 0 

39 

40 

41def new_uid(): 

42 global g_uid 

43 g_uid += 1 

44 return '#%i' % g_uid 

45 

46 

47class Talkie(Object): 

48 

49 def __setattr__(self, name, value): 

50 try: 

51 t = self.T.get_property(name) 

52 except ValueError: 

53 Object.__setattr__(self, name, value) 

54 return 

55 

56 if isinstance(t, List.T): 

57 value = TalkieList(value) 

58 

59 oldvalue = getattr(self, name, None) 

60 if oldvalue: 

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

62 oldvalue.unset_parent() 

63 

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

65 value.set_parent(self, name) 

66 

67 Object.__setattr__(self, name, value) 

68 self.fire([name], value) 

69 

70 def fire(self, path, value): 

71 self.fire_event(path, value) 

72 if hasattr(self, '_talkie_parent'): 

73 root, name_at_parent = self._talkie_parent 

74 path.append(name_at_parent) 

75 root.fire(path, value) 

76 

77 def set_parent(self, parent, name): 

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

79 

80 def unset_parent(self): 

81 Object.__delattr__(self, '_talkie_parent') 

82 

83 def fire_event(self, path, value): 

84 pass 

85 

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

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

88 

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

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

91 

92 if not s_prop.multivalued: 

93 if isinstance(s_val, Talkie) \ 

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

95 

96 for x in s_val.diff(o_val, path + (s_prop.name,)): 

97 yield x 

98 else: 

99 if not equal(s_val, o_val): 

100 yield 'set', path + (s_prop.name,), clone(o_val) 

101 else: 

102 if issubclass(s_prop.content_t.cls, Talkie): 

103 sm = difflib.SequenceMatcher( 

104 None, 

105 type_eq_proxy_seq(s_val), 

106 type_eq_proxy_seq(o_val)) 

107 mode = 1 

108 

109 else: 

110 sm = difflib.SequenceMatcher( 

111 None, 

112 eq_proxy_seq(s_val), 

113 eq_proxy_seq(o_val)) 

114 mode = 2 

115 

116 for tag, i1, i2, j1, j2 in list(sm.get_opcodes()): 

117 if tag == 'equal' and mode == 1: 

118 for koff, (s_element, o_element) in enumerate(zip( 

119 s_val[i1:i2], o_val[j1:j2])): 

120 

121 for x in s_element.diff( 

122 o_element, 

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

124 yield x 

125 

126 if tag == 'replace': 

127 yield ( 

128 'replace', 

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

130 lclone(o_val[j1:j2])) 

131 

132 elif tag == 'delete': 

133 yield ( 

134 'delete', 

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

136 None) 

137 

138 elif tag == 'insert': 

139 yield ( 

140 'insert', 

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

142 lclone(o_val[j1:j2])) 

143 

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

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

146 

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

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

149 

150 if not s_prop.multivalued: 

151 if isinstance(s_val, Talkie) \ 

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

153 

154 s_val.diff_update(o_val, path + (s_prop.name,)) 

155 else: 

156 if not equal(s_val, o_val): 

157 setattr(self, s_prop.name, clone(o_val)) 

158 else: 

159 if issubclass(s_prop.content_t.cls, Talkie): 

160 sm = difflib.SequenceMatcher( 

161 None, 

162 type_eq_proxy_seq(s_val), 

163 type_eq_proxy_seq(o_val)) 

164 mode = 1 

165 

166 else: 

167 sm = difflib.SequenceMatcher( 

168 None, 

169 eq_proxy_seq(s_val), 

170 eq_proxy_seq(o_val)) 

171 mode = 2 

172 

173 ioff = 0 

174 for tag, i1, i2, j1, j2 in list(sm.get_opcodes()): 

175 if tag == 'equal' and mode == 1: 

176 for koff, (s_element, o_element) in enumerate(zip( 

177 s_val[i1+ioff:i2+ioff], o_val[j1:j2])): 

178 

179 s_element.diff_update( 

180 o_element, 

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

182 

183 elif tag == 'replace': 

184 for _ in range(i1, i2): 

185 s_val.pop(i1+ioff) 

186 

187 for j in range(j1, j2): 

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

189 

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

191 

192 elif tag == 'delete': 

193 for _ in range(i1, i2): 

194 s_val.pop(i1 + ioff) 

195 

196 ioff -= (i2 - i1) 

197 

198 elif tag == 'insert': 

199 for j in range(j1, j2): 

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

201 

202 ioff += (j2 - j1) 

203 

204 

205def equal(a, b): 

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

207 

208 

209def ghash(obj): 

210 return hash(str(obj)) 

211 

212 

213class GutsEqProxy(object): 

214 def __init__(self, obj): 

215 self._obj = obj 

216 

217 def __eq__(self, other): 

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

219 

220 def __hash__(self): 

221 return ghash(self._obj) 

222 

223 

224class TypeEqProxy(object): 

225 def __init__(self, obj): 

226 self._obj = obj 

227 

228 def __eq__(self, other): 

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

230 

231 def __hash__(self): 

232 return hash(type(self._obj)) 

233 

234 

235def eq_proxy_seq(seq): 

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

237 

238 

239def type_eq_proxy_seq(seq): 

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

241 

242 

243class TalkieConnection(object): 

244 def __init__(self, talkie_root, path, listener): 

245 self._talkie_root = talkie_root 

246 self._listener = listener 

247 self._path = path 

248 self._ref_listener = ref(listener) 

249 

250 def release(self): 

251 self._talkie_root.disconnect(self) 

252 

253 

254class TalkieRoot(Talkie): 

255 

256 def __init__(self, **kwargs): 

257 self._listeners = listdict() 

258 Talkie.__init__(self, **kwargs) 

259 

260 def talkie_connect(self, path, listener): 

261 connection = TalkieConnection(self, path, listener) 

262 self._listeners[path].append(connection._ref_listener) 

263 return connection 

264 

265 def talkie_disconnect(self, connection): 

266 try: 

267 self._listeners[connection._path].remove( 

268 connection._ref_listener) 

269 except ValueError: 

270 pass 

271 

272 def fire_event(self, path, value): 

273 path = '.'.join(path[::-1]) 

274 # print('fire_event:', path, value) 

275 parts = path.split('.') 

276 for i in range(len(parts)+1): 

277 subpath = '.'.join(parts[:i]) 

278 target_refs = self._listeners[subpath] 

279 delete = [] 

280 for target_ref in target_refs: 

281 target = target_ref() 

282 if target: 

283 target(path, value) 

284 else: 

285 delete.append(target_ref) 

286 

287 for target_ref in delete: 

288 target_refs.remove(target_ref) 

289 

290 def get(self, path): 

291 x = self 

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

293 x = getattr(x, s) 

294 

295 return x 

296 

297 def set(self, path, value): 

298 x = self 

299 p = path.split('.') 

300 for s in p[:-1]: 

301 x = getattr(x, s) 

302 

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

304 

305 

306class TalkieList(list): 

307 

308 def fire(self, path, value): 

309 if self._talkie_parent: 

310 root, name_at_parent = self._talkie_parent 

311 path.append(name_at_parent) 

312 root.fire(path, value) 

313 

314 def set_parent(self, parent, name): 

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

316 

317 def unset_parent(self): 

318 list.__delattr__(self, '_talkie_parent') 

319 

320 def append(self, element): 

321 retval = list.append(self, element) 

322 name = new_uid() 

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

324 element.set_parent(self, name) 

325 

326 self.fire([], self) 

327 return retval 

328 

329 def insert(self, index, element): 

330 retval = list.insert(self, index, element) 

331 name = new_uid() 

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

333 element.set_parent(self, name) 

334 

335 self.fire([], self) 

336 return retval 

337 

338 def remove(self, element): 

339 list.remove(self, element) 

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

341 element.unset_parent() 

342 

343 self.fire([], self) 

344 

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

346 element = list.pop(self, index) 

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

348 element.unset_parent() 

349 

350 self.fire([], self) 

351 return element 

352 

353 def extend(self, elements): 

354 for element in elements: 

355 self.append(element) 

356 

357 self.fire([], self) 

358 

359 def __setitem__(self, key, value): 

360 try: 

361 element = self[key] 

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

363 element.unset_parent() 

364 

365 except IndexError: 

366 pass 

367 

368 list.__setitem__(self, key, value) 

369 self.fire([], self) 

370 

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

372 raise Exception('not implemented') 

373 

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

375 raise Exception('not implemented') 

376 

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

378 raise Exception('not implemented') 

379 

380 

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

382 

383 def x(): 

384 list_meth = getattr(list, method_name) 

385 

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

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

388 self.fire([], self) 

389 return retval 

390 

391 return meth 

392 

393 try: 

394 setattr(TalkieList, method_name, x()) 

395 except AttributeError: 

396 pass 

397 

398 

399def drop_args_wrapper(f): 

400 def f_drop_args(*args): 

401 f() 

402 

403 return f_drop_args 

404 

405 

406class TalkieConnectionOwner(object): 

407 def __init__(self): 

408 self._connections = [] 

409 

410 def talkie_connect(self, state, path, listener, drop_args=False): 

411 if drop_args: 

412 listener = drop_args_wrapper(listener) 

413 

414 if not isinstance(path, str): 

415 return [ 

416 self.talkie_connect(state, path_, listener, False) 

417 for path_ in path] 

418 

419 connection = state.talkie_connect(path, listener) 

420 self._connections.append(connection) 

421 return connection 

422 

423 def talkie_disconnect_all(self): 

424 while self._connections: 

425 try: 

426 self._connections.pop().release() 

427 except Exception: 

428 pass