Skip to content

Commit 7b8b303

Browse files
committed
Add support for links via #METASPACE_NAVIGATION
1 parent 31aeec8 commit 7b8b303

File tree

6 files changed

+138
-3
lines changed

6 files changed

+138
-3
lines changed

Sources/MTSPDecoder/Container.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,7 @@ public struct Container: Equatable, Sendable, Hashable, Codable {
2323

2424
/// The URL to the metaspace scene, a `.usdz` 3D file.
2525
public let sceneURL: URL
26+
27+
/// The dictionary of entity names to `Container` URL destinations.
28+
public let navigation: [String: URL]
2629
}

Sources/MTSPDecoder/DecoderError.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,6 @@ public enum DecoderError: Error {
2121
case containerNotRecognised
2222
case containerNoPreviewURL
2323
case containerNoSceneURL
24+
case containerNavigationPathNotRecognised
25+
case containerNavigationKeyNotRecognised
2426
}

Sources/MTSPDecoder/Util/Parser.swift

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ internal enum Parser {
2020
private static let mtspName: String = "#METASPACE_NAME"
2121
private static let mtspPreviewPath: String = "#METASPACE_PREVIEW_PATH"
2222
private static let mtsp3DPath: String = "#METASPACE_3D_PATH"
23+
private static let mtspNavigation: String = "#METASPACE_NAVIGATION"
24+
private static let mtspNavigationSeparator: String = " -> "
2325

2426
private static let mtspExpectedVersion: Int = 1
2527

@@ -40,13 +42,18 @@ internal enum Parser {
4042
var name: String?
4143
var previewPath: String?
4244
var scenePath: String?
45+
var navigationPaths: [String] = []
4346

4447
for line in mtspLines {
4548
switch line.components(separatedBy: ":").first {
4649
case mtspVersion: version = getIntFrom(line)
4750
case mtspName: name = getStringFrom(line)
4851
case mtspPreviewPath: previewPath = getStringFrom(line)
4952
case mtsp3DPath: scenePath = getStringFrom(line)
53+
case mtspNavigation:
54+
if let path = getStringFrom(line) {
55+
navigationPaths.append(path)
56+
}
5057
default: break
5158
}
5259
}
@@ -63,10 +70,29 @@ internal enum Parser {
6370
throw DecoderError.containerNoSceneURL
6471
}
6572

73+
var navigation: [String: URL] = [:]
74+
75+
for navigationPath in navigationPaths {
76+
if let navigationURL = URLMap.getNavigationURL(
77+
from: navigationPath,
78+
separator: mtspNavigationSeparator,
79+
using: containerURL
80+
) {
81+
if let dir = navigationPath.components(separatedBy: mtspNavigationSeparator).first {
82+
navigation[dir] = navigationURL
83+
} else {
84+
throw DecoderError.containerNavigationKeyNotRecognised
85+
}
86+
} else {
87+
throw DecoderError.containerNavigationPathNotRecognised
88+
}
89+
}
90+
6691
return Container(
6792
name: name,
6893
previewImageURL: previewURL,
69-
sceneURL: sceneURL
94+
sceneURL: sceneURL,
95+
navigation: navigation
7096
)
7197
}
7298

Sources/MTSPDecoder/Util/URLMap.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,29 @@ internal enum URLMap {
4848

4949
return containerURL.deletingLastPathComponent().appending(component: path)
5050
}
51+
52+
static func getNavigationURL(from path: String, separator: String, using containerURL: URL) -> URL? {
53+
guard path.contains(separator) else {
54+
return nil
55+
}
56+
57+
let components = path.components(separatedBy: separator)
58+
59+
guard components.count == 2 else {
60+
return nil
61+
}
62+
63+
var containerURL = containerURL.deletingLastPathComponent()
64+
var upPathComponents = components[1].components(separatedBy: "../")
65+
let count = upPathComponents.count - 1
66+
67+
if count > 0 {
68+
for _ in 0..<count {
69+
upPathComponents.removeFirst()
70+
containerURL.deleteLastPathComponent()
71+
}
72+
}
73+
74+
return containerURL.appending(path: upPathComponents.joined())
75+
}
5176
}

Tests/MTSPDecoderTests/ParserTests.swift

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,53 @@ final class ParserTests: XCTestCase {
4242
let expected = Container(
4343
name: "Untitled",
4444
previewImageURL: URL(string: "https://metaspace.rocks/mtsp/preview.jpg")!,
45-
sceneURL: URL(string: "https://metaspace.rocks/mtsp/file.usdz")!
45+
sceneURL: URL(string: "https://metaspace.rocks/mtsp/file.usdz")!,
46+
navigation: [:]
47+
)
48+
49+
let data = input.data(using: .utf8)!
50+
let result = try Parser.parse(data, containerURL: containerURL)
51+
52+
XCTAssertEqual(expected, result)
53+
}
54+
55+
func testParse_ValidWithNavigation_Succeed() throws {
56+
let containerURL = URL(string: "https://metaspace.rocks/mtsp/index.html")!
57+
let input: String = """
58+
// Copyright 2024 Rafal Kopiec
59+
//
60+
// Licensed under the Apache License, Version 2.0 (the "License");
61+
// you may not use this file except in compliance with the License.
62+
// You may obtain a copy of the License at
63+
// http://www.apache.org/licenses/LICENSE-2.0
64+
//
65+
// Unless required by applicable law or agreed to in writing, software
66+
// distributed under the License is distributed on an "AS IS" BASIS,
67+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
68+
// See the License for the specific language governing permissions and
69+
// limitations under the License.
70+
71+
#METASPACE_HOST
72+
#METASPACE_VERSION:1
73+
#METASPACE_NAME:"Untitled"
74+
#METASPACE_PREVIEW_PATH:"preview.jpg"
75+
#METASPACE_3D_PATH:"file.usdz"
76+
#METASPACE_NAVIGATION:"red_box" -> "red_box/index.mtsp"
77+
#METASPACE_NAVIGATION:"yellow_box" -> "yellow_box/index.mtsp"
78+
#METASPACE_NAVIGATION:"green_box" -> "green_box/index.mtsp"
79+
#METASPACE_NAVIGATION:"up_level_box" -> "../index.mtsp"
80+
"""
81+
82+
let expected = Container(
83+
name: "Untitled",
84+
previewImageURL: URL(string: "https://metaspace.rocks/mtsp/preview.jpg")!,
85+
sceneURL: URL(string: "https://metaspace.rocks/mtsp/file.usdz")!,
86+
navigation: [
87+
"red_box": URL(string: "https://metaspace.rocks/mtsp/red_box/index.mtsp")!,
88+
"yellow_box": URL(string: "https://metaspace.rocks/mtsp/yellow_box/index.mtsp")!,
89+
"green_box": URL(string: "https://metaspace.rocks/mtsp/green_box/index.mtsp")!,
90+
"up_level_box": URL(string: "https://metaspace.rocks/index.mtsp")!,
91+
]
4692
)
4793

4894
let data = input.data(using: .utf8)!
@@ -77,7 +123,8 @@ final class ParserTests: XCTestCase {
77123
let expected = Container(
78124
name: "Untitled",
79125
previewImageURL: URL(string: "https://metaspace.rocks/mtsp/preview.jpg")!,
80-
sceneURL: URL(string: "https://metaspace.rocks/mtsp/file.usdz")!
126+
sceneURL: URL(string: "https://metaspace.rocks/mtsp/file.usdz")!,
127+
navigation: [:]
81128
)
82129

83130
let data = input.data(using: .utf8)!

Tests/MTSPDecoderTests/URLMapTests.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,36 @@ final class URLMapTests: XCTestCase {
7070
let result = URLMap.getFileURL(from: path, using: containerURL)
7171
XCTAssertNil(result)
7272
}
73+
74+
func testNavigationURL_ViablePath_Succeed() {
75+
let path = "some_path -> result/index.mtsp"
76+
let containerURL = URL(string: "https://metaspace.rocks/mtsp/index.mtsp")!
77+
let expected = URL(string: "https://metaspace.rocks/mtsp/result/index.mtsp")
78+
let result = URLMap.getNavigationURL(from: path, separator: " -> ", using: containerURL)
79+
XCTAssertEqual(expected, result)
80+
}
81+
82+
func testNavigationURL_ViablePathUpOneLevel_Succeed() {
83+
let path = "some_path -> ../index.mtsp"
84+
let containerURL = URL(string: "https://metaspace.rocks/mtsp/index.mtsp")!
85+
let expected = URL(string: "https://metaspace.rocks/index.mtsp")
86+
let result = URLMap.getNavigationURL(from: path, separator: " -> ", using: containerURL)
87+
XCTAssertEqual(expected, result)
88+
}
89+
90+
func testNavigationURL_ViablePathUpTwoLevels_Succeed() {
91+
let path = "some_path -> ../../index.mtsp"
92+
let containerURL = URL(string: "https://metaspace.rocks/mtsp/path/index.mtsp")!
93+
let expected = URL(string: "https://metaspace.rocks/index.mtsp")
94+
let result = URLMap.getNavigationURL(from: path, separator: " -> ", using: containerURL)
95+
XCTAssertEqual(expected, result)
96+
}
97+
98+
func testNavigationURL_NonViablePath_Fail() {
99+
let path = "some_path - result/index.mtsp"
100+
let containerURL = URL(string: "https://metaspace.rocks/mtsp/index.mtsp")!
101+
let expected = URL(string: "https://metaspace.rocks/mtsp/result/index.mtsp")
102+
let result = URLMap.getNavigationURL(from: path, separator: " -> ", using: containerURL)
103+
XCTAssertNotEqual(expected, result)
104+
}
73105
}

0 commit comments

Comments
 (0)