Skip to content

Commit

Permalink
First pass at decoding changes
Browse files Browse the repository at this point in the history
  • Loading branch information
waltflanagan committed Aug 7, 2024
1 parent 9dd12c6 commit 88b50f9
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 22 deletions.
6 changes: 3 additions & 3 deletions Sources/XcodeProj/Extensions/Dictionary+Enumerate.swift
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import Foundation

extension Dictionary {
extension Dictionary where Key == String, Value == PlistObject{
func enumerateKeysAndObjects(
options opts: NSEnumerationOptions = [],
using block: (Any, Any, UnsafeMutablePointer<ObjCBool>) throws -> Void
using block: (String, PlistObject, UnsafeMutablePointer<ObjCBool>) throws -> Void
) throws {
var blockError: Error?
// For performance it is very important to create a separate dictionary instance.
// (self as NSDictionary).enumerateKeys... - works much slower
let dictionary = NSDictionary(dictionary: self)
dictionary.enumerateKeysAndObjects(options: opts) { key, obj, stops in
do {
try block(key, obj, stops)
try block(key as! String, obj as! PlistObject, stops)

Check failure on line 14 in Sources/XcodeProj/Extensions/Dictionary+Enumerate.swift

View workflow job for this annotation

GitHub Actions / Swiftlint

Force Cast Violation: Force casts should be avoided (force_cast)

Check failure on line 14 in Sources/XcodeProj/Extensions/Dictionary+Enumerate.swift

View workflow job for this annotation

GitHub Actions / Swiftlint

Force Cast Violation: Force casts should be avoided (force_cast)

Check failure on line 14 in Sources/XcodeProj/Extensions/Dictionary+Enumerate.swift

View workflow job for this annotation

GitHub Actions / Swiftlint

Force Cast Violation: Force casts should be avoided (force_cast)

Check failure on line 14 in Sources/XcodeProj/Extensions/Dictionary+Enumerate.swift

View workflow job for this annotation

GitHub Actions / Swiftlint

Force Cast Violation: Force casts should be avoided (force_cast)
} catch {
blockError = error
stops.pointee = true
Expand Down
6 changes: 4 additions & 2 deletions Sources/XcodeProj/Extensions/Dictionary+Extras.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import Foundation
///
/// - Parameter path: the path of the .plist file.
/// - Returns: initialized dictionary.
public func loadPlist(path: String) -> [String: AnyObject]? {
NSDictionary(contentsOfFile: path) as? [String: AnyObject]
public func loadPlist(path: String) -> [String: PlistObject]? {
let fileURL = URL(fileURLWithPath: path)
guard let data = try? Data(contentsOf: fileURL) else { return nil }
return try? PropertyListDecoder().decode([String: PlistObject].self, from: data)
}
10 changes: 6 additions & 4 deletions Sources/XcodeProj/Objects/Project/PBXObjectParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ final class PBXObjectParser {
}

// swiftlint:disable function_body_length
public func parse(reference: String, dictionary: [String: Any]) throws -> PBXObject {
public func parse(reference: String, dictionary: [String: PlistObject]) throws -> PBXObject {
var mutableDictionary = dictionary
mutableDictionary["reference"] = reference
let data = try JSONSerialization.data(withJSONObject: mutableDictionary, options: [])
guard let isa = dictionary["isa"] as? String else { throw PBXObjectError.missingIsa }
mutableDictionary["reference"] = .string(reference)
// let data = try JSONSerialization.data(withJSONObject: mutableDictionary, options: [])
let data = try JSONEncoder().encode(mutableDictionary)
guard case let .string(isa) = dictionary["isa"] else { throw PBXObjectError.missingIsa }

// Order is important for performance
switch isa {
case PBXFileElement.isa:
Expand Down
12 changes: 7 additions & 5 deletions Sources/XcodeProj/Objects/Project/PBXProj.swift
Original file line number Diff line number Diff line change
Expand Up @@ -135,16 +135,18 @@ public final class PBXProj: Decodable {
objectVersion = try container.decodeIntIfPresent(.objectVersion) ?? 0
archiveVersion = try container.decodeIntIfPresent(.archiveVersion) ?? 1
classes = try container.decodeIfPresent([String: Any].self, forKey: .classes) ?? [:]
let objectsDictionary: [String: Any] = try container.decodeIfPresent([String: Any].self, forKey: .objects) ?? [:]
let objectsDictionaries: [String: [String: Any]] = (objectsDictionary as? [String: [String: Any]]) ?? [:]
let objectsDictionary: [String: PlistObject] = try container.decodeIfPresent([String: PlistObject].self, forKey: .objects) ?? [:]
// let objectsDictionaries: [String: [String: PlistObject]] = (objectsDictionary as? [String: [String: PlistObject]]) ?? [:]

let parser = PBXObjectParser(
userInfo: decoder.userInfo
)
try objectsDictionaries.enumerateKeysAndObjects(options: .concurrent) { key, obj, _ in

try objectsDictionary.enumerateKeysAndObjects(options: .concurrent) { key, obj, _ in
guard case let .dictionary(dictionary) = obj else { return }
// swiftlint:disable force_cast
let reference = key as! String
let dictionary = obj as! [String: Any]
let reference = key
// let dictionary = obj as! [String: PlistObject]
// swiftlint:enable force_cast
let object = try parser.parse(
reference: reference,
Expand Down
51 changes: 51 additions & 0 deletions Sources/XcodeProj/Utils/PlistDecoding.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import Foundation

public indirect enum PlistObject: Sendable, Equatable {
case string(String)
case array([String])
case dictionary([String: PlistObject])
}

extension PlistObject: Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
let string = try container.decode(String.self)
self = .string(string)
} catch {
do {
let array = try container.decode([String].self)
self = .array(array)
} catch {
let dictionary = try container.decode([String: PlistObject].self)
self = .dictionary(dictionary)
}
}
}

public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .string(let string):
try container.encode(string)
case .array(let array):
try container.encode(array)
case .dictionary(let dictionary):
try container.encode(dictionary)
}
}
}














4 changes: 2 additions & 2 deletions Tests/XcodeProjTests/Extensions/Decodable+Dictionary.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ extension Decodable {
///
/// - Parameter jsonDictionary: json dictionary.
/// - Throws: throws an error if the initialization fails.
init(jsonDictionary: [String: Any]) throws {
let data = try JSONSerialization.data(withJSONObject: jsonDictionary, options: [])
init(jsonDictionary: [String: PlistObject]) throws {
let data = try JSONEncoder().encode(jsonDictionary)
let decoder = XcodeprojJSONDecoder(
context: ProjectDecodingContext(pbxProjValueReader: { key in
jsonDictionary[key]
Expand Down
12 changes: 6 additions & 6 deletions Tests/XcodeProjTests/Tests/Fixtures.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,33 @@ func fixturesPath() -> Path {
Path(#file).parent().parent().parent().parent() + "Fixtures"
}

func iosProjectDictionary() -> (Path, [String: Any]) {
func iosProjectDictionary() -> (Path, [String: PlistObject]) {
let iosProject = fixturesPath() + "iOS/Project.xcodeproj/project.pbxproj"
return (iosProject, loadPlist(path: iosProject.string)!)
}

func fileSharedAcrossTargetsDictionary() -> (Path, [String: Any]) {
func fileSharedAcrossTargetsDictionary() -> (Path, [String: PlistObject]) {
let fileSharedAcrossTargetsProject = fixturesPath() + "FileSharedAcrossTargets/FileSharedAcrossTargets.xcodeproj/project.pbxproj"
return (fileSharedAcrossTargetsProject, loadPlist(path: fileSharedAcrossTargetsProject.string)!)
}


func targetWithCustomBuildRulesDictionary() -> (Path, [String: Any]) {
func targetWithCustomBuildRulesDictionary() -> (Path, [String: PlistObject]) {
let targetWithCustomBuildRulesProject = fixturesPath() + "TargetWithCustomBuildRules/TargetWithCustomBuildRules.xcodeproj/project.pbxproj"
return (targetWithCustomBuildRulesProject, loadPlist(path: targetWithCustomBuildRulesProject.string)!)
}

func iosProjectWithXCLocalSwiftPackageReference() -> (Path, [String: Any]) {
func iosProjectWithXCLocalSwiftPackageReference() -> (Path, [String: PlistObject]) {
let iosProjectWithXCLocalSwiftPackageReference = fixturesPath() + "iOS/ProjectWithXCLocalSwiftPackageReference.xcodeproj/project.pbxproj"
return (iosProjectWithXCLocalSwiftPackageReference, loadPlist(path: iosProjectWithXCLocalSwiftPackageReference.string)!)
}

func iosProjectWithRelativeXCLocalSwiftPackageReferences() -> (Path, [String: Any]) {
func iosProjectWithRelativeXCLocalSwiftPackageReferences() -> (Path, [String: PlistObject]) {
let iosProjectWithXCLocalSwiftPackageReference = fixturesPath() + "iOS/ProjectWithRelativeXCLocalSwiftPackageReference/ProjectWithRelativeXCLocalSwiftPackageReference.xcodeproj/project.pbxproj"
return (iosProjectWithXCLocalSwiftPackageReference, loadPlist(path: iosProjectWithXCLocalSwiftPackageReference.string)!)
}

func iosProjectWithXCLocalSwiftPackageReferences() -> (Path, [String: Any]) {
func iosProjectWithXCLocalSwiftPackageReferences() -> (Path, [String: PlistObject]) {
let iosProjectWithXCLocalSwiftPackageReference = fixturesPath() + "iOS/ProjectWithXCLocalSwiftPackageReferences.xcodeproj/project.pbxproj"
return (iosProjectWithXCLocalSwiftPackageReference, loadPlist(path: iosProjectWithXCLocalSwiftPackageReference.string)!)
}

0 comments on commit 88b50f9

Please sign in to comment.