From 50c29bbe051b65259f894accfd07eafcbfd467b2 Mon Sep 17 00:00:00 2001 From: Leo Dion Date: Tue, 26 Mar 2024 21:07:25 -0400 Subject: [PATCH] finished extension implmentation --- .../App/Keychain/CompositeCredentials.swift | 138 ++-------- .../App/Views/CompositeStealthyView.swift | 4 +- Sources/StealthyStash/StealthyModel.swift | 56 ++++ .../CompositeModelMacro.swift | 250 ++++++++++-------- 4 files changed, 218 insertions(+), 230 deletions(-) diff --git a/Samples/Demo/App/Keychain/CompositeCredentials.swift b/Samples/Demo/App/Keychain/CompositeCredentials.swift index 56dd57c..5032a0a 100644 --- a/Samples/Demo/App/Keychain/CompositeCredentials.swift +++ b/Samples/Demo/App/Keychain/CompositeCredentials.swift @@ -2,128 +2,17 @@ import StealthyStash @StealthyModel struct CompositeCredentials { - //typealias QueryBuilder = CompositeCredentialsQueryBuilder - -// struct QueryBuilder: ModelQueryBuilder { -// static func updates( -// from previousItem: CompositeCredentials, -// to newItem: CompositeCredentials -// ) -> [StealthyPropertyUpdate] { -// let newPasswordData = newItem.password.flatMap { -// $0.data(using: .utf8) -// }.map { -// InternetPasswordItem(account: newItem.userName, data: $0) -// } -// -// let oldPasswordData = previousItem.password.flatMap { -// $0.data(using: .utf8) -// }.map { -// InternetPasswordItem(account: previousItem.userName, data: $0) -// } -// -// let previousTokenData = previousItem.token.flatMap { -// $0.data(using: .utf8) -// }.map { -// GenericPasswordItem(account: previousItem.userName, data: $0) -// } -// -// let newTokenData = newItem.token.flatMap { -// $0.data(using: .utf8) -// }.map { -// GenericPasswordItem(account: newItem.userName, data: $0) -// } -// -// let passwordUpdate = StealthyPropertyUpdate( -// previousProperty: oldPasswordData, -// newProperty: newPasswordData -// ) -// let tokenUpdate = StealthyPropertyUpdate( -// previousProperty: previousTokenData, -// newProperty: newTokenData -// ) -// return [passwordUpdate, tokenUpdate] -// } -// -// static func properties( -// from model: CompositeCredentials, -// for _: ModelOperation -// ) -> [AnyStealthyProperty] { -// let passwordData = model.password.flatMap { -// $0.data(using: .utf8) -// } -// -// let passwordProperty: AnyStealthyProperty = .init( -// property: InternetPasswordItem( -// account: model.userName, -// data: passwordData ?? .init() -// ) -// ) -// -// let tokenData = model.token.flatMap { -// $0.data(using: .utf8) -// } -// -// let tokenProperty: AnyStealthyProperty = .init( -// property: GenericPasswordItem( -// account: model.userName, -// data: tokenData ?? .init() -// ) -// ) -// -// return [passwordProperty, tokenProperty] -// } -// -// static func queries(from _: Void) -> [String: Query] { -// [ -// "password": TypeQuery(type: .internet), -// "token": TypeQuery(type: .generic) -// ] -// } -// -// static func model( -// from properties: [String: [AnyStealthyProperty]] -// ) throws -> CompositeCredentials? { -// for internet in properties["password"] ?? [] { -// for generic in properties["token"] ?? [] { -// if internet.account == generic.account { -// return .init( -// userName: internet.account, -// password: internet.dataString, -// token: generic.dataString -// ) -// } -// } -// } -// let properties = properties.values.flatMap { $0 }.enumerated().sorted { lhs, rhs in -// if lhs.element.propertyType == rhs.element.propertyType { -// return lhs.offset < rhs.offset -// } else { -// return lhs.element.propertyType == .internet -// } -// }.map(\.element) -// -// guard let username = properties.map(\.account).first else { -// return nil -// } -// let password = properties -// .first { $0.propertyType == .internet }? -// .data -// let token = properties.first { -// $0.propertyType == .generic && $0.account == username -// }?.data -// -// return CompositeCredentials( -// userName: username, -// password: password?.string(), -// token: token?.string() -// ) -// } -// -// typealias QueryType = Void -// -// typealias StealthyModelType = CompositeCredentials -// } - +#warning("deal with queries") + #warning("add init(properties:)") + init?(properties: [String: AnyStealthyProperty]) { + guard let account = properties["password"]?.account else { + return nil + } + self.account = account + self.password = properties["password"]?.dataString + self.token = properties["token"]?.dataString + } + internal init(account: String, password: String?, token: String?) { self.account = account self.password = password @@ -132,6 +21,11 @@ struct CompositeCredentials { let account: String + // @Key + #warning("Add @Key") + // @InternetPassword +#warning("Add @Internet") +#warning("Add @Generic") let password: String? let token: String? diff --git a/Samples/Demo/App/Views/CompositeStealthyView.swift b/Samples/Demo/App/Views/CompositeStealthyView.swift index 82dc1be..d8c9dc0 100644 --- a/Samples/Demo/App/Views/CompositeStealthyView.swift +++ b/Samples/Demo/App/Views/CompositeStealthyView.swift @@ -29,7 +29,7 @@ struct CompositeStealthyView: View { if object.isLoaded { Form { Section("User Name") { - TextField("User name", text: self.$object.secret.userName) + TextField("User name", text: self.$object.secret.account) } Section("Password") { @@ -71,7 +71,7 @@ struct CompositeStealthyView_Previews: PreviewProvider { CompositeStealthyView( repository: PreviewRepository(items: []), triggerSet: .init(), - secret: .init(userName: "username", password: "password", token: "token") + secret: .init(account: "username", password: "password", token: "token") ) } } diff --git a/Sources/StealthyStash/StealthyModel.swift b/Sources/StealthyStash/StealthyModel.swift index d1c55d2..0ae28c4 100644 --- a/Sources/StealthyStash/StealthyModel.swift +++ b/Sources/StealthyStash/StealthyModel.swift @@ -5,3 +5,59 @@ public protocol StealthyModel { associatedtype QueryBuilder: ModelQueryBuilder where QueryBuilder.StealthyModelType == Self } + + +public extension StealthyModel { + + static func dictionary( + for keys: [String], + from dictionary: [String: [AnyStealthyProperty]] + ) -> [String: AnyStealthyProperty]? { + let catalog = catalogAccounts(keys: .init(keys), dictionary: dictionary) + return findAccountWithMostMatches(accountCatalog: catalog, dictionary: dictionary) + } + + private static func catalogAccounts(keys: Set, dictionary: [String: [AnyStealthyProperty]]) -> [String: [(String, Int)]] { + var accountCatalog = [String: [(String, Int)]]() + + for key in keys { + guard let properties = dictionary[key] else { continue } + + for (index, property) in properties.enumerated() { + let keyIndexTuple = (key, index) + if var accountIndexArray = accountCatalog[property.account] { + accountIndexArray.append(keyIndexTuple) + accountCatalog[property.account] = accountIndexArray + } else { + accountCatalog[property.account] = [keyIndexTuple] + } + } + } + + return accountCatalog + } + + private static func findAccountWithMostMatches(accountCatalog: [String: [(String, Int)]], dictionary: [String: [AnyStealthyProperty]]) -> [String: AnyStealthyProperty]? { + var maxAccount: String? + var maxCount = 0 + + for (account, keyIndexArray) in accountCatalog { + if keyIndexArray.count > maxCount { + maxCount = keyIndexArray.count + maxAccount = account + } + } + + guard let account = maxAccount else { return nil } + + var resultDictionary = [String: AnyStealthyProperty]() + + for (key, index) in accountCatalog[account]! { + if let properties = dictionary[key], properties.indices.contains(index) { + resultDictionary[key] = properties[index] + } + } + + return resultDictionary + } +} diff --git a/Sources/StealthyStashMacros/CompositeModelMacro.swift b/Sources/StealthyStashMacros/CompositeModelMacro.swift index 120cf33..4512916 100644 --- a/Sources/StealthyStashMacros/CompositeModelMacro.swift +++ b/Sources/StealthyStashMacros/CompositeModelMacro.swift @@ -26,8 +26,82 @@ public struct StealthyModelMacro : MemberMacro, ExtensionMacro { struct StealthyPropertyUpdateSyntax { - init (syntax : TokenSyntax) { - + enum PropertyType : String{ + case internet + case generic + } + internal init(propertiesDataToken: TokenSyntax, propertiesPropertyToken: TokenSyntax, updateToken: TokenSyntax, previousToken: TokenSyntax, newToken: TokenSyntax, propertyToken: TokenSyntax, propertyType : PropertyType = .generic) { + self.propertiesDataToken = propertiesDataToken + self.propertiesPropertyToken = propertiesPropertyToken + self.updateToken = updateToken + self.previousToken = previousToken + self.newToken = newToken + self.propertyToken = propertyToken + self.propertyType = propertyType + } + + + + let propertiesDataToken : TokenSyntax + let propertiesPropertyToken : TokenSyntax + let updateToken : TokenSyntax + let previousToken : TokenSyntax + let newToken : TokenSyntax + let propertyToken : TokenSyntax + let propertyType: PropertyType + + init (syntax : TokenSyntax, attributes: [AttributeSyntax], uniqueID : @escaping @Sendable (String) -> TokenSyntax) { + let previousToken = uniqueID("previous") +let newToken = uniqueID("new") + let updateToken = uniqueID("update") + self.init(propertiesDataToken: uniqueID("propertyData"), propertiesPropertyToken: uniqueID("property") , updateToken: updateToken, previousToken: previousToken, newToken: newToken, propertyToken: syntax) + } + + + func syntax () -> String { + """ + let \(previousToken) = previousItem.\(propertyToken).flatMap { + $0.data(using: .utf8) + }.map { + GenericPasswordItem(account: previousItem.account, data: $0) + } + + let \(newToken) = newItem.\(propertyToken).flatMap { + $0.data(using: .utf8) + }.map { + GenericPasswordItem(account: newItem.account, data: $0) + } + """ + } + + func updateSyntax () -> String { + """ + let \(updateToken) = StealthyPropertyUpdate( + previousProperty: \(previousToken), + newProperty: \(newToken) + ) +""" + } + + func propertiesSyntax () -> String { + """ + let \(propertiesDataToken) = model.\(propertyToken).flatMap { + $0.data(using: .utf8) + } + + let \(propertiesPropertyToken): AnyStealthyProperty = .init( + property: GenericPasswordItem( + account: model.account, + data: \(propertiesDataToken) ?? .init() + ) + ) +""" + } + + func queryPairString () -> String { + """ + "\(self.propertyToken)": TypeQuery(type: .\(self.propertyType)) + """ } } @@ -35,6 +109,7 @@ public struct StealthyModelMacro : MemberMacro, ExtensionMacro { guard let structDecl = declaration.as(StructDeclSyntax.self) else { throw CustomError.message("Type must be struct.") } + let members : [(TokenSyntax, [AttributeSyntax])] = structDecl.memberBlock.members.flatMap { syntax -> [(TokenSyntax, [AttributeSyntax])] in guard let variable = syntax.decl.as(VariableDeclSyntax.self) else { return [] @@ -50,23 +125,31 @@ public struct StealthyModelMacro : MemberMacro, ExtensionMacro { }.map { token in (token, attributes) } - }.filter{ $0.0.text != "account" } - - //context.makeUniqueName(<#T##name: String##String#>) - let sample = - """ - let previousTokenData = previousItem.token.flatMap { - $0.data(using: .utf8) - }.map { - GenericPasswordItem(account: previousItem.account, data: $0) } - - let newTokenData = newItem.token.flatMap { - $0.data(using: .utf8) - }.map { - GenericPasswordItem(account: newItem.account, data: $0) + .filter{ $0.0.text != "account" } + + let updates = members.map{ + StealthyPropertyUpdateSyntax(syntax: $0.0, attributes: $0.1, uniqueID: context.makeUniqueName(_:)) } - """ + + let syntaxStrings = updates.map{$0.syntax()}.joined(separator: "\n") + let updateStrings = updates.map{$0.updateSyntax()}.joined(separator: "\n") + let propertiesStrings = updates.map{$0.propertiesSyntax()}.joined(separator: "\n") + //context.makeUniqueName(<#T##name: String##String#>) +// let sample = +// """ +// let previousTokenData = previousItem.token.flatMap { +// $0.data(using: .utf8) +// }.map { +// GenericPasswordItem(account: previousItem.account, data: $0) +// } +// +// let newTokenData = newItem.token.flatMap { +// $0.data(using: .utf8) +// }.map { +// GenericPasswordItem(account: newItem.account, data: $0) +// } +// """ let typeName = structDecl.name.trimmed @@ -95,114 +178,69 @@ public struct StealthyModelMacro : MemberMacro, ExtensionMacro { from previousItem: \(typeName), to newItem: \(typeName) ) -> [StealthyPropertyUpdate] { -// let newPasswordData = newItem.password.flatMap { -// $0.data(using: .utf8) -// }.map { -// InternetPasswordItem(account: newItem.account, data: $0) -// } -// -// let oldPasswordData = previousItem.password.flatMap { -// $0.data(using: .utf8) -// }.map { -// InternetPasswordItem(account: previousItem.account, data: $0) -// } - - let previousTokenData = previousItem.token.flatMap { - $0.data(using: .utf8) - }.map { - GenericPasswordItem(account: previousItem.account, data: $0) - } - let newTokenData = newItem.token.flatMap { - $0.data(using: .utf8) - }.map { - GenericPasswordItem(account: newItem.account, data: $0) - } + \(raw: syntaxStrings) - let passwordUpdate = StealthyPropertyUpdate( - previousProperty: oldPasswordData, - newProperty: newPasswordData - ) - let tokenUpdate = StealthyPropertyUpdate( - previousProperty: previousTokenData, - newProperty: newTokenData - ) - return [passwordUpdate, tokenUpdate] + \(raw: updateStrings) + return [\(raw: updates.map{$0.updateToken.text}.joined(separator: ","))] } static func properties( from model: \(typeName), for _: ModelOperation ) -> [AnyStealthyProperty] { - let passwordData = model.password.flatMap { - $0.data(using: .utf8) - } - - let passwordProperty: AnyStealthyProperty = .init( - property: InternetPasswordItem( - account: model.account, - data: passwordData ?? .init() - ) - ) - - let tokenData = model.token.flatMap { - $0.data(using: .utf8) - } - - let tokenProperty: AnyStealthyProperty = .init( - property: GenericPasswordItem( - account: model.account, - data: tokenData ?? .init() - ) - ) + \(raw: propertiesStrings) - return [passwordProperty, tokenProperty] + return [\(raw: updates.map{$0.propertiesPropertyToken.text}.joined(separator: ","))] } static func queries(from _: Void) -> [String: Query] { [ - "password": TypeQuery(type: .internet), - "token": TypeQuery(type: .generic) + \(raw: updates.map{$0.queryPairString()}.joined(separator: ",")) ] } static func model( from properties: [String: [AnyStealthyProperty]] ) throws -> \(typeName)? { - for internet in properties["password"] ?? [] { - for generic in properties["token"] ?? [] { - if internet.account == generic.account { - return .init( - account: internet.account, - password: internet.dataString, - token: generic.dataString - ) - } - } - } - let properties = properties.values.flatMap { $0 }.enumerated().sorted { lhs, rhs in - if lhs.element.propertyType == rhs.element.propertyType { - return lhs.offset < rhs.offset - } else { - return lhs.element.propertyType == .internet - } - }.map(\\.element) - - guard let account = properties.map(\\.account).first else { - return nil - } - let password = properties - .first { $0.propertyType == .internet }? - .data - let token = properties.first { - $0.propertyType == .generic && $0.account == account - }?.data - - return \(typeName)( - account: account, - password: password?.string(), - token: token?.string() - ) + return dictionary( +for: + [\(raw: updates.map{"\"\($0.propertyToken)\""}.joined(separator: ","))], + from: properties).flatMap(\(typeName).init(properties:)) +// for internet in properties["password"] ?? [] { +// for generic in properties["token"] ?? [] { +// if internet.account == generic.account { +// return .init( +// account: internet.account, +// password: internet.dataString, +// token: generic.dataString +// ) +// } +// } +// } +// let properties = properties.values.flatMap { $0 }.enumerated().sorted { lhs, rhs in +// if lhs.element.propertyType == rhs.element.propertyType { +// return lhs.offset < rhs.offset +// } else { +// return lhs.element.propertyType == .internet +// } +// }.map(\\.element) +// +// guard let account = properties.map(\\.account).first else { +// return nil +// } +// let password = properties +// .first { $0.propertyType == .internet }? +// .data +// let token = properties.first { +// $0.propertyType == .generic && $0.account == account +// }?.data +// +// return \(typeName)( +// account: account, +// password: password?.string(), +// token: token?.string() +// ) } internal typealias QueryType = Void