comments.py 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154
  1. # coding: utf-8
  2. from __future__ import absolute_import, print_function
  3. """
  4. stuff to deal with comments and formatting on dict/list/ordereddict/set
  5. these are not really related, formatting could be factored out as
  6. a separate base
  7. """
  8. import sys
  9. import copy
  10. from ruamel.yaml.compat import ordereddict # type: ignore
  11. from ruamel.yaml.compat import PY2, string_types, MutableSliceableSequence
  12. from ruamel.yaml.scalarstring import ScalarString
  13. from ruamel.yaml.anchor import Anchor
  14. if PY2:
  15. from collections import MutableSet, Sized, Set, Mapping
  16. else:
  17. from collections.abc import MutableSet, Sized, Set, Mapping
  18. if False: # MYPY
  19. from typing import Any, Dict, Optional, List, Union, Optional, Iterator # NOQA
  20. # fmt: off
  21. __all__ = ['CommentedSeq', 'CommentedKeySeq',
  22. 'CommentedMap', 'CommentedOrderedMap',
  23. 'CommentedSet', 'comment_attrib', 'merge_attrib']
  24. # fmt: on
  25. comment_attrib = '_yaml_comment'
  26. format_attrib = '_yaml_format'
  27. line_col_attrib = '_yaml_line_col'
  28. merge_attrib = '_yaml_merge'
  29. tag_attrib = '_yaml_tag'
  30. class Comment(object):
  31. # sys.getsize tested the Comment objects, __slots__ makes them bigger
  32. # and adding self.end did not matter
  33. __slots__ = 'comment', '_items', '_end', '_start'
  34. attrib = comment_attrib
  35. def __init__(self):
  36. # type: () -> None
  37. self.comment = None # [post, [pre]]
  38. # map key (mapping/omap/dict) or index (sequence/list) to a list of
  39. # dict: post_key, pre_key, post_value, pre_value
  40. # list: pre item, post item
  41. self._items = {} # type: Dict[Any, Any]
  42. # self._start = [] # should not put these on first item
  43. self._end = [] # type: List[Any] # end of document comments
  44. def __str__(self):
  45. # type: () -> str
  46. if bool(self._end):
  47. end = ',\n end=' + str(self._end)
  48. else:
  49. end = ""
  50. return 'Comment(comment={0},\n items={1}{2})'.format(self.comment, self._items, end)
  51. @property
  52. def items(self):
  53. # type: () -> Any
  54. return self._items
  55. @property
  56. def end(self):
  57. # type: () -> Any
  58. return self._end
  59. @end.setter
  60. def end(self, value):
  61. # type: (Any) -> None
  62. self._end = value
  63. @property
  64. def start(self):
  65. # type: () -> Any
  66. return self._start
  67. @start.setter
  68. def start(self, value):
  69. # type: (Any) -> None
  70. self._start = value
  71. # to distinguish key from None
  72. def NoComment():
  73. # type: () -> None
  74. pass
  75. class Format(object):
  76. __slots__ = ('_flow_style',)
  77. attrib = format_attrib
  78. def __init__(self):
  79. # type: () -> None
  80. self._flow_style = None # type: Any
  81. def set_flow_style(self):
  82. # type: () -> None
  83. self._flow_style = True
  84. def set_block_style(self):
  85. # type: () -> None
  86. self._flow_style = False
  87. def flow_style(self, default=None):
  88. # type: (Optional[Any]) -> Any
  89. """if default (the flow_style) is None, the flow style tacked on to
  90. the object explicitly will be taken. If that is None as well the
  91. default flow style rules the format down the line, or the type
  92. of the constituent values (simple -> flow, map/list -> block)"""
  93. if self._flow_style is None:
  94. return default
  95. return self._flow_style
  96. class LineCol(object):
  97. attrib = line_col_attrib
  98. def __init__(self):
  99. # type: () -> None
  100. self.line = None
  101. self.col = None
  102. self.data = None # type: Optional[Dict[Any, Any]]
  103. def add_kv_line_col(self, key, data):
  104. # type: (Any, Any) -> None
  105. if self.data is None:
  106. self.data = {}
  107. self.data[key] = data
  108. def key(self, k):
  109. # type: (Any) -> Any
  110. return self._kv(k, 0, 1)
  111. def value(self, k):
  112. # type: (Any) -> Any
  113. return self._kv(k, 2, 3)
  114. def _kv(self, k, x0, x1):
  115. # type: (Any, Any, Any) -> Any
  116. if self.data is None:
  117. return None
  118. data = self.data[k]
  119. return data[x0], data[x1]
  120. def item(self, idx):
  121. # type: (Any) -> Any
  122. if self.data is None:
  123. return None
  124. return self.data[idx][0], self.data[idx][1]
  125. def add_idx_line_col(self, key, data):
  126. # type: (Any, Any) -> None
  127. if self.data is None:
  128. self.data = {}
  129. self.data[key] = data
  130. class Tag(object):
  131. """store tag information for roundtripping"""
  132. __slots__ = ('value',)
  133. attrib = tag_attrib
  134. def __init__(self):
  135. # type: () -> None
  136. self.value = None
  137. def __repr__(self):
  138. # type: () -> Any
  139. return '{0.__class__.__name__}({0.value!r})'.format(self)
  140. class CommentedBase(object):
  141. @property
  142. def ca(self):
  143. # type: () -> Any
  144. if not hasattr(self, Comment.attrib):
  145. setattr(self, Comment.attrib, Comment())
  146. return getattr(self, Comment.attrib)
  147. def yaml_end_comment_extend(self, comment, clear=False):
  148. # type: (Any, bool) -> None
  149. if comment is None:
  150. return
  151. if clear or self.ca.end is None:
  152. self.ca.end = []
  153. self.ca.end.extend(comment)
  154. def yaml_key_comment_extend(self, key, comment, clear=False):
  155. # type: (Any, Any, bool) -> None
  156. r = self.ca._items.setdefault(key, [None, None, None, None])
  157. if clear or r[1] is None:
  158. if comment[1] is not None:
  159. assert isinstance(comment[1], list)
  160. r[1] = comment[1]
  161. else:
  162. r[1].extend(comment[0])
  163. r[0] = comment[0]
  164. def yaml_value_comment_extend(self, key, comment, clear=False):
  165. # type: (Any, Any, bool) -> None
  166. r = self.ca._items.setdefault(key, [None, None, None, None])
  167. if clear or r[3] is None:
  168. if comment[1] is not None:
  169. assert isinstance(comment[1], list)
  170. r[3] = comment[1]
  171. else:
  172. r[3].extend(comment[0])
  173. r[2] = comment[0]
  174. def yaml_set_start_comment(self, comment, indent=0):
  175. # type: (Any, Any) -> None
  176. """overwrites any preceding comment lines on an object
  177. expects comment to be without `#` and possible have multiple lines
  178. """
  179. from .error import CommentMark
  180. from .tokens import CommentToken
  181. pre_comments = self._yaml_get_pre_comment()
  182. if comment[-1] == '\n':
  183. comment = comment[:-1] # strip final newline if there
  184. start_mark = CommentMark(indent)
  185. for com in comment.split('\n'):
  186. c = com.strip()
  187. if len(c) > 0 and c[0] != '#':
  188. com = '# ' + com
  189. pre_comments.append(CommentToken(com + '\n', start_mark, None))
  190. def yaml_set_comment_before_after_key(
  191. self, key, before=None, indent=0, after=None, after_indent=None
  192. ):
  193. # type: (Any, Any, Any, Any, Any) -> None
  194. """
  195. expects comment (before/after) to be without `#` and possible have multiple lines
  196. """
  197. from ruamel.yaml.error import CommentMark
  198. from ruamel.yaml.tokens import CommentToken
  199. def comment_token(s, mark):
  200. # type: (Any, Any) -> Any
  201. # handle empty lines as having no comment
  202. return CommentToken(('# ' if s else "") + s + '\n', mark, None)
  203. if after_indent is None:
  204. after_indent = indent + 2
  205. if before and (len(before) > 1) and before[-1] == '\n':
  206. before = before[:-1] # strip final newline if there
  207. if after and after[-1] == '\n':
  208. after = after[:-1] # strip final newline if there
  209. start_mark = CommentMark(indent)
  210. c = self.ca.items.setdefault(key, [None, [], None, None])
  211. if before == '\n':
  212. c[1].append(comment_token("", start_mark))
  213. elif before:
  214. for com in before.split('\n'):
  215. c[1].append(comment_token(com, start_mark))
  216. if after:
  217. start_mark = CommentMark(after_indent)
  218. if c[3] is None:
  219. c[3] = []
  220. for com in after.split('\n'):
  221. c[3].append(comment_token(com, start_mark)) # type: ignore
  222. @property
  223. def fa(self):
  224. # type: () -> Any
  225. """format attribute
  226. set_flow_style()/set_block_style()"""
  227. if not hasattr(self, Format.attrib):
  228. setattr(self, Format.attrib, Format())
  229. return getattr(self, Format.attrib)
  230. def yaml_add_eol_comment(self, comment, key=NoComment, column=None):
  231. # type: (Any, Optional[Any], Optional[Any]) -> None
  232. """
  233. there is a problem as eol comments should start with ' #'
  234. (but at the beginning of the line the space doesn't have to be before
  235. the #. The column index is for the # mark
  236. """
  237. from .tokens import CommentToken
  238. from .error import CommentMark
  239. if column is None:
  240. try:
  241. column = self._yaml_get_column(key)
  242. except AttributeError:
  243. column = 0
  244. if comment[0] != '#':
  245. comment = '# ' + comment
  246. if column is None:
  247. if comment[0] == '#':
  248. comment = ' ' + comment
  249. column = 0
  250. start_mark = CommentMark(column)
  251. ct = [CommentToken(comment, start_mark, None), None]
  252. self._yaml_add_eol_comment(ct, key=key)
  253. @property
  254. def lc(self):
  255. # type: () -> Any
  256. if not hasattr(self, LineCol.attrib):
  257. setattr(self, LineCol.attrib, LineCol())
  258. return getattr(self, LineCol.attrib)
  259. def _yaml_set_line_col(self, line, col):
  260. # type: (Any, Any) -> None
  261. self.lc.line = line
  262. self.lc.col = col
  263. def _yaml_set_kv_line_col(self, key, data):
  264. # type: (Any, Any) -> None
  265. self.lc.add_kv_line_col(key, data)
  266. def _yaml_set_idx_line_col(self, key, data):
  267. # type: (Any, Any) -> None
  268. self.lc.add_idx_line_col(key, data)
  269. @property
  270. def anchor(self):
  271. # type: () -> Any
  272. if not hasattr(self, Anchor.attrib):
  273. setattr(self, Anchor.attrib, Anchor())
  274. return getattr(self, Anchor.attrib)
  275. def yaml_anchor(self):
  276. # type: () -> Any
  277. if not hasattr(self, Anchor.attrib):
  278. return None
  279. return self.anchor
  280. def yaml_set_anchor(self, value, always_dump=False):
  281. # type: (Any, bool) -> None
  282. self.anchor.value = value
  283. self.anchor.always_dump = always_dump
  284. @property
  285. def tag(self):
  286. # type: () -> Any
  287. if not hasattr(self, Tag.attrib):
  288. setattr(self, Tag.attrib, Tag())
  289. return getattr(self, Tag.attrib)
  290. def yaml_set_tag(self, value):
  291. # type: (Any) -> None
  292. self.tag.value = value
  293. def copy_attributes(self, t, memo=None):
  294. # type: (Any, Any) -> None
  295. # fmt: off
  296. for a in [Comment.attrib, Format.attrib, LineCol.attrib, Anchor.attrib,
  297. Tag.attrib, merge_attrib]:
  298. if hasattr(self, a):
  299. if memo is not None:
  300. setattr(t, a, copy.deepcopy(getattr(self, a, memo)))
  301. else:
  302. setattr(t, a, getattr(self, a))
  303. # fmt: on
  304. def _yaml_add_eol_comment(self, comment, key):
  305. # type: (Any, Any) -> None
  306. raise NotImplementedError
  307. def _yaml_get_pre_comment(self):
  308. # type: () -> Any
  309. raise NotImplementedError
  310. def _yaml_get_column(self, key):
  311. # type: (Any) -> Any
  312. raise NotImplementedError
  313. class CommentedSeq(MutableSliceableSequence, list, CommentedBase): # type: ignore
  314. __slots__ = (Comment.attrib, '_lst')
  315. def __init__(self, *args, **kw):
  316. # type: (Any, Any) -> None
  317. list.__init__(self, *args, **kw)
  318. def __getsingleitem__(self, idx):
  319. # type: (Any) -> Any
  320. return list.__getitem__(self, idx)
  321. def __setsingleitem__(self, idx, value):
  322. # type: (Any, Any) -> None
  323. # try to preserve the scalarstring type if setting an existing key to a new value
  324. if idx < len(self):
  325. if (
  326. isinstance(value, string_types)
  327. and not isinstance(value, ScalarString)
  328. and isinstance(self[idx], ScalarString)
  329. ):
  330. value = type(self[idx])(value)
  331. list.__setitem__(self, idx, value)
  332. def __delsingleitem__(self, idx=None):
  333. # type: (Any) -> Any
  334. list.__delitem__(self, idx)
  335. self.ca.items.pop(idx, None) # might not be there -> default value
  336. for list_index in sorted(self.ca.items):
  337. if list_index < idx:
  338. continue
  339. self.ca.items[list_index - 1] = self.ca.items.pop(list_index)
  340. def __len__(self):
  341. # type: () -> int
  342. return list.__len__(self)
  343. def insert(self, idx, val):
  344. # type: (Any, Any) -> None
  345. """the comments after the insertion have to move forward"""
  346. list.insert(self, idx, val)
  347. for list_index in sorted(self.ca.items, reverse=True):
  348. if list_index < idx:
  349. break
  350. self.ca.items[list_index + 1] = self.ca.items.pop(list_index)
  351. def extend(self, val):
  352. # type: (Any) -> None
  353. list.extend(self, val)
  354. def __eq__(self, other):
  355. # type: (Any) -> bool
  356. return list.__eq__(self, other)
  357. def _yaml_add_comment(self, comment, key=NoComment):
  358. # type: (Any, Optional[Any]) -> None
  359. if key is not NoComment:
  360. self.yaml_key_comment_extend(key, comment)
  361. else:
  362. self.ca.comment = comment
  363. def _yaml_add_eol_comment(self, comment, key):
  364. # type: (Any, Any) -> None
  365. self._yaml_add_comment(comment, key=key)
  366. def _yaml_get_columnX(self, key):
  367. # type: (Any) -> Any
  368. return self.ca.items[key][0].start_mark.column
  369. def _yaml_get_column(self, key):
  370. # type: (Any) -> Any
  371. column = None
  372. sel_idx = None
  373. pre, post = key - 1, key + 1
  374. if pre in self.ca.items:
  375. sel_idx = pre
  376. elif post in self.ca.items:
  377. sel_idx = post
  378. else:
  379. # self.ca.items is not ordered
  380. for row_idx, _k1 in enumerate(self):
  381. if row_idx >= key:
  382. break
  383. if row_idx not in self.ca.items:
  384. continue
  385. sel_idx = row_idx
  386. if sel_idx is not None:
  387. column = self._yaml_get_columnX(sel_idx)
  388. return column
  389. def _yaml_get_pre_comment(self):
  390. # type: () -> Any
  391. pre_comments = [] # type: List[Any]
  392. if self.ca.comment is None:
  393. self.ca.comment = [None, pre_comments]
  394. else:
  395. self.ca.comment[1] = pre_comments
  396. return pre_comments
  397. def __deepcopy__(self, memo):
  398. # type: (Any) -> Any
  399. res = self.__class__()
  400. memo[id(self)] = res
  401. for k in self:
  402. res.append(copy.deepcopy(k, memo))
  403. self.copy_attributes(res, memo=memo)
  404. return res
  405. def __add__(self, other):
  406. # type: (Any) -> Any
  407. return list.__add__(self, other)
  408. def sort(self, key=None, reverse=False): # type: ignore
  409. # type: (Any, bool) -> None
  410. if key is None:
  411. tmp_lst = sorted(zip(self, range(len(self))), reverse=reverse)
  412. list.__init__(self, [x[0] for x in tmp_lst])
  413. else:
  414. tmp_lst = sorted(
  415. zip(map(key, list.__iter__(self)), range(len(self))), reverse=reverse
  416. )
  417. list.__init__(self, [list.__getitem__(self, x[1]) for x in tmp_lst])
  418. itm = self.ca.items
  419. self.ca._items = {}
  420. for idx, x in enumerate(tmp_lst):
  421. old_index = x[1]
  422. if old_index in itm:
  423. self.ca.items[idx] = itm[old_index]
  424. def __repr__(self):
  425. # type: () -> Any
  426. return list.__repr__(self)
  427. class CommentedKeySeq(tuple, CommentedBase): # type: ignore
  428. """This primarily exists to be able to roundtrip keys that are sequences"""
  429. def _yaml_add_comment(self, comment, key=NoComment):
  430. # type: (Any, Optional[Any]) -> None
  431. if key is not NoComment:
  432. self.yaml_key_comment_extend(key, comment)
  433. else:
  434. self.ca.comment = comment
  435. def _yaml_add_eol_comment(self, comment, key):
  436. # type: (Any, Any) -> None
  437. self._yaml_add_comment(comment, key=key)
  438. def _yaml_get_columnX(self, key):
  439. # type: (Any) -> Any
  440. return self.ca.items[key][0].start_mark.column
  441. def _yaml_get_column(self, key):
  442. # type: (Any) -> Any
  443. column = None
  444. sel_idx = None
  445. pre, post = key - 1, key + 1
  446. if pre in self.ca.items:
  447. sel_idx = pre
  448. elif post in self.ca.items:
  449. sel_idx = post
  450. else:
  451. # self.ca.items is not ordered
  452. for row_idx, _k1 in enumerate(self):
  453. if row_idx >= key:
  454. break
  455. if row_idx not in self.ca.items:
  456. continue
  457. sel_idx = row_idx
  458. if sel_idx is not None:
  459. column = self._yaml_get_columnX(sel_idx)
  460. return column
  461. def _yaml_get_pre_comment(self):
  462. # type: () -> Any
  463. pre_comments = [] # type: List[Any]
  464. if self.ca.comment is None:
  465. self.ca.comment = [None, pre_comments]
  466. else:
  467. self.ca.comment[1] = pre_comments
  468. return pre_comments
  469. class CommentedMapView(Sized):
  470. __slots__ = ('_mapping',)
  471. def __init__(self, mapping):
  472. # type: (Any) -> None
  473. self._mapping = mapping
  474. def __len__(self):
  475. # type: () -> int
  476. count = len(self._mapping)
  477. return count
  478. class CommentedMapKeysView(CommentedMapView, Set): # type: ignore
  479. __slots__ = ()
  480. @classmethod
  481. def _from_iterable(self, it):
  482. # type: (Any) -> Any
  483. return set(it)
  484. def __contains__(self, key):
  485. # type: (Any) -> Any
  486. return key in self._mapping
  487. def __iter__(self):
  488. # type: () -> Any # yield from self._mapping # not in py27, pypy
  489. # for x in self._mapping._keys():
  490. for x in self._mapping:
  491. yield x
  492. class CommentedMapItemsView(CommentedMapView, Set): # type: ignore
  493. __slots__ = ()
  494. @classmethod
  495. def _from_iterable(self, it):
  496. # type: (Any) -> Any
  497. return set(it)
  498. def __contains__(self, item):
  499. # type: (Any) -> Any
  500. key, value = item
  501. try:
  502. v = self._mapping[key]
  503. except KeyError:
  504. return False
  505. else:
  506. return v == value
  507. def __iter__(self):
  508. # type: () -> Any
  509. for key in self._mapping._keys():
  510. yield (key, self._mapping[key])
  511. class CommentedMapValuesView(CommentedMapView):
  512. __slots__ = ()
  513. def __contains__(self, value):
  514. # type: (Any) -> Any
  515. for key in self._mapping:
  516. if value == self._mapping[key]:
  517. return True
  518. return False
  519. def __iter__(self):
  520. # type: () -> Any
  521. for key in self._mapping._keys():
  522. yield self._mapping[key]
  523. class CommentedMap(ordereddict, CommentedBase): # type: ignore
  524. __slots__ = (Comment.attrib, '_ok', '_ref')
  525. def __init__(self, *args, **kw):
  526. # type: (Any, Any) -> None
  527. self._ok = set() # type: MutableSet[Any] # own keys
  528. self._ref = [] # type: List[CommentedMap]
  529. ordereddict.__init__(self, *args, **kw)
  530. def _yaml_add_comment(self, comment, key=NoComment, value=NoComment):
  531. # type: (Any, Optional[Any], Optional[Any]) -> None
  532. """values is set to key to indicate a value attachment of comment"""
  533. if key is not NoComment:
  534. self.yaml_key_comment_extend(key, comment)
  535. return
  536. if value is not NoComment:
  537. self.yaml_value_comment_extend(value, comment)
  538. else:
  539. self.ca.comment = comment
  540. def _yaml_add_eol_comment(self, comment, key):
  541. # type: (Any, Any) -> None
  542. """add on the value line, with value specified by the key"""
  543. self._yaml_add_comment(comment, value=key)
  544. def _yaml_get_columnX(self, key):
  545. # type: (Any) -> Any
  546. return self.ca.items[key][2].start_mark.column
  547. def _yaml_get_column(self, key):
  548. # type: (Any) -> Any
  549. column = None
  550. sel_idx = None
  551. pre, post, last = None, None, None
  552. for x in self:
  553. if pre is not None and x != key:
  554. post = x
  555. break
  556. if x == key:
  557. pre = last
  558. last = x
  559. if pre in self.ca.items:
  560. sel_idx = pre
  561. elif post in self.ca.items:
  562. sel_idx = post
  563. else:
  564. # self.ca.items is not ordered
  565. for k1 in self:
  566. if k1 >= key:
  567. break
  568. if k1 not in self.ca.items:
  569. continue
  570. sel_idx = k1
  571. if sel_idx is not None:
  572. column = self._yaml_get_columnX(sel_idx)
  573. return column
  574. def _yaml_get_pre_comment(self):
  575. # type: () -> Any
  576. pre_comments = [] # type: List[Any]
  577. if self.ca.comment is None:
  578. self.ca.comment = [None, pre_comments]
  579. else:
  580. self.ca.comment[1] = pre_comments
  581. return pre_comments
  582. def update(self, *vals, **kw):
  583. # type: (Any, Any) -> None
  584. try:
  585. ordereddict.update(self, *vals, **kw)
  586. except TypeError:
  587. # probably a dict that is used
  588. for x in vals[0]:
  589. self[x] = vals[0][x]
  590. try:
  591. self._ok.update(vals.keys()) # type: ignore
  592. except AttributeError:
  593. # assume one argument that is a list/tuple of two element lists/tuples
  594. for x in vals[0]:
  595. self._ok.add(x[0])
  596. if kw:
  597. self._ok.add(*kw.keys())
  598. def insert(self, pos, key, value, comment=None):
  599. # type: (Any, Any, Any, Optional[Any]) -> None
  600. """insert key value into given position
  601. attach comment if provided
  602. """
  603. ordereddict.insert(self, pos, key, value)
  604. self._ok.add(key)
  605. if comment is not None:
  606. self.yaml_add_eol_comment(comment, key=key)
  607. def mlget(self, key, default=None, list_ok=False):
  608. # type: (Any, Any, Any) -> Any
  609. """multi-level get that expects dicts within dicts"""
  610. if not isinstance(key, list):
  611. return self.get(key, default)
  612. # assume that the key is a list of recursively accessible dicts
  613. def get_one_level(key_list, level, d):
  614. # type: (Any, Any, Any) -> Any
  615. if not list_ok:
  616. assert isinstance(d, dict)
  617. if level >= len(key_list):
  618. if level > len(key_list):
  619. raise IndexError
  620. return d[key_list[level - 1]]
  621. return get_one_level(key_list, level + 1, d[key_list[level - 1]])
  622. try:
  623. return get_one_level(key, 1, self)
  624. except KeyError:
  625. return default
  626. except (TypeError, IndexError):
  627. if not list_ok:
  628. raise
  629. return default
  630. def __getitem__(self, key):
  631. # type: (Any) -> Any
  632. try:
  633. return ordereddict.__getitem__(self, key)
  634. except KeyError:
  635. for merged in getattr(self, merge_attrib, []):
  636. if key in merged[1]:
  637. return merged[1][key]
  638. raise
  639. def __setitem__(self, key, value):
  640. # type: (Any, Any) -> None
  641. # try to preserve the scalarstring type if setting an existing key to a new value
  642. if key in self:
  643. if (
  644. isinstance(value, string_types)
  645. and not isinstance(value, ScalarString)
  646. and isinstance(self[key], ScalarString)
  647. ):
  648. value = type(self[key])(value)
  649. ordereddict.__setitem__(self, key, value)
  650. self._ok.add(key)
  651. def _unmerged_contains(self, key):
  652. # type: (Any) -> Any
  653. if key in self._ok:
  654. return True
  655. return None
  656. def __contains__(self, key):
  657. # type: (Any) -> bool
  658. return bool(ordereddict.__contains__(self, key))
  659. def get(self, key, default=None):
  660. # type: (Any, Any) -> Any
  661. try:
  662. return self.__getitem__(key)
  663. except: # NOQA
  664. return default
  665. def __repr__(self):
  666. # type: () -> Any
  667. return ordereddict.__repr__(self).replace('CommentedMap', 'ordereddict')
  668. def non_merged_items(self):
  669. # type: () -> Any
  670. for x in ordereddict.__iter__(self):
  671. if x in self._ok:
  672. yield x, ordereddict.__getitem__(self, x)
  673. def __delitem__(self, key):
  674. # type: (Any) -> None
  675. # for merged in getattr(self, merge_attrib, []):
  676. # if key in merged[1]:
  677. # value = merged[1][key]
  678. # break
  679. # else:
  680. # # not found in merged in stuff
  681. # ordereddict.__delitem__(self, key)
  682. # for referer in self._ref:
  683. # referer.update_key_value(key)
  684. # return
  685. #
  686. # ordereddict.__setitem__(self, key, value) # merge might have different value
  687. # self._ok.discard(key)
  688. self._ok.discard(key)
  689. ordereddict.__delitem__(self, key)
  690. for referer in self._ref:
  691. referer.update_key_value(key)
  692. def __iter__(self):
  693. # type: () -> Any
  694. for x in ordereddict.__iter__(self):
  695. yield x
  696. def _keys(self):
  697. # type: () -> Any
  698. for x in ordereddict.__iter__(self):
  699. yield x
  700. def __len__(self):
  701. # type: () -> int
  702. return int(ordereddict.__len__(self))
  703. def __eq__(self, other):
  704. # type: (Any) -> bool
  705. return bool(dict(self) == other)
  706. if PY2:
  707. def keys(self):
  708. # type: () -> Any
  709. return list(self._keys())
  710. def iterkeys(self):
  711. # type: () -> Any
  712. return self._keys()
  713. def viewkeys(self):
  714. # type: () -> Any
  715. return CommentedMapKeysView(self)
  716. else:
  717. def keys(self):
  718. # type: () -> Any
  719. return CommentedMapKeysView(self)
  720. if PY2:
  721. def _values(self):
  722. # type: () -> Any
  723. for x in ordereddict.__iter__(self):
  724. yield ordereddict.__getitem__(self, x)
  725. def values(self):
  726. # type: () -> Any
  727. return list(self._values())
  728. def itervalues(self):
  729. # type: () -> Any
  730. return self._values()
  731. def viewvalues(self):
  732. # type: () -> Any
  733. return CommentedMapValuesView(self)
  734. else:
  735. def values(self):
  736. # type: () -> Any
  737. return CommentedMapValuesView(self)
  738. def _items(self):
  739. # type: () -> Any
  740. for x in ordereddict.__iter__(self):
  741. yield x, ordereddict.__getitem__(self, x)
  742. if PY2:
  743. def items(self):
  744. # type: () -> Any
  745. return list(self._items())
  746. def iteritems(self):
  747. # type: () -> Any
  748. return self._items()
  749. def viewitems(self):
  750. # type: () -> Any
  751. return CommentedMapItemsView(self)
  752. else:
  753. def items(self):
  754. # type: () -> Any
  755. return CommentedMapItemsView(self)
  756. @property
  757. def merge(self):
  758. # type: () -> Any
  759. if not hasattr(self, merge_attrib):
  760. setattr(self, merge_attrib, [])
  761. return getattr(self, merge_attrib)
  762. def copy(self):
  763. # type: () -> Any
  764. x = type(self)() # update doesn't work
  765. for k, v in self._items():
  766. x[k] = v
  767. self.copy_attributes(x)
  768. return x
  769. def add_referent(self, cm):
  770. # type: (Any) -> None
  771. if cm not in self._ref:
  772. self._ref.append(cm)
  773. def add_yaml_merge(self, value):
  774. # type: (Any) -> None
  775. for v in value:
  776. v[1].add_referent(self)
  777. for k, v in v[1].items():
  778. if ordereddict.__contains__(self, k):
  779. continue
  780. ordereddict.__setitem__(self, k, v)
  781. self.merge.extend(value)
  782. def update_key_value(self, key):
  783. # type: (Any) -> None
  784. if key in self._ok:
  785. return
  786. for v in self.merge:
  787. if key in v[1]:
  788. ordereddict.__setitem__(self, key, v[1][key])
  789. return
  790. ordereddict.__delitem__(self, key)
  791. def __deepcopy__(self, memo):
  792. # type: (Any) -> Any
  793. res = self.__class__()
  794. memo[id(self)] = res
  795. for k in self:
  796. res[k] = copy.deepcopy(self[k], memo)
  797. self.copy_attributes(res, memo=memo)
  798. return res
  799. # based on brownie mappings
  800. @classmethod # type: ignore
  801. def raise_immutable(cls, *args, **kwargs):
  802. # type: (Any, *Any, **Any) -> None
  803. raise TypeError('{} objects are immutable'.format(cls.__name__))
  804. class CommentedKeyMap(CommentedBase, Mapping): # type: ignore
  805. __slots__ = Comment.attrib, '_od'
  806. """This primarily exists to be able to roundtrip keys that are mappings"""
  807. def __init__(self, *args, **kw):
  808. # type: (Any, Any) -> None
  809. if hasattr(self, '_od'):
  810. raise_immutable(self)
  811. try:
  812. self._od = ordereddict(*args, **kw)
  813. except TypeError:
  814. if PY2:
  815. self._od = ordereddict(args[0].items())
  816. else:
  817. raise
  818. __delitem__ = __setitem__ = clear = pop = popitem = setdefault = update = raise_immutable
  819. # need to implement __getitem__, __iter__ and __len__
  820. def __getitem__(self, index):
  821. # type: (Any) -> Any
  822. return self._od[index]
  823. def __iter__(self):
  824. # type: () -> Iterator[Any]
  825. for x in self._od.__iter__():
  826. yield x
  827. def __len__(self):
  828. # type: () -> int
  829. return len(self._od)
  830. def __hash__(self):
  831. # type: () -> Any
  832. return hash(tuple(self.items()))
  833. def __repr__(self):
  834. # type: () -> Any
  835. if not hasattr(self, merge_attrib):
  836. return self._od.__repr__()
  837. return 'ordereddict(' + repr(list(self._od.items())) + ')'
  838. @classmethod
  839. def fromkeys(keys, v=None):
  840. # type: (Any, Any) -> Any
  841. return CommentedKeyMap(dict.fromkeys(keys, v))
  842. def _yaml_add_comment(self, comment, key=NoComment):
  843. # type: (Any, Optional[Any]) -> None
  844. if key is not NoComment:
  845. self.yaml_key_comment_extend(key, comment)
  846. else:
  847. self.ca.comment = comment
  848. def _yaml_add_eol_comment(self, comment, key):
  849. # type: (Any, Any) -> None
  850. self._yaml_add_comment(comment, key=key)
  851. def _yaml_get_columnX(self, key):
  852. # type: (Any) -> Any
  853. return self.ca.items[key][0].start_mark.column
  854. def _yaml_get_column(self, key):
  855. # type: (Any) -> Any
  856. column = None
  857. sel_idx = None
  858. pre, post = key - 1, key + 1
  859. if pre in self.ca.items:
  860. sel_idx = pre
  861. elif post in self.ca.items:
  862. sel_idx = post
  863. else:
  864. # self.ca.items is not ordered
  865. for row_idx, _k1 in enumerate(self):
  866. if row_idx >= key:
  867. break
  868. if row_idx not in self.ca.items:
  869. continue
  870. sel_idx = row_idx
  871. if sel_idx is not None:
  872. column = self._yaml_get_columnX(sel_idx)
  873. return column
  874. def _yaml_get_pre_comment(self):
  875. # type: () -> Any
  876. pre_comments = [] # type: List[Any]
  877. if self.ca.comment is None:
  878. self.ca.comment = [None, pre_comments]
  879. else:
  880. self.ca.comment[1] = pre_comments
  881. return pre_comments
  882. class CommentedOrderedMap(CommentedMap):
  883. __slots__ = (Comment.attrib,)
  884. class CommentedSet(MutableSet, CommentedBase): # type: ignore # NOQA
  885. __slots__ = Comment.attrib, 'odict'
  886. def __init__(self, values=None):
  887. # type: (Any) -> None
  888. self.odict = ordereddict()
  889. MutableSet.__init__(self)
  890. if values is not None:
  891. self |= values # type: ignore
  892. def _yaml_add_comment(self, comment, key=NoComment, value=NoComment):
  893. # type: (Any, Optional[Any], Optional[Any]) -> None
  894. """values is set to key to indicate a value attachment of comment"""
  895. if key is not NoComment:
  896. self.yaml_key_comment_extend(key, comment)
  897. return
  898. if value is not NoComment:
  899. self.yaml_value_comment_extend(value, comment)
  900. else:
  901. self.ca.comment = comment
  902. def _yaml_add_eol_comment(self, comment, key):
  903. # type: (Any, Any) -> None
  904. """add on the value line, with value specified by the key"""
  905. self._yaml_add_comment(comment, value=key)
  906. def add(self, value):
  907. # type: (Any) -> None
  908. """Add an element."""
  909. self.odict[value] = None
  910. def discard(self, value):
  911. # type: (Any) -> None
  912. """Remove an element. Do not raise an exception if absent."""
  913. del self.odict[value]
  914. def __contains__(self, x):
  915. # type: (Any) -> Any
  916. return x in self.odict
  917. def __iter__(self):
  918. # type: () -> Any
  919. for x in self.odict:
  920. yield x
  921. def __len__(self):
  922. # type: () -> int
  923. return len(self.odict)
  924. def __repr__(self):
  925. # type: () -> str
  926. return 'set({0!r})'.format(self.odict.keys())
  927. class TaggedScalar(CommentedBase):
  928. # the value and style attributes are set during roundtrip construction
  929. def __init__(self, value=None, style=None, tag=None):
  930. # type: (Any, Any, Any) -> None
  931. self.value = value
  932. self.style = style
  933. if tag is not None:
  934. self.yaml_set_tag(tag)
  935. def __str__(self):
  936. # type: () -> Any
  937. return self.value
  938. def dump_comments(d, name="", sep='.', out=sys.stdout):
  939. # type: (Any, str, str, Any) -> None
  940. """
  941. recursively dump comments, all but the toplevel preceded by the path
  942. in dotted form x.0.a
  943. """
  944. if isinstance(d, dict) and hasattr(d, 'ca'):
  945. if name:
  946. sys.stdout.write('{}\n'.format(name))
  947. out.write('{}\n'.format(d.ca)) # type: ignore
  948. for k in d:
  949. dump_comments(d[k], name=(name + sep + k) if name else k, sep=sep, out=out)
  950. elif isinstance(d, list) and hasattr(d, 'ca'):
  951. if name:
  952. sys.stdout.write('{}\n'.format(name))
  953. out.write('{}\n'.format(d.ca)) # type: ignore
  954. for idx, k in enumerate(d):
  955. dump_comments(
  956. k, name=(name + sep + str(idx)) if name else str(idx), sep=sep, out=out
  957. )