Add a PathMap class
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e23e380..3dd398c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 1.6.0
+
+* Add a `PathMap` class that uses path equality for its keys.
+
## 1.5.1
* Fix a number of bugs that occurred when the current working directory was `/`
diff --git a/lib/path.dart b/lib/path.dart
index 9885859..7348a34 100644
--- a/lib/path.dart
+++ b/lib/path.dart
@@ -49,6 +49,7 @@
export 'src/context.dart' hide createInternal;
export 'src/path_exception.dart';
+export 'src/path_map.dart';
export 'src/style.dart';
/// A default context for manipulating POSIX paths.
diff --git a/lib/src/path_map.dart b/lib/src/path_map.dart
new file mode 100644
index 0000000..53205ad
--- /dev/null
+++ b/lib/src/path_map.dart
@@ -0,0 +1,38 @@
+// Copyright (c) 2018, 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 '../path.dart' as p;
+
+/// A map whose keys are paths, compared using [equals] and [hash].
+class PathMap<V> extends MapView<String, V> {
+ /// Creates an empty [PathMap] whose keys are compared using `context.equals`
+ /// and `context.hash`.
+ ///
+ /// The [context] defaults to the current path context.
+ PathMap({p.Context context}) : super(_create(context));
+
+ /// Creates a [PathMap] with the same keys and values as [other] whose keys
+ /// are compared using `context.equals` and `context.hash`.
+ ///
+ /// The [context] defaults to the current path context. If multiple keys in
+ /// [other] represent the same logical path, the last key's value will be
+ /// used.
+ PathMap.of(Map<String, V> other, {p.Context context})
+ : super(_create(context)..addAll(other));
+
+ /// Creates a map that uses [context] for equality and hashing.
+ static Map<String, V> _create<V>(p.Context context) {
+ context ??= p.context;
+ return new LinkedHashMap(
+ equals: (path1, path2) {
+ if (path1 == null) return path2 == null;
+ if (path2 == null) return false;
+ return context.equals(path1, path2);
+ },
+ hashCode: (path) => path == null ? 0 : context.hash(path),
+ isValidKey: (path) => path is String || path == null);
+ }
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index 30a8dff..e3c477c 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
name: path
-version: 1.5.1
+version: 1.6.0
author: Dart Team <misc@dartlang.org>
description: >
A string-based path manipulation library. All of the path operations you know
diff --git a/test/path_map_test.dart b/test/path_map_test.dart
new file mode 100644
index 0000000..ce025db
--- /dev/null
+++ b/test/path_map_test.dart
@@ -0,0 +1,80 @@
+// Copyright (c) 2018, 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 'package:test/test.dart';
+
+import 'package:path/path.dart';
+
+void main() {
+ group("considers equal", () {
+ test("two identical paths", () {
+ var map = new PathMap<int>();
+ map[join("foo", "bar")] = 1;
+ map[join("foo", "bar")] = 2;
+ expect(map, hasLength(1));
+ expect(map, containsPair(join("foo", "bar"), 2));
+ });
+
+ test("two logically equivalent paths", () {
+ var map = new PathMap<int>();
+ map["foo"] = 1;
+ map[absolute("foo")] = 2;
+ expect(map, hasLength(1));
+ expect(map, containsPair("foo", 2));
+ expect(map, containsPair(absolute("foo"), 2));
+ });
+
+ test("two nulls", () {
+ var map = new PathMap<int>();
+ map[null] = 1;
+ map[null] = 2;
+ expect(map, hasLength(1));
+ expect(map, containsPair(null, 2));
+ });
+ });
+
+ group("considers unequal", () {
+ test("two distinct paths", () {
+ var map = new PathMap<int>();
+ map["foo"] = 1;
+ map["bar"] = 2;
+ expect(map, hasLength(2));
+ expect(map, containsPair("foo", 1));
+ expect(map, containsPair("bar", 2));
+ });
+
+ test("a path and null", () {
+ var map = new PathMap<int>();
+ map["foo"] = 1;
+ map[null] = 2;
+ expect(map, hasLength(2));
+ expect(map, containsPair("foo", 1));
+ expect(map, containsPair(null, 2));
+ });
+ });
+
+ test("uses the custom context", () {
+ var map = new PathMap<int>(context: windows);
+ map["FOO"] = 1;
+ map["foo"] = 2;
+ expect(map, hasLength(1));
+ expect(map, containsPair("fOo", 2));
+ });
+
+ group(".of()", () {
+ test("copies the existing map's keys", () {
+ var map = new PathMap.of({"foo": 1, "bar": 2});
+ expect(map, hasLength(2));
+ expect(map, containsPair("foo", 1));
+ expect(map, containsPair("bar", 2));
+ });
+
+ test("uses the second value in the case of duplicates", () {
+ var map = new PathMap.of({"foo": 1, absolute("foo"): 2});
+ expect(map, hasLength(1));
+ expect(map, containsPair("foo", 2));
+ expect(map, containsPair(absolute("foo"), 2));
+ });
+ });
+}