// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. import 'dart:collection' as collection; import 'package:collection/collection.dart'; import 'package:source_span/source_span.dart'; import 'package:yaml/yaml.dart'; import 'equality.dart'; import 'utils.dart'; /// Returns a new [YamlMap] constructed by applying [update] onto the nodes of /// this [YamlMap]. YamlMap updatedYamlMap(YamlMap map, Function(Map) update) { final dummyMap = deepEqualsMap(); dummyMap.addAll(map.nodes); update(dummyMap); return wrapAsYamlNode(dummyMap) as YamlMap; } /// Wraps [value] into a [YamlNode]. /// /// [Map]s, [List]s and Scalars will be wrapped as [YamlMap]s, [YamlList]s, /// and [YamlScalar]s respectively. If [collectionStyle]/[scalarStyle] is /// defined, and [value] is a collection or scalar, the wrapped [YamlNode] will /// have the respective style, otherwise it defaults to the ANY style. /// /// If [value] is a [Map] or [List], then [wrapAsYamlNode] will be called /// recursively on all children, and [collectionStyle]/[scalarStyle] will be /// applied to any children that are not instances of [YamlNode]. /// /// If a [YamlNode] is passed in, no further wrapping will be done, and the /// [collectionStyle]/[scalarStyle] will not be applied. YamlNode wrapAsYamlNode( Object? value, { CollectionStyle collectionStyle = CollectionStyle.ANY, ScalarStyle scalarStyle = ScalarStyle.ANY, }) { if (value is YamlScalar) { assertValidScalar(value.value); return value; } else if (value is YamlList) { for (final item in value.nodes) { wrapAsYamlNode(item); } return value; } else if (value is YamlMap) { /// Both [entry.key] and [entry.values] are guaranteed to be [YamlNode]s, /// so running this will just assert that they are valid scalars. for (final entry in value.nodes.entries) { wrapAsYamlNode(entry.key); wrapAsYamlNode(entry.value); } return value; } else if (value is Map) { return YamlMapWrap( value, collectionStyle: collectionStyle, scalarStyle: scalarStyle, ); } else if (value is List) { return YamlListWrap( value, collectionStyle: collectionStyle, scalarStyle: scalarStyle, ); } else { assertValidScalar(value); return YamlScalarWrap(value, style: scalarStyle); } } /// Internal class that allows us to define a constructor on [YamlScalar] /// which takes in [style] as an argument. class YamlScalarWrap implements YamlScalar { /// The [ScalarStyle] to be used for the scalar. @override final ScalarStyle style; @override final SourceSpan span; @override final dynamic value; YamlScalarWrap(this.value, {this.style = ScalarStyle.ANY, Object? sourceUrl}) : span = shellSpan(sourceUrl); @override String toString() => value.toString(); } /// Internal class that allows us to define a constructor on [YamlMap] /// which takes in [style] as an argument. class YamlMapWrap with collection.MapMixin, UnmodifiableMapMixin implements YamlMap { /// The [CollectionStyle] to be used for the map. @override final CollectionStyle style; @override final Map nodes; @override final SourceSpan span; factory YamlMapWrap( Map dartMap, { CollectionStyle collectionStyle = CollectionStyle.ANY, ScalarStyle scalarStyle = ScalarStyle.ANY, Object? sourceUrl, }) { final wrappedMap = deepEqualsMap(); for (final entry in dartMap.entries) { final wrappedKey = wrapAsYamlNode( entry.key, collectionStyle: collectionStyle, scalarStyle: scalarStyle, ); final wrappedValue = wrapAsYamlNode( entry.value, collectionStyle: collectionStyle, scalarStyle: scalarStyle, ); wrappedMap[wrappedKey] = wrappedValue; } return YamlMapWrap._( wrappedMap, style: collectionStyle, sourceUrl: sourceUrl, ); } YamlMapWrap._( this.nodes, { CollectionStyle style = CollectionStyle.ANY, Object? sourceUrl, }) : span = shellSpan(sourceUrl), style = nodes.isEmpty ? CollectionStyle.FLOW : style; @override dynamic operator [](Object? key) => nodes[key]?.value; @override Iterable get keys => nodes.keys.map((node) => (node as YamlNode).value); @override Map get value => this; } /// Internal class that allows us to define a constructor on [YamlList] /// which takes in [style] as an argument. class YamlListWrap with collection.ListMixin implements YamlList { /// The [CollectionStyle] to be used for the list. @override final CollectionStyle style; @override final List nodes; @override final SourceSpan span; @override int get length => nodes.length; @override set length(int index) { throw UnsupportedError('Cannot modify an unmodifiable List'); } factory YamlListWrap( List dartList, { CollectionStyle collectionStyle = CollectionStyle.ANY, ScalarStyle scalarStyle = ScalarStyle.ANY, Object? sourceUrl, }) { return YamlListWrap._( dartList .map((v) => wrapAsYamlNode( v, collectionStyle: collectionStyle, scalarStyle: scalarStyle, )) .toList(), style: collectionStyle, sourceUrl: sourceUrl, ); } YamlListWrap._(this.nodes, {CollectionStyle style = CollectionStyle.ANY, Object? sourceUrl}) : span = shellSpan(sourceUrl), style = nodes.isEmpty ? CollectionStyle.FLOW : style; @override dynamic operator [](int index) => nodes[index].value; @override void operator []=(int index, Object? value) { throw UnsupportedError('Cannot modify an unmodifiable List'); } @override List get value => this; }