Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/yaml/emitter.py: 6%

838 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1 

2# Emitter expects events obeying the following grammar: 

3# stream ::= STREAM-START document* STREAM-END 

4# document ::= DOCUMENT-START node DOCUMENT-END 

5# node ::= SCALAR | sequence | mapping 

6# sequence ::= SEQUENCE-START node* SEQUENCE-END 

7# mapping ::= MAPPING-START (node node)* MAPPING-END 

8 

9__all__ = ['Emitter', 'EmitterError'] 

10 

11from .error import YAMLError 

12from .events import * 

13 

14class EmitterError(YAMLError): 

15 pass 

16 

17class ScalarAnalysis: 

18 def __init__(self, scalar, empty, multiline, 

19 allow_flow_plain, allow_block_plain, 

20 allow_single_quoted, allow_double_quoted, 

21 allow_block): 

22 self.scalar = scalar 

23 self.empty = empty 

24 self.multiline = multiline 

25 self.allow_flow_plain = allow_flow_plain 

26 self.allow_block_plain = allow_block_plain 

27 self.allow_single_quoted = allow_single_quoted 

28 self.allow_double_quoted = allow_double_quoted 

29 self.allow_block = allow_block 

30 

31class Emitter: 

32 

33 DEFAULT_TAG_PREFIXES = { 

34 '!' : '!', 

35 'tag:yaml.org,2002:' : '!!', 

36 } 

37 

38 def __init__(self, stream, canonical=None, indent=None, width=None, 

39 allow_unicode=None, line_break=None): 

40 

41 # The stream should have the methods `write` and possibly `flush`. 

42 self.stream = stream 

43 

44 # Encoding can be overridden by STREAM-START. 

45 self.encoding = None 

46 

47 # Emitter is a state machine with a stack of states to handle nested 

48 # structures. 

49 self.states = [] 

50 self.state = self.expect_stream_start 

51 

52 # Current event and the event queue. 

53 self.events = [] 

54 self.event = None 

55 

56 # The current indentation level and the stack of previous indents. 

57 self.indents = [] 

58 self.indent = None 

59 

60 # Flow level. 

61 self.flow_level = 0 

62 

63 # Contexts. 

64 self.root_context = False 

65 self.sequence_context = False 

66 self.mapping_context = False 

67 self.simple_key_context = False 

68 

69 # Characteristics of the last emitted character: 

70 # - current position. 

71 # - is it a whitespace? 

72 # - is it an indention character 

73 # (indentation space, '-', '?', or ':')? 

74 self.line = 0 

75 self.column = 0 

76 self.whitespace = True 

77 self.indention = True 

78 

79 # Whether the document requires an explicit document indicator 

80 self.open_ended = False 

81 

82 # Formatting details. 

83 self.canonical = canonical 

84 self.allow_unicode = allow_unicode 

85 self.best_indent = 2 

86 if indent and 1 < indent < 10: 

87 self.best_indent = indent 

88 self.best_width = 80 

89 if width and width > self.best_indent*2: 

90 self.best_width = width 

91 self.best_line_break = '\n' 

92 if line_break in ['\r', '\n', '\r\n']: 

93 self.best_line_break = line_break 

94 

95 # Tag prefixes. 

96 self.tag_prefixes = None 

97 

98 # Prepared anchor and tag. 

99 self.prepared_anchor = None 

100 self.prepared_tag = None 

101 

102 # Scalar analysis and style. 

103 self.analysis = None 

104 self.style = None 

105 

106 def dispose(self): 

107 # Reset the state attributes (to clear self-references) 

108 self.states = [] 

109 self.state = None 

110 

111 def emit(self, event): 

112 self.events.append(event) 

113 while not self.need_more_events(): 

114 self.event = self.events.pop(0) 

115 self.state() 

116 self.event = None 

117 

118 # In some cases, we wait for a few next events before emitting. 

119 

120 def need_more_events(self): 

121 if not self.events: 

122 return True 

123 event = self.events[0] 

124 if isinstance(event, DocumentStartEvent): 

125 return self.need_events(1) 

126 elif isinstance(event, SequenceStartEvent): 

127 return self.need_events(2) 

128 elif isinstance(event, MappingStartEvent): 

129 return self.need_events(3) 

130 else: 

131 return False 

132 

133 def need_events(self, count): 

134 level = 0 

135 for event in self.events[1:]: 

136 if isinstance(event, (DocumentStartEvent, CollectionStartEvent)): 

137 level += 1 

138 elif isinstance(event, (DocumentEndEvent, CollectionEndEvent)): 

139 level -= 1 

140 elif isinstance(event, StreamEndEvent): 

141 level = -1 

142 if level < 0: 

143 return False 

144 return (len(self.events) < count+1) 

145 

146 def increase_indent(self, flow=False, indentless=False): 

147 self.indents.append(self.indent) 

148 if self.indent is None: 

149 if flow: 

150 self.indent = self.best_indent 

151 else: 

152 self.indent = 0 

153 elif not indentless: 

154 self.indent += self.best_indent 

155 

156 # States. 

157 

158 # Stream handlers. 

159 

160 def expect_stream_start(self): 

161 if isinstance(self.event, StreamStartEvent): 

162 if self.event.encoding and not hasattr(self.stream, 'encoding'): 

163 self.encoding = self.event.encoding 

164 self.write_stream_start() 

165 self.state = self.expect_first_document_start 

166 else: 

167 raise EmitterError("expected StreamStartEvent, but got %s" 

168 % self.event) 

169 

170 def expect_nothing(self): 

171 raise EmitterError("expected nothing, but got %s" % self.event) 

172 

173 # Document handlers. 

174 

175 def expect_first_document_start(self): 

176 return self.expect_document_start(first=True) 

177 

178 def expect_document_start(self, first=False): 

179 if isinstance(self.event, DocumentStartEvent): 

180 if (self.event.version or self.event.tags) and self.open_ended: 

181 self.write_indicator('...', True) 

182 self.write_indent() 

183 if self.event.version: 

184 version_text = self.prepare_version(self.event.version) 

185 self.write_version_directive(version_text) 

186 self.tag_prefixes = self.DEFAULT_TAG_PREFIXES.copy() 

187 if self.event.tags: 

188 handles = sorted(self.event.tags.keys()) 

189 for handle in handles: 

190 prefix = self.event.tags[handle] 

191 self.tag_prefixes[prefix] = handle 

192 handle_text = self.prepare_tag_handle(handle) 

193 prefix_text = self.prepare_tag_prefix(prefix) 

194 self.write_tag_directive(handle_text, prefix_text) 

195 implicit = (first and not self.event.explicit and not self.canonical 

196 and not self.event.version and not self.event.tags 

197 and not self.check_empty_document()) 

198 if not implicit: 

199 self.write_indent() 

200 self.write_indicator('---', True) 

201 if self.canonical: 

202 self.write_indent() 

203 self.state = self.expect_document_root 

204 elif isinstance(self.event, StreamEndEvent): 

205 if self.open_ended: 

206 self.write_indicator('...', True) 

207 self.write_indent() 

208 self.write_stream_end() 

209 self.state = self.expect_nothing 

210 else: 

211 raise EmitterError("expected DocumentStartEvent, but got %s" 

212 % self.event) 

213 

214 def expect_document_end(self): 

215 if isinstance(self.event, DocumentEndEvent): 

216 self.write_indent() 

217 if self.event.explicit: 

218 self.write_indicator('...', True) 

219 self.write_indent() 

220 self.flush_stream() 

221 self.state = self.expect_document_start 

222 else: 

223 raise EmitterError("expected DocumentEndEvent, but got %s" 

224 % self.event) 

225 

226 def expect_document_root(self): 

227 self.states.append(self.expect_document_end) 

228 self.expect_node(root=True) 

229 

230 # Node handlers. 

231 

232 def expect_node(self, root=False, sequence=False, mapping=False, 

233 simple_key=False): 

234 self.root_context = root 

235 self.sequence_context = sequence 

236 self.mapping_context = mapping 

237 self.simple_key_context = simple_key 

238 if isinstance(self.event, AliasEvent): 

239 self.expect_alias() 

240 elif isinstance(self.event, (ScalarEvent, CollectionStartEvent)): 

241 self.process_anchor('&') 

242 self.process_tag() 

243 if isinstance(self.event, ScalarEvent): 

244 self.expect_scalar() 

245 elif isinstance(self.event, SequenceStartEvent): 

246 if self.flow_level or self.canonical or self.event.flow_style \ 

247 or self.check_empty_sequence(): 

248 self.expect_flow_sequence() 

249 else: 

250 self.expect_block_sequence() 

251 elif isinstance(self.event, MappingStartEvent): 

252 if self.flow_level or self.canonical or self.event.flow_style \ 

253 or self.check_empty_mapping(): 

254 self.expect_flow_mapping() 

255 else: 

256 self.expect_block_mapping() 

257 else: 

258 raise EmitterError("expected NodeEvent, but got %s" % self.event) 

259 

260 def expect_alias(self): 

261 if self.event.anchor is None: 

262 raise EmitterError("anchor is not specified for alias") 

263 self.process_anchor('*') 

264 self.state = self.states.pop() 

265 

266 def expect_scalar(self): 

267 self.increase_indent(flow=True) 

268 self.process_scalar() 

269 self.indent = self.indents.pop() 

270 self.state = self.states.pop() 

271 

272 # Flow sequence handlers. 

273 

274 def expect_flow_sequence(self): 

275 self.write_indicator('[', True, whitespace=True) 

276 self.flow_level += 1 

277 self.increase_indent(flow=True) 

278 self.state = self.expect_first_flow_sequence_item 

279 

280 def expect_first_flow_sequence_item(self): 

281 if isinstance(self.event, SequenceEndEvent): 

282 self.indent = self.indents.pop() 

283 self.flow_level -= 1 

284 self.write_indicator(']', False) 

285 self.state = self.states.pop() 

286 else: 

287 if self.canonical or self.column > self.best_width: 

288 self.write_indent() 

289 self.states.append(self.expect_flow_sequence_item) 

290 self.expect_node(sequence=True) 

291 

292 def expect_flow_sequence_item(self): 

293 if isinstance(self.event, SequenceEndEvent): 

294 self.indent = self.indents.pop() 

295 self.flow_level -= 1 

296 if self.canonical: 

297 self.write_indicator(',', False) 

298 self.write_indent() 

299 self.write_indicator(']', False) 

300 self.state = self.states.pop() 

301 else: 

302 self.write_indicator(',', False) 

303 if self.canonical or self.column > self.best_width: 

304 self.write_indent() 

305 self.states.append(self.expect_flow_sequence_item) 

306 self.expect_node(sequence=True) 

307 

308 # Flow mapping handlers. 

309 

310 def expect_flow_mapping(self): 

311 self.write_indicator('{', True, whitespace=True) 

312 self.flow_level += 1 

313 self.increase_indent(flow=True) 

314 self.state = self.expect_first_flow_mapping_key 

315 

316 def expect_first_flow_mapping_key(self): 

317 if isinstance(self.event, MappingEndEvent): 

318 self.indent = self.indents.pop() 

319 self.flow_level -= 1 

320 self.write_indicator('}', False) 

321 self.state = self.states.pop() 

322 else: 

323 if self.canonical or self.column > self.best_width: 

324 self.write_indent() 

325 if not self.canonical and self.check_simple_key(): 

326 self.states.append(self.expect_flow_mapping_simple_value) 

327 self.expect_node(mapping=True, simple_key=True) 

328 else: 

329 self.write_indicator('?', True) 

330 self.states.append(self.expect_flow_mapping_value) 

331 self.expect_node(mapping=True) 

332 

333 def expect_flow_mapping_key(self): 

334 if isinstance(self.event, MappingEndEvent): 

335 self.indent = self.indents.pop() 

336 self.flow_level -= 1 

337 if self.canonical: 

338 self.write_indicator(',', False) 

339 self.write_indent() 

340 self.write_indicator('}', False) 

341 self.state = self.states.pop() 

342 else: 

343 self.write_indicator(',', False) 

344 if self.canonical or self.column > self.best_width: 

345 self.write_indent() 

346 if not self.canonical and self.check_simple_key(): 

347 self.states.append(self.expect_flow_mapping_simple_value) 

348 self.expect_node(mapping=True, simple_key=True) 

349 else: 

350 self.write_indicator('?', True) 

351 self.states.append(self.expect_flow_mapping_value) 

352 self.expect_node(mapping=True) 

353 

354 def expect_flow_mapping_simple_value(self): 

355 self.write_indicator(':', False) 

356 self.states.append(self.expect_flow_mapping_key) 

357 self.expect_node(mapping=True) 

358 

359 def expect_flow_mapping_value(self): 

360 if self.canonical or self.column > self.best_width: 

361 self.write_indent() 

362 self.write_indicator(':', True) 

363 self.states.append(self.expect_flow_mapping_key) 

364 self.expect_node(mapping=True) 

365 

366 # Block sequence handlers. 

367 

368 def expect_block_sequence(self): 

369 indentless = (self.mapping_context and not self.indention) 

370 self.increase_indent(flow=False, indentless=indentless) 

371 self.state = self.expect_first_block_sequence_item 

372 

373 def expect_first_block_sequence_item(self): 

374 return self.expect_block_sequence_item(first=True) 

375 

376 def expect_block_sequence_item(self, first=False): 

377 if not first and isinstance(self.event, SequenceEndEvent): 

378 self.indent = self.indents.pop() 

379 self.state = self.states.pop() 

380 else: 

381 self.write_indent() 

382 self.write_indicator('-', True, indention=True) 

383 self.states.append(self.expect_block_sequence_item) 

384 self.expect_node(sequence=True) 

385 

386 # Block mapping handlers. 

387 

388 def expect_block_mapping(self): 

389 self.increase_indent(flow=False) 

390 self.state = self.expect_first_block_mapping_key 

391 

392 def expect_first_block_mapping_key(self): 

393 return self.expect_block_mapping_key(first=True) 

394 

395 def expect_block_mapping_key(self, first=False): 

396 if not first and isinstance(self.event, MappingEndEvent): 

397 self.indent = self.indents.pop() 

398 self.state = self.states.pop() 

399 else: 

400 self.write_indent() 

401 if self.check_simple_key(): 

402 self.states.append(self.expect_block_mapping_simple_value) 

403 self.expect_node(mapping=True, simple_key=True) 

404 else: 

405 self.write_indicator('?', True, indention=True) 

406 self.states.append(self.expect_block_mapping_value) 

407 self.expect_node(mapping=True) 

408 

409 def expect_block_mapping_simple_value(self): 

410 self.write_indicator(':', False) 

411 self.states.append(self.expect_block_mapping_key) 

412 self.expect_node(mapping=True) 

413 

414 def expect_block_mapping_value(self): 

415 self.write_indent() 

416 self.write_indicator(':', True, indention=True) 

417 self.states.append(self.expect_block_mapping_key) 

418 self.expect_node(mapping=True) 

419 

420 # Checkers. 

421 

422 def check_empty_sequence(self): 

423 return (isinstance(self.event, SequenceStartEvent) and self.events 

424 and isinstance(self.events[0], SequenceEndEvent)) 

425 

426 def check_empty_mapping(self): 

427 return (isinstance(self.event, MappingStartEvent) and self.events 

428 and isinstance(self.events[0], MappingEndEvent)) 

429 

430 def check_empty_document(self): 

431 if not isinstance(self.event, DocumentStartEvent) or not self.events: 

432 return False 

433 event = self.events[0] 

434 return (isinstance(event, ScalarEvent) and event.anchor is None 

435 and event.tag is None and event.implicit and event.value == '') 

436 

437 def check_simple_key(self): 

438 length = 0 

439 if isinstance(self.event, NodeEvent) and self.event.anchor is not None: 

440 if self.prepared_anchor is None: 

441 self.prepared_anchor = self.prepare_anchor(self.event.anchor) 

442 length += len(self.prepared_anchor) 

443 if isinstance(self.event, (ScalarEvent, CollectionStartEvent)) \ 

444 and self.event.tag is not None: 

445 if self.prepared_tag is None: 

446 self.prepared_tag = self.prepare_tag(self.event.tag) 

447 length += len(self.prepared_tag) 

448 if isinstance(self.event, ScalarEvent): 

449 if self.analysis is None: 

450 self.analysis = self.analyze_scalar(self.event.value) 

451 length += len(self.analysis.scalar) 

452 return (length < 128 and (isinstance(self.event, AliasEvent) 

453 or (isinstance(self.event, ScalarEvent) 

454 and not self.analysis.empty and not self.analysis.multiline) 

455 or self.check_empty_sequence() or self.check_empty_mapping())) 

456 

457 # Anchor, Tag, and Scalar processors. 

458 

459 def process_anchor(self, indicator): 

460 if self.event.anchor is None: 

461 self.prepared_anchor = None 

462 return 

463 if self.prepared_anchor is None: 

464 self.prepared_anchor = self.prepare_anchor(self.event.anchor) 

465 if self.prepared_anchor: 

466 self.write_indicator(indicator+self.prepared_anchor, True) 

467 self.prepared_anchor = None 

468 

469 def process_tag(self): 

470 tag = self.event.tag 

471 if isinstance(self.event, ScalarEvent): 

472 if self.style is None: 

473 self.style = self.choose_scalar_style() 

474 if ((not self.canonical or tag is None) and 

475 ((self.style == '' and self.event.implicit[0]) 

476 or (self.style != '' and self.event.implicit[1]))): 

477 self.prepared_tag = None 

478 return 

479 if self.event.implicit[0] and tag is None: 

480 tag = '!' 

481 self.prepared_tag = None 

482 else: 

483 if (not self.canonical or tag is None) and self.event.implicit: 

484 self.prepared_tag = None 

485 return 

486 if tag is None: 

487 raise EmitterError("tag is not specified") 

488 if self.prepared_tag is None: 

489 self.prepared_tag = self.prepare_tag(tag) 

490 if self.prepared_tag: 

491 self.write_indicator(self.prepared_tag, True) 

492 self.prepared_tag = None 

493 

494 def choose_scalar_style(self): 

495 if self.analysis is None: 

496 self.analysis = self.analyze_scalar(self.event.value) 

497 if self.event.style == '"' or self.canonical: 

498 return '"' 

499 if not self.event.style and self.event.implicit[0]: 

500 if (not (self.simple_key_context and 

501 (self.analysis.empty or self.analysis.multiline)) 

502 and (self.flow_level and self.analysis.allow_flow_plain 

503 or (not self.flow_level and self.analysis.allow_block_plain))): 

504 return '' 

505 if self.event.style and self.event.style in '|>': 

506 if (not self.flow_level and not self.simple_key_context 

507 and self.analysis.allow_block): 

508 return self.event.style 

509 if not self.event.style or self.event.style == '\'': 

510 if (self.analysis.allow_single_quoted and 

511 not (self.simple_key_context and self.analysis.multiline)): 

512 return '\'' 

513 return '"' 

514 

515 def process_scalar(self): 

516 if self.analysis is None: 

517 self.analysis = self.analyze_scalar(self.event.value) 

518 if self.style is None: 

519 self.style = self.choose_scalar_style() 

520 split = (not self.simple_key_context) 

521 #if self.analysis.multiline and split \ 

522 # and (not self.style or self.style in '\'\"'): 

523 # self.write_indent() 

524 if self.style == '"': 

525 self.write_double_quoted(self.analysis.scalar, split) 

526 elif self.style == '\'': 

527 self.write_single_quoted(self.analysis.scalar, split) 

528 elif self.style == '>': 

529 self.write_folded(self.analysis.scalar) 

530 elif self.style == '|': 

531 self.write_literal(self.analysis.scalar) 

532 else: 

533 self.write_plain(self.analysis.scalar, split) 

534 self.analysis = None 

535 self.style = None 

536 

537 # Analyzers. 

538 

539 def prepare_version(self, version): 

540 major, minor = version 

541 if major != 1: 

542 raise EmitterError("unsupported YAML version: %d.%d" % (major, minor)) 

543 return '%d.%d' % (major, minor) 

544 

545 def prepare_tag_handle(self, handle): 

546 if not handle: 

547 raise EmitterError("tag handle must not be empty") 

548 if handle[0] != '!' or handle[-1] != '!': 

549 raise EmitterError("tag handle must start and end with '!': %r" % handle) 

550 for ch in handle[1:-1]: 

551 if not ('0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ 

552 or ch in '-_'): 

553 raise EmitterError("invalid character %r in the tag handle: %r" 

554 % (ch, handle)) 

555 return handle 

556 

557 def prepare_tag_prefix(self, prefix): 

558 if not prefix: 

559 raise EmitterError("tag prefix must not be empty") 

560 chunks = [] 

561 start = end = 0 

562 if prefix[0] == '!': 

563 end = 1 

564 while end < len(prefix): 

565 ch = prefix[end] 

566 if '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ 

567 or ch in '-;/?!:@&=+$,_.~*\'()[]': 

568 end += 1 

569 else: 

570 if start < end: 

571 chunks.append(prefix[start:end]) 

572 start = end = end+1 

573 data = ch.encode('utf-8') 

574 for ch in data: 

575 chunks.append('%%%02X' % ord(ch)) 

576 if start < end: 

577 chunks.append(prefix[start:end]) 

578 return ''.join(chunks) 

579 

580 def prepare_tag(self, tag): 

581 if not tag: 

582 raise EmitterError("tag must not be empty") 

583 if tag == '!': 

584 return tag 

585 handle = None 

586 suffix = tag 

587 prefixes = sorted(self.tag_prefixes.keys()) 

588 for prefix in prefixes: 

589 if tag.startswith(prefix) \ 

590 and (prefix == '!' or len(prefix) < len(tag)): 

591 handle = self.tag_prefixes[prefix] 

592 suffix = tag[len(prefix):] 

593 chunks = [] 

594 start = end = 0 

595 while end < len(suffix): 

596 ch = suffix[end] 

597 if '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ 

598 or ch in '-;/?:@&=+$,_.~*\'()[]' \ 

599 or (ch == '!' and handle != '!'): 

600 end += 1 

601 else: 

602 if start < end: 

603 chunks.append(suffix[start:end]) 

604 start = end = end+1 

605 data = ch.encode('utf-8') 

606 for ch in data: 

607 chunks.append('%%%02X' % ch) 

608 if start < end: 

609 chunks.append(suffix[start:end]) 

610 suffix_text = ''.join(chunks) 

611 if handle: 

612 return '%s%s' % (handle, suffix_text) 

613 else: 

614 return '!<%s>' % suffix_text 

615 

616 def prepare_anchor(self, anchor): 

617 if not anchor: 

618 raise EmitterError("anchor must not be empty") 

619 for ch in anchor: 

620 if not ('0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ 

621 or ch in '-_'): 

622 raise EmitterError("invalid character %r in the anchor: %r" 

623 % (ch, anchor)) 

624 return anchor 

625 

626 def analyze_scalar(self, scalar): 

627 

628 # Empty scalar is a special case. 

629 if not scalar: 

630 return ScalarAnalysis(scalar=scalar, empty=True, multiline=False, 

631 allow_flow_plain=False, allow_block_plain=True, 

632 allow_single_quoted=True, allow_double_quoted=True, 

633 allow_block=False) 

634 

635 # Indicators and special characters. 

636 block_indicators = False 

637 flow_indicators = False 

638 line_breaks = False 

639 special_characters = False 

640 

641 # Important whitespace combinations. 

642 leading_space = False 

643 leading_break = False 

644 trailing_space = False 

645 trailing_break = False 

646 break_space = False 

647 space_break = False 

648 

649 # Check document indicators. 

650 if scalar.startswith('---') or scalar.startswith('...'): 

651 block_indicators = True 

652 flow_indicators = True 

653 

654 # First character or preceded by a whitespace. 

655 preceded_by_whitespace = True 

656 

657 # Last character or followed by a whitespace. 

658 followed_by_whitespace = (len(scalar) == 1 or 

659 scalar[1] in '\0 \t\r\n\x85\u2028\u2029') 

660 

661 # The previous character is a space. 

662 previous_space = False 

663 

664 # The previous character is a break. 

665 previous_break = False 

666 

667 index = 0 

668 while index < len(scalar): 

669 ch = scalar[index] 

670 

671 # Check for indicators. 

672 if index == 0: 

673 # Leading indicators are special characters. 

674 if ch in '#,[]{}&*!|>\'\"%@`': 

675 flow_indicators = True 

676 block_indicators = True 

677 if ch in '?:': 

678 flow_indicators = True 

679 if followed_by_whitespace: 

680 block_indicators = True 

681 if ch == '-' and followed_by_whitespace: 

682 flow_indicators = True 

683 block_indicators = True 

684 else: 

685 # Some indicators cannot appear within a scalar as well. 

686 if ch in ',?[]{}': 

687 flow_indicators = True 

688 if ch == ':': 

689 flow_indicators = True 

690 if followed_by_whitespace: 

691 block_indicators = True 

692 if ch == '#' and preceded_by_whitespace: 

693 flow_indicators = True 

694 block_indicators = True 

695 

696 # Check for line breaks, special, and unicode characters. 

697 if ch in '\n\x85\u2028\u2029': 

698 line_breaks = True 

699 if not (ch == '\n' or '\x20' <= ch <= '\x7E'): 

700 if (ch == '\x85' or '\xA0' <= ch <= '\uD7FF' 

701 or '\uE000' <= ch <= '\uFFFD' 

702 or '\U00010000' <= ch < '\U0010ffff') and ch != '\uFEFF': 

703 unicode_characters = True 

704 if not self.allow_unicode: 

705 special_characters = True 

706 else: 

707 special_characters = True 

708 

709 # Detect important whitespace combinations. 

710 if ch == ' ': 

711 if index == 0: 

712 leading_space = True 

713 if index == len(scalar)-1: 

714 trailing_space = True 

715 if previous_break: 

716 break_space = True 

717 previous_space = True 

718 previous_break = False 

719 elif ch in '\n\x85\u2028\u2029': 

720 if index == 0: 

721 leading_break = True 

722 if index == len(scalar)-1: 

723 trailing_break = True 

724 if previous_space: 

725 space_break = True 

726 previous_space = False 

727 previous_break = True 

728 else: 

729 previous_space = False 

730 previous_break = False 

731 

732 # Prepare for the next character. 

733 index += 1 

734 preceded_by_whitespace = (ch in '\0 \t\r\n\x85\u2028\u2029') 

735 followed_by_whitespace = (index+1 >= len(scalar) or 

736 scalar[index+1] in '\0 \t\r\n\x85\u2028\u2029') 

737 

738 # Let's decide what styles are allowed. 

739 allow_flow_plain = True 

740 allow_block_plain = True 

741 allow_single_quoted = True 

742 allow_double_quoted = True 

743 allow_block = True 

744 

745 # Leading and trailing whitespaces are bad for plain scalars. 

746 if (leading_space or leading_break 

747 or trailing_space or trailing_break): 

748 allow_flow_plain = allow_block_plain = False 

749 

750 # We do not permit trailing spaces for block scalars. 

751 if trailing_space: 

752 allow_block = False 

753 

754 # Spaces at the beginning of a new line are only acceptable for block 

755 # scalars. 

756 if break_space: 

757 allow_flow_plain = allow_block_plain = allow_single_quoted = False 

758 

759 # Spaces followed by breaks, as well as special character are only 

760 # allowed for double quoted scalars. 

761 if space_break or special_characters: 

762 allow_flow_plain = allow_block_plain = \ 

763 allow_single_quoted = allow_block = False 

764 

765 # Although the plain scalar writer supports breaks, we never emit 

766 # multiline plain scalars. 

767 if line_breaks: 

768 allow_flow_plain = allow_block_plain = False 

769 

770 # Flow indicators are forbidden for flow plain scalars. 

771 if flow_indicators: 

772 allow_flow_plain = False 

773 

774 # Block indicators are forbidden for block plain scalars. 

775 if block_indicators: 

776 allow_block_plain = False 

777 

778 return ScalarAnalysis(scalar=scalar, 

779 empty=False, multiline=line_breaks, 

780 allow_flow_plain=allow_flow_plain, 

781 allow_block_plain=allow_block_plain, 

782 allow_single_quoted=allow_single_quoted, 

783 allow_double_quoted=allow_double_quoted, 

784 allow_block=allow_block) 

785 

786 # Writers. 

787 

788 def flush_stream(self): 

789 if hasattr(self.stream, 'flush'): 

790 self.stream.flush() 

791 

792 def write_stream_start(self): 

793 # Write BOM if needed. 

794 if self.encoding and self.encoding.startswith('utf-16'): 

795 self.stream.write('\uFEFF'.encode(self.encoding)) 

796 

797 def write_stream_end(self): 

798 self.flush_stream() 

799 

800 def write_indicator(self, indicator, need_whitespace, 

801 whitespace=False, indention=False): 

802 if self.whitespace or not need_whitespace: 

803 data = indicator 

804 else: 

805 data = ' '+indicator 

806 self.whitespace = whitespace 

807 self.indention = self.indention and indention 

808 self.column += len(data) 

809 self.open_ended = False 

810 if self.encoding: 

811 data = data.encode(self.encoding) 

812 self.stream.write(data) 

813 

814 def write_indent(self): 

815 indent = self.indent or 0 

816 if not self.indention or self.column > indent \ 

817 or (self.column == indent and not self.whitespace): 

818 self.write_line_break() 

819 if self.column < indent: 

820 self.whitespace = True 

821 data = ' '*(indent-self.column) 

822 self.column = indent 

823 if self.encoding: 

824 data = data.encode(self.encoding) 

825 self.stream.write(data) 

826 

827 def write_line_break(self, data=None): 

828 if data is None: 

829 data = self.best_line_break 

830 self.whitespace = True 

831 self.indention = True 

832 self.line += 1 

833 self.column = 0 

834 if self.encoding: 

835 data = data.encode(self.encoding) 

836 self.stream.write(data) 

837 

838 def write_version_directive(self, version_text): 

839 data = '%%YAML %s' % version_text 

840 if self.encoding: 

841 data = data.encode(self.encoding) 

842 self.stream.write(data) 

843 self.write_line_break() 

844 

845 def write_tag_directive(self, handle_text, prefix_text): 

846 data = '%%TAG %s %s' % (handle_text, prefix_text) 

847 if self.encoding: 

848 data = data.encode(self.encoding) 

849 self.stream.write(data) 

850 self.write_line_break() 

851 

852 # Scalar streams. 

853 

854 def write_single_quoted(self, text, split=True): 

855 self.write_indicator('\'', True) 

856 spaces = False 

857 breaks = False 

858 start = end = 0 

859 while end <= len(text): 

860 ch = None 

861 if end < len(text): 

862 ch = text[end] 

863 if spaces: 

864 if ch is None or ch != ' ': 

865 if start+1 == end and self.column > self.best_width and split \ 

866 and start != 0 and end != len(text): 

867 self.write_indent() 

868 else: 

869 data = text[start:end] 

870 self.column += len(data) 

871 if self.encoding: 

872 data = data.encode(self.encoding) 

873 self.stream.write(data) 

874 start = end 

875 elif breaks: 

876 if ch is None or ch not in '\n\x85\u2028\u2029': 

877 if text[start] == '\n': 

878 self.write_line_break() 

879 for br in text[start:end]: 

880 if br == '\n': 

881 self.write_line_break() 

882 else: 

883 self.write_line_break(br) 

884 self.write_indent() 

885 start = end 

886 else: 

887 if ch is None or ch in ' \n\x85\u2028\u2029' or ch == '\'': 

888 if start < end: 

889 data = text[start:end] 

890 self.column += len(data) 

891 if self.encoding: 

892 data = data.encode(self.encoding) 

893 self.stream.write(data) 

894 start = end 

895 if ch == '\'': 

896 data = '\'\'' 

897 self.column += 2 

898 if self.encoding: 

899 data = data.encode(self.encoding) 

900 self.stream.write(data) 

901 start = end + 1 

902 if ch is not None: 

903 spaces = (ch == ' ') 

904 breaks = (ch in '\n\x85\u2028\u2029') 

905 end += 1 

906 self.write_indicator('\'', False) 

907 

908 ESCAPE_REPLACEMENTS = { 

909 '\0': '0', 

910 '\x07': 'a', 

911 '\x08': 'b', 

912 '\x09': 't', 

913 '\x0A': 'n', 

914 '\x0B': 'v', 

915 '\x0C': 'f', 

916 '\x0D': 'r', 

917 '\x1B': 'e', 

918 '\"': '\"', 

919 '\\': '\\', 

920 '\x85': 'N', 

921 '\xA0': '_', 

922 '\u2028': 'L', 

923 '\u2029': 'P', 

924 } 

925 

926 def write_double_quoted(self, text, split=True): 

927 self.write_indicator('"', True) 

928 start = end = 0 

929 while end <= len(text): 

930 ch = None 

931 if end < len(text): 

932 ch = text[end] 

933 if ch is None or ch in '"\\\x85\u2028\u2029\uFEFF' \ 

934 or not ('\x20' <= ch <= '\x7E' 

935 or (self.allow_unicode 

936 and ('\xA0' <= ch <= '\uD7FF' 

937 or '\uE000' <= ch <= '\uFFFD'))): 

938 if start < end: 

939 data = text[start:end] 

940 self.column += len(data) 

941 if self.encoding: 

942 data = data.encode(self.encoding) 

943 self.stream.write(data) 

944 start = end 

945 if ch is not None: 

946 if ch in self.ESCAPE_REPLACEMENTS: 

947 data = '\\'+self.ESCAPE_REPLACEMENTS[ch] 

948 elif ch <= '\xFF': 

949 data = '\\x%02X' % ord(ch) 

950 elif ch <= '\uFFFF': 

951 data = '\\u%04X' % ord(ch) 

952 else: 

953 data = '\\U%08X' % ord(ch) 

954 self.column += len(data) 

955 if self.encoding: 

956 data = data.encode(self.encoding) 

957 self.stream.write(data) 

958 start = end+1 

959 if 0 < end < len(text)-1 and (ch == ' ' or start >= end) \ 

960 and self.column+(end-start) > self.best_width and split: 

961 data = text[start:end]+'\\' 

962 if start < end: 

963 start = end 

964 self.column += len(data) 

965 if self.encoding: 

966 data = data.encode(self.encoding) 

967 self.stream.write(data) 

968 self.write_indent() 

969 self.whitespace = False 

970 self.indention = False 

971 if text[start] == ' ': 

972 data = '\\' 

973 self.column += len(data) 

974 if self.encoding: 

975 data = data.encode(self.encoding) 

976 self.stream.write(data) 

977 end += 1 

978 self.write_indicator('"', False) 

979 

980 def determine_block_hints(self, text): 

981 hints = '' 

982 if text: 

983 if text[0] in ' \n\x85\u2028\u2029': 

984 hints += str(self.best_indent) 

985 if text[-1] not in '\n\x85\u2028\u2029': 

986 hints += '-' 

987 elif len(text) == 1 or text[-2] in '\n\x85\u2028\u2029': 

988 hints += '+' 

989 return hints 

990 

991 def write_folded(self, text): 

992 hints = self.determine_block_hints(text) 

993 self.write_indicator('>'+hints, True) 

994 if hints[-1:] == '+': 

995 self.open_ended = True 

996 self.write_line_break() 

997 leading_space = True 

998 spaces = False 

999 breaks = True 

1000 start = end = 0 

1001 while end <= len(text): 

1002 ch = None 

1003 if end < len(text): 

1004 ch = text[end] 

1005 if breaks: 

1006 if ch is None or ch not in '\n\x85\u2028\u2029': 

1007 if not leading_space and ch is not None and ch != ' ' \ 

1008 and text[start] == '\n': 

1009 self.write_line_break() 

1010 leading_space = (ch == ' ') 

1011 for br in text[start:end]: 

1012 if br == '\n': 

1013 self.write_line_break() 

1014 else: 

1015 self.write_line_break(br) 

1016 if ch is not None: 

1017 self.write_indent() 

1018 start = end 

1019 elif spaces: 

1020 if ch != ' ': 

1021 if start+1 == end and self.column > self.best_width: 

1022 self.write_indent() 

1023 else: 

1024 data = text[start:end] 

1025 self.column += len(data) 

1026 if self.encoding: 

1027 data = data.encode(self.encoding) 

1028 self.stream.write(data) 

1029 start = end 

1030 else: 

1031 if ch is None or ch in ' \n\x85\u2028\u2029': 

1032 data = text[start:end] 

1033 self.column += len(data) 

1034 if self.encoding: 

1035 data = data.encode(self.encoding) 

1036 self.stream.write(data) 

1037 if ch is None: 

1038 self.write_line_break() 

1039 start = end 

1040 if ch is not None: 

1041 breaks = (ch in '\n\x85\u2028\u2029') 

1042 spaces = (ch == ' ') 

1043 end += 1 

1044 

1045 def write_literal(self, text): 

1046 hints = self.determine_block_hints(text) 

1047 self.write_indicator('|'+hints, True) 

1048 if hints[-1:] == '+': 

1049 self.open_ended = True 

1050 self.write_line_break() 

1051 breaks = True 

1052 start = end = 0 

1053 while end <= len(text): 

1054 ch = None 

1055 if end < len(text): 

1056 ch = text[end] 

1057 if breaks: 

1058 if ch is None or ch not in '\n\x85\u2028\u2029': 

1059 for br in text[start:end]: 

1060 if br == '\n': 

1061 self.write_line_break() 

1062 else: 

1063 self.write_line_break(br) 

1064 if ch is not None: 

1065 self.write_indent() 

1066 start = end 

1067 else: 

1068 if ch is None or ch in '\n\x85\u2028\u2029': 

1069 data = text[start:end] 

1070 if self.encoding: 

1071 data = data.encode(self.encoding) 

1072 self.stream.write(data) 

1073 if ch is None: 

1074 self.write_line_break() 

1075 start = end 

1076 if ch is not None: 

1077 breaks = (ch in '\n\x85\u2028\u2029') 

1078 end += 1 

1079 

1080 def write_plain(self, text, split=True): 

1081 if self.root_context: 

1082 self.open_ended = True 

1083 if not text: 

1084 return 

1085 if not self.whitespace: 

1086 data = ' ' 

1087 self.column += len(data) 

1088 if self.encoding: 

1089 data = data.encode(self.encoding) 

1090 self.stream.write(data) 

1091 self.whitespace = False 

1092 self.indention = False 

1093 spaces = False 

1094 breaks = False 

1095 start = end = 0 

1096 while end <= len(text): 

1097 ch = None 

1098 if end < len(text): 

1099 ch = text[end] 

1100 if spaces: 

1101 if ch != ' ': 

1102 if start+1 == end and self.column > self.best_width and split: 

1103 self.write_indent() 

1104 self.whitespace = False 

1105 self.indention = False 

1106 else: 

1107 data = text[start:end] 

1108 self.column += len(data) 

1109 if self.encoding: 

1110 data = data.encode(self.encoding) 

1111 self.stream.write(data) 

1112 start = end 

1113 elif breaks: 

1114 if ch not in '\n\x85\u2028\u2029': 

1115 if text[start] == '\n': 

1116 self.write_line_break() 

1117 for br in text[start:end]: 

1118 if br == '\n': 

1119 self.write_line_break() 

1120 else: 

1121 self.write_line_break(br) 

1122 self.write_indent() 

1123 self.whitespace = False 

1124 self.indention = False 

1125 start = end 

1126 else: 

1127 if ch is None or ch in ' \n\x85\u2028\u2029': 

1128 data = text[start:end] 

1129 self.column += len(data) 

1130 if self.encoding: 

1131 data = data.encode(self.encoding) 

1132 self.stream.write(data) 

1133 start = end 

1134 if ch is not None: 

1135 spaces = (ch == ' ') 

1136 breaks = (ch in '\n\x85\u2028\u2029') 

1137 end += 1