| OLD | NEW |
| (Empty) | |
| 1 library io; |
| 2 |
| 3 import 'dart:collection'; |
| 4 import 'dart:io'; |
| 5 import 'package:pathos/path.dart' as path; |
| 6 |
| 7 // TODO(janicejl): listDir, canonicalize, resolveLink, and linkExists are from |
| 8 // pub/lib/src/io.dart. If the io.dart file becomes a package, should remove |
| 9 // copy of the functions. |
| 10 /// Lists the contents of [dir]. If [recursive] is `true`, lists subdirectory |
| 11 /// contents (defaults to `false`). If [includeHidden] is `true`, includes files |
| 12 /// and directories beginning with `.` (defaults to `false`). |
| 13 /// |
| 14 /// The returned paths are guaranteed to begin with [dir]. |
| 15 List<String> listDir(String dir, {bool recursive: false, |
| 16 bool includeHidden: false}) { |
| 17 List<String> doList(String dir, Set<String> listedDirectories) { |
| 18 var contents = <String>[]; |
| 19 |
| 20 // Avoid recursive symlinks. |
| 21 var resolvedPath = canonicalize(dir); |
| 22 if (listedDirectories.contains(resolvedPath)) return []; |
| 23 |
| 24 listedDirectories = new Set<String>.from(listedDirectories); |
| 25 listedDirectories.add(resolvedPath); |
| 26 |
| 27 var children = <String>[]; |
| 28 for (var entity in new Directory(dir).listSync()) { |
| 29 if (!includeHidden && path.basename(entity.path).startsWith('.')) { |
| 30 continue; |
| 31 } |
| 32 |
| 33 contents.add(entity.path); |
| 34 if (entity is Directory) { |
| 35 // TODO(nweiz): don't manually recurse once issue 4794 is fixed. |
| 36 // Note that once we remove the manual recursion, we'll need to |
| 37 // explicitly filter out files in hidden directories. |
| 38 if (recursive) { |
| 39 children.addAll(doList(entity.path, listedDirectories)); |
| 40 } |
| 41 } |
| 42 } |
| 43 |
| 44 contents.addAll(children); |
| 45 return contents; |
| 46 } |
| 47 |
| 48 return doList(dir, new Set<String>()); |
| 49 } |
| 50 |
| 51 /// Returns the canonical path for [pathString]. This is the normalized, |
| 52 /// absolute path, with symlinks resolved. As in [transitiveTarget], broken or |
| 53 /// recursive symlinks will not be fully resolved. |
| 54 /// |
| 55 /// This doesn't require [pathString] to point to a path that exists on the |
| 56 /// filesystem; nonexistent or unreadable path entries are treated as normal |
| 57 /// directories. |
| 58 String canonicalize(String pathString) { |
| 59 var seen = new Set<String>(); |
| 60 var components = new Queue<String>.from( |
| 61 path.split(path.normalize(path.absolute(pathString)))); |
| 62 |
| 63 // The canonical path, built incrementally as we iterate through [components]. |
| 64 var newPath = components.removeFirst(); |
| 65 |
| 66 // Move through the components of the path, resolving each one's symlinks as |
| 67 // necessary. A resolved component may also add new components that need to be |
| 68 // resolved in turn. |
| 69 while (!components.isEmpty) { |
| 70 seen.add(path.join(newPath, path.joinAll(components))); |
| 71 var resolvedPath = resolveLink( |
| 72 path.join(newPath, components.removeFirst())); |
| 73 var relative = path.relative(resolvedPath, from: newPath); |
| 74 |
| 75 // If the resolved path of the component relative to `newPath` is just ".", |
| 76 // that means component was a symlink pointing to its parent directory. We |
| 77 // can safely ignore such components. |
| 78 if (relative == '.') continue; |
| 79 |
| 80 var relativeComponents = new Queue<String>.from(path.split(relative)); |
| 81 |
| 82 // If the resolved path is absolute relative to `newPath`, that means it's |
| 83 // on a different drive. We need to canonicalize the entire target of that |
| 84 // symlink again. |
| 85 if (path.isAbsolute(relative)) { |
| 86 // If we've already tried to canonicalize the new path, we've encountered |
| 87 // a symlink loop. Avoid going infinite by treating the recursive symlink |
| 88 // as the canonical path. |
| 89 if (seen.contains(relative)) { |
| 90 newPath = relative; |
| 91 } else { |
| 92 newPath = relativeComponents.removeFirst(); |
| 93 relativeComponents.addAll(components); |
| 94 components = relativeComponents; |
| 95 } |
| 96 continue; |
| 97 } |
| 98 |
| 99 // Pop directories off `newPath` if the component links upwards in the |
| 100 // directory hierarchy. |
| 101 while (relativeComponents.first == '..') { |
| 102 newPath = path.dirname(newPath); |
| 103 relativeComponents.removeFirst(); |
| 104 } |
| 105 |
| 106 // If there's only one component left, [resolveLink] guarantees that it's |
| 107 // not a link (or is a broken link). We can just add it to `newPath` and |
| 108 // continue resolving the remaining components. |
| 109 if (relativeComponents.length == 1) { |
| 110 newPath = path.join(newPath, relativeComponents.single); |
| 111 continue; |
| 112 } |
| 113 |
| 114 // If we've already tried to canonicalize the new path, we've encountered a |
| 115 // symlink loop. Avoid going infinite by treating the recursive symlink as |
| 116 // the canonical path. |
| 117 var newSubPath = path.join(newPath, path.joinAll(relativeComponents)); |
| 118 if (seen.contains(newSubPath)) { |
| 119 newPath = newSubPath; |
| 120 continue; |
| 121 } |
| 122 |
| 123 // If there are multiple new components to resolve, add them to the |
| 124 // beginning of the queue. |
| 125 relativeComponents.addAll(components); |
| 126 components = relativeComponents; |
| 127 } |
| 128 return newPath; |
| 129 } |
| 130 |
| 131 /// Returns the transitive target of [link] (if A links to B which links to C, |
| 132 /// this will return C). If [link] is part of a symlink loop (e.g. A links to B |
| 133 /// which links back to A), this returns the path to the first repeated link (so |
| 134 /// `transitiveTarget("A")` would return `"A"` and `transitiveTarget("A")` would |
| 135 /// return `"B"`). |
| 136 /// |
| 137 /// This accepts paths to non-links or broken links, and returns them as-is. |
| 138 String resolveLink(String link) { |
| 139 var seen = new Set<String>(); |
| 140 while (linkExists(link) && !seen.contains(link)) { |
| 141 seen.add(link); |
| 142 link = path.normalize(path.join( |
| 143 path.dirname(link), new Link(link).targetSync())); |
| 144 } |
| 145 return link; |
| 146 } |
| 147 |
| 148 /// Returns whether [link] exists on the file system. This will return `true` |
| 149 /// for any symlink, regardless of what it points at or whether it's broken. |
| 150 bool linkExists(String link) => new Link(link).existsSync(); |
| OLD | NEW |