-
Notifications
You must be signed in to change notification settings - Fork 29
/
__init__.py
187 lines (156 loc) · 6.96 KB
/
__init__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# -*- coding: utf-8 -*-
from __future__ import unicode_literals, print_function
import itertools as it, operator as op, functools as ft
from collections import defaultdict, OrderedDict, namedtuple
import os, sys, io, yaml
if sys.version_info.major > 2: unicode = str
class PrettyYAMLDumper(yaml.dumper.SafeDumper):
def __init__(self, *args, **kws):
self.pyaml_force_embed = kws.pop('force_embed', False)
self.pyaml_string_val_style = kws.pop('string_val_style', None)
return super(PrettyYAMLDumper, self).__init__(*args, **kws)
def represent_odict(dumper, data):
value = list()
node = yaml.nodes.MappingNode(
'tag:yaml.org,2002:map', value, flow_style=None )
if dumper.alias_key is not None:
dumper.represented_objects[dumper.alias_key] = node
for item_key, item_value in data.items():
node_key = dumper.represent_data(item_key)
node_value = dumper.represent_data(item_value)
value.append((node_key, node_value))
node.flow_style = False
return node
def represent_undefined(dumper, data):
if isinstance(data, tuple) and hasattr(data, '_make') and hasattr(data, '_asdict'):
return dumper.represent_odict(data._asdict()) # assuming namedtuple
elif isinstance(data, OrderedDict): return dumper.represent_odict(data)
elif isinstance(data, dict): return dumper.represent_dict(data)
return super(PrettyYAMLDumper, dumper).represent_undefined(data)
def serialize_node(self, node, parent, index):
if self.pyaml_force_embed: self.serialized_nodes.clear()
return super(PrettyYAMLDumper, self).serialize_node(node, parent, index)
@staticmethod
def pyaml_transliterate(string):
from unidecode import unidecode
string_new = ''
for ch in unidecode(string):
if '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' or ch in '-_': string_new += ch
else: string_new += '_'
return string_new.lower()
def anchor_node(self, node, hint=list()):
if node in self.anchors:
if self.anchors[node] is None and not self.pyaml_force_embed:
self.anchors[node] = self.generate_anchor(node)\
if not hint else '{}'.format(
self.pyaml_transliterate(
'_-_'.join(map(op.attrgetter('value'), hint)) ) )
else:
self.anchors[node] = None
if isinstance(node, yaml.nodes.SequenceNode):
for item in node.value:
self.anchor_node(item)
elif isinstance(node, yaml.nodes.MappingNode):
for key, value in node.value:
self.anchor_node(key)
self.anchor_node(value, hint=hint+[key])
PrettyYAMLDumper.add_representer(defaultdict, PrettyYAMLDumper.represent_dict)
PrettyYAMLDumper.add_representer(OrderedDict, PrettyYAMLDumper.represent_odict)
PrettyYAMLDumper.add_representer(set, PrettyYAMLDumper.represent_list)
PrettyYAMLDumper.add_representer(None, PrettyYAMLDumper.represent_undefined)
class UnsafePrettyYAMLDumper(PrettyYAMLDumper):
def expect_block_sequence(self):
self.increase_indent(flow=False, indentless=False)
self.state = self.expect_first_block_sequence_item
def expect_block_sequence_item(self, first=False):
if not first and isinstance(self.event, yaml.events.SequenceEndEvent):
self.indent = self.indents.pop()
self.state = self.states.pop()
else:
self.write_indent()
self.write_indicator('-', True, indention=True)
self.states.append(self.expect_block_sequence_item)
self.expect_node(sequence=True)
def choose_scalar_style(self):
is_dict_key = self.states[-1] == self.expect_block_mapping_simple_value
if is_dict_key:
# Don't mess-up (replace) styles for dict keys, if possible
if self.pyaml_string_val_style: self.event.style = 'plain'
else:
# Make sure we don't create "key: null" mapping accidentally
if self.event.value.endswith(':'): self.event.style = "'"
return super(UnsafePrettyYAMLDumper, self).choose_scalar_style()\
if self.event.style != 'plain' else ("'" if ' ' in self.event.value else None)
def represent_stringish(dumper, data):
# Will crash on bytestrings with weird chars in them,
# because we can't tell if it's supposed to be e.g. utf-8 readable string
# or an arbitrary binary buffer, and former one *must* be pretty-printed
# PyYAML's Representer.represent_str does the guesswork and !!binary or !!python/str
# Explicit crash on any bytes object might be more sane, but also annoying
# Use something like base64 to encode such buffer values instead
# Having such binary stuff pretty much everywhere on unix (e.g. paths) kinda sucks
data = unicode(data) # read the comment above
# Try to use '|' style for multiline data,
# quoting it with 'literal' if lines are too long anyway,
# not sure if Emitter.analyze_scalar can also provide useful info here
style = dumper.pyaml_string_val_style
if not style:
style = 'plain'
if '\n' in data or not data or data == '-' or data[0] in '!&*[':
style = 'literal'
if '\n' in data[:-1]:
for line in data.splitlines():
if len(line) > dumper.best_width: break
else: style = '|'
return yaml.representer.ScalarNode('tag:yaml.org,2002:str', data, style=style)
for str_type in {bytes, unicode}:
UnsafePrettyYAMLDumper.add_representer(
str_type, UnsafePrettyYAMLDumper.represent_stringish )
UnsafePrettyYAMLDumper.add_representer(
type(None), lambda s,o: s.represent_scalar('tag:yaml.org,2002:null', '') )
def add_representer(*args, **kws):
PrettyYAMLDumper.add_representer(*args, **kws)
UnsafePrettyYAMLDumper.add_representer(*args, **kws)
def dump_add_vspacing(buff, vspacing):
'Post-processing to add some nice-ish spacing for deeper map/list levels.'
if isinstance(vspacing, int):
vspacing = ['\n']*(vspacing+1)
buff.seek(0)
result = list()
for line in buff:
level = 0
line = line.decode('utf-8')
result.append(line)
if ':' in line:
while line.startswith(' '):
level, line = level + 1, line[2:]
if len(vspacing) > level and len(result) != 1:
vspace = vspacing[level]
result.insert( -1, vspace
if not isinstance(vspace, int) else '\n'*vspace )
buff.seek(0), buff.truncate()
buff.write(''.join(result).encode('utf-8'))
def dump( data, dst=unicode, safe=False,
force_embed=False, vspacing=None, string_val_style=None, **pyyaml_kws ):
buff = io.BytesIO()
Dumper = PrettyYAMLDumper if safe else UnsafePrettyYAMLDumper
Dumper = ft.partial(Dumper, force_embed=force_embed, string_val_style=string_val_style)
yaml.dump_all( [data], buff, Dumper=Dumper,
default_flow_style=False, allow_unicode=True, encoding='utf-8', **pyyaml_kws )
if vspacing is not None:
dump_add_vspacing(buff, vspacing)
buff = buff.getvalue()
if dst is bytes: return buff
elif dst is unicode: return buff.decode('utf-8')
else:
try: dst.write(b'') # tests if dst is unicode- or bytestream
except: dst.write(buff.decode('utf-8'))
else: dst.write(buff)
def dumps(data, **dump_kws):
return dump(data, dst=bytes, **dump_kws)
def pprint(*data, **dump_kws):
dst = dump_kws.pop('file', dump_kws.pop('dst', sys.stdout))
if len(data) == 1: data, = data
dump(data, dst=dst, **dump_kws)
p, _p = pprint, print
print = pprint # pyaml.print() won't work without "from __future__ import print_function"