diff --git a/.swiftlint.yml b/.swiftlint.yml index e45c7ed..4dc454e 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -1,3 +1,7 @@ +excluded: + - Sources/*/FileSystemTestHelper.swift + - Tests/*/*.swift + opt_in_rules: - array_init - attributes @@ -104,6 +108,24 @@ line_length: ignores_urls: true ignores_function_declarations: true ignores_comments: true + ignores_interpolated_strings: true + excluded_lines_patterns: + # String format + - 'NSLocalizedString\(' + - 'String\.localizedStringWithFormat\(' + - 'String\(format' + - 'format: ".*",$' + - 'fatalError\(' + - 'NSLog\(' + - 'print\(' + - 'NSPredicate\(format' + + # Constraints + - 'constraint\(equalTo' + + # App Specific + - 'folder-\d+(-open)?' + - 'FileSizeFormatter\.default' multiline_arguments: first_argument_location: next_line diff --git a/AppDefaults/VDDefaults.plist b/AppDefaults/VDDefaults.plist index 280aab5..048a5ac 100644 --- a/AppDefaults/VDDefaults.plist +++ b/AppDefaults/VDDefaults.plist @@ -47,7 +47,7 @@ foldersDifferenceNavigatorCenterInWindow comparatorBinaryBufferSize - 131072 + 2097152 filesStatusBarShowMessageTimeout 3 tabWidth diff --git a/Sources/App/AppDelegate.swift b/Sources/App/AppDelegate.swift index 832777a..f5143f1 100644 --- a/Sources/App/AppDelegate.swift +++ b/Sources/App/AppDelegate.swift @@ -55,7 +55,11 @@ class AppDelegate: NSObject, NSApplicationDelegate { private func initDefaults() { if let defaultsPath = Bundle.main.url(forResource: "VDDefaults", withExtension: "plist"), let data = try? Data(contentsOf: defaultsPath), - let defaultsDict = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: Any] { + let defaultsDict = try? PropertyListSerialization.propertyList( + from: data, + options: [], + format: nil + ) as? [String: Any] { UserDefaults.standard.register(defaults: defaultsDict) } } diff --git a/Sources/Core/Common/ColorScheme.swift b/Sources/Core/Common/ColorScheme.swift index bfe6e4f..a56fcf4 100644 --- a/Sources/Core/Common/ColorScheme.swift +++ b/Sources/Core/Common/ColorScheme.swift @@ -6,6 +6,8 @@ // Copyright (c) 2025 visualdiffer.com // +import os.log + struct ColorSet { enum Key: String { case text = "textColor" @@ -47,7 +49,7 @@ extension ColorSet { case .background: tempBackground = color default: - NSLog("Found invalid color type: \(key)") + Logger.general.error("Found invalid color type: \(key)") } } diff --git a/Sources/Core/CommonPrefs/CommonPrefs+FolderCompare.swift b/Sources/Core/CommonPrefs/CommonPrefs+FolderCompare.swift index 69f7e84..90e919d 100644 --- a/Sources/Core/CommonPrefs/CommonPrefs+FolderCompare.swift +++ b/Sources/Core/CommonPrefs/CommonPrefs+FolderCompare.swift @@ -6,7 +6,7 @@ // Copyright (c) 2025 visualdiffer.com // -let defaultComparatorBinaryBufferSize = 128 * 1024 +let defaultComparatorBinaryBufferSize = 2 * 1024 * 1024 enum FolderColorAttribute: String { case unknown @@ -114,16 +114,6 @@ extension CommonPrefs { } } - var checkResourceForks: Bool { - get { - fileExtraOptions.hasCheckResourceForks - } - - set { - fileExtraOptions = fileExtraOptions.changeCheckResourceForks(newValue) - } - } - // MARK: - Filters var defaultFileFilters: String? { diff --git a/Sources/Core/CommonPrefs/CommonPrefs.swift b/Sources/Core/CommonPrefs/CommonPrefs.swift index e2101cc..43f52ca 100644 --- a/Sources/Core/CommonPrefs/CommonPrefs.swift +++ b/Sources/Core/CommonPrefs/CommonPrefs.swift @@ -6,6 +6,8 @@ // Copyright (c) 2025 visualdiffer.com // +import os.log + private let darkColorSchemeFileName: String = "colorsDark" private let lightColorSchemeFileName: String = "colors" @@ -65,7 +67,7 @@ public class CommonPrefs: @unchecked Sendable { do { return try readJSON(configPath) as? [String: Any] } catch { - NSLog("Error while loading colors from %@, error %@. Try to load default colors config", configPath, error.localizedDescription) + Logger.general.error("Error while loading colors from \(configPath), error \(error.localizedDescription). Try to load default colors config") // fallback to defaults if let path = defaultColorsConfigPath { return try? readJSON(path) as? [String: Any] diff --git a/Sources/Core/Document/VDDocument.swift b/Sources/Core/Document/VDDocument.swift index 607f748..51cadcc 100644 --- a/Sources/Core/Document/VDDocument.swift +++ b/Sources/Core/Document/VDDocument.swift @@ -6,6 +6,8 @@ // Copyright (c) 2010 visualdiffer.com // +import os.log + public protocol DocumentWindowControllerDelegate: AnyObject { func canClose(_ document: VDDocument) -> Bool } @@ -181,7 +183,7 @@ public protocol DocumentWindowControllerDelegate: AnyObject { addWindowController(fwc) fwc.startComparison() default: - NSLog("Invalid session type") + Logger.general.error("Invalid session type") } // disable restoration for new documents // otherwise when the application restarts it restores an invalid document diff --git a/Sources/Core/Document/Window/DocumentWindow.swift b/Sources/Core/Document/Window/DocumentWindow.swift index 7194089..dd3bf80 100644 --- a/Sources/Core/Document/Window/DocumentWindow.swift +++ b/Sources/Core/Document/Window/DocumentWindow.swift @@ -192,7 +192,8 @@ class DocumentWindow: NSWindow, FileDropImageViewDelegate, HistoryControllerDele leftPathChooser.addPath(leftPath) rightPathChooser.addPath(rightPath) - if !userChosenPreferences, HistorySessionManager.shared.containsHistory(leftPath: leftPath, rightPath: rightPath) { + if !userChosenPreferences, + HistorySessionManager.shared.containsHistory(leftPath: leftPath, rightPath: rightPath) { try? HistorySessionManager.shared.openDocument(leftPath: leftPath, rightPath: rightPath) } else { // Create a new document diff --git a/Sources/Core/History/HistorySessionManager.swift b/Sources/Core/History/HistorySessionManager.swift index ca3dc08..bc87d08 100644 --- a/Sources/Core/History/HistorySessionManager.swift +++ b/Sources/Core/History/HistorySessionManager.swift @@ -6,6 +6,8 @@ // Copyright (c) 2015 visualdiffer.com // +import os.log + class HistorySessionManager: @unchecked Sendable { static let defaultMaxItemsCount = 50 @@ -25,7 +27,10 @@ class HistorySessionManager: @unchecked Sendable { } func maxItemCountPref() -> Int { - let maxItemCount = CommonPrefs.shared.number(forKey: .History.maximumHistoryCount, Self.defaultMaxItemsCount).intValue + let maxItemCount = CommonPrefs.shared.number( + forKey: .History.maximumHistoryCount, + Self.defaultMaxItemsCount + ).intValue if maxItemCount >= 0 { return maxItemCount @@ -80,14 +85,17 @@ class HistorySessionManager: @unchecked Sendable { do { try historyMOC.save() } catch { - NSLog("Unable to save history \(error)") + Logger.general.error("Unable to save history \(error)") } documents.insert(document.uuid) } } func createNewHistory() -> HistoryEntity? { - guard let history = NSEntityDescription.insertNewObject(forEntityName: HistoryEntity.name, into: historyMOC) as? HistoryEntity else { + guard let history = NSEntityDescription.insertNewObject( + forEntityName: HistoryEntity.name, + into: historyMOC + ) as? HistoryEntity else { return nil } return history @@ -98,7 +106,9 @@ class HistorySessionManager: @unchecked Sendable { rightPath: String ) -> Bool { do { - let count = try historyMOC.count(for: HistoryEntity.searchPathRequest(leftPath: leftPath, rightPath: rightPath)) + let count = try historyMOC.count( + for: HistoryEntity.searchPathRequest(leftPath: leftPath, rightPath: rightPath) + ) return (count == NSNotFound || count == 0) ? false : true } catch {} return false @@ -140,7 +150,7 @@ class HistorySessionManager: @unchecked Sendable { do { return try historyMOC.fetch(HistoryEntity.searchPathRequest(leftPath: leftPath, rightPath: rightPath)).first } catch { - NSLog("Error fetching objects: \(error.localizedDescription)") + Logger.general.error("Error fetching objects: \(error.localizedDescription)") } return nil } @@ -192,7 +202,7 @@ class HistorySessionManager: @unchecked Sendable { withIntermediateDirectories: true ) } catch { - NSLog("Unable to create history directory \(historyPath), reason \(error)") + Logger.general.error("Unable to create history directory \(historyPath), reason \(error)") return nil } } diff --git a/Sources/Core/SessionDiff/SessionDiff+AlignRule.swift b/Sources/Core/SessionDiff/SessionDiff+AlignRule.swift index ae05a3b..92da9dc 100644 --- a/Sources/Core/SessionDiff/SessionDiff+AlignRule.swift +++ b/Sources/Core/SessionDiff/SessionDiff+AlignRule.swift @@ -6,6 +6,8 @@ // Copyright (c) 2025 visualdiffer.com // +import os.log + extension SessionDiff { var fileNameAlignments: [AlignRule]? { get { @@ -25,7 +27,7 @@ extension SessionDiff { ) return (data as? [[String: Any]])?.compactMap { AlignRule($0) } } catch { - NSLog("Unable to unarchive file name alignments array \(error)") + Logger.general.error("Unable to unarchive file name alignments array \(error)") return nil } @@ -40,7 +42,7 @@ extension SessionDiff { requiringSecureCoding: false ) } catch { - NSLog("Unable to save file name alignments array \(error)") + Logger.general.error("Unable to save file name alignments array \(error)") fileNameAlignmentsData = nil } } else { diff --git a/Sources/Features/FilesCompare/Controller/FilesWindowController+NSToolbarDelegate.swift b/Sources/Features/FilesCompare/Controller/FilesWindowController+NSToolbarDelegate.swift index a1e0938..6580589 100644 --- a/Sources/Features/FilesCompare/Controller/FilesWindowController+NSToolbarDelegate.swift +++ b/Sources/Features/FilesCompare/Controller/FilesWindowController+NSToolbarDelegate.swift @@ -208,7 +208,9 @@ extension FilesWindowController: NSToolbarDelegate, NSToolbarItemValidation { @MainActor func updateToolbarButton(_ item: NSToolbarItem) { if item.itemIdentifier == .Files.wordWrap { - item.image = NSImage(named: rowHeightCalculator.isWordWrapEnabled ? VDImageNameWordWrapOn : VDImageNameWordWrapOff) + item.image = NSImage( + named: rowHeightCalculator.isWordWrapEnabled ? VDImageNameWordWrapOn : VDImageNameWordWrapOff + ) return } switch lastUsedView.side { diff --git a/Sources/Features/FilesCompare/Controller/FilesWindowController+Navigate.swift b/Sources/Features/FilesCompare/Controller/FilesWindowController+Navigate.swift index 03fd5b3..629c2a1 100644 --- a/Sources/Features/FilesCompare/Controller/FilesWindowController+Navigate.swift +++ b/Sources/Features/FilesCompare/Controller/FilesWindowController+Navigate.swift @@ -71,9 +71,17 @@ extension FilesWindowController { } if navigateToNext { - parentSession.nextDifferenceFiles(from: sessionDiff.leftPath, rightPath: sessionDiff.rightPath, block: block) + parentSession.nextDifferenceFiles( + from: sessionDiff.leftPath, + rightPath: sessionDiff.rightPath, + block: block + ) } else { - parentSession.prevDifferenceFiles(from: sessionDiff.leftPath, rightPath: sessionDiff.rightPath, block: block) + parentSession.prevDifferenceFiles( + from: sessionDiff.leftPath, + rightPath: sessionDiff.rightPath, + block: block + ) } } diff --git a/Sources/Features/FilesCompare/Controller/FilesWindowController+TableView.swift b/Sources/Features/FilesCompare/Controller/FilesWindowController+TableView.swift index 6850476..be3de94 100644 --- a/Sources/Features/FilesCompare/Controller/FilesWindowController+TableView.swift +++ b/Sources/Features/FilesCompare/Controller/FilesWindowController+TableView.swift @@ -27,7 +27,10 @@ let arr = diffSide.lines let diffLine = arr[row] - let view = tableView.makeView(withIdentifier: identifier, owner: nil) as? LineNumberTableCellView ?? LineNumberTableCellView() + let view = tableView.makeView( + withIdentifier: identifier, + owner: nil + ) as? LineNumberTableCellView ?? LineNumberTableCellView() view.diffLine = diffLine view.font = treeViewFont() @@ -52,7 +55,11 @@ let visibleRows = tableView.rows(in: tableView.visibleRect) for row in visibleRows.location ..< NSMaxRange(visibleRows) { - if let cellView = tableView.view(atColumn: 0, row: row, makeIfNecessary: false) as? LineNumberTableCellView { + if let cellView = tableView.view( + atColumn: 0, + row: row, + makeIfNecessary: false + ) as? LineNumberTableCellView { cellView.isSelected = tableView.isRowSelected(row) } } @@ -75,7 +82,10 @@ let path1 = arr[0].osPath var isDir = ObjCBool(false) - let isValidPath1 = FileManager.default.fileExists(atPath: path1, isDirectory: &isDir) && isDir.boolValue == false + let isValidPath1 = FileManager.default.fileExists( + atPath: path1, + isDirectory: &isDir + ) && isDir.boolValue == false if arr.count < 2 { if isValidPath1 { @@ -83,7 +93,10 @@ } } else { let path2 = arr[1].osPath - let isValidPath2 = FileManager.default.fileExists(atPath: path2, isDirectory: &isDir) && isDir.boolValue == false + let isValidPath2 = FileManager.default.fileExists( + atPath: path2, + isDirectory: &isDir + ) && isDir.boolValue == false if isValidPath1, isValidPath2 { result = .copy } diff --git a/Sources/Features/FoldersCompare/CompareItem/CompareItem+Description.swift b/Sources/Features/FoldersCompare/CompareItem/CompareItem+Description.swift index b3549e4..ea5889e 100644 --- a/Sources/Features/FoldersCompare/CompareItem/CompareItem+Description.swift +++ b/Sources/Features/FoldersCompare/CompareItem/CompareItem+Description.swift @@ -9,7 +9,6 @@ extension CompareItem { override public var description: String { String( - // swiftlint:disable:next line_length format: "Path %@, isFileValid %d, isFolder %d, isFiltered %d, isDisp %d, subs , type %@, older %ld, changed %ld, orphan %ld, matched %ld, tags = %ld, labels = %ld, linkedItem %@", path ?? "", isValidFile, diff --git a/Sources/Features/FoldersCompare/CompareItem/CompareItem+VisibleItem.swift b/Sources/Features/FoldersCompare/CompareItem/CompareItem+VisibleItem.swift index 8f7bce9..7f8c499 100644 --- a/Sources/Features/FoldersCompare/CompareItem/CompareItem+VisibleItem.swift +++ b/Sources/Features/FoldersCompare/CompareItem/CompareItem+VisibleItem.swift @@ -121,7 +121,10 @@ extension CompareItem { setAttributes(try? fm.attributesOfItem(atPath: path), fileExtraOptions: filterConfig.fileExtraOptions) } if let path = destRoot.path { - destRoot.setAttributes(try? fm.attributesOfItem(atPath: path), fileExtraOptions: filterConfig.fileExtraOptions) + destRoot.setAttributes( + try? fm.attributesOfItem(atPath: path), + fileExtraOptions: filterConfig.fileExtraOptions + ) } srcDiffSize = fileSize - srcDiffSize diff --git a/Sources/Features/FoldersCompare/CompareItem/CompareItem.swift b/Sources/Features/FoldersCompare/CompareItem/CompareItem.swift index 9049371..8fb49ed 100644 --- a/Sources/Features/FoldersCompare/CompareItem/CompareItem.swift +++ b/Sources/Features/FoldersCompare/CompareItem/CompareItem.swift @@ -8,9 +8,8 @@ typealias CompareItemComparison = (CompareItem, CompareItem) -> ComparisonResult -// swiftlint:disable file_length public class CompareItem: NSObject { - struct FileOptions: OptionSet { + struct FileOptions: FlagSet { var rawValue: Int static let isFile = FileOptions(rawValue: 1 << 0) @@ -20,20 +19,6 @@ public class CompareItem: NSObject { static let isResourceFork = FileOptions(rawValue: 1 << 4) static let isLocked = FileOptions(rawValue: 1 << 5) static let isValidFile = FileOptions(rawValue: 1 << 6) - - subscript(option: FileOptions) -> Bool { - get { - contains(option) - } - - set { - if newValue { - insert(option) - } else { - remove(option) - } - } - } } var linkedItem: CompareItem? @@ -401,5 +386,3 @@ public class CompareItem: NSObject { isFiltered = false } } - -// swiftlint:enable file_length diff --git a/Sources/Features/FoldersCompare/CompareItem/FileExtraOptions.swift b/Sources/Features/FoldersCompare/CompareItem/FileExtraOptions.swift index be04113..f96599c 100644 --- a/Sources/Features/FoldersCompare/CompareItem/FileExtraOptions.swift +++ b/Sources/Features/FoldersCompare/CompareItem/FileExtraOptions.swift @@ -6,7 +6,7 @@ // Copyright (c) 2025 visualdiffer.com // -public struct FileExtraOptions: OptionSet, Sendable { +public struct FileExtraOptions: FlagSet, Sendable { public let rawValue: Int static let resourceFork = FileExtraOptions(rawValue: 1 << 0) @@ -15,17 +15,3 @@ public struct FileExtraOptions: OptionSet, Sendable { self.rawValue = rawValue } } - -public extension FileExtraOptions { - var hasCheckResourceForks: Bool { - contains(.resourceFork) - } - - func changeCheckResourceForks(_ isOn: Bool) -> Self { - if isOn { - union(.resourceFork) - } else { - subtracting(.resourceFork) - } - } -} diff --git a/Sources/Features/FoldersCompare/Components/CompareItemTableCellView.swift b/Sources/Features/FoldersCompare/Components/CompareItemTableCellView.swift index 6380d9b..6480d45 100644 --- a/Sources/Features/FoldersCompare/Components/CompareItemTableCellView.swift +++ b/Sources/Features/FoldersCompare/Components/CompareItemTableCellView.swift @@ -98,7 +98,12 @@ class CompareItemTableCellView: NSView { text.lineBreakMode = .byTruncatingMiddle } text.font = font - icon?.image = IconUtils.shared.icon(for: item, size: 16, isExpanded: isExpanded, hideEmptyFolders: hideEmptyFolders) + icon?.image = IconUtils.shared.icon( + for: item, + size: 16, + isExpanded: isExpanded, + hideEmptyFolders: hideEmptyFolders + ) } func fileSize( diff --git a/Sources/Features/FoldersCompare/Components/FoldersOutlineView/FoldersOutlineView+Columns.swift b/Sources/Features/FoldersCompare/Components/FoldersOutlineView/FoldersOutlineView+Columns.swift index fe3e0a1..4e22c9a 100644 --- a/Sources/Features/FoldersCompare/Components/FoldersOutlineView/FoldersOutlineView+Columns.swift +++ b/Sources/Features/FoldersCompare/Components/FoldersOutlineView/FoldersOutlineView+Columns.swift @@ -67,7 +67,8 @@ extension FoldersOutlineView { + NSScroller.scrollerWidth(for: .regular, scrollerStyle: .legacy) let fileSizeFormatter = FileSizeFormatter(showInBytes: true, showUnitForBytes: false) - let sizeCellWidth = (fileSizeFormatter.string(from: NSNumber(value: 999_999_999_999)) as? NSString)?.size(withAttributes: textAttrs).width ?? 0 + let sizeCellWidth = (fileSizeFormatter.string(from: NSNumber(value: 999_999_999_999)) as? NSString)? + .size(withAttributes: textAttrs).width ?? 0 tableColumn(withIdentifier: .Folders.cellName)?.width = totalWidth - dateCellWidth - sizeCellWidth tableColumn(withIdentifier: .Folders.cellSize)?.width = sizeCellWidth diff --git a/Sources/Features/FoldersCompare/Components/FoldersOutlineView/IconUtils+CompareItemIconUtils.swift b/Sources/Features/FoldersCompare/Components/FoldersOutlineView/IconUtils+CompareItemIconUtils.swift index 17ec79f..40c7e78 100644 --- a/Sources/Features/FoldersCompare/Components/FoldersOutlineView/IconUtils+CompareItemIconUtils.swift +++ b/Sources/Features/FoldersCompare/Components/FoldersOutlineView/IconUtils+CompareItemIconUtils.swift @@ -19,7 +19,11 @@ let url = item.toUrl() { if item.isLocked { if item.isFolder { - let name = ColoredFoldersManager.shared.iconName(item, isExpanded: isExpanded, hideEmptyFolders: hideEmptyFolders) + let name = ColoredFoldersManager.shared.iconName( + item, + isExpanded: isExpanded, + hideEmptyFolders: hideEmptyFolders + ) let url = URL(filePath: name) icon = self.icon(forLockedFile: url, size: size) } else { @@ -27,7 +31,11 @@ } } else if item.isSymbolicLink { if item.isFolder { - let name = ColoredFoldersManager.shared.iconName(item, isExpanded: isExpanded, hideEmptyFolders: hideEmptyFolders) + let name = ColoredFoldersManager.shared.iconName( + item, + isExpanded: isExpanded, + hideEmptyFolders: hideEmptyFolders + ) let url = URL(filePath: name) icon = self.icon(forSymbolicLink: url, size: size) } else { @@ -35,7 +43,12 @@ } } else { if item.isFolder { - icon = ColoredFoldersManager.shared.icon(forFolder: item, size: size, isExpanded: isExpanded, hideEmptyFolders: hideEmptyFolders) + icon = ColoredFoldersManager.shared.icon( + forFolder: item, + size: size, + isExpanded: isExpanded, + hideEmptyFolders: hideEmptyFolders + ) } else { // get the icon from path because for some files (eg resource forks) // the file type should be irrelevant diff --git a/Sources/Features/FoldersCompare/Controller/FileSystemController/FileSystemController.swift b/Sources/Features/FoldersCompare/Controller/FileSystemController/FileSystemController.swift index 058039d..8062b83 100644 --- a/Sources/Features/FoldersCompare/Controller/FileSystemController/FileSystemController.swift +++ b/Sources/Features/FoldersCompare/Controller/FileSystemController/FileSystemController.swift @@ -262,7 +262,10 @@ public class FileSystemController: NSWindowCon totalSize = fileCount.subfoldersSize if includesFiltered { - totalFiles += fileFilteredCount.olderFiles + fileFilteredCount.changedFiles + fileFilteredCount.orphanFiles + fileFilteredCount.matchedFiles + totalFiles += fileFilteredCount.olderFiles + + fileFilteredCount.changedFiles + + fileFilteredCount.orphanFiles + + fileFilteredCount.matchedFiles totalFolders += fileFilteredCount.folders totalSize += fileFilteredCount.subfoldersSize } @@ -341,7 +344,10 @@ public class FileSystemController: NSWindowCon func itemsCount() -> Int { var count = fileCount.olderFiles + fileCount.changedFiles + fileCount.orphanFiles + fileCount.matchedFiles count += fileCount.folders - count += fileFilteredCount.olderFiles + fileFilteredCount.changedFiles + fileFilteredCount.orphanFiles + fileFilteredCount.matchedFiles + count += fileFilteredCount.olderFiles + + fileFilteredCount.changedFiles + + fileFilteredCount.orphanFiles + + fileFilteredCount.matchedFiles count += fileFilteredCount.folders return count diff --git a/Sources/Features/FoldersCompare/Controller/FileSystemController/FileSystemTestHelper.swift b/Sources/Features/FoldersCompare/Controller/FileSystemController/FileSystemTestHelper.swift new file mode 100644 index 0000000..9366bf1 --- /dev/null +++ b/Sources/Features/FoldersCompare/Controller/FileSystemController/FileSystemTestHelper.swift @@ -0,0 +1,450 @@ +// +// FileSystemTestHelper.swift +// VisualDiffer +// +// Created by davide ficano on 25/12/25. +// Copyright (c) 2025 visualdiffer.com +// + +// swiftlint:disable identifier_name force_unwrapping force_try file_length +#if DEBUG + + private let fakeFile = "12345678901234567890" + + private let headerPattern = + """ + let comparatorDelegate = MockItemComparatorDelegate() + let comparator = ItemComparator( + options: %@, + delegate: comparatorDelegate, + bufferSize: 8192, + isLeftCaseSensitive: %@, + isRightCaseSensitive: %@ + ) + let filterConfig = FilterConfig( + showFilteredFiles: %@, + hideEmptyFolders: %@, + followSymLinks: %@, + skipPackages: %@, + traverseFilteredFolders: %@, + predicate: defaultPredicate, + fileExtraOptions: [], + displayOptions: %@ + ) + let folderReaderDelegate = MockFolderReaderDelegate(isRunning: true) + let folderReader = FolderReader( + with: folderReaderDelegate, + comparator: comparator, + filterConfig: filterConfig, + refreshInfo: RefreshInfo(initState: true) + ) + + try removeItem("l") + try removeItem("r") + + """ + + private let readFolderPattern = + """ + folderReader.start( + withLeftRoot: nil, + rightRoot: nil, + leftPath: appendFolder("l"), + rightPath: appendFolder("r") + ) + + let rootL = folderReader.leftRoot! + let rootR = folderReader.rightRoot! + let vi = rootL.visibleItem! + """ + + class FileSystemTestHelper { + var strFolders = "// create folders\n" + var strFiles = "// create files\n" + var strAssert = "" + var strAssertVisibleItems = "// VisibleItems\n" + var leftPos = 0 + var rightPos = 0 + var childNum = 1 + + var header = "" + + private static let comparatorStrings = [ + ComparatorOptions.timestamp.rawValue: ".timestamp", + ComparatorOptions.size.rawValue: ".size", + ComparatorOptions.content.rawValue: ".content", + ComparatorOptions.contentTimestamp.rawValue: ".contentTimestamp", + ComparatorOptions.asText.rawValue: ".asText", + ComparatorOptions.finderLabel.rawValue: ".finderLabel", + ComparatorOptions.finderTags.rawValue: ".finderTags", + ComparatorOptions.filename.rawValue: ".filename", + ComparatorOptions.alignFileSystemCase.rawValue: ".alignFileSystemCase", + ComparatorOptions.alignMatchCase.rawValue: ".alignMatchCase", + ComparatorOptions.alignIgnoreCase.rawValue: ".alignIgnoreCase", + ] + private static let displayFiltersStrings = [ + DisplayOptions.onlyMatches.rawValue: ".onlyMatches", + DisplayOptions.onlyLeftSideNewer.rawValue: ".onlyLeftSideNewer", + DisplayOptions.onlyLeftSideOrphans.rawValue: ".onlyLeftSideOrphans", + DisplayOptions.onlyRightSideNewer.rawValue: ".onlyRightSideNewer", + DisplayOptions.onlyRightSideOrphans.rawValue: ".onlyRightSideOrphans", + DisplayOptions.mismatchesButNoOrphans.rawValue: ".mismatchesButNoOrphans", + DisplayOptions.leftNewerAndLeftOrphans.rawValue: ".leftNewerAndLeftOrphans", + DisplayOptions.rightNewerAndRightOrphans.rawValue: ".rightNewerAndRightOrphans", + DisplayOptions.onlyMismatches.rawValue: ".onlyMismatches", + DisplayOptions.noOrphan.rawValue: ".noOrphan", + DisplayOptions.onlyOrphans.rawValue: ".onlyOrphans", + DisplayOptions.showAll.rawValue: ".showAll", + DisplayOptions.dontFollowSymlinks.rawValue: ".dontFollowSymlinks", + ] + + private var comparatorFlags: ComparatorOptions = [] + + init(sessionDiff: SessionDiff) { + comparatorFlags = sessionDiff.comparatorOptions + + leftPos = URL(filePath: sessionDiff.leftPath!).deletingLastPathComponent().osPath.count + 1 + rightPos = URL(filePath: sessionDiff.rightPath!).deletingLastPathComponent().osPath.count + 1 + header = Self.formatHeader(sessionDiff) + } + + private static func formatHeader(_ sessionDiff: SessionDiff) -> String { + let leftURL = URL(filePath: sessionDiff.leftPath!) + let rightURL = URL(filePath: sessionDiff.rightPath!) + + return String( + format: headerPattern, + Self.stringify(flag: sessionDiff.comparatorOptions.rawValue, stringNumberDictionary: Self.comparatorStrings), + Self.bool2String(try! leftURL.volumeSupportsCaseSensitive()), + Self.bool2String(try! rightURL.volumeSupportsCaseSensitive()), + Self.bool2String(false), + Self.bool2String(true), + Self.bool2String(sessionDiff.followSymLinks), + Self.bool2String(sessionDiff.skipPackages), + Self.bool2String(sessionDiff.traverseFilteredFolders), + Self.stringify(flag: sessionDiff.displayOptions.rawValue, stringNumberDictionary: Self.displayFiltersStrings) + ) + } + + private static func bool2String(_ value: Bool) -> String { value ? "true" : "false" } + + private static func stringify( + flag: Int, + stringNumberDictionary: [Int: String] + ) -> String { + var orFlags = [String]() + + if let str = stringNumberDictionary[flag] { + orFlags.append(str) + } else { + var currentFlag = 1 + var flag = flag + + while flag != 0 { + if (currentFlag & flag) != 0, let value = stringNumberDictionary[currentFlag] { + orFlags.append(value) + flag &= ~currentFlag + } + currentFlag <<= 1 + } + } + let stringFlags = orFlags.joined(separator: ", ") + + if orFlags.count > 1 { + return "[\(stringFlags)]" + } + return stringFlags + } + + @MainActor static func createTestCode( + _ view: FoldersOutlineView, + sessionDiff: SessionDiff + ) { + guard let vi = view.dataSource?.outlineView?(view, child: 0, ofItem: nil) as? VisibleItem, + let fs = vi.item.parent else { + return + } + let testHelper = FileSystemTestHelper(sessionDiff: sessionDiff) + testHelper.generateTest(fs) + testHelper.generateTest(fs.visibleItem!) + testHelper.copyTestToClipboard() + } + + // MARK: - CompareItem generators + + private func generateTest(_ root: CompareItem) { + childNum = 1 + generateTest(root, parentVarName: "rootL", index: 0) + } + + private func generateTest( + _ root: CompareItem, + parentVarName: String, + index: Int + ) { + createFolders(root) + createAssert(root, parentVarName: parentVarName, outString: &strAssert, index: index) + + let parentName = String(format: "child%ld", childNum) + for (index, fs) in root.children.enumerated() { + childNum += 1 + + if fs.isFolder { + generateTest(fs, parentVarName: parentName, index: index) + } else { + createFolders(fs) + createAssert(fs, parentVarName: parentName, outString: &strAssert, index: index) + } + } + } + + // MARK: - VisibleItem generators + + private func generateTest(_ root: VisibleItem) { + childNum = 1 + + generateTest(root, parentVarName: "vi", index: 0) + } + + private func generateTest( + _ root: VisibleItem, + parentVarName: String, + index: Int + ) { + let childVarName = String(format: "childVI%ld", childNum) + + // root element for simplicity is assigned to child and printed out + if parentVarName == "vi" { + strAssertVisibleItems.append(String( + format: "let %@ = vi // %@ <--> %@\n", + childVarName, + root.item.fileName!, + root.item.linkedItem!.fileName! + )) + } else { + strAssertVisibleItems.append(String( + format: "let %@ = %@.children[%ld] // %@ <--> %@\n", + childVarName, + parentVarName, + index, + root.item.parent!.fileName!, + root.item.linkedItem!.parent!.fileName! + )) + } + strAssertVisibleItems.append(String(format: "assertArrayCount(%@.children, %ld)\n", childVarName, root.children.count)) + createAssert(root, parentVarName: childVarName, outString: &strAssertVisibleItems, index: index) + + for (index, vi) in root.children.enumerated() { + childNum += 1 + + generateTest(vi, parentVarName: childVarName, index: index) + } + } + + // MARK: - Assert generator methods + + private func createAssert( + _ fsOrVi: AnyObject, + parentVarName: String, + outString: inout String, + index: Int + ) { + var fs: CompareItem + var stringFormat = "" + + if let vi = fsOrVi as? VisibleItem { + fs = vi.item + stringFormat = "let child%ld = %@.item // %@ <-> %@\n" + outString.append(String( + format: stringFormat, + childNum, + parentVarName, + fs.parent?.fileName ?? "nil", + fs.parent?.linkedItem?.fileName ?? "nil" + )) + } else if let fsOrVi = fsOrVi as? CompareItem { + fs = fsOrVi + if parentVarName == "rootL" { + stringFormat = "let child%ld = %@ // %@ <-> %@\n" + outString.append(String( + format: stringFormat, + childNum, + parentVarName, + fs.fileName ?? "nil", + fs.linkedItem?.fileName ?? "nil" + )) + } else { + stringFormat = "let child%ld = %@.children[%ld] // %@ <-> %@\n" + outString.append(String( + format: stringFormat, + childNum, + parentVarName, + index, + fs.parent?.fileName ?? "nil", + fs.parent?.linkedItem?.fileName ?? "nil" + )) + } + } else { + return + } + + createAssert(fs, outString: &outString, linked: false) + createAssert(fs.linkedItem!, outString: &outString, linked: true) + + outString.append("\n") + } + + private func createAssert( + _ fs: CompareItem, + outString: inout String, + linked: Bool + ) { + let fileName = if let fileName = fs.fileName { + String(format: "\"%@\"", fileName) + } else { + "nil" + } + + let linkedString = linked ? ".linkedItem" : "" + + outString.append(String( + format: "assertItem(child%ld%@, %ld, %ld, %ld, %ld, %ld, %@, .%@, %lld)\n", + childNum, + linkedString, + fs.olderFiles, + fs.changedFiles, + fs.orphanFiles, + fs.matchedFiles, + fs.children.count, + fileName, + fs.type.description, + fs.isFolder ? fs.subfoldersSize : fs.fileSize + )) + if fs.isValidFile, fs.isFolder { + let linkedStringForceUnwrapping = linked ? ".linkedItem!" : "" + outString.append(String( + format: "#expect(child%ld%@.orphanFolders == %ld, \"OrphanFolder: Expected count %ld found \\(child%ld%@.orphanFolders)\")\n", + childNum, + linkedStringForceUnwrapping, + fs.orphanFolders, + fs.orphanFolders, + childNum, + linkedStringForceUnwrapping + )) + } + + if comparatorFlags.contains(.finderTags) { + outString.append(String(format: "assertFolderTags(child%ld%@, %@, %@)\n", childNum, linkedString, Self.bool2String(fs.summary.hasMetadataTags), fileName)) + outString.append(String(format: "assertMismatchingTags(child%ld%@, %ld, %@)\n", childNum, linkedString, fs.mismatchingTags, fileName)) + } + if comparatorFlags.contains(.finderLabel) { + outString.append(String(format: "assertFolderLabels(child%ld%@, %@, %@)\n", childNum, linkedString, Self.bool2String(fs.summary.hasMetadataLabels), fileName)) + outString.append(String(format: "assertMismatchingLabels(child%ld%@, %ld, %@)\n", childNum, linkedString, fs.mismatchingLabels, fileName)) + addAssertLabelsOnDisk(&outString, fs: fs, linked: linked, index: childNum) + } + } + + private func addAssertLabelsOnDisk( + _ outString: inout String, + fs: CompareItem, + linked: Bool, + index: Int + ) { + guard fs.isValidFile else { + return + } + let path = fs.path! + if let labelNumber = URL(filePath: path).labelNumber() { + let linkedString = linked ? ".linkedItem" : "" + let startIndex = path.index(path.startIndex, offsetBy: linked ? rightPos : leftPos) + let subPath = String(path[startIndex ..< path.endIndex]) + outString.append(String( + format: "assertResourceFileLabels(child%ld%@, %ld, appendFolder(\"%@\"))\n", + index, + linkedString, + labelNumber, + subPath + )) + } + } + + // MARK: - Folder, size, metadata + + private func createFolders(_ fs: CompareItem) { + appendCreateFoldersExpressions(fs, pathIndex: leftPos) + appendCreateFoldersExpressions(fs.linkedItem!, pathIndex: rightPos) + } + + private func appendCreateFoldersExpressions( + _ fs: CompareItem, + pathIndex: Int + ) { + guard fs.isValidFile, + let path = fs.path else { + return + } + let startIndex = path.index(path.startIndex, offsetBy: pathIndex) + let subPath = String(path[startIndex ..< path.endIndex]) + if fs.isFolder { + strFolders.append(String(format: "try createFolder(\"%@\")\n", subPath)) + } else { + if fs.olderFiles > 0 { + strFiles.append(String(format: "try createFile(\"%@\", \"%@\")\n", subPath, fakeFileBySize(fs.fileSize, subPath))) + strFiles.append(String(format: "try setFileTimestamp(\"%@\", \"2001-03-24 10:45:32 +0600\")\n", subPath)) + } else { + strFiles.append(String(format: "try createFile(\"%@\", \"%@\")\n", subPath, fakeFileBySize(fs.fileSize, subPath))) + } + } + if comparatorFlags.contains(.finderTags) { + addTags(fs, subPath: subPath) + } + if comparatorFlags.contains(.finderLabel) { + addLabels(fs, subPath: subPath) + } + } + + private func fakeFileBySize(_ size: Int64, _ path: String) -> String { + assert(size < fakeFile.count, "File size (\(size)) is greater then fakeFile length (\(fakeFile.count)) for \(path)") + + return String(fakeFile[.. NSView? { guard let tableColumn, let itemCompare = (item as? VisibleItem)?.item, - let result = outlineView.makeView(withIdentifier: tableColumn.identifier, owner: nil) as? CompareItemTableCellView else { + let result = outlineView.makeView( + withIdentifier: tableColumn.identifier, + owner: nil + ) as? CompareItemTableCellView else { return nil } diff --git a/Sources/Features/FoldersCompare/Controller/FoldersWindowController+MenuDelegate.swift b/Sources/Features/FoldersCompare/Controller/FoldersWindowController+MenuDelegate.swift index a47eb8a..108044f 100644 --- a/Sources/Features/FoldersCompare/Controller/FoldersWindowController+MenuDelegate.swift +++ b/Sources/Features/FoldersCompare/Controller/FoldersWindowController+MenuDelegate.swift @@ -49,7 +49,10 @@ extension FoldersWindowController: NSMenuDelegate, return fsi.validateCopyFiles(sessionDiff) } else if action == #selector(deleteFiles) { return fsi.validateDeleteFiles(sessionDiff) - } else if action == #selector(copyFullPaths) || action == #selector(copyFileNames) || action == #selector(copy(_:)) || action == #selector(copyUrls) { + } else if action == #selector(copyFullPaths) + || action == #selector(copyFileNames) + || action == #selector(copy(_:)) + || action == #selector(copyUrls) { return fsi.validateClipboardCopy() } else if action == #selector(setAsBaseFolder) { return fsi.validateSetAsBaseFolder() diff --git a/Sources/Features/FoldersCompare/Controller/FoldersWindowController.swift b/Sources/Features/FoldersCompare/Controller/FoldersWindowController.swift index 8ec927d..3da7896 100644 --- a/Sources/Features/FoldersCompare/Controller/FoldersWindowController.swift +++ b/Sources/Features/FoldersCompare/Controller/FoldersWindowController.swift @@ -8,6 +8,7 @@ import Quartz import UserNotifications +import os.log // the heights are in flipped coordinates private let splitViewMinHeight: CGFloat = 160.0 @@ -263,4 +264,19 @@ public class FoldersWindowController: NSWindowController, @objc func zoomResetFont(_: AnyObject) { fontZoomFactor = 0 } + + #if DEBUG + override public func keyDown(with event: NSEvent) { + // Cmd + F12 pressed, create the test code + if event.modifierFlags.contains(.command), + event.charactersIgnoringModifiers?.unicodeScalars.first?.value == UInt32(NSF12FunctionKey) { + FileSystemTestHelper.createTestCode(leftView, sessionDiff: sessionDiff) + Logger.ui.info("Unit Test generated and copied on clipboard") + + return + } + + super.keyDown(with: event) + } + #endif } diff --git a/Sources/Features/FoldersCompare/FileManager/CopyCompareItem.swift b/Sources/Features/FoldersCompare/FileManager/CopyCompareItem.swift index 32f8ba7..d72db14 100644 --- a/Sources/Features/FoldersCompare/FileManager/CopyCompareItem.swift +++ b/Sources/Features/FoldersCompare/FileManager/CopyCompareItem.swift @@ -380,7 +380,11 @@ class CopyCompareItem: NSObject { // set the timestamps at the end so we are sure they are not 'overwritten' // For example modification date for folders changes after a file is copied into it - try fm.setFileAttributes(operationManager.timestampAttributesFrom(attributes), ofItemAtPath: destFullPath, volumeType: volumeType) + try fm.setFileAttributes( + operationManager.timestampAttributesFrom(attributes), + ofItemAtPath: destFullPath, + volumeType: volumeType + ) // the delete above reset path and file to nil // here we assign again the copied values let destAttrs = try fm.attributesOfItem(atPath: destFullPath.osPath) diff --git a/Sources/Features/FoldersCompare/FileManager/MoveCompareItem.swift b/Sources/Features/FoldersCompare/FileManager/MoveCompareItem.swift index fdc7651..c5ce545 100644 --- a/Sources/Features/FoldersCompare/FileManager/MoveCompareItem.swift +++ b/Sources/Features/FoldersCompare/FileManager/MoveCompareItem.swift @@ -446,7 +446,10 @@ public class MoveCompareItem: NSObject { } // update file information after move destRoot.path = destFullPath.osPath - destRoot.setAttributes(try? FileManager.default.attributesOfItem(atPath: destFullPath.osPath), fileExtraOptions: operationManager.filterConfig.fileExtraOptions) + destRoot.setAttributes( + try? FileManager.default.attributesOfItem(atPath: destFullPath.osPath), + fileExtraOptions: operationManager.filterConfig.fileExtraOptions + ) if destRoot.isFiltered, let filter = operationManager.filterConfig.predicate { diff --git a/Sources/Features/FoldersCompare/Services/FolderReader/FolderReader.swift b/Sources/Features/FoldersCompare/Services/FolderReader/FolderReader.swift index 8d7dc70..78824ae 100644 --- a/Sources/Features/FoldersCompare/Services/FolderReader/FolderReader.swift +++ b/Sources/Features/FoldersCompare/Services/FolderReader/FolderReader.swift @@ -356,16 +356,18 @@ public class FolderReader: @unchecked Sendable { // but the order can change from a different sort in the delegate // so we get the current order and use it to traverse the folders var traversalOrder = [CompareItem]() + var seenItems = Set() if let visibleItem = leftItem.visibleItem { for vi in visibleItem.children { traversalOrder.append(vi.item) + seenItems.insert(ObjectIdentifier(vi.item)) } } // Only VisibleItems are ordered so filtered elements are not present inside the array // We must iterate also the not filtered items to correctly update folders informations like the subfolders size // We add the missing CompareItem to the end, this is correct because they are not visible - for item in leftItem.children where !traversalOrder.contains(where: { $0 == item }) { + for item in leftItem.children where !seenItems.contains(ObjectIdentifier(item)) { traversalOrder.append(item) } diff --git a/Sources/Features/FoldersCompare/Services/ItemComparator/ItemComparator+AlignRegularExpression.swift b/Sources/Features/FoldersCompare/Services/ItemComparator/ItemComparator+AlignRegularExpression.swift index 9e1e6f1..82345e8 100644 --- a/Sources/Features/FoldersCompare/Services/ItemComparator/ItemComparator+AlignRegularExpression.swift +++ b/Sources/Features/FoldersCompare/Services/ItemComparator/ItemComparator+AlignRegularExpression.swift @@ -20,7 +20,10 @@ extension ItemComparator { var lIndex = leftIndex var rIndex = rightIndex - let result = leftChild.compare(rightChild, followSymLinks: alignConfig.followSymLinks) { self.compareByRegularExpression( + let result = leftChild.compare( + rightChild, + followSymLinks: alignConfig.followSymLinks + ) { self.compareByRegularExpression( lhs: $0, rhs: $1, leftRoot: leftRoot, diff --git a/Sources/Features/FoldersCompare/Services/ItemComparator/ItemComparator+Compare.swift b/Sources/Features/FoldersCompare/Services/ItemComparator/ItemComparator+Compare.swift index 94f5467..baf9556 100644 --- a/Sources/Features/FoldersCompare/Services/ItemComparator/ItemComparator+Compare.swift +++ b/Sources/Features/FoldersCompare/Services/ItemComparator/ItemComparator+Compare.swift @@ -97,7 +97,9 @@ extension ItemComparator { return .orderedSame } - var res: ComparisonResult = leftTags.count == rightTags.count ? .orderedSame : leftTags.count < rightTags.count ? .orderedAscending : .orderedDescending + var res: ComparisonResult = leftTags.count == rightTags.count + ? .orderedSame + : leftTags.count < rightTags.count ? .orderedAscending : .orderedDescending if res == .orderedSame { for (index, item) in leftTags.enumerated() where res == .orderedSame { res = item.caseInsensitiveCompare(rightTags[index]) diff --git a/Sources/Features/Preferences/Box/ComparisonStandardUserDataSource.swift b/Sources/Features/Preferences/Box/ComparisonStandardUserDataSource.swift index 9f08ae2..a428a86 100644 --- a/Sources/Features/Preferences/Box/ComparisonStandardUserDataSource.swift +++ b/Sources/Features/Preferences/Box/ComparisonStandardUserDataSource.swift @@ -13,7 +13,7 @@ class ComparisonStandardUserDataSource: StandardUserPreferencesBoxDataSource { ) -> Bool { switch key { case .virtualResourceFork: - CommonPrefs.shared.checkResourceForks + CommonPrefs.shared.fileExtraOptions[.resourceFork] case .virtualFinderLabel: CommonPrefs.shared.finderLabel case .virtualFinderTags: @@ -30,7 +30,7 @@ class ComparisonStandardUserDataSource: StandardUserPreferencesBoxDataSource { ) { switch key { case .virtualResourceFork: - CommonPrefs.shared.checkResourceForks = value + CommonPrefs.shared.fileExtraOptions[.resourceFork] = value case .virtualFinderLabel: CommonPrefs.shared.finderLabel = value case .virtualFinderTags: diff --git a/Sources/Features/Preferences/Main/Controllers/BasePreferences.swift b/Sources/Features/Preferences/Main/Controllers/BasePreferences.swift index 0dbd9f2..d4e4404 100644 --- a/Sources/Features/Preferences/Main/Controllers/BasePreferences.swift +++ b/Sources/Features/Preferences/Main/Controllers/BasePreferences.swift @@ -210,8 +210,17 @@ class BasePreferences: NSWindowController, NSToolbarDelegate, NSTabViewDelegate, func resize() { let windowFrame = NSWindow.contentRect(forFrameRect: prefPanel.frame, styleMask: prefPanel.styleMask) let height = minWindowHeight() - let frameRect = NSRect(x: windowFrame.origin.x, y: windowFrame.origin.y + windowFrame.size.height - height, width: windowFrame.size.width, height: height) - prefPanel.setFrame(NSWindow.frameRect(forContentRect: frameRect, styleMask: prefPanel.styleMask), display: true, animate: prefPanel.isVisible) + let frameRect = NSRect( + x: windowFrame.origin.x, + y: windowFrame.origin.y + windowFrame.size.height - height, + width: windowFrame.size.width, + height: height + ) + prefPanel.setFrame( + NSWindow.frameRect(forContentRect: frameRect, styleMask: prefPanel.styleMask), + display: true, + animate: prefPanel.isVisible + ) } func tabView(_: NSTabView, didSelect _: NSTabViewItem?) { diff --git a/Sources/Features/Preferences/Session/Controllers/SessionPreferencesWindow.swift b/Sources/Features/Preferences/Session/Controllers/SessionPreferencesWindow.swift index 85b25b4..3643aa2 100644 --- a/Sources/Features/Preferences/Session/Controllers/SessionPreferencesWindow.swift +++ b/Sources/Features/Preferences/Session/Controllers/SessionPreferencesWindow.swift @@ -205,7 +205,7 @@ class SessionPreferencesWindow: NSWindowController, NSTabViewDelegate, @preconcu func preferenceBox(_: PreferencesBox, boolForKey key: CommonPrefs.Name) -> Bool { switch key { case .virtualResourceFork: - currentPreferences.fileExtraOptions.hasCheckResourceForks + currentPreferences.fileExtraOptions[.resourceFork] case .virtualFinderLabel: currentPreferences.comparatorOptions.hasFinderLabel case .virtualFinderTags: @@ -226,7 +226,7 @@ class SessionPreferencesWindow: NSWindowController, NSTabViewDelegate, @preconcu func preferenceBox(_: PreferencesBox, setBool value: Bool, forKey key: CommonPrefs.Name) { switch key { case .virtualResourceFork: - currentPreferences.fileExtraOptions = currentPreferences.fileExtraOptions.changeCheckResourceForks(value) + currentPreferences.fileExtraOptions[.resourceFork] = value case .virtualFinderLabel: currentPreferences.comparatorOptions = currentPreferences.comparatorOptions.changeFinderLabel(value) case .virtualFinderTags: diff --git a/Sources/Legacy/TOPMacros.h b/Sources/Legacy/TOPMacros.h deleted file mode 100644 index b260bd5..0000000 --- a/Sources/Legacy/TOPMacros.h +++ /dev/null @@ -1,30 +0,0 @@ -// -// TOPMacros.h -// TernaryOp -// -// Created by davide ficano on 21/11/14. -// Copyright (c) 2014 visualdiffer.com -// - -#ifndef TernaryOp_TOPMacros_h -#define TernaryOp_TOPMacros_h - -// Output log only when in debug mode showing the method name -#if defined(DEBUG) -#define TOP_DEBUG_METHOD_LOG(text, ...) NSLog(@"%@ " text, NSStringFromSelector(_cmd), ## __VA_ARGS__) -#else -#define TOP_DEBUG_METHOD_LOG(text, ...) -#endif - -#define TOP_LOG(text, ...) NSLog(text, ## __VA_ARGS__) -#define TOP_METHOD_LOG(text, ...) NSLog(@"%@ " text, NSStringFromSelector(_cmd), ## __VA_ARGS__) - -#define TOP_IS_FLAG_ON(wholeFlags, flagToTest) ((wholeFlags & flagToTest) == flagToTest) -#define TOP_SET_FLAG_ON(wholeFlags, flagToSet) (wholeFlags | flagToSet) -#define TOP_SET_FLAG_OFF(wholeFlags, flagToSet) (wholeFlags & ~flagToSet) -#define TOP_TOGGLE_FLAG(wholeFlags, flagToTest) ((wholeFlags & flagToTest) == flagToTest ? (wholeFlags & ~flagToTest) : (wholeFlags | flagToTest)) -// If flagToTest is on then return NSControlStateValueOn otherwise NSControlStateValueOff -#define TOP_BUTTON_STATE(wholeFlags, flagToTest) ((wholeFlags & flagToTest) == flagToTest ? NSControlStateValueOn : NSControlStateValueOff) -#define TOP_TOGGLE_BUTTON_STATE(wholeFlags, flagToTest, state) (state == NSControlStateValueOn ? TOP_SET_FLAG_ON(wholeFlags, flagToTest) : TOP_SET_FLAG_OFF(wholeFlags, flagToTest)) - -#endif diff --git a/Sources/SharedKit/Extensions/Color/NSColor+Hex.swift b/Sources/SharedKit/Extensions/Color/NSColor+Hex.swift index b5c8ded..205ff35 100644 --- a/Sources/SharedKit/Extensions/Color/NSColor+Hex.swift +++ b/Sources/SharedKit/Extensions/Color/NSColor+Hex.swift @@ -10,7 +10,12 @@ import Cocoa extension NSColor { @objc static func colorRGBA(_ red: UInt, green: UInt, blue: UInt, alpha: UInt) -> NSColor { - NSColor(calibratedRed: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue) / 255.0, alpha: CGFloat(alpha) / 255.0) + NSColor( + calibratedRed: CGFloat(red) / 255.0, + green: CGFloat(green) / 255.0, + blue: CGFloat(blue) / 255.0, + alpha: CGFloat(alpha) / 255.0 + ) } @objc static func colorFromHexRGBA(_ inColorString: String) -> NSColor? { diff --git a/Sources/SharedKit/Extensions/Image/NSImage+Tint.swift b/Sources/SharedKit/Extensions/Image/NSImage+Tint.swift index 33a5bb1..3a94498 100644 --- a/Sources/SharedKit/Extensions/Image/NSImage+Tint.swift +++ b/Sources/SharedKit/Extensions/Image/NSImage+Tint.swift @@ -100,8 +100,14 @@ public extension NSImage { return nil } - compositingFilter.setValue(colorFilter.value(forKey: kCIOutputImageKey), forKey: kCIInputImageKey) - compositingFilter.setValue(monochromeFilter.value(forKey: kCIOutputImageKey), forKey: kCIInputBackgroundImageKey) + compositingFilter.setValue( + colorFilter.value(forKey: kCIOutputImageKey), + forKey: kCIInputImageKey + ) + compositingFilter.setValue( + monochromeFilter.value(forKey: kCIOutputImageKey), + forKey: kCIInputBackgroundImageKey + ) return compositingFilter } diff --git a/Sources/SharedKit/Extensions/OptionSet/FlagSet.swift b/Sources/SharedKit/Extensions/OptionSet/FlagSet.swift new file mode 100644 index 0000000..62d86a3 --- /dev/null +++ b/Sources/SharedKit/Extensions/OptionSet/FlagSet.swift @@ -0,0 +1,25 @@ +// +// FlagSet.swift +// VisualDiffer +// +// Created by davide ficano on 31/12/25. +// Copyright (c) 2025 visualdiffer.com +// + +public protocol FlagSet: OptionSet {} + +public extension FlagSet { + @inlinable + subscript(option: Self.Element) -> Bool { + get { + contains(option) + } + set { + if newValue { + insert(option) + } else { + remove(option) + } + } + } +} diff --git a/Sources/SharedKit/Extensions/TableView/NSTableView+Row.swift b/Sources/SharedKit/Extensions/TableView/NSTableView+Row.swift index 4be4ce3..cdc32c1 100644 --- a/Sources/SharedKit/Extensions/TableView/NSTableView+Row.swift +++ b/Sources/SharedKit/Extensions/TableView/NSTableView+Row.swift @@ -91,9 +91,15 @@ extension NSTableView { let point = if center, let scrollView { - NSPoint(x: 0, y: rowRect.origin.y - headerHeight + (rowRect.size.height / 2) - (scrollView.frame.size.height / 2)) + NSPoint( + x: 0, + y: rowRect.origin.y - headerHeight + (rowRect.size.height / 2) - (scrollView.frame.size.height / 2) + ) } else { - NSPoint(x: 0, y: rowRect.origin.y - headerHeight) + NSPoint( + x: 0, + y: rowRect.origin.y - headerHeight + ) } scroll(point) } diff --git a/Sources/SharedKit/Extensions/URL/URL+Path.swift b/Sources/SharedKit/Extensions/URL/URL+Path.swift index d180599..fa9e426 100644 --- a/Sources/SharedKit/Extensions/URL/URL+Path.swift +++ b/Sources/SharedKit/Extensions/URL/URL+Path.swift @@ -48,7 +48,10 @@ public extension URL { return destBaseURL .appending(path: String(relativePath)) - .appending(path: destURL.lastPathComponent, directoryHint: destURL.hasDirectoryPath ? .isDirectory : .notDirectory) + .appending( + path: destURL.lastPathComponent, + directoryHint: destURL.hasDirectoryPath ? .isDirectory : .notDirectory + ) } let trailingPath = String(srcPath[srcBaseDir.endIndex...]).trimmingPrefix("/") return destBaseURL diff --git a/Sources/SharedKit/Extensions/URL/URL+StructuredContent.swift b/Sources/SharedKit/Extensions/URL/URL+StructuredContent.swift index e092452..b22390e 100644 --- a/Sources/SharedKit/Extensions/URL/URL+StructuredContent.swift +++ b/Sources/SharedKit/Extensions/URL/URL+StructuredContent.swift @@ -34,8 +34,14 @@ extension URL { return text.string } // try fallback encoding NSWindowsCP1252StringEncoding - let optionsWithBestEncoding = options.merging([.characterEncoding: NSWindowsCP1252StringEncoding]) { _, new in new } - if let text = try readHandlingInapplicableStringEncoding(options: optionsWithBestEncoding, documentAttributes: &docAttrs) { + let optionsWithBestEncoding = options.merging( + [.characterEncoding: NSWindowsCP1252StringEncoding] + ) { _, new in new } + + if let text = try readHandlingInapplicableStringEncoding( + options: optionsWithBestEncoding, + documentAttributes: &docAttrs + ) { return text.string } // try removing encoding diff --git a/Sources/SharedKit/Extensions/View/TextView/NSTextView+Style.swift b/Sources/SharedKit/Extensions/View/TextView/NSTextView+Style.swift index 8bcbabb..0400533 100644 --- a/Sources/SharedKit/Extensions/View/TextView/NSTextView+Style.swift +++ b/Sources/SharedKit/Extensions/View/TextView/NSTextView+Style.swift @@ -11,7 +11,7 @@ guard let font else { return } - guard let paragraphStyle: NSMutableParagraphStyle = defaultParagraphStyle?.mutableCopy() as? NSMutableParagraphStyle + guard let paragraphStyle = defaultParagraphStyle?.mutableCopy() as? NSMutableParagraphStyle ?? NSParagraphStyle.default.mutableCopy() as? NSMutableParagraphStyle else { return } diff --git a/Sources/SharedKit/Utilities/Document/SecureBookmark.swift b/Sources/SharedKit/Utilities/Document/SecureBookmark.swift index da265bd..40095ee 100644 --- a/Sources/SharedKit/Utilities/Document/SecureBookmark.swift +++ b/Sources/SharedKit/Utilities/Document/SecureBookmark.swift @@ -7,6 +7,7 @@ // import Foundation +import os.log private let sandboxedPaths = "sandboxedPaths" @@ -48,7 +49,7 @@ class SecureBookmark: @unchecked Sendable { dict![path.osPath] = bookmark UserDefaults.standard.set(dict, forKey: sandboxedPaths) } catch { - NSLog("Secure bookmark failed \(error)") + Logger.general.error("Secure bookmark failed \(error)") return false } } @@ -77,7 +78,7 @@ class SecureBookmark: @unchecked Sendable { } return url } catch { - NSLog("Secure bookmark error while resolving bookmark \(error)") + Logger.general.error("Secure bookmark error while resolving bookmark \(error)") } return nil } diff --git a/Sources/SharedKit/Utilities/IO/BufferedInputStream.swift b/Sources/SharedKit/Utilities/IO/BufferedInputStream.swift index e75542b..24b0631 100644 --- a/Sources/SharedKit/Utilities/IO/BufferedInputStream.swift +++ b/Sources/SharedKit/Utilities/IO/BufferedInputStream.swift @@ -110,7 +110,11 @@ public class BufferedInputStream: InputStream { ] do { let line = try withUnsafeMutablePointer(to: &docAttrs) { docAttrsPointer in - try NSAttributedString(data: data, options: options, documentAttributes: AutoreleasingUnsafeMutablePointer(docAttrsPointer)).string + try NSAttributedString( + data: data, + options: options, + documentAttributes: AutoreleasingUnsafeMutablePointer(docAttrsPointer) + ).string } if let val = docAttrs[NSAttributedString.DocumentAttributeKey.characterEncoding] as? NSNumber { diff --git a/Sources/SharedKit/Utilities/IO/FileError.swift b/Sources/SharedKit/Utilities/IO/FileError.swift index 4e89b28..fa23cd4 100644 --- a/Sources/SharedKit/Utilities/IO/FileError.swift +++ b/Sources/SharedKit/Utilities/IO/FileError.swift @@ -14,7 +14,6 @@ public enum FileError: Error, Equatable { case unknownVolumeType } -// swiftlint:disable line_length extension FileError: LocalizedError { public var errorDescription: String? { switch self { @@ -49,5 +48,3 @@ extension FileError: LocalizedError { } } } - -// swiftlint:enable line_length diff --git a/Sources/SharedKit/Utilities/OpenEditor/OpenEditor.swift b/Sources/SharedKit/Utilities/OpenEditor/OpenEditor.swift index 61387c2..ef90759 100644 --- a/Sources/SharedKit/Utilities/OpenEditor/OpenEditor.swift +++ b/Sources/SharedKit/Utilities/OpenEditor/OpenEditor.swift @@ -6,6 +6,8 @@ // Copyright (c) 2025 visualdiffer.com // +import os.log + struct OpenEditor { let attributes: [OpenEditorAttribute] @@ -62,7 +64,7 @@ extension OpenEditor { configuration: NSWorkspace.OpenConfiguration() ) { _, error in if let error { - NSLog("Unable to open file \(item.path): \(error)") + Logger.general.error("Unable to open file \(item.path): \(error)") } } } @@ -93,7 +95,7 @@ extension OpenEditor { let task = try NSUserUnixTask(url: scriptURL) task.execute(withArguments: arguments()) { taskError in if let taskError { - NSLog("Unable to launch \(scriptURL.osPath), \(taskError)") + Logger.general.error("Unable to launch \(scriptURL.osPath), \(taskError)") } } } catch { diff --git a/Sources/SharedKit/Utilities/OpenEditor/OpenEditorError.swift b/Sources/SharedKit/Utilities/OpenEditor/OpenEditorError.swift index 41e3d3f..ce688e7 100644 --- a/Sources/SharedKit/Utilities/OpenEditor/OpenEditorError.swift +++ b/Sources/SharedKit/Utilities/OpenEditor/OpenEditorError.swift @@ -11,7 +11,6 @@ enum OpenEditorError: Error { case missingExecutePermission(URL) } -// swiftlint:disable line_length extension OpenEditorError: LocalizedError { var errorDescription: String? { switch self { @@ -24,5 +23,3 @@ extension OpenEditorError: LocalizedError { } } } - -// swiftlint:enable line_length diff --git a/Sources/SharedKit/Utilities/View/Encoding/SelectEncodingsPanel.swift b/Sources/SharedKit/Utilities/View/Encoding/SelectEncodingsPanel.swift index 3c2e070..aeb12a4 100644 --- a/Sources/SharedKit/Utilities/View/Encoding/SelectEncodingsPanel.swift +++ b/Sources/SharedKit/Utilities/View/Encoding/SelectEncodingsPanel.swift @@ -187,7 +187,10 @@ class SelectEncodingsPanel: NSWindow, NSTableViewDataSource, NSTableViewDelegate guard let identifier = tableColumn?.identifier else { return nil } - let cell = tableView.makeView(withIdentifier: identifier, owner: self) as? NSTableCellView ?? createCell(identifier) + let cell = tableView.makeView( + withIdentifier: identifier, + owner: self + ) as? NSTableCellView ?? createCell(identifier) cell.textField?.stringValue = String.localizedName(of: encodingsList[row]) diff --git a/Sources/SharedUI/Cells/FilePathTableCellView.swift b/Sources/SharedUI/Cells/FilePathTableCellView.swift index 0962119..e165582 100644 --- a/Sources/SharedUI/Cells/FilePathTableCellView.swift +++ b/Sources/SharedUI/Cells/FilePathTableCellView.swift @@ -85,7 +85,10 @@ class FilePathTableCellView: NSTableCellView { } if pathTextField.fileExists { - imageView.image = IconUtils.shared.icon(forFile: URL(filePath: path, directoryHint: .notDirectory), size: 16.0) + imageView.image = IconUtils.shared.icon( + forFile: URL(filePath: path, directoryHint: .notDirectory), + size: 16.0 + ) } else { imageView.image = NSImage(named: NSImage.cautionName) imageView.image?.size = NSSize(width: 16.0, height: 16.0) diff --git a/VisualDiffer.xcodeproj/project.pbxproj b/VisualDiffer.xcodeproj/project.pbxproj index 740593f..fd309e2 100644 --- a/VisualDiffer.xcodeproj/project.pbxproj +++ b/VisualDiffer.xcodeproj/project.pbxproj @@ -463,6 +463,7 @@ 55C1CC012E8BB755004FE322 /* visdiff in Copy Visdiff to Helpers */ = {isa = PBXBuildFile; fileRef = 55C1CBE22E8BB6A2004FE322 /* visdiff */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 55C1CC072E8BBD85004FE322 /* DocumentWaiter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55C1CC062E8BBAD1004FE322 /* DocumentWaiter.swift */; }; 55C84A712DA93ADB00BA39DA /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 55C84A702DA93ADB00BA39DA /* UserNotifications.framework */; }; + 55D352E72EFD0C5B00C944D5 /* FileSystemTestHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55D352E62EFD0C4D00C944D5 /* FileSystemTestHelper.swift */; }; 55D71FD62EE584B7007E0FA0 /* AppUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55D71FD52EE584B7007E0FA0 /* AppUpdater.swift */; }; 55DF3DD22E924BB10044CC0C /* BaseTests+FileSystemOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55DF3DC52E924BB10044CC0C /* BaseTests+FileSystemOperations.swift */; }; 55DF3DD32E924BB10044CC0C /* FiltersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55DF3DBF2E924BB10044CC0C /* FiltersTests.swift */; }; @@ -507,6 +508,7 @@ 55F9E79A2EDD5DBD001218C7 /* DiffResult.Options.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55F9E7992EDD5DAD001218C7 /* DiffResult.Options.swift */; }; 55F9E79C2EDD6221001218C7 /* WhitespacesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55F9E79B2EDD6221001218C7 /* WhitespacesTests.swift */; }; 55FD7E28180C484500CF473C /* prefs_folder@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 55FD7E25180C484500CF473C /* prefs_folder@2x.png */; }; + 55FE894A2F054B260003864A /* FlagSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55FE89492F054B260003864A /* FlagSet.swift */; }; 775DFF38067A968500C5B868 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A7FEA54F5311CA2CBB /* Cocoa.framework */; }; 8D15AC2C0486D014006FF6A4 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 2A37F4B9FDCFA73011CA2CEA /* Credits.rtf */; }; 8D15AC2F0486D014006FF6A4 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165FFE840EACC02AAC07 /* InfoPlist.strings */; }; @@ -869,7 +871,6 @@ 553740D92E9372DF00AB56D0 /* TOPFileSizePredicateEditorRowTemplate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TOPFileSizePredicateEditorRowTemplate.m; sourceTree = ""; }; 553740DA2E9372DF00AB56D0 /* TOPTimestampPredicateEditorRowTemplate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TOPTimestampPredicateEditorRowTemplate.h; sourceTree = ""; }; 553740DB2E9372DF00AB56D0 /* TOPTimestampPredicateEditorRowTemplate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TOPTimestampPredicateEditorRowTemplate.m; sourceTree = ""; }; - 553740E02E9372DF00AB56D0 /* TOPMacros.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TOPMacros.h; sourceTree = ""; }; 553740E22E9372DF00AB56D0 /* OpenDiffCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenDiffCommand.swift; sourceTree = ""; }; 553740E52E9372DF00AB56D0 /* NSAppearance+DarkMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSAppearance+DarkMode.swift"; sourceTree = ""; }; 553740E72E9372DF00AB56D0 /* Array+MoveIndexes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+MoveIndexes.swift"; sourceTree = ""; }; @@ -1056,6 +1057,7 @@ 55C1CC062E8BBAD1004FE322 /* DocumentWaiter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentWaiter.swift; sourceTree = ""; }; 55C84A702DA93ADB00BA39DA /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; }; 55D26E84140E2EAD00646F50 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; + 55D352E62EFD0C4D00C944D5 /* FileSystemTestHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileSystemTestHelper.swift; sourceTree = ""; }; 55D71FD52EE584B7007E0FA0 /* AppUpdater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUpdater.swift; sourceTree = ""; }; 55D71FD72EE584FF007E0FA0 /* AppConfig-Sparkle.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "AppConfig-Sparkle.xcconfig"; sourceTree = ""; }; 55D71FD82EE584FF007E0FA0 /* VisualDiffer-Sparkle.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "VisualDiffer-Sparkle.entitlements"; sourceTree = ""; }; @@ -1106,6 +1108,7 @@ 55F9E79B2EDD6221001218C7 /* WhitespacesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhitespacesTests.swift; sourceTree = ""; }; 55FC15F22557CD000075E30D /* ScriptingBridge.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ScriptingBridge.framework; path = System/Library/Frameworks/ScriptingBridge.framework; sourceTree = SDKROOT; }; 55FD7E25180C484500CF473C /* prefs_folder@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "prefs_folder@2x.png"; sourceTree = ""; }; + 55FE89492F054B260003864A /* FlagSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlagSet.swift; sourceTree = ""; }; 7788DA0506752A1600599AAD /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = ""; }; 8D15AC360486D014006FF6A4 /* VisualDiffer-Info.plist */ = {isa = PBXFileReference; explicitFileType = text.plist.xml; fileEncoding = 4; path = "VisualDiffer-Info.plist"; sourceTree = ""; }; 8D15AC370486D014006FF6A4 /* VisualDiffer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = VisualDiffer.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1671,14 +1674,15 @@ children = ( 55373FFC2E9372DF00AB56D0 /* Copy */, 55373FFE2E9372DF00AB56D0 /* Delete */, - 553740002E9372DF00AB56D0 /* Move */, - 5537400B2E9372DF00AB56D0 /* Progress */, - 553740102E9372DF00AB56D0 /* Sync */, - 553740152E9372DF00AB56D0 /* Touch */, 553740162E9372DF00AB56D0 /* FileOperationExecutor.swift */, 553740172E9372DF00AB56D0 /* FileSummaryView.swift */, 553740182E9372DF00AB56D0 /* FileSystemController.swift */, + 55D352E62EFD0C4D00C944D5 /* FileSystemTestHelper.swift */, + 553740002E9372DF00AB56D0 /* Move */, 553740192E9372DF00AB56D0 /* OperationSummaryView.swift */, + 5537400B2E9372DF00AB56D0 /* Progress */, + 553740102E9372DF00AB56D0 /* Sync */, + 553740152E9372DF00AB56D0 /* Touch */, ); path = FileSystemController; sourceTree = ""; @@ -2149,7 +2153,6 @@ 553740C92E9372DF00AB56D0 /* FileManager */, 553740D32E9372DF00AB56D0 /* MGScopeBar */, 553740DC2E9372DF00AB56D0 /* Predicate */, - 553740E02E9372DF00AB56D0 /* TOPMacros.h */, ); path = Legacy; sourceTree = ""; @@ -2253,6 +2256,7 @@ 553740FA2E9372DF00AB56D0 /* OptionSet */ = { isa = PBXGroup; children = ( + 55FE89492F054B260003864A /* FlagSet.swift */, 553740F92E9372DF00AB56D0 /* OptionSet+Toggle.swift */, ); path = OptionSet; @@ -3220,6 +3224,7 @@ 55916A102E8A814200ED6629 /* MyDocument.xcdatamodeld in Sources */, 553741792E9372DF00AB56D0 /* NSAppearance+DarkMode.swift in Sources */, 5537417A2E9372DF00AB56D0 /* QLPreviewPanel.swift in Sources */, + 55FE894A2F054B260003864A /* FlagSet.swift in Sources */, 5537417B2E9372DF00AB56D0 /* TrustedPathsPreferencesPanel.swift in Sources */, 5537417C2E9372DF00AB56D0 /* TouchPickersStackView.swift in Sources */, 5537417D2E9372DF00AB56D0 /* BasePreferences.swift in Sources */, @@ -3251,6 +3256,7 @@ 553741982E9372DF00AB56D0 /* CommonPrefs+FolderCompare.swift in Sources */, 553741992E9372DF00AB56D0 /* FoldersWindowController+PathControlDelegate.swift in Sources */, 551DE3B82ED439AF0067AB18 /* PreferencesBoxDataSource+Default.swift in Sources */, + 55D352E72EFD0C5B00C944D5 /* FileSystemTestHelper.swift in Sources */, 5537419A2E9372DF00AB56D0 /* FontBox.swift in Sources */, 5537419B2E9372DF00AB56D0 /* FilesTableViewFindTextDelegate.swift in Sources */, 5537419C2E9372DF00AB56D0 /* DeleteFileOperationExecutor.swift in Sources */, diff --git a/VisualDiffer_Prefix.pch b/VisualDiffer_Prefix.pch index 0c2776c..e0d8658 100644 --- a/VisualDiffer_Prefix.pch +++ b/VisualDiffer_Prefix.pch @@ -2,8 +2,6 @@ // Prefix header for all source files of the 'VisualDiffer' target in the 'VisualDiffer' project // -#import "TOPMacros.h" - #ifdef __OBJC__ #import #import diff --git a/scripts/changelog.sh b/scripts/changelog.sh new file mode 100755 index 0000000..d8c55f9 --- /dev/null +++ b/scripts/changelog.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +set -euo pipefail + +XC_CONFIG_FILE="Versions.local.xcconfig" +CONTEXT_JSON="changelog-context.local.json" + +get_key_from_xcconfig() { + local xcconfig_file="$1" + local key="${2:-APP_VERSION}" + + if [[ ! -f "$xcconfig_file" ]]; then + echo "Error: file not found: $xcconfig_file" >&2 + return 1 + fi + + local value + value="$(sed -nE "s/^[[:space:]]*$key[[:space:]]*=[[:space:]]*(.*)\$/\1/p" "$xcconfig_file")" + value="${value%%[[:space:]]*}" + + if [[ -z "$value" ]]; then + echo "Error: $key not found in $xcconfig_file" >&2 + return 1 + fi + + echo "$value" +} + +update_json_property() { + local json_file="$1" + local property="$2" + local prop_value="$3" + + if [[ ! -f "$json_file" ]]; then + echo "Error: file not found: $json_file" >&2 + return 1 + fi + + sed -i '' -E \ + 's/^([[:space:]]*"'$property'":[[:space:]]*")[^"]*(")/\1'"$prop_value"'\2/' \ + "$json_file" +} + +rename_json_property() { + local json_file="$1" + local old_name="$2" + local new_name="$3" + + if [[ ! -f "$json_file" ]]; then + echo "Error: file not found: $json_file" >&2 + return 1 + fi + + sed -i '' -E \ + 's/^([[:space:]]*")'$old_name'(":)/\1'"$new_name"'\2/' \ + "$json_file" +} + + +version="$(get_key_from_xcconfig $XC_CONFIG_FILE)" +update_json_property $CONTEXT_JSON "appVersion" "v$version" + +if [ "$1" == "all" ]; then + rename_json_property $CONTEXT_JSON "excludeTypes" "_excludeTypes" +else + rename_json_property $CONTEXT_JSON "_excludeTypes" "excludeTypes" +fi + +npx conventional-changelog -p visualdiffer -c $CONTEXT_JSON