diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 889a1df..5c10b03 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,27 +2,49 @@ name: CI on: push: - branches: [ "main" ] + branches: ["main"] pull_request: - branches: [ "main" ] + branches: ["main"] jobs: - test: - runs-on: macos-latest - + test-macos: + name: Swift ${{ matrix.swift }} on macOS ${{ matrix.macos }} with Xcode ${{ matrix.xcode }} + runs-on: macos-${{ matrix.macos }} + env: + DEVELOPER_DIR: "/Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer" strategy: + fail-fast: false matrix: - swift-version: - - ^6 + include: + - swift: "6.0" + xcode: "16.0" + macos: "15" + - swift: "6.1" + xcode: "16.3" + macos: "15" + - swift: "6.2" + xcode: "26.0" + macos: "26" + timeout-minutes: 10 + steps: + - name: Checkout code + uses: actions/checkout@v6 - name: Build and Test (Swift ${{ matrix.swift-version }}) + - name: Cache Swift Package Manager dependencies + uses: actions/cache@v5 + with: + path: | + ~/.cache/org.swift.swiftpm + .build + key: ${{ runner.os }}-swift-${{ matrix.swift }}-spm-${{ hashFiles('Package.swift', 'Package.resolved') }} + restore-keys: | + ${{ runner.os }}-swift-${{ matrix.swift }}-spm- - steps: - - uses: actions/checkout@v4 - - uses: swift-actions/setup-swift@v2 - with: - swift-version: ${{ matrix.swift-version }} - - name: Build - run: swift build -v - - name: Run tests - run: swift test -v + - name: Lint + run: swift format lint --strict --recursive . + + - name: Build + run: swift build + + - name: Test + run: swift test diff --git a/.swift-format b/.swift-format new file mode 100644 index 0000000..8cfd52e --- /dev/null +++ b/.swift-format @@ -0,0 +1,15 @@ +{ + "version": 1, + "indentation": { + "spaces": 4 + }, + "lineLength": 160, + "maximumBlankLines": 1, + "respectsExistingLineBreaks": true, + "lineBreakBeforeEachArgument": true, + "multiElementCollectionTrailingCommas": true, + "spacesAroundRangeFormationOperators": true, + "rules": { + "AlwaysUseLowerCamelCase": false + } +} \ No newline at end of file diff --git a/Package.swift b/Package.swift index 469f5a0..663bfe3 100644 --- a/Package.swift +++ b/Package.swift @@ -12,20 +12,24 @@ let package = Package( // Products define the executables and libraries a package produces, making them visible to other packages. .library( name: "TypedStream", - targets: ["TypedStream"]), + targets: ["TypedStream"] + ), .library( name: "iMessage", - targets: ["iMessage"]), + targets: ["iMessage"] + ), ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. // Targets can depend on other targets in this package and products from dependencies. .target( name: "TypedStream", - dependencies: []), + dependencies: [] + ), .target( name: "iMessage", - dependencies: ["TypedStream"]), + dependencies: ["TypedStream"] + ), .testTarget( name: "iMessageTests", dependencies: ["iMessage"] diff --git a/Sources/TypedStream/Archivable.swift b/Sources/TypedStream/Archivable.swift index eefc6a0..26e0a43 100644 --- a/Sources/TypedStream/Archivable.swift +++ b/Sources/TypedStream/Archivable.swift @@ -14,25 +14,23 @@ public enum Archivable: Hashable, Sendable { // MARK: - Convenience Properties - /** - If this archivable represents an `NSString` or `NSMutableString` object, - returns its string value. - - ### Example - ```swift - let nsstring = Archivable.object( - Class(name: "NSString", version: 1), - [.string("Hello world")] - ) - print(nsstring.stringValue) // Optional("Hello world") - - let notNSString = Archivable.object( - Class(name: "NSNumber", version: 1), - [.signedInteger(100)] - ) - print(notNSString.stringValue) // nil - ``` - */ + /// If this archivable represents an `NSString` or `NSMutableString` object, + /// returns its string value. + /// + /// ### Example + /// ```swift + /// let nsstring = Archivable.object( + /// Class(name: "NSString", version: 1), + /// [.string("Hello world")] + /// ) + /// print(nsstring.stringValue) // Optional("Hello world") + /// + /// let notNSString = Archivable.object( + /// Class(name: "NSNumber", version: 1), + /// [.signedInteger(100)] + /// ) + /// print(notNSString.stringValue) // nil + /// ``` public var stringValue: String? { if case let .object(classInfo, value) = self, classInfo.name == "NSString" || classInfo.name == "NSMutableString", @@ -47,31 +45,29 @@ public enum Archivable: Hashable, Sendable { { return nil } - + return text } return nil } - /** - If this archivable represents an `NSNumber` object containing an integer, - returns its 64-bit integer value. - - ### Example - ```swift - let nsnumber = Archivable.object( - Class(name: "NSNumber", version: 1), - [.signedInteger(100)] - ) - print(nsnumber.integerValue) // Optional(100) - - let notNSNumber = Archivable.object( - Class(name: "NSString", version: 1), - [.string("Hello world")] - ) - print(notNSNumber.integerValue) // nil - ``` - */ + /// If this archivable represents an `NSNumber` object containing an integer, + /// returns its 64-bit integer value. + /// + /// ### Example + /// ```swift + /// let nsnumber = Archivable.object( + /// Class(name: "NSNumber", version: 1), + /// [.signedInteger(100)] + /// ) + /// print(nsnumber.integerValue) // Optional(100) + /// + /// let notNSNumber = Archivable.object( + /// Class(name: "NSString", version: 1), + /// [.string("Hello world")] + /// ) + /// print(notNSNumber.integerValue) // nil + /// ``` public var integerValue: Int64? { if case let .object(classInfo, value) = self, classInfo.name == "NSNumber", @@ -83,25 +79,23 @@ public enum Archivable: Hashable, Sendable { return nil } - /** - If this archivable represents an `NSNumber` object containing a floating-point value, - returns its double-precision value. - - ### Example - ```swift - let nsnumber = Archivable.object( - Class(name: "NSNumber", version: 1), - [.double(100.001)] - ) - print(nsnumber.doubleValue) // Optional(100.001) - - let notNSNumber = Archivable.object( - Class(name: "NSString", version: 1), - [.string("Hello world")] - ) - print(notNSNumber.doubleValue) // nil - ``` - */ + /// If this archivable represents an `NSNumber` object containing a floating-point value, + /// returns its double-precision value. + /// + /// ### Example + /// ```swift + /// let nsnumber = Archivable.object( + /// Class(name: "NSNumber", version: 1), + /// [.double(100.001)] + /// ) + /// print(nsnumber.doubleValue) // Optional(100.001) + /// + /// let notNSNumber = Archivable.object( + /// Class(name: "NSString", version: 1), + /// [.string("Hello world")] + /// ) + /// print(notNSNumber.doubleValue) // nil + /// ``` public var doubleValue: Double? { if case let .object(classInfo, value) = self, classInfo.name == "NSNumber", diff --git a/Sources/TypedStream/Type.swift b/Sources/TypedStream/Type.swift index 437149d..f7c4f39 100644 --- a/Sources/TypedStream/Type.swift +++ b/Sources/TypedStream/Type.swift @@ -92,7 +92,7 @@ public enum Type: Hashable, Sendable { var index = 1 while index < types.count, let digit = UInt8(exactly: types[index]), - (48...57).contains(digit) + (48 ... 57).contains(digit) { length = length * 10 + Int(digit - 48) // ASCII '0' is 48 index += 1 diff --git a/Sources/TypedStream/TypedStreamDecoder.swift b/Sources/TypedStream/TypedStreamDecoder.swift index 4000abc..0e21024 100644 --- a/Sources/TypedStream/TypedStreamDecoder.swift +++ b/Sources/TypedStream/TypedStreamDecoder.swift @@ -247,7 +247,7 @@ public final class TypedStreamDecoder { guard idx + size <= stream.count else { throw Error.outOfBounds(index: idx + size, length: stream.count) } - let data = Data(stream[idx..<(idx + size)]) + let data = Data(stream[idx ..< (idx + size)]) idx += size return data } diff --git a/Sources/iMessage/Database.swift b/Sources/iMessage/Database.swift index eae1f3a..9d6fdeb 100644 --- a/Sources/iMessage/Database.swift +++ b/Sources/iMessage/Database.swift @@ -173,7 +173,8 @@ public final class Database { GROUP BY chat_id HAVING COUNT(DISTINCT handle_id) = ? ) - """) + """ + ) // Add each participant as a value handles.forEach { handle in @@ -207,7 +208,8 @@ public final class Database { let displayName = sqlite3_column_text(statement, 1).map { String(cString: $0) } let lastMessageDate = Date( - nanosecondsSinceReferenceDate: sqlite3_column_int64(statement, 3)) + nanosecondsSinceReferenceDate: sqlite3_column_int64(statement, 3) + ) // Fetch participants for this chat let participants = try fetchParticipants(for: chatId) @@ -257,7 +259,8 @@ public final class Database { JOIN handle h ON m.handle_id = h.ROWID WHERE h.id IN (\(String(repeating: "?,", count: handles.count).dropLast())) ) - """) + """ + ) // Add each participant as a value handles.forEach { handle in @@ -325,7 +328,8 @@ public final class Database { } let date = Date( - nanosecondsSinceReferenceDate: sqlite3_column_int64(statement, 3)) + nanosecondsSinceReferenceDate: sqlite3_column_int64(statement, 3) + ) let isFromMe = sqlite3_column_int(statement, 4) != 0 let rawReadAt = sqlite3_column_int64(statement, 7) let readAt = diff --git a/Sources/iMessage/Extensions/Data+Extensions.swift b/Sources/iMessage/Extensions/Data+Extensions.swift index 735699f..65ef69e 100644 --- a/Sources/iMessage/Extensions/Data+Extensions.swift +++ b/Sources/iMessage/Extensions/Data+Extensions.swift @@ -9,7 +9,7 @@ extension Data { while index < string.endIndex { let nextIndex = string.index(index, offsetBy: 2) guard nextIndex <= string.endIndex, - let byte = UInt8(string[index..