-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Rewrite workspace project discovery to break it up into more sensible & logical pieces. Add a test asset (test to come). The biggest change is that the group naming only follows the nearest group parent - not all of them
- Loading branch information
1 parent
253b1cd
commit 98ca442
Showing
17 changed files
with
1,059 additions
and
114 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,4 +16,5 @@ output/ | |
*.xcarchive/ | ||
*.bc | ||
*.dia | ||
_build/ | ||
_build/ | ||
**/.build/* |
87 changes: 87 additions & 0 deletions
87
PBXProjParser/Sources/PBXProjParser/Workspace/Reference.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
// | ||
// Reference.swift | ||
// | ||
// | ||
// Created by Thomas Hedderwick on 18/10/2023. | ||
// | ||
import Foundation | ||
|
||
protocol Reference { | ||
var location: Location { get } | ||
static var elementName: String { get } | ||
} | ||
|
||
enum Location { | ||
// TODO: Find where we can get a definitive list of these. Xcode must have them somewhere? | ||
case container(String) | ||
case group(String) | ||
|
||
enum Error: Swift.Error { | ||
case invalidLocation(String) | ||
} | ||
|
||
init(_ location: String) throws { | ||
let split = location | ||
.split(separator: ":", maxSplits: 1, omittingEmptySubsequences: false) | ||
.map(String.init) | ||
|
||
guard | ||
let key = split.first, | ||
let value = split.last | ||
else { throw Error.invalidLocation("Couldn't extract key/value pair from split: \(split)") } | ||
|
||
switch key { | ||
case "container": self = .container(value) | ||
case "group": self = .group(value) | ||
default: throw Error.invalidLocation("Key didn't match a supported location key: \(key)") | ||
} | ||
} | ||
|
||
var path: String { | ||
switch self { | ||
case .container(let path): return path | ||
case .group(let path): return path | ||
} | ||
} | ||
} | ||
|
||
class Group: Reference { | ||
static let elementName: String = "Group" | ||
|
||
let location: Location | ||
let name: String? | ||
var references: [Reference] = [] | ||
|
||
init(location: String, name: String?) throws { | ||
self.location = try .init(location) | ||
self.name = name | ||
} | ||
} | ||
|
||
struct FileRef: Reference { | ||
static let elementName: String = "FileRef" | ||
|
||
let location: Location | ||
let enclosingGroup: Group? | ||
|
||
init(location: String, enclosingGroup: Group? = nil) throws { | ||
self.location = try .init(location) | ||
self.enclosingGroup = enclosingGroup | ||
} | ||
|
||
var path: String { | ||
guard | ||
let enclosingGroup | ||
else { return location.path } | ||
|
||
switch enclosingGroup.location { | ||
case let .group(path), let .container(path): | ||
if path.last == "/" { | ||
return path + location.path | ||
} | ||
|
||
return path + "/" + location.path | ||
// return URL(fileURLWithPath: path).appendingPathComponent(location.path).path | ||
} | ||
} | ||
} |
88 changes: 88 additions & 0 deletions
88
PBXProjParser/Sources/PBXProjParser/Workspace/WorkspaceParser.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
// | ||
// WorkspaceParser.swift | ||
// | ||
// | ||
// Created by Thomas Hedderwick on 18/10/2023. | ||
// | ||
|
||
import Foundation | ||
|
||
struct Workspace { | ||
private(set) var fileReferences: [FileRef] = [] | ||
private(set) var groupReferences: [Group] = [] | ||
} | ||
|
||
struct WorkspaceParser { | ||
static func parse(_ path: URL) throws -> Workspace { | ||
// Parse the `contents.xcworkspacedata` (XML) file and get the list of projects | ||
let contentsPath = path.appendingPathComponent("contents.xcworkspacedata") | ||
|
||
let data = try Data(contentsOf: contentsPath) | ||
let delegate = WorkspaceDataParserDelegate() | ||
let parser = XMLParser(data: data) | ||
parser.delegate = delegate | ||
parser.parse() | ||
|
||
return .init( | ||
fileReferences: delegate.fileReferences, | ||
groupReferences: delegate.groupReferences | ||
) | ||
} | ||
} | ||
|
||
private class WorkspaceDataParserDelegate: NSObject, XMLParserDelegate { | ||
private(set) var fileReferences: [FileRef] = [] | ||
private(set) var groupReferences: [Group] = [] | ||
|
||
static let supportedElements = [Group.elementName, FileRef.elementName] | ||
|
||
private var groupPath: [Group] = [] | ||
|
||
func parser( | ||
_ parser: XMLParser, | ||
didStartElement elementName: String, | ||
namespaceURI: String?, | ||
qualifiedName qName: String?, | ||
attributes attributeDict: [String: String] = [:] | ||
) { | ||
guard Self.supportedElements.contains(elementName) else { | ||
logger.debug("Skipping parsing of unsupported element: \(elementName)") | ||
return | ||
} | ||
|
||
guard | ||
let location = attributeDict["location"] | ||
else { | ||
logger.debug("Location attribute for element \(elementName) is nil, this shouldn't be the case: \(attributeDict)") | ||
return | ||
} | ||
|
||
do { | ||
switch elementName { | ||
case Group.elementName: | ||
let group = try Group(location: location, name: attributeDict["name"]) | ||
groupPath.append(group) | ||
groupReferences.append(group) | ||
case FileRef.elementName: | ||
let file = try FileRef(location: location, enclosingGroup: groupPath.last) | ||
fileReferences.append(file) | ||
groupPath.last?.references.append(file) | ||
// Ignore any element that doesn't match the search space | ||
default: | ||
break | ||
} | ||
} catch { | ||
logger.debug("Parsing element: \(elementName) failed. Reason: \(error)") | ||
} | ||
} | ||
|
||
func parser( | ||
_ parser: XMLParser, | ||
didEndElement elementName: String, | ||
namespaceURI: String?, | ||
qualifiedName qName: String? | ||
) { | ||
guard elementName == Group.elementName else { return } | ||
groupPath.removeLast() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.