feat(fls): import host file systems into the model

importProject turns a PC's or notebook's Java file tree into Swift's
virtual file system, as Swift's registerVirtualFileSystem does: images
by type or extension from Base64, text by type, unknown types as binary
data or text, unsafe names and collisions left out with a warning, and
the whole import rejected over the project quota. NSString.pathExtension
is measured in the oracle, which now writes every imported file system.

On the 13 files Swift reads completely every file matches, images
compared by hash. Intended: a host whose file has no file system stays
empty, as in Java, where Swift adds the demo files of a new device.
Pending the applications step, the comparison reads our /webserver as
Swift's /www and leaves out the e-mail and Gnutella state files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-29 21:28:55 +00:00
co-authored by Claude Opus 5.5
parent a198d10b4a
commit be3b332655
9 changed files with 4091 additions and 10 deletions
+13
View File
@@ -69,6 +69,19 @@ export function swiftHasPrefix(value: string, prefix: string): boolean {
return value.startsWith(prefix) || value.normalize('NFC').startsWith(prefix.normalize('NFC'));
}
/**
* `(path as NSString).pathExtension` (measured): what follows the last dot
* of the last component, trailing slashes ignored — empty when the dot
* starts or ends the name (".bashrc", "a.").
*/
export function swiftPathExtension(path: string): string {
const trimmed = path.replace(/\/+$/, '');
const name = trimmed.slice(trimmed.lastIndexOf('/') + 1);
const dot = name.lastIndexOf('.');
if (dot <= 0) return '';
return name.slice(dot + 1);
}
const BASE64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
/**
+18
View File
@@ -126,5 +126,23 @@
"\ufeff",
"a\ud83d\ude00",
"a\ue000"
],
"pathExtension": [
"a.txt",
"a.tar.gz",
".bashrc",
"a.",
"a",
"/x/a.png",
"a.PNG",
"a..b",
"a b.c d",
"x.y/z",
"/x.y/",
"webserver/index.html",
"..",
"a.b.",
".a.b",
"\u00e9.PNG"
]
}
+18
View File
@@ -2253,6 +2253,24 @@
"äöü",
"k"
],
"pathExtension" : [
"txt",
"gz",
"",
"",
"",
"png",
"PNG",
"b",
"c d",
"",
"y",
"html",
"",
"",
"b",
"PNG"
],
"sort" : [
[
"B",
+7
View File
@@ -12,6 +12,7 @@ import {
swiftCompare,
swiftEqual,
swiftLowercased,
swiftPathExtension,
swiftTrim,
} from '../src/index.js';
@@ -23,6 +24,7 @@ const cases = JSON.parse(readFileSync(join(here, 'strings.cases.json'), 'utf8'))
sort: string[][];
base64: string[];
controlCharacters: string[];
pathExtension: string[];
};
const swift = JSON.parse(readFileSync(join(here, 'strings.swift.json'), 'utf8')) as {
trim: string[];
@@ -31,6 +33,7 @@ const swift = JSON.parse(readFileSync(join(here, 'strings.swift.json'), 'utf8'))
sort: string[][];
base64: (number[] | null)[];
controlCharacters: boolean[];
pathExtension: string[];
whitespacesAndNewlinesSet: [number, number][];
controlCharactersSet: [number, number][];
};
@@ -76,6 +79,10 @@ describe('Swift string semantics', () => {
expect(cases.controlCharacters.map(containsSwiftControlCharacter)).toEqual(swift.controlCharacters);
});
it('finds the extension like NSString.pathExtension', () => {
expect(cases.pathExtension.map(swiftPathExtension)).toEqual(swift.pathExtension);
});
it('trims exactly the scalars Swift trims, all of Unicode', () => {
expect(scalarRanges((scalar) => swiftTrim(`${scalar}a`) === 'a')).toEqual(swift.whitespacesAndNewlinesSet);
});