From 6e971dbc23a8f15f94f565780a890649c2a3782f Mon Sep 17 00:00:00 2001 From: Jason Morley Date: Mon, 24 Feb 2025 10:44:13 -1000 Subject: [PATCH] fix: Sort file changes alphabetically (#43) This change also extracts `Item` and `Snapshot` models into separate files. --- Sources/ReporterCore/Model/Item.swift | 31 ++++++++++++++ Sources/ReporterCore/Model/Snapshot.swift | 49 +++++++++++++++++++++++ Sources/ReporterCore/Model/State.swift | 33 --------------- Sources/ReporterCore/Reporter.swift | 12 +++--- 4 files changed, 86 insertions(+), 39 deletions(-) create mode 100644 Sources/ReporterCore/Model/Item.swift create mode 100644 Sources/ReporterCore/Model/Snapshot.swift diff --git a/Sources/ReporterCore/Model/Item.swift b/Sources/ReporterCore/Model/Item.swift new file mode 100644 index 0000000..e8aeccb --- /dev/null +++ b/Sources/ReporterCore/Model/Item.swift @@ -0,0 +1,31 @@ +// Copyright (c) 2024-2025 Jason Morley +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import Foundation + +public struct Item: Codable, Hashable, Sendable { + public let path: String + public let checksum: Data? + + public init(path: String, checksum: Data?) { + self.path = path + self.checksum = checksum + } +} diff --git a/Sources/ReporterCore/Model/Snapshot.swift b/Sources/ReporterCore/Model/Snapshot.swift new file mode 100644 index 0000000..28c964f --- /dev/null +++ b/Sources/ReporterCore/Model/Snapshot.swift @@ -0,0 +1,49 @@ +// Copyright (c) 2024-2025 Jason Morley +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import Foundation + +public struct Snapshot: Codable { + + public var description: String { + return "\(items.count) files" + } + + public let items: Set + + public init(items: [Item] = []) { + self.items = Set(items) + } + + public func changes(from initialState: Snapshot) -> Changes { + let additions = items.subtracting(initialState.items) + let deletions = initialState.items.subtracting(items) + return Changes( + additions: additions + .map { $0.path } + .sorted { $0.localizedStandardCompare($1) == .orderedAscending }, + deletions: deletions + .map { $0.path } + .sorted { $0.localizedStandardCompare($1) == .orderedAscending } + + ) + } + +} diff --git a/Sources/ReporterCore/Model/State.swift b/Sources/ReporterCore/Model/State.swift index c480860..312c658 100644 --- a/Sources/ReporterCore/Model/State.swift +++ b/Sources/ReporterCore/Model/State.swift @@ -22,39 +22,6 @@ import Foundation public struct State: Codable { - public struct Item: Codable, Hashable, Sendable { - public let path: String - public let checksum: Data? - - public init(path: String, checksum: Data?) { - self.path = path - self.checksum = checksum - } - } - - public struct Snapshot: Codable { - - public var description: String { - return "\(items.count) files" - } - - public let items: Set - - public init(items: [Item] = []) { - self.items = Set(items) - } - - public func changes(from initialState: Snapshot) -> Changes { - let additions = items.subtracting(initialState.items) - let deletions = initialState.items.subtracting(items) - return Changes( - additions: additions.map { $0.path }, - deletions: deletions.map { $0.path } - ) - } - - } - public var snapshots: [URL: Snapshot] public init() { diff --git a/Sources/ReporterCore/Reporter.swift b/Sources/ReporterCore/Reporter.swift index 63dc62a..732347a 100644 --- a/Sources/ReporterCore/Reporter.swift +++ b/Sources/ReporterCore/Reporter.swift @@ -27,7 +27,7 @@ import SwiftSMTP public class Reporter { - static func snapshot(path: URL, console: Console) async throws -> State.Snapshot { + static func snapshot(path: URL, console: Console) async throws -> Snapshot { var files = [URL]() @@ -52,19 +52,19 @@ public class Reporter { } // Generate the hashes for the files concurrently. - let items = try await withThrowingTaskGroup(of: State.Item.self) { group in + let items = try await withThrowingTaskGroup(of: Item.self) { group in let progress = Progress(totalUnitCount: Int64(files.count)) for url in files { group.addTask { return try await Task { - let item = State.Item(path: url.path, checksum: try Self.checksum(url: url)) + let item = Item(path: url.path, checksum: try Self.checksum(url: url)) progress.completedUnitCount += 1 console.progress(progress, message: path.lastPathComponent) return item }.value } } - var items: [State.Item] = [] + var items: [Item] = [] for try await result in group { items.append(result) } @@ -72,7 +72,7 @@ public class Reporter { } // Create the snapshot - let snapshot = State.Snapshot(items: items) + let snapshot = Snapshot(items: items) return snapshot } @@ -130,7 +130,7 @@ public class Reporter { var folders: [KeyedChanges] = [] for (url, snapshot) in newState.snapshots { console.log("Checking '\(url.path)'...") - let oldSnapshot = oldState.snapshots[url] ?? State.Snapshot() + let oldSnapshot = oldState.snapshots[url] ?? Snapshot() let changes = snapshot.changes(from: oldSnapshot) folders.append(KeyedChanges(url: url, changes: changes)) }