Skip to content

Instantly share code, notes, and snippets.

@TekExplorer
Last active November 9, 2023 03:54
Show Gist options
  • Save TekExplorer/46fb721f9c7a21e763c396d4e6ce8708 to your computer and use it in GitHub Desktop.
Save TekExplorer/46fb721f9c7a21e763c396d4e6ce8708 to your computer and use it in GitHub Desktop.
Utility classes for ensuring that dir and file paths are consistent (tossed together)
final class DirUri extends PathUri {
DirUri(String path)
: _uri = _parsePathUri(path: '$path/'),
super._();
final Uri _uri;
@override
String? get filename => _uri.filename;
@override
String get directory => _uri.directory;
@override
String get fullPath => _uri.filenameAndDirectory;
@override
DirUri copyWith() => DirUri(fullPath);
@override
String toString() => 'FileUri($fullPath)';
}
final class FileUri extends PathUri /* implements DirUri */ {
factory FileUri(String path) {
if (path.endsWith('/')) {
throw InvalidPathException('path ($path) Must not end with /');
}
final uri = FileUri._unsafe(path);
final _filename = uri.filenameOrNull;
if (_filename == null) {
throw InvalidPathException('path ($path) Must have a file name');
}
return uri;
}
FileUri._unsafe(String path)
: assert(!path.endsWith('/'), "Path can't end with '/' in FileUri!"),
_uri = _parsePathUri(path: path),
super._();
final Uri _uri;
@override
String get filename =>
filenameOrNull ??
(throw StateError("Filename can't be null! Path: '$fullPath'"));
String? get filenameOrNull => _uri.filename;
@override
String get directory => _uri.directory;
@override
String get fullPath => _uri.filenameAndDirectory;
@override
FileUri copyWith() => FileUri._unsafe(fullPath);
@override
String toString() => 'FileUri($fullPath)';
}
/// A class that standardizes slashes in a path, and exposes access to
/// file name or directory paths, depending on
@immutable
sealed class PathUri {
const PathUri._();
factory PathUri.parse(String path) =>
_parsePathUri(path: path)._hasFile ? FileUri(path) : DirUri(path);
static const dir = DirUri.new;
static const file = FileUri.new;
String? get filename;
String get directory;
String get fullPath;
PathUri copyWith();
@override
String toString() => 'PathUri($fullPath)';
@override
int get hashCode => Object.hash(fullPath, directory, filename);
@override
bool operator ==(Object other) {
if (other is! PathUri) return false;
return other.fullPath == fullPath;
}
}
class InvalidPathException implements Exception {
const InvalidPathException(this.message);
final String message;
}
Uri _parsePathUri({
required String path,
bool leadingSlash = true,
}) {
path = path.withoutDuplicateSlashes;
if (path.isEmpty) return Uri(path: '/');
if (path == '/') return Uri(path: '/');
Uri uri = Uri.parse(path);
uri = Uri.parse(uri.pathSegments.join('/'));
final realSegments = uri.pathSegments.where((e) => e.isNotEmpty);
uri = Uri.parse(realSegments.join('/'));
String transitionary = uri.pathSegments.join('/');
// cleanup
if (path.endsWith('/')) transitionary = '$transitionary/';
if (leadingSlash) transitionary = '/$transitionary';
if (!leadingSlash && transitionary.startsWith('/')) {
transitionary = transitionary.substring(1);
}
return Uri.parse(transitionary);
}
extension DirUriX on DirUri {
bool matches(String other) => DirUri(other).fullPath == fullPath;
DirUri get parent {
final segments = [_uri.pathSegments];
segments.removeLast();
return DirUri(segments.join('/'));
}
}
extension PathUriX on PathUri {
DirUri get directoryUri => DirUri(directory);
bool matchesDirectory(String other) => directoryUri.matches(other);
bool matchesFullPath(String other) {
// == compares full paths
// return PathUri.parse(other) == this;
// parsing will normalize/standardize the shape of a path. Keep it explicit?
return PathUri.parse(other).fullPath == fullPath;
}
bool matchesFilename(String other) => filename == other;
bool get isDirectory => !hasFile;
bool get hasFile => filename != null;
}
extension on String {
/// Converts any number of repeating `/` like `//` or `///` into one `/`
String get withoutDuplicateSlashes => replaceAll(RegExp(r'(\/\/+)'), '/');
}
extension on Uri {
String get filenameAndDirectory => directory + (filename ?? '');
String? get filename => fileAndDir.filename;
String get directory => fileAndDir.dir;
bool get _hasFile => !path.endsWith('/');
({String dir, String? filename}) get fileAndDir {
if (pathSegments.isEmpty) {
return (dir: '/', filename: null);
}
final _path = [...pathSegments];
String? _last;
if (_hasFile) _last = _path.removeLast();
String _dir = _path.join('/');
if (!_dir.startsWith('/')) _dir = '/$_dir';
if (!_dir.endsWith('/')) _dir += '/';
return (dir: _dir.withoutDuplicateSlashes, filename: _last);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment