blob: f82bd72b7da996c42d5161d47332c982c6a6794a [file] [log] [blame]
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
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) {
ArgumentError.checkNotNull(map, 'map');
final dummyMap = deepEqualsMap();
dummyMap.addAll(map.nodes);
update(dummyMap);
return wrapAsYamlNode(dummyMap);
}
/// 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 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) {
ArgumentError.checkNotNull(collectionStyle, 'collectionStyle');
return YamlMapWrap(value, collectionStyle: collectionStyle);
} else if (value is List) {
ArgumentError.checkNotNull(collectionStyle, 'collectionStyle');
return YamlListWrap(value, collectionStyle: collectionStyle);
} else {
assertValidScalar(value);
ArgumentError.checkNotNull(scalarStyle, 'scalarStyle');
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) {
ArgumentError.checkNotNull(style, 'scalarStyle');
}
@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<dynamic, YamlNode> nodes;
@override
final SourceSpan span;
factory YamlMapWrap(Map dartMap,
{CollectionStyle collectionStyle = CollectionStyle.ANY,
Object sourceUrl}) {
ArgumentError.checkNotNull(collectionStyle, 'collectionStyle');
final wrappedMap = deepEqualsMap<dynamic, YamlNode>();
for (final entry in dartMap.entries) {
final wrappedKey = wrapAsYamlNode(entry.key);
final wrappedValue = wrapAsYamlNode(entry.value);
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.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<YamlNode> 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,
Object sourceUrl}) {
ArgumentError.checkNotNull(collectionStyle, 'collectionStyle');
final wrappedList = dartList.map(wrapAsYamlNode).toList();
return YamlListWrap._(wrappedList,
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
operator []=(int index, value) {
throw UnsupportedError('Cannot modify an unmodifiable List');
}
@override
List get value => this;
}