From 41229077d7fc408c772f5d558c4f411ea329a25c Mon Sep 17 00:00:00 2001 From: Rick Hohler Date: Sat, 24 Oct 2026 19:50:47 -0500 Subject: [PATCH] Platform truth, no placeholder digests, DocC in target, test layout platform-and-test-hygiene (DesignAlgorithmsKit-Specs). - #error without CryptoKit instead of a placeholder "SHA-256"; SHA256 enum delegates to SHA256Strategy; strategies drop fatalError fallbacks - Remove WASM/tvOS/watchOS claims and the dead WASMGuard.swift; CI builds macOS and the iOS Simulator (the declared platforms) - DocC catalog moved into the target, articles renamed so they no longer collide with symbols; docbuild is warning-free. Drop the Pages publish workflow and script (they required swift-docc-plugin and only ever published a fallback page) - gzip CLI tests move to DesignAlgorithmsKitIntegrationTests (XCTSkip when tools are missing); remove the swift-testing placeholder; add HybridEncryption tests; tokenizer cases for the documented examples - Fix FilenameTokenizer, ObjectFactory and HashAlgorithm doc comments - Backfill CHANGELOG 1.8.0, 1.9.0 and 1.10.0 Co-Authored-By: Claude Opus 5.5 --- .github/workflows/publish-github-pages.yml | 49 --- .github/workflows/test.yml | 3 + CHANGELOG.md | 35 ++ Package.swift | 15 +- README.md | 10 +- .../Algorithms/Compression/Gzip.swift | 2 +- .../Algorithms/Hashing/HashAlgorithm.swift | 4 +- .../Hashing/HashAlgorithmProtocol.swift | 40 +-- .../Hashing/Strategies/MD5Strategy.swift | 6 - .../Hashing/Strategies/SHA1Strategy.swift | 6 - .../Hashing/Strategies/SHA256Strategy.swift | 8 - .../Algorithms/Text/FilenameTokenizer.swift | 4 +- .../Algorithms/WASMGuard.swift | 6 - .../Creational/Factory.swift | 19 +- .../DesignAlgorithmsKit.md | 39 ++- .../RegistryPattern.md | 0 .../UsingBloomFilters.md | 0 .../UsingMerkleTrees.md | 0 .../DesignAlgorithmsKit.swift | 11 +- .../GzipCommandLineTests.swift} | 20 +- .../Crypto/HybridEncryptionTests.swift | 65 ++++ .../Text/FilenameTokenizerTests.swift | 2 + .../DesignAlgorithmsKitTests.swift | 6 - scripts/publish-docc-to-github-pages.sh | 328 ------------------ 24 files changed, 184 insertions(+), 494 deletions(-) delete mode 100644 .github/workflows/publish-github-pages.yml delete mode 100644 Sources/DesignAlgorithmsKit/Algorithms/WASMGuard.swift rename {Documentation.docc => Sources/DesignAlgorithmsKit/DesignAlgorithmsKit.docc}/DesignAlgorithmsKit.md (55%) rename Documentation.docc/Registry.md => Sources/DesignAlgorithmsKit/DesignAlgorithmsKit.docc/RegistryPattern.md (100%) rename Documentation.docc/BloomFilter.md => Sources/DesignAlgorithmsKit/DesignAlgorithmsKit.docc/UsingBloomFilters.md (100%) rename Documentation.docc/MerkleTree.md => Sources/DesignAlgorithmsKit/DesignAlgorithmsKit.docc/UsingMerkleTrees.md (100%) rename Tests/{DesignAlgorithmsKitTests/IntegrationCompressionTests.swift => DesignAlgorithmsKitIntegrationTests/GzipCommandLineTests.swift} (87%) create mode 100644 Tests/DesignAlgorithmsKitTests/Algorithms/Crypto/HybridEncryptionTests.swift delete mode 100644 Tests/DesignAlgorithmsKitTests/DesignAlgorithmsKitTests.swift delete mode 100755 scripts/publish-docc-to-github-pages.sh diff --git a/.github/workflows/publish-github-pages.yml b/.github/workflows/publish-github-pages.yml deleted file mode 100644 index 3cad8b7..0000000 --- a/.github/workflows/publish-github-pages.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: Publish Documentation to GitHub Pages - -on: - push: - branches: [ main ] - tags: - - 'v*' - workflow_dispatch: - -jobs: - publish: - runs-on: macos-latest - permissions: - contents: write - repository-projects: read - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Configure Git - run: | - git config --global user.name "github-actions[bot]" - git config --global user.email "github-actions[bot]@users.noreply.github.com" - - - name: Select Xcode version - run: sudo xcode-select -s /Applications/Xcode.app - - - name: Show Swift version - run: swift --version - - - name: Resolve dependencies - run: swift package resolve - - - name: Build package - run: swift build - - - name: Generate and Publish DocC Documentation - run: | - chmod +x scripts/publish-docc-to-github-pages.sh - ./scripts/publish-docc-to-github-pages.sh - env: - # Use PAT if available, otherwise fall back to GITHUB_TOKEN (may have limited access) - # GitHub Actions expressions: use ternary operator with fromJSON for boolean check - GITHUB_TOKEN: ${{ secrets.GH_PAT != '' && secrets.GH_PAT || secrets.GITHUB_TOKEN }} - GH_TOKEN: ${{ secrets.GH_PAT != '' && secrets.GH_PAT || secrets.GITHUB_TOKEN }} - continue-on-error: true diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 39d6238..44ebd2c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,6 +31,9 @@ jobs: - name: Build package run: swift build + + - name: Build for iOS Simulator + run: xcodebuild build -scheme DesignAlgorithmsKit -destination 'generic/platform=iOS Simulator' - name: Run tests with code coverage run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d70792..c52d08d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed (`platform-and-test-hygiene`) +- Building without CryptoKit is a compile-time error. The non-cryptographic placeholder that `SHA256.hash` returned on such platforms, and the `fatalError` paths in `MD5Strategy`/`SHA1Strategy`, are gone. `SHA256.hash(data:)` delegates to `SHA256Strategy`. +- Platform claims match the build: macOS 12 and iOS 15 (both built in CI). WASM, tvOS and watchOS are no longer claimed. +- The DocC catalog lives in the target (`Sources/DesignAlgorithmsKit/DesignAlgorithmsKit.docc`), and its symbol links resolve with no warnings. +- The gzip/gunzip interoperability tests moved to a separate `DesignAlgorithmsKitIntegrationTests` target (macOS, skipped when the tools are missing). +- Corrected doc comments for `FilenameTokenizer`, `ObjectFactory` and `HashAlgorithm`. + +### Added (`platform-and-test-hygiene`) +- `HybridEncryption` tests. + +### Removed (`platform-and-test-hygiene`) +- The empty `Algorithms/WASMGuard.swift` and its `exclude` entry. +- The GitHub Pages publish workflow and script. They needed `swift-docc-plugin`, which this package does not depend on, so they only ever published a static fallback page. +- The placeholder swift-testing `example()` test. + ### Changed (`fix-ulid-spec-compliance`) - **ULID text format:** `ULID.string`/`description` and `ULID(string:)` now follow the ULID specification (2 padding bits at the front, so the first character is `0`-`7`). `ULID(string:)` rejects strings starting above `7`. The 16 bytes are unchanged; only the text differs. - **Migration:** strings stored by 1.10.x/1.11.x do not parse the same way. Read them with the new `ULID(legacyString:)` and re-save `string`. Known consumers: RetroboxBBS and RetroServe (TermFoundation). @@ -49,6 +64,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Notes - Releases v1.8.0 (FilenameTokenizer, UUIDv5Generator), v1.9.0 (Visitor, Graph, Gzip) and v1.10.0 were tagged without CHANGELOG entries; backfill is tracked in `platform-and-test-hygiene` +## [1.10.0] - 2026-02-04 + +### Changed +- `.gitignore` excludes `.build` and other build artifacts. + +### Notes +- A MerkleTree consolidation was merged and reverted before the tag; the public API matches 1.9.0. + +## [1.9.0] - 2026-01-02 + +### Added +- `Visitor` pattern and `Graph` data structure (#14). +- `Gzip` in-memory GZIP compression and decompression (#15). + +## [1.8.0] - 2025-12-29 + +### Added +- `UUIDv5Generator` for name-based (RFC 4122 version 5) UUIDs. +- `FilenameTokenizer` for splitting file names into words (#13). + ## [1.7.0] - 2025-12-21 ### Added diff --git a/Package.swift b/Package.swift index cae678a..7a0146f 100644 --- a/Package.swift +++ b/Package.swift @@ -14,18 +14,13 @@ let package = Package( name: "DesignAlgorithmsKit", targets: ["DesignAlgorithmsKit"]), ], - dependencies: [ - // No dependencies - keep it simple for WASM compatibility - ], + // No package dependencies. Apple platforms only (CryptoKit, NSLock). + dependencies: [], targets: [ .target( name: "DesignAlgorithmsKit", dependencies: [], path: "Sources/DesignAlgorithmsKit", - exclude: [ - // Exclude hash/crypto types for WASM builds (they use NSLock) - "Algorithms/WASMGuard.swift" - ], linkerSettings: [ .linkedLibrary("z") ] @@ -38,5 +33,11 @@ let package = Package( .linkedLibrary("z") ] ), + // Tests that run system tools (gzip/gunzip); macOS only, skipped when the tools are missing. + .testTarget( + name: "DesignAlgorithmsKitIntegrationTests", + dependencies: ["DesignAlgorithmsKit"], + path: "Tests/DesignAlgorithmsKitIntegrationTests" + ), ] ) diff --git a/README.md b/README.md index 71290b1..f6ac009 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ One library product and target: **`DesignAlgorithmsKit`** (`Sources/DesignAlgori ## Platforms -`Package.swift` declares macOS 12 and iOS 15, with swift-tools-version 5.10. The library imports CryptoKit and links the system zlib, so in practice it builds only for Apple platforms. The older WASM, tvOS and watchOS claims are not backed by the build. +macOS 12 and iOS 15 (swift-tools-version 5.10); CI builds both. The library uses CryptoKit, `NSLock` and the system zlib, so it builds only for Apple platforms; building without CryptoKit fails with a compile-time error instead of producing placeholder digests. tvOS and watchOS are not declared or built. ## Dependencies @@ -93,16 +93,16 @@ swift test ``` - No other packages are needed to build DAK. -- Current baseline: `swift build` succeeds with two warnings, and `swift test` runs 343 XCTest tests and 1 swift-testing placeholder with 0 failures. -- `IntegrationCompressionTests` runs `/usr/bin/gzip` and `/usr/bin/gunzip`, so it needs macOS. +- `swift build` is warning-free. `swift test` runs the unit tests (`DesignAlgorithmsKitTests`, 374 XCTest tests) and the integration tests (`DesignAlgorithmsKitIntegrationTests`), which call `/usr/bin/gzip` and `/usr/bin/gunzip` and are skipped when those tools are missing. +- CI also builds for the iOS Simulator: `xcodebuild build -scheme DesignAlgorithmsKit -destination 'generic/platform=iOS Simulator'`. ## Known issues -- Platform claims are broader than tested, `WASMGuard.swift` is dead code, SHA code is duplicated, and the DocC catalog is not attached to the target. +None known. ## Documentation -- `Documentation.docc/`: DocC articles. They are not yet attached to the target. +- `Sources/DesignAlgorithmsKit/DesignAlgorithmsKit.docc/`: DocC catalog. Build it with Xcode's Product > Build Documentation or `xcodebuild docbuild -scheme DesignAlgorithmsKit -destination 'generic/platform=macOS'`. - [CHANGELOG.md](CHANGELOG.md), [CONTRIBUTING.md](CONTRIBUTING.md), [SECURITY.md](SECURITY.md), [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). ## License diff --git a/Sources/DesignAlgorithmsKit/Algorithms/Compression/Gzip.swift b/Sources/DesignAlgorithmsKit/Algorithms/Compression/Gzip.swift index 5e38644..a86f5f5 100644 --- a/Sources/DesignAlgorithmsKit/Algorithms/Compression/Gzip.swift +++ b/Sources/DesignAlgorithmsKit/Algorithms/Compression/Gzip.swift @@ -39,7 +39,7 @@ public struct Gzip { } /// Decompresses GZIP compressed data. - /// - Parameter data: The compressed data (including GZIP header). + /// - Parameter input: The compressed data (including GZIP header). /// - Returns: The uncompressed data. /// - Throws: `Gzip.Error.decompressionFailed`, or `invalidData` for a malformed header or a /// trailer whose CRC-32 or ISIZE does not match the inflated bytes. diff --git a/Sources/DesignAlgorithmsKit/Algorithms/Hashing/HashAlgorithm.swift b/Sources/DesignAlgorithmsKit/Algorithms/Hashing/HashAlgorithm.swift index 26a9054..913fafa 100644 --- a/Sources/DesignAlgorithmsKit/Algorithms/Hashing/HashAlgorithm.swift +++ b/Sources/DesignAlgorithmsKit/Algorithms/Hashing/HashAlgorithm.swift @@ -9,7 +9,7 @@ import Foundation -/// Hash algorithms supported for file and disk image hashing +/// Hash algorithms for content hashing and checksum validation public enum HashAlgorithm: String, CaseIterable, Codable, Sendable { /// SHA-256: Recommended default for new hash generation (cryptographically secure) case sha256 = "sha256" @@ -17,7 +17,7 @@ public enum HashAlgorithm: String, CaseIterable, Codable, Sendable { /// SHA-1: Legacy support for existing systems (deprecated but collision-resistant) case sha1 = "sha1" - /// MD5: Read-only legacy support for validation against companion files and existing checksums + /// MD5: Read-only legacy support for validation against existing checksums /// ⚠️ Do not use for new hash generation - use SHA-256 instead /// ✅ Supported for: reading companion checksum files (.md5, .md5sum), validating against existing MD5 hashes case md5 = "md5" diff --git a/Sources/DesignAlgorithmsKit/Algorithms/Hashing/HashAlgorithmProtocol.swift b/Sources/DesignAlgorithmsKit/Algorithms/Hashing/HashAlgorithmProtocol.swift index 6b90b63..74e36bf 100644 --- a/Sources/DesignAlgorithmsKit/Algorithms/Hashing/HashAlgorithmProtocol.swift +++ b/Sources/DesignAlgorithmsKit/Algorithms/Hashing/HashAlgorithmProtocol.swift @@ -7,8 +7,8 @@ import Foundation -#if canImport(CryptoKit) -import CryptoKit +#if !canImport(CryptoKit) +#error("DesignAlgorithmsKit needs CryptoKit for its digests; it builds only for Apple platforms.") #endif /// Protocol for hash algorithms @@ -43,43 +43,11 @@ public extension HashAlgorithmProtocol { } } -/// SHA-256 hash algorithm +/// SHA-256 hash algorithm (CryptoKit, through ``SHA256Strategy``) public enum SHA256: HashAlgorithmProtocol { public static let name = "SHA-256" public static func hash(data: Data) -> Data { - #if canImport(CryptoKit) - let digest = CryptoKit.SHA256.hash(data: data) - return Data(digest) - #else - // Fallback implementation - // In production, use CommonCrypto or another crypto library - return fallbackHash(data: data) - #endif + SHA256Strategy().compute(data: data) } - - #if !canImport(CryptoKit) - /// Fallback hash implementation (simple, not cryptographically secure) - /// For production use, import CryptoKit or CommonCrypto - /// - Note: This path is conditionally compiled and only available when CryptoKit is not available. - /// It cannot be tested in environments where CryptoKit is available (like macOS/iOS test environments). - /// The fallback implementation is intentionally simple and not cryptographically secure. - private static func fallbackHash(data: Data) -> Data { - var hash = Data(count: 32) - data.withUnsafeBytes { dataBytes in - hash.withUnsafeMutableBytes { hashBytes in - // Simple hash (NOT cryptographically secure) - // This is a placeholder - use CryptoKit in production - for i in 0..<32 { - var value: UInt8 = 0 - for j in 0.. Data { - #if canImport(CryptoKit) let digest = Insecure.MD5.hash(data: data) return Data(digest) - #else - fatalError("CryptoKit not available for MD5") - #endif } } diff --git a/Sources/DesignAlgorithmsKit/Algorithms/Hashing/Strategies/SHA1Strategy.swift b/Sources/DesignAlgorithmsKit/Algorithms/Hashing/Strategies/SHA1Strategy.swift index df556f6..884c3e5 100644 --- a/Sources/DesignAlgorithmsKit/Algorithms/Hashing/Strategies/SHA1Strategy.swift +++ b/Sources/DesignAlgorithmsKit/Algorithms/Hashing/Strategies/SHA1Strategy.swift @@ -2,9 +2,7 @@ // SHA-1 Hash Strategy import Foundation -#if canImport(CryptoKit) import CryptoKit -#endif /// Strategy for SHA-1 hashing /// Note: SHA-1 is insecure and should only be used for legacy compatibility @@ -14,11 +12,7 @@ public struct SHA1Strategy: HashStrategy { public init() {} public func compute(data: Data) -> Data { - #if canImport(CryptoKit) let digest = Insecure.SHA1.hash(data: data) return Data(digest) - #else - fatalError("CryptoKit not available for SHA1") - #endif } } diff --git a/Sources/DesignAlgorithmsKit/Algorithms/Hashing/Strategies/SHA256Strategy.swift b/Sources/DesignAlgorithmsKit/Algorithms/Hashing/Strategies/SHA256Strategy.swift index 5d48da9..0fffda7 100644 --- a/Sources/DesignAlgorithmsKit/Algorithms/Hashing/Strategies/SHA256Strategy.swift +++ b/Sources/DesignAlgorithmsKit/Algorithms/Hashing/Strategies/SHA256Strategy.swift @@ -2,9 +2,7 @@ // SHA-256 Hash Strategy import Foundation -#if canImport(CryptoKit) import CryptoKit -#endif /// Strategy for SHA-256 hashing public struct SHA256Strategy: HashStrategy { @@ -13,13 +11,7 @@ public struct SHA256Strategy: HashStrategy { public init() {} public func compute(data: Data) -> Data { - #if canImport(CryptoKit) let digest = CryptoKit.SHA256.hash(data: data) return Data(digest) - #else - // Uses the existing SHA256 implementation in DesignAlgorithmsKit (from HashAlgorithmProtocol) - // Since we are INSIDE DAK, we can access SHA256 enum directly. - return SHA256.hash(data: data) - #endif } } diff --git a/Sources/DesignAlgorithmsKit/Algorithms/Text/FilenameTokenizer.swift b/Sources/DesignAlgorithmsKit/Algorithms/Text/FilenameTokenizer.swift index 1e2ad0a..2319060 100644 --- a/Sources/DesignAlgorithmsKit/Algorithms/Text/FilenameTokenizer.swift +++ b/Sources/DesignAlgorithmsKit/Algorithms/Text/FilenameTokenizer.swift @@ -7,8 +7,8 @@ public struct FilenameTokenizer { /// /// Examples: /// - "PDFExtractor.swift" -> ["PDF", "Extractor", "swift"] - /// - "disk_ii_controller" -> ["disk", "ii", "controller"] - /// - "iPhone12Pro" -> ["iPhone", "12", "Pro"] + /// - "user_profile_view" -> ["user", "profile", "view"] + /// - "iPhone12Pro" -> ["i", "Phone", "12", "Pro"] (every lowercase-to-uppercase change splits) public static func tokenize(_ filename: String) -> [String] { // 1. Replace common separators with spaces let separators = CharacterSet(charactersIn: "_-.") diff --git a/Sources/DesignAlgorithmsKit/Algorithms/WASMGuard.swift b/Sources/DesignAlgorithmsKit/Algorithms/WASMGuard.swift deleted file mode 100644 index 6d2e181..0000000 --- a/Sources/DesignAlgorithmsKit/Algorithms/WASMGuard.swift +++ /dev/null @@ -1,6 +0,0 @@ -// -// WASMGuard.swift -// DesignAlgorithmsKit -// -// Placeholder for WASM compatibility guards -// diff --git a/Sources/DesignAlgorithmsKit/Creational/Factory.swift b/Sources/DesignAlgorithmsKit/Creational/Factory.swift index 2530e07..ff45d1e 100644 --- a/Sources/DesignAlgorithmsKit/Creational/Factory.swift +++ b/Sources/DesignAlgorithmsKit/Creational/Factory.swift @@ -22,13 +22,18 @@ public protocol FactoryProduct { /// ## Usage /// /// ```swift -/// // Register a factory method -/// ObjectFactory.register(type: "myType") { config in -/// return MyType(configuration: config) +/// let factory = ObjectFactory.shared +/// +/// // Register a factory closure +/// factory.register(type: "myType") { config in +/// return try MyType(configuration: config) /// } /// -/// // Create object via factory -/// let object = try ObjectFactory.create(type: "myType", configuration: [:]) +/// // Or register a FactoryProduct type under its type name +/// factory.register(MyType.self) +/// +/// // Create an object +/// let object = try factory.create(type: "myType", configuration: [:]) /// ``` public final class ObjectFactory: @unchecked Sendable { /// Shared singleton instance (lazy initialization) @@ -77,7 +82,9 @@ public final class ObjectFactory: @unchecked Sendable { } /// Register a factory method for a FactoryProduct type - /// - Parameter type: The FactoryProduct type + /// - Parameters: + /// - type: The FactoryProduct type + /// - key: Registration key; defaults to the type's name /// Thread-safe: Can be called concurrently public func register(_ type: T.Type, key: String? = nil) { #if !os(WASI) && !arch(wasm32) diff --git a/Documentation.docc/DesignAlgorithmsKit.md b/Sources/DesignAlgorithmsKit/DesignAlgorithmsKit.docc/DesignAlgorithmsKit.md similarity index 55% rename from Documentation.docc/DesignAlgorithmsKit.md rename to Sources/DesignAlgorithmsKit/DesignAlgorithmsKit.docc/DesignAlgorithmsKit.md index b1421ee..a7c20f7 100644 --- a/Documentation.docc/DesignAlgorithmsKit.md +++ b/Sources/DesignAlgorithmsKit/DesignAlgorithmsKit.docc/DesignAlgorithmsKit.md @@ -1,4 +1,4 @@ -# DesignAlgorithmsKit +# ``DesignAlgorithmsKit`` A Swift package providing common design patterns and algorithms with protocols and base types for extensibility. @@ -12,35 +12,42 @@ All patterns and algorithms follow consistent implementation guidelines for main ## Topics +### Articles + +- +- +- + ### Design Patterns #### Creational Patterns -- ``ThreadSafeSingleton`` - Thread-safe singleton base class -- ``ObjectFactory`` - Object creation without specifying concrete classes -- ``BaseBuilder`` - Step-by-step object construction +- ``ThreadSafeSingleton`` +- ``ObjectFactory`` +- ``BaseBuilder`` #### Structural Patterns -- ``BaseFacade`` - Simplified interface to complex subsystems -- ``BaseAdapter`` - Adapting interfaces to client expectations +- ``BaseFacade`` +- ``BaseAdapter`` #### Behavioral Patterns -- ``StrategyContext`` - Interchangeable algorithms -- ``BaseObservable`` - Event notification and subscription +- ``StrategyContext`` +- ``BaseObservable`` #### Modern Patterns -- ``TypeRegistry`` / ``Registry`` - Centralized type and value registration +- ``TypeRegistry`` +- ``Registry`` ### Algorithms & Data Structures #### Data Structures -- ``MerkleTree`` - Hash tree for efficient data verification -- ``BloomFilter`` - Probabilistic data structure for membership testing -- ``CountingBloomFilter`` - Bloom Filter variant that supports element removal +- ``MerkleTree`` +- ``BloomFilter`` +- ``CountingBloomFilter`` #### Hashing -- ``HashAlgorithm`` - Supported hash algorithms (SHA-256, SHA-1, MD5, CRC-32) -- ``HashComputation`` - Hash computation helpers -- ``SHA256`` - SHA-256 hash algorithm implementation +- ``HashAlgorithm`` +- ``HashComputation`` +- ``SHA256`` ## Getting Started @@ -48,7 +55,7 @@ Add DesignAlgorithmsKit to your `Package.swift`: ```swift dependencies: [ - .package(url: "https://github.com/nibbleware/DesignAlgorithmsKit.git", from: "1.0.0") + .package(url: "https://github.com/nibbleware/DesignAlgorithmsKit.git", from: "1.11.0") ] ``` diff --git a/Documentation.docc/Registry.md b/Sources/DesignAlgorithmsKit/DesignAlgorithmsKit.docc/RegistryPattern.md similarity index 100% rename from Documentation.docc/Registry.md rename to Sources/DesignAlgorithmsKit/DesignAlgorithmsKit.docc/RegistryPattern.md diff --git a/Documentation.docc/BloomFilter.md b/Sources/DesignAlgorithmsKit/DesignAlgorithmsKit.docc/UsingBloomFilters.md similarity index 100% rename from Documentation.docc/BloomFilter.md rename to Sources/DesignAlgorithmsKit/DesignAlgorithmsKit.docc/UsingBloomFilters.md diff --git a/Documentation.docc/MerkleTree.md b/Sources/DesignAlgorithmsKit/DesignAlgorithmsKit.docc/UsingMerkleTrees.md similarity index 100% rename from Documentation.docc/MerkleTree.md rename to Sources/DesignAlgorithmsKit/DesignAlgorithmsKit.docc/UsingMerkleTrees.md diff --git a/Sources/DesignAlgorithmsKit/DesignAlgorithmsKit.swift b/Sources/DesignAlgorithmsKit/DesignAlgorithmsKit.swift index dff2458..dabc233 100644 --- a/Sources/DesignAlgorithmsKit/DesignAlgorithmsKit.swift +++ b/Sources/DesignAlgorithmsKit/DesignAlgorithmsKit.swift @@ -35,9 +35,9 @@ import Foundation /// - Queue Processing Pattern /// - Merger Pattern /// -/// ### Data Structures (not available in WASM builds) -/// - Bloom Filter (requires NSLock) -/// - Merkle Tree (requires NSLock) +/// ### Data Structures +/// - Bloom Filter +/// - Merkle Tree /// /// ### Core Utilities /// - Type Registry @@ -46,9 +46,8 @@ import Foundation /// /// - macOS 12.0+ /// - iOS 15.0+ -/// - tvOS 15.0+ -/// - watchOS 8.0+ -/// - WASM (limited - excludes hash/crypto types that require NSLock) +/// +/// Apple platforms only: the library uses CryptoKit, `NSLock` and the system zlib. public struct DesignAlgorithmsKit { /// Library version public static let version = "1.11.0" diff --git a/Tests/DesignAlgorithmsKitTests/IntegrationCompressionTests.swift b/Tests/DesignAlgorithmsKitIntegrationTests/GzipCommandLineTests.swift similarity index 87% rename from Tests/DesignAlgorithmsKitTests/IntegrationCompressionTests.swift rename to Tests/DesignAlgorithmsKitIntegrationTests/GzipCommandLineTests.swift index ff3db9e..68b7926 100644 --- a/Tests/DesignAlgorithmsKitTests/IntegrationCompressionTests.swift +++ b/Tests/DesignAlgorithmsKitIntegrationTests/GzipCommandLineTests.swift @@ -1,13 +1,24 @@ +// +// GzipCommandLineTests.swift +// DesignAlgorithmsKitIntegrationTests +// +// Interoperability with the system gzip/gunzip tools (macOS only). +// + import XCTest -@testable import DesignAlgorithmsKit +import DesignAlgorithmsKit -final class IntegrationCompressionTests: XCTestCase { +#if os(macOS) +final class GzipCommandLineTests: XCTestCase { let fileManager = FileManager.default var tempDir: URL! - override func setUp() { - super.setUp() + override func setUpWithError() throws { + try super.setUpWithError() + for tool in ["/usr/bin/gzip", "/usr/bin/gunzip"] where !fileManager.isExecutableFile(atPath: tool) { + throw XCTSkip("\(tool) is not available") + } tempDir = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString) try? fileManager.createDirectory(at: tempDir, withIntermediateDirectories: true) } @@ -83,3 +94,4 @@ final class IntegrationCompressionTests: XCTestCase { XCTAssertEqual(decompressedData, originalData) } } +#endif diff --git a/Tests/DesignAlgorithmsKitTests/Algorithms/Crypto/HybridEncryptionTests.swift b/Tests/DesignAlgorithmsKitTests/Algorithms/Crypto/HybridEncryptionTests.swift new file mode 100644 index 0000000..83c26b5 --- /dev/null +++ b/Tests/DesignAlgorithmsKitTests/Algorithms/Crypto/HybridEncryptionTests.swift @@ -0,0 +1,65 @@ +// +// HybridEncryptionTests.swift +// DesignAlgorithmsKitTests +// + +import XCTest +import CryptoKit +@testable import DesignAlgorithmsKit + +final class HybridEncryptionTests: XCTestCase { + func testRoundTrip() throws { + let recipient = Curve25519.KeyAgreement.PrivateKey() + let message = Data("hybrid encryption round trip".utf8) + let packet = try HybridEncryption.encrypt(message, to: recipient.publicKey) + XCTAssertEqual(try HybridEncryption.decrypt(packet, with: recipient), message) + } + + func testEmptyMessageRoundTrip() throws { + let recipient = Curve25519.KeyAgreement.PrivateKey() + let packet = try HybridEncryption.encrypt(Data(), to: recipient.publicKey) + XCTAssertEqual(try HybridEncryption.decrypt(packet, with: recipient), Data()) + } + + func testPacketLayout() throws { + let recipient = Curve25519.KeyAgreement.PrivateKey() + let message = Data(repeating: 0x42, count: 100) + let packet = try HybridEncryption.encrypt(message, to: recipient.publicKey) + // 32-byte ephemeral key + 12-byte nonce + ciphertext + 16-byte tag + XCTAssertEqual(packet.count, 32 + 12 + message.count + 16) + } + + func testEachEncryptionUsesAFreshEphemeralKey() throws { + let recipient = Curve25519.KeyAgreement.PrivateKey() + let message = Data("same input".utf8) + let first = try HybridEncryption.encrypt(message, to: recipient.publicKey) + let second = try HybridEncryption.encrypt(message, to: recipient.publicKey) + XCTAssertNotEqual(first.prefix(32), second.prefix(32)) + XCTAssertNotEqual(first, second) + } + + func testShortPacketThrowsInvalidLength() { + let recipient = Curve25519.KeyAgreement.PrivateKey() + for count in [0, 1, 31, 32] { + XCTAssertThrowsError(try HybridEncryption.decrypt(Data(count: count), with: recipient)) { error in + guard case CryptoError.invalidDataLength = error else { + return XCTFail("Expected invalidDataLength for \(count) bytes, got \(error)") + } + } + } + } + + func testWrongRecipientCannotDecrypt() throws { + let recipient = Curve25519.KeyAgreement.PrivateKey() + let other = Curve25519.KeyAgreement.PrivateKey() + let packet = try HybridEncryption.encrypt(Data("secret".utf8), to: recipient.publicKey) + XCTAssertThrowsError(try HybridEncryption.decrypt(packet, with: other)) + } + + func testTamperedCiphertextIsRejected() throws { + let recipient = Curve25519.KeyAgreement.PrivateKey() + var packet = try HybridEncryption.encrypt(Data("secret".utf8), to: recipient.publicKey) + packet[packet.count - 1] ^= 0x01 + XCTAssertThrowsError(try HybridEncryption.decrypt(packet, with: recipient)) + } +} diff --git a/Tests/DesignAlgorithmsKitTests/Algorithms/Text/FilenameTokenizerTests.swift b/Tests/DesignAlgorithmsKitTests/Algorithms/Text/FilenameTokenizerTests.swift index 74ab485..110431f 100644 --- a/Tests/DesignAlgorithmsKitTests/Algorithms/Text/FilenameTokenizerTests.swift +++ b/Tests/DesignAlgorithmsKitTests/Algorithms/Text/FilenameTokenizerTests.swift @@ -10,6 +10,8 @@ final class FilenameTokenizerTests: XCTestCase { ("PascalCaseFile", ["Pascal", "Case", "File"]), ("kebab-case-file", ["kebab", "case", "file"]), ("PDFExtractor", ["PDF", "Extractor"]), + ("user_profile_view", ["user", "profile", "view"]), + ("iPhone12Pro", ["i", "Phone", "12", "Pro"]), ("PDFExtractor.swift", ["PDF", "Extractor", "swift"]), ("file123", ["file", "123"]), ("123file", ["123", "file"]), diff --git a/Tests/DesignAlgorithmsKitTests/DesignAlgorithmsKitTests.swift b/Tests/DesignAlgorithmsKitTests/DesignAlgorithmsKitTests.swift deleted file mode 100644 index e10df74..0000000 --- a/Tests/DesignAlgorithmsKitTests/DesignAlgorithmsKitTests.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Testing -@testable import DesignAlgorithmsKit - -@Test func example() async throws { - // Write your test here and use APIs like `#expect(...)` to check expected conditions. -} diff --git a/scripts/publish-docc-to-github-pages.sh b/scripts/publish-docc-to-github-pages.sh deleted file mode 100755 index 313d7fc..0000000 --- a/scripts/publish-docc-to-github-pages.sh +++ /dev/null @@ -1,328 +0,0 @@ -#!/bin/bash - -# Script to generate DocC documentation and publish it to GitHub Pages -# This script generates documentation using xcodebuild docbuild -# and publishes it to a GitHub Pages repository (rickhohler.github.io) - -set -e - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' # No Color - -# Get the repository root directory -REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -PACKAGE_NAME="DesignAlgorithmsKit" - -# Get GitHub username -# In GitHub Actions, use GITHUB_REPOSITORY_OWNER if available -if [ -n "$GITHUB_ACTIONS" ] && [ -n "$GITHUB_REPOSITORY_OWNER" ]; then - GITHUB_USER="$GITHUB_REPOSITORY_OWNER" -elif [ -n "$GITHUB_ACTIONS" ] && [ -n "$GITHUB_REPOSITORY" ]; then - # Extract username from GITHUB_REPOSITORY (format: owner/repo) - GITHUB_USER=$(echo "$GITHUB_REPOSITORY" | cut -d'/' -f1) -elif [ -n "$GH_TOKEN" ] || [ -n "$GITHUB_TOKEN" ]; then - # Try to get username from GitHub API using token - GITHUB_USER=$(gh api user --jq '.login' 2>/dev/null || echo "") - if [ -z "$GITHUB_USER" ]; then - echo -e "${YELLOW}Warning: Could not get username from API, trying GITHUB_REPOSITORY${NC}" - if [ -n "$GITHUB_REPOSITORY" ]; then - GITHUB_USER=$(echo "$GITHUB_REPOSITORY" | cut -d'/' -f1) - fi - fi -else - GITHUB_USER=$(gh api user --jq '.login') -fi -if [ -z "$GITHUB_USER" ]; then - echo -e "${RED}Error: Could not get GitHub username${NC}" - exit 1 -fi - -# GitHub Pages repository name (typically username.github.io) -GITHUB_PAGES_REPO="${GITHUB_USER}.github.io" -GITHUB_PAGES_URL="https://github.com/${GITHUB_USER}/${GITHUB_PAGES_REPO}.git" - -# Local clone directory - try to use existing clone, otherwise use temp location -# In GitHub Actions, use a temp directory -if [ -n "$GITHUB_ACTIONS" ]; then - CLONE_DIR="${REPO_ROOT}/.github-pages-clone" -elif [ -d "/Users/${GITHUB_USER}/CODE/GITHUB/${GITHUB_PAGES_REPO}" ]; then - CLONE_DIR="/Users/${GITHUB_USER}/CODE/GITHUB/${GITHUB_PAGES_REPO}" -else - CLONE_DIR="${REPO_ROOT}/.github-pages-clone" -fi - -echo -e "${GREEN}DocC Documentation Publisher${NC}" -echo "User: $GITHUB_USER" -echo "Repository: $GITHUB_PAGES_REPO" -echo "Package: $PACKAGE_NAME" -echo "" - -# Change to repository root -cd "$REPO_ROOT" - -# Generate DocC documentation using xcodebuild -echo -e "${GREEN}Generating DocC documentation...${NC}" - -# Create a temporary directory for docbuild output -DOCBUILD_DIR="${REPO_ROOT}/.docbuild" -rm -rf "$DOCBUILD_DIR" -mkdir -p "$DOCBUILD_DIR" - -# Generate documentation using Swift DocC Plugin -echo "Generating documentation with Swift DocC Plugin..." -cd "$REPO_ROOT" - -# Resolve package dependencies first -swift package resolve - -# Convert package name to lowercase (bash 3.2 compatible) -PACKAGE_NAME_LOWER=$(echo "$PACKAGE_NAME" | tr '[:upper:]' '[:lower:]') - -# Generate documentation using swift package generate-documentation -# This requires the swift-docc-plugin to be added to Package.swift -if swift package --allow-writing-to-directory "$DOCBUILD_DIR" \ - generate-documentation \ - --target "$PACKAGE_NAME" \ - --output-path "$DOCBUILD_DIR" \ - --transform-for-static-hosting \ - --hosting-base-path "/${PACKAGE_NAME_LOWER}" 2>&1; then - echo -e "${GREEN}DocC documentation generated successfully${NC}" - - # The documentation is already transformed for static hosting in DOCBUILD_DIR - STATIC_DOCS_DIR="$DOCBUILD_DIR" - - if [ -d "$STATIC_DOCS_DIR" ] && [ -n "$(ls -A "$STATIC_DOCS_DIR" 2>/dev/null)" ]; then - echo "Found generated documentation in: $STATIC_DOCS_DIR" - else - echo -e "${YELLOW}Documentation directory is empty, creating static fallback site${NC}" - STATIC_DOCS_DIR="" - fi -else - echo -e "${YELLOW}swift package generate-documentation failed, creating static fallback site${NC}" - STATIC_DOCS_DIR="" -fi - -# Clone or update the GitHub Pages repository -if [ -d "$CLONE_DIR" ] && [ -d "$CLONE_DIR/.git" ]; then - echo "Updating existing clone..." - cd "$CLONE_DIR" - # Configure remote URL with token for pushing (if in GitHub Actions) - if [ -n "$GITHUB_ACTIONS" ] && [ -n "$GITHUB_TOKEN" ]; then - git remote set-url origin "https://${GITHUB_USER}:${GITHUB_TOKEN}@github.com/${GITHUB_USER}/${GITHUB_PAGES_REPO}.git" - fi - # Try master first (common for older repos), then main - git pull origin master 2>/dev/null || git pull origin main 2>/dev/null || true -else - echo "Cloning GitHub Pages repository..." - # In GitHub Actions, use token for authentication - if [ -n "$GITHUB_ACTIONS" ] && [ -n "$GITHUB_TOKEN" ]; then - # Try using gh CLI first (handles authentication better) - if command -v gh >/dev/null 2>&1; then - gh repo clone "${GITHUB_USER}/${GITHUB_PAGES_REPO}" "$CLONE_DIR" 2>/dev/null || { - echo -e "${YELLOW}gh clone failed, trying git with token...${NC}" - # Fallback to git with token in URL - git config --global credential.helper store - echo "https://${GITHUB_USER}:${GITHUB_TOKEN}@github.com" > ~/.git-credentials - git clone "https://github.com/${GITHUB_USER}/${GITHUB_PAGES_REPO}.git" "$CLONE_DIR" || { - echo -e "${RED}Error: Could not clone repository.${NC}" - echo "Repository: https://github.com/${GITHUB_USER}/${GITHUB_PAGES_REPO}.git" - echo "" - echo -e "${YELLOW}Note: GITHUB_TOKEN in GitHub Actions only has access to the current repository.${NC}" - echo "To access other repositories, create a Personal Access Token (PAT) with 'repo' scope:" - echo "1. Go to https://github.com/settings/tokens" - echo "2. Generate a new token with 'repo' scope" - echo "3. Add it as a secret named 'GH_PAT' in your repository settings" - echo "4. The workflow will automatically use GH_PAT if available" - exit 1 - } - } - else - # gh CLI not available, use git with token - git config --global credential.helper store - echo "https://${GITHUB_USER}:${GITHUB_TOKEN}@github.com" > ~/.git-credentials - git clone "https://github.com/${GITHUB_USER}/${GITHUB_PAGES_REPO}.git" "$CLONE_DIR" || { - echo -e "${RED}Error: Could not clone repository.${NC}" - echo "Repository: https://github.com/${GITHUB_USER}/${GITHUB_PAGES_REPO}.git" - echo "" - echo -e "${YELLOW}Note: GITHUB_TOKEN in GitHub Actions only has access to the current repository.${NC}" - echo "To access other repositories, create a Personal Access Token (PAT) with 'repo' scope:" - echo "1. Go to https://github.com/settings/tokens" - echo "2. Generate a new token with 'repo' scope" - echo "3. Add it as a secret named 'GH_PAT' in your repository settings" - exit 1 - } - fi - else - git clone "$GITHUB_PAGES_URL" "$CLONE_DIR" || { - echo -e "${RED}Error: Could not clone repository.${NC}" - echo "Repository: $GITHUB_PAGES_URL" - echo "Please ensure the repository exists and you have access." - exit 1 - } - fi - cd "$CLONE_DIR" - - # Configure remote URL with token for pushing (if in GitHub Actions) - if [ -n "$GITHUB_ACTIONS" ] && [ -n "$GITHUB_TOKEN" ]; then - echo "Configuring remote URL with authentication..." - git remote set-url origin "https://${GITHUB_USER}:${GITHUB_TOKEN}@github.com/${GITHUB_USER}/${GITHUB_PAGES_REPO}.git" - fi -fi - -# Determine the documentation directory -# For Swift packages, we'll publish to a subdirectory like /designalgorithmskit/ -DOCS_DIR="$PACKAGE_NAME_LOWER" # Use lowercase version -mkdir -p "$DOCS_DIR" - -echo "Using documentation directory: $DOCS_DIR" -echo "" - -# Copy documentation to GitHub Pages repository -if [ -n "$STATIC_DOCS_DIR" ] && [ -d "$STATIC_DOCS_DIR" ]; then - echo -e "${GREEN}Copying DocC documentation to GitHub Pages...${NC}" - echo "Source: $STATIC_DOCS_DIR" - echo "Destination: $CLONE_DIR/$DOCS_DIR" - - # Remove existing documentation - rm -rf "$CLONE_DIR/$DOCS_DIR"/* - - # Copy the entire documentation contents - cp -R "$STATIC_DOCS_DIR"/* "$CLONE_DIR/$DOCS_DIR/" || { - echo -e "${RED}Error: Failed to copy documentation${NC}" - exit 1 - } - - # Create a simplified index.html that redirects to the documentation - echo -e "${GREEN}Creating simplified index.html...${NC}" - cat > "$CLONE_DIR/$DOCS_DIR/index.html" << EOF - - - - - - - ${PACKAGE_NAME} Documentation - - - -

Redirecting to ${PACKAGE_NAME} Documentation...

- - -EOF - - echo -e "${GREEN}Documentation copied successfully${NC}" -else - echo -e "${YELLOW}DocC documentation not found, creating static fallback site${NC}" - # Create a simple static HTML site as fallback - cat > "$CLONE_DIR/$DOCS_DIR/index.html" << 'EOF' - - - - - - DesignAlgorithmsKit Documentation - - - -

DesignAlgorithmsKit

-

A Swift package providing common design patterns and algorithms with protocols and base types for extensibility.

- - - -
-

Design Patterns

-
    -
  • Registry Pattern - TypeRegistry for centralized type registration
  • -
  • Factory Pattern - ObjectFactory for object creation
  • -
  • Builder Pattern - BaseBuilder for fluent API construction
  • -
  • Singleton Pattern - ThreadSafeSingleton and ActorSingleton
  • -
  • Strategy Pattern - Strategy protocol and StrategyContext
  • -
  • Observer Pattern - Observer and Observable protocols
  • -
  • Adapter Pattern - Adapter protocol
  • -
  • Facade Pattern - Facade protocol
  • -
-
- -
-

Algorithms

-
    -
  • Merkle Tree - Data structure for efficient data verification
  • -
  • Bloom Filter - Probabilistic data structure for membership testing
  • -
  • Counting Bloom Filter - Bloom filter variant supporting element removal
  • -
  • Hash Algorithms - SHA256 and other hash implementations
  • -
-
- -
-

Usage

-

Add DesignAlgorithmsKit to your Package.swift:

-
.package(url: "https://github.com/rickhohler/DesignAlgorithmsKit.git", from: "1.0.0")
-

For detailed API documentation, see the README and source code comments.

-
- - -EOF -fi - -# Commit and push changes -cd "$CLONE_DIR" -git add "$DOCS_DIR" || true -git add -A || true - -# Check if there are changes to commit -if git diff --staged --quiet; then - echo -e "${YELLOW}No changes to commit${NC}" -else - echo -e "${GREEN}Committing changes...${NC}" - git commit -m "Update $PACKAGE_NAME documentation" || { - echo -e "${YELLOW}No changes to commit${NC}" - } - - echo -e "${GREEN}Pushing to GitHub Pages...${NC}" - # Try master first (common for older repos), then main - git push origin master 2>/dev/null || git push origin main 2>/dev/null || { - echo -e "${RED}Error: Failed to push to GitHub Pages repository${NC}" - exit 1 - } - - echo -e "${GREEN}Documentation published successfully!${NC}" - echo "Documentation will be available at: https://${GITHUB_USER}.github.io/${DOCS_DIR}/" -fi - -# Cleanup -cd "$REPO_ROOT" -rm -rf "$DOCBUILD_DIR" "$STATIC_DOCS_DIR" 2>/dev/null || true