|
2 | 2 | from json import JSONDecoder
|
3 | 3 | from typing import Union, Any, List, Optional, Type, Callable, Dict
|
4 | 4 | from pprint import pformat
|
| 5 | +import textwrap |
| 6 | +import re |
5 | 7 |
|
6 | 8 | import yaml
|
7 | 9 | from deprecated.classic import deprecated
|
@@ -279,19 +281,45 @@ def MissingRequiredField(self, field_name: str) -> None:
|
279 | 281 | raise ValueError(f"{field_name} must be supplied")
|
280 | 282 |
|
281 | 283 | def __repr__(self):
|
282 |
| - """Only reformat 1-layer deep to preserve __repr__ of child objects""" |
283 |
| - res = {} |
284 |
| - for key, val in items(self): |
285 |
| - if val == [] or val == {} or val is None: |
286 |
| - continue |
287 |
| - res[key] = val |
288 |
| - return self.__class__.__name__ + '(' + pformat(res, indent=2, |
289 |
| - compact=True, sort_dicts=False) + ')' |
| 284 | + return _pformat(items(self), self.__class__.__name__) |
290 | 285 |
|
291 | 286 | def __str__(self):
|
292 |
| - """Dump everything into a dict, recursively, stringifying it all""" |
293 |
| - res = remove_empty_items(self) |
294 |
| - return self.__class__.__name__ + '(' + pformat(res, indent=2, compact=True) + ')' |
| 287 | + return repr(self) |
| 288 | + |
| 289 | +def _pformat(fields:dict, cls_name:str, indent:str = ' ') -> str: |
| 290 | + """ |
| 291 | + pretty format the fields of the items of a ``YAMLRoot`` object without the wonky indentation of pformat. |
| 292 | + see ``YAMLRoot.__repr__``. |
| 293 | +
|
| 294 | + formatting is similar to black - items at similar levels of nesting have similar levels of indentation, |
| 295 | + rather than getting placed at essentially random levels of indentation depending on what came before them. |
| 296 | + """ |
| 297 | + res = [] |
| 298 | + total_len = 0 |
| 299 | + for key, val in fields: |
| 300 | + if val == [] or val == {} or val is None: |
| 301 | + continue |
| 302 | + # pformat handles everything else that isn't a YAMLRoot object, but it sure does look ugly |
| 303 | + # use it to split lines and as the thing of last resort, but otherwise indent = 0, we'll do that |
| 304 | + val_str = pformat(val, indent=0, compact=True, sort_dicts=False) |
| 305 | + # now we indent everything except the first line by indenting and then using regex to remove just the first indent |
| 306 | + val_str = re.sub(rf'\A{re.escape(indent)}', '', textwrap.indent(val_str, indent)) |
| 307 | + # now recombine with the key in a format that can be re-eval'd into an object if indent is just whitespace |
| 308 | + val_str = f"'{key}': " + val_str |
| 309 | + |
| 310 | + # count the total length of this string so we know if we need to linebreak or not later |
| 311 | + total_len += len(val_str) |
| 312 | + res.append(val_str) |
| 313 | + |
| 314 | + if total_len > 80: |
| 315 | + inside = ',\n'.join(res) |
| 316 | + # we indent twice - once for the inner contents of every inner object, and one to |
| 317 | + # offset from the root element. that keeps us from needing to be recursive except for the |
| 318 | + # single pformat call |
| 319 | + inside = textwrap.indent(inside, indent) |
| 320 | + return cls_name + '({\n' + inside + '\n})' |
| 321 | + else: |
| 322 | + return cls_name + '({' + ', '.join(res) + '})' |
295 | 323 |
|
296 | 324 |
|
297 | 325 | def root_representer(dumper: yaml.Dumper, data: YAMLRoot):
|
|
0 commit comments