comparison env/lib/python3.7/site-packages/pyaml/__init__.py @ 0:26e78fe6e8c4 draft

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