blob: 4485072976a0d98dbac3808c1455b8119ecf7488 [file] [log] [blame]
// Copyright (c) 2015, 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';
import 'package:yaml/src/event.dart';
import 'package:yaml/yaml.dart';
/// Given a [map], return the [YamlNode] associated with the given [key], or
/// `null` if there is no matching key.
YamlNode getKey(YamlMap map, String key) {
for (YamlNode k in map.nodes.keys) {
if (k is YamlScalar && k.value == key) {
return k;
}
}
return null;
}
/// Given a [map], return the value associated with the key whose value matches
/// the given [key], or `null` if there is no matching key.
YamlNode getValue(YamlMap map, String key) {
for (var k in map.nodes.keys) {
if (k is YamlScalar && k.value == key) {
return map.nodes[k];
}
}
return null;
}
/// If all of the elements of [list] are strings, return a list of strings
/// containing the same elements. Otherwise, return `null`.
List<String> toStringList(List list) {
if (list == null) {
return null;
}
List<String> stringList = <String>[];
for (var element in list) {
if (element is String) {
stringList.add(element);
} else {
return null;
}
}
return stringList;
}
bool _contains(YamlList l1, YamlNode n2) {
for (YamlNode n1 in l1.nodes) {
if (n1.value == n2.value) {
return true;
}
}
return false;
}
/// Merges two maps (of yaml) with simple override semantics, suitable for
/// merging two maps where one map defines default values that are added to
/// (and possibly overridden) by an overriding map.
class Merger {
/// Merges a default [o1] with an overriding object [o2].
///
/// * lists are merged (without duplicates).
/// * lists of scalar values can be promoted to simple maps when merged with
/// maps of strings to booleans (e.g., ['opt1', 'opt2'] becomes
/// {'opt1': true, 'opt2': true}.
/// * maps are merged recursively.
/// * if map values cannot be merged, the overriding value is taken.
///
YamlNode merge(YamlNode o1, YamlNode o2) {
// Handle promotion first.
YamlMap listToMap(YamlList list) {
Map<YamlNode, YamlNode> map = new HashMap<YamlNode,
YamlNode>(); // equals: _equals, hashCode: _hashCode
ScalarEvent event = new ScalarEvent(null, 'true', ScalarStyle.PLAIN);
for (var element in list.nodes) {
map[element] = new YamlScalar.internal(true, event);
}
return new YamlMap.internal(map, null, CollectionStyle.BLOCK);
}
if (isListOfString(o1) && isMapToBools(o2)) {
o1 = listToMap(o1 as YamlList);
} else if (isMapToBools(o1) && isListOfString(o2)) {
o2 = listToMap(o2 as YamlList);
}
if (o1 is YamlMap && o2 is YamlMap) {
return mergeMap(o1, o2);
}
if (o1 is YamlList && o2 is YamlList) {
return mergeList(o1, o2);
}
// Default to override, unless the overriding value is `null`.
return o2 ?? o1;
}
/// Merge lists, avoiding duplicates.
YamlList mergeList(YamlList l1, YamlList l2) {
List<YamlNode> list = <YamlNode>[];
list.addAll(l1.nodes);
for (YamlNode n2 in l2.nodes) {
if (!_contains(l1, n2)) {
list.add(n2);
}
}
return new YamlList.internal(list, null, CollectionStyle.BLOCK);
}
/// Merge maps (recursively).
YamlMap mergeMap(YamlMap m1, YamlMap m2) {
Map<YamlNode, YamlNode> merged = new HashMap<YamlNode,
YamlNode>(); // equals: _equals, hashCode: _hashCode
m1.nodes.forEach((k, v) {
merged[k] = v;
});
m2.nodes.forEach((k, v) {
YamlScalar mergedKey = merged.keys
.firstWhere((key) => key.value == k.value, orElse: () => k);
merged[mergedKey] = merge(merged[mergedKey], v);
});
return new YamlMap.internal(merged, null, CollectionStyle.BLOCK);
}
static bool isListOfString(Object o) =>
o is YamlList &&
o.nodes.every((e) => e is YamlScalar && e.value is String);
static bool isMapToBools(Object o) =>
o is YamlMap &&
o.nodes.values.every((v) => v is YamlScalar && v.value is bool);
}