blob: b8f4a10fc98fd32865f1299a0ffb9ce944637f94 [file] [log] [blame]
// Copyright (c) 2025, 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:io';
import 'package:path/path.dart' as p;
import 'event.dart';
import 'watch_event.dart';
/// An absolute file path.
extension type AbsolutePath(String _string) {
/// Whether this immediate parent directory of this path is [directory].
bool isIn(AbsolutePath directory) => p.dirname(_string) == directory._string;
AbsolutePath get parent => AbsolutePath(p.dirname(_string));
/// This path relative to [root].
///
/// Returns the empty string if this path is [root].
///
/// Otherwise, return null if this path does not start with [root].
RelativePath? tryRelativeTo(AbsolutePath root) {
if (!_string.startsWith(root._string)) return null;
if (_string == root._string) return RelativePath('');
if (_string.substring(root._string.length, root._string.length + 1) !=
Platform.pathSeparator) {
return null;
}
return RelativePath(_string.substring(root._string.length + 1));
}
/// This path relative to [root].
///
/// Returns the empty string if this path is [root].
///
/// Otherwise, throws if this path does not start with [root].
RelativePath relativeTo(AbsolutePath root) {
return tryRelativeTo(root) ??
(throw ArgumentError('$this relativeTo $root'));
}
/// This path relative to [root] as a single segment.
///
/// Throws if this path is not a single segment under [root].
PathSegment segmentRelativeTo(AbsolutePath root) {
if (!_string.startsWith(root._string)) {
throw ArgumentError('$this segmentRelativeTo $root');
}
if (_string == root._string) throw ArgumentError(root);
final result = _string.substring(root._string.length + 1);
return PathSegment(result);
}
/// The last path segment of this path.
RelativePath get basename => RelativePath(p.basename(_string));
/// Lists the directory at this path, ignoring symlinks.
List<FileSystemEntity> listSync() =>
Directory(_string).listSync(followLinks: false);
/// Watches the directory at this path.
Stream<FileSystemEvent> watch({bool recursive = false}) =>
Directory(_string).watch(recursive: recursive);
/// Gets the [FileSystemEntityType] for this path.
FileSystemEntityType typeSync() =>
FileSystemEntity.typeSync(_string, followLinks: false);
/// Returns this path followed by [path].
AbsolutePath append(RelativePath path) =>
AbsolutePath('$_string${Platform.pathSeparator}${path._string}');
/// Add event for this path.
WatchEvent get addEvent => WatchEvent(ChangeType.ADD, _string);
/// Modify event for this path.
WatchEvent get modifyEvent => WatchEvent(ChangeType.MODIFY, _string);
/// Remove event for this path.
WatchEvent get removeEvent => WatchEvent(ChangeType.REMOVE, _string);
}
extension FileSystemEntityExtensions on FileSystemEntity {
/// The event path relative to [root].
///
/// Throws if not under [root].
RelativePath pathRelativeTo(AbsolutePath root) =>
AbsolutePath(path).relativeTo(root);
/// The path segment under [root].
///
/// Throws if not a single path segment under [root].
PathSegment pathSegmentRelativeTo(AbsolutePath root) =>
AbsolutePath(path).segmentRelativeTo(root);
}
extension EventExtensions on Event {
/// The event [path] as an [AbsolutePath].
AbsolutePath get absolutePath => AbsolutePath(path);
/// The event [path] relative to [root].
RelativePath pathRelativeTo(AbsolutePath root) =>
AbsolutePath(path).relativeTo(root);
/// Whether the event path parent directory is exactly [directory].
bool isIn(AbsolutePath directory) => AbsolutePath(path).isIn(directory);
}
/// A relative file path.
extension type RelativePath(String _string) {
List<PathSegment> get segments => _string.isEmpty
? const <PathSegment>[]
: _string.split(Platform.pathSeparator) as List<PathSegment>;
}
/// A path segment.
extension type PathSegment._(String _string) implements RelativePath {
factory PathSegment(String segment) {
if (segment.isEmpty) throw ArgumentError('Segment cannot be empty.');
if (segment.contains(Platform.pathSeparator)) {
throw ArgumentError(
'Segment cannot contain `${Platform.pathSeparator}`.',
segment,
);
}
return PathSegment._(segment);
}
}