tryParse static method
Null rather than throwing, for the many places that read user input.
Implementation
static Address? tryParse(String source) {
const prefix = '$scheme://';
if (!source.startsWith(prefix)) return null;
var rest = source.substring(prefix.length);
// The fragment is not part of the grammar. Reject rather than silently drop
// it — a plugin segment containing `#` has to arrive percent-encoded, and
// quietly truncating there would lose half of an entry id.
if (rest.contains('#')) return null;
var axes = const <String, String>{};
var queryStart = rest.indexOf('?');
if (queryStart >= 0) {
var query = rest.substring(queryStart + 1);
rest = rest.substring(0, queryStart);
var parsed = <String, String>{};
for (var pair in query.split('&')) {
if (pair.isEmpty) continue;
var eq = pair.indexOf('=');
if (eq < 0) return null;
var key = _decode(pair.substring(0, eq));
var value = _decode(pair.substring(eq + 1));
if (key == null || value == null) return null;
parsed[key] = value;
}
axes = parsed;
}
// Hand-parsed rather than `Uri.parse`, to keep the percent-encoding
// deliberately sparse — `_encode` escapes only what would be read as
// structure, so `team.dart%23TeamList` stays legible where `Uri` would
// escape more. The two agree on everything they both decide, and
// `address_test.dart` asserts it.
var parts = rest.split('/');
// The authority. Empty is the normal case and means "this session's
// project"; it is not a segment and never was one.
var project = parts.first;
var path = parts.skip(1).toList();
// `fw:///worktrees/wt/` and `fw:///worktrees/wt` name the same worktree.
if (path.isNotEmpty && path.last.isEmpty) path.removeLast();
if (path.any((s) => s.isEmpty)) return null;
var decoded = <String>[];
for (var part in [project, ...path]) {
var value = _decode(part);
if (value == null) return null;
decoded.add(value);
}
var decodedProject = decoded.first;
var decodedPath = decoded.skip(1).toList();
// The constructor asserts what the grammar cannot — that a worktree lives
// in the worktrees space, and so on — and those refusals are this
// function's `null` rather than its caller's exception.
try {
return Address(
project: decodedProject.isEmpty ? null : decodedProject,
space: decodedPath.isEmpty ? null : decodedPath.first,
worktree: decodedPath.length < 2 ? null : decodedPath[1],
plugin: decodedPath.length < 3 ? null : decodedPath[2],
segments: decodedPath.skip(3).toList(),
axes: axes,
);
} on ArgumentError {
return null;
}
}