feat(fls): import a .fls topology into the editor model
importProject builds what the simulation core runs from Java's view of a
file: nodes with kinds and ports, links between port ids, device and
interface IP configurations, documentation items with Swift's derived
ids. The core gains the model types it needs (node, link, graph,
documentation item), ported from filius-ipad.
Checked against the Swift import, which the Swift oracle now writes in
full (corpus.swift.json, "state"), on the 13 files Swift reads
completely. Two differences are intended and named in the test:
- a device the file leaves unnamed has Java's default name
("Switch / WLAN"), not Swift's;
- multicast_server.fls: useIPAsName refers to a shared Boolean that
Swift cannot follow; Java shows the IP address, and so do we.
All 23 files import with as many devices and cables as Java has, and an
imported example pings across its router in the core.
Port counts of switches and routers follow Java's lists instead of
Swift's guess from references.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -283,6 +283,7 @@ func readCorpus(root: URL) throws -> JSON {
|
||||
"warnings": imported.report.warnings,
|
||||
"residual": imported.opaqueContent.residualCount,
|
||||
"kinds": kinds,
|
||||
"state": describeImportedState(state),
|
||||
] as JSON
|
||||
} catch {
|
||||
result[name] = ["error": String(describing: error)] as JSON
|
||||
@@ -291,6 +292,61 @@ func readCorpus(root: URL) throws -> JSON {
|
||||
return result
|
||||
}
|
||||
|
||||
/// The imported model without its random UUIDs: nodes, ports and links by
|
||||
/// index; the IP configurations the runtime starts from; documentation items
|
||||
/// with their deterministic ids.
|
||||
func describeImportedState(_ state: TopologyEditorState) -> JSON {
|
||||
let nodeIndex = Dictionary(uniqueKeysWithValues: state.graph.nodes.enumerated().map { ($1.id, $0) })
|
||||
func portIndex(_ nodeID: UUID, _ portID: UUID) -> Int {
|
||||
state.graph.node(withID: nodeID)?.ports.firstIndex { $0.id == portID } ?? -1
|
||||
}
|
||||
let nodes: [JSON] = state.graph.nodes.map { node in
|
||||
let device: Any = state.runtimeDeviceConfigurations[node.id].map { configuration -> JSON in
|
||||
[
|
||||
"ipAddress": configuration.ipAddress,
|
||||
"subnetMask": configuration.subnetMask,
|
||||
"defaultGateway": configuration.defaultGateway,
|
||||
"dnsServer": configuration.dnsServer,
|
||||
]
|
||||
} ?? NSNull()
|
||||
let interfaces: [Any] = node.ports.map { port in
|
||||
state.runtimeInterfaceConfigurations[TopologyRuntimeInterfaceKey(nodeID: node.id, portID: port.id)]
|
||||
.map { ["ipAddress": $0.ipAddress, "subnetMask": $0.subnetMask] as JSON } ?? NSNull()
|
||||
}
|
||||
return [
|
||||
"kind": node.kind.rawValue,
|
||||
"displayName": node.displayName,
|
||||
"hostLabelPolicy": node.hostLabelPolicy.rawValue,
|
||||
"position": [Double(node.position.x), Double(node.position.y)],
|
||||
"ports": node.ports.map { port -> JSON in
|
||||
["label": port.label, "mac": port.importedMACAddress ?? NSNull(), "occupied": port.isOccupied]
|
||||
},
|
||||
"device": device,
|
||||
"interfaces": interfaces,
|
||||
]
|
||||
}
|
||||
let links: [[Int]] = state.graph.links.map { link in
|
||||
[
|
||||
nodeIndex[link.sourceNodeID] ?? -1, portIndex(link.sourceNodeID, link.sourcePortID),
|
||||
nodeIndex[link.targetNodeID] ?? -1, portIndex(link.targetNodeID, link.targetPortID),
|
||||
]
|
||||
}
|
||||
let documentation: [JSON] = state.documentationItems.map { item in
|
||||
[
|
||||
"id": item.id.uuidString,
|
||||
"kind": item.kind.rawValue,
|
||||
"frame": [item.frame.origin.x, item.frame.origin.y, item.frame.width, item.frame.height].map { Double($0) },
|
||||
"text": item.text,
|
||||
"color": [item.color.red, item.color.green, item.color.blue, item.color.alpha],
|
||||
"fontName": item.fontName,
|
||||
"fontSize": Double(item.fontSize),
|
||||
"isBold": item.isBold,
|
||||
"order": item.order,
|
||||
]
|
||||
}
|
||||
return ["nodes": nodes, "links": links, "documentation": documentation]
|
||||
}
|
||||
|
||||
// MARK: - Archive path folding
|
||||
|
||||
struct PathCase: Decodable { let name: String; let first: String; let second: String }
|
||||
|
||||
Reference in New Issue
Block a user