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));
+    });
+  });
+}