From 65e8f9a14a516ab97ee496bfca07f2001b56c62a Mon Sep 17 00:00:00 2001 From: Henrik Panhans Date: Thu, 7 Mar 2024 09:38:40 +0100 Subject: [PATCH 01/25] Upgrade dependencies Signed-off-by: Henrik Panhans --- Package.resolved | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Package.resolved b/Package.resolved index ea15f5b..43ea6c6 100644 --- a/Package.resolved +++ b/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/apple/swift-argument-parser", "state": { "branch": null, - "revision": "8f4d2753f0e4778c76d5f05ad16c74f707390531", - "version": "1.2.3" + "revision": "c8ed701b513cf5177118a175d85fbbbcd707ab41", + "version": "1.3.0" } }, { From be6fb441229ec97c566b9967ad6126d88502d3a3 Mon Sep 17 00:00:00 2001 From: Henrik Panhans Date: Thu, 7 Mar 2024 09:39:01 +0100 Subject: [PATCH 02/25] Upgrade Swift tools version Signed-off-by: Henrik Panhans --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 3bb2cc5..7c09840 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.4 +// swift-tools-version:5.7 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription From 41f90446c99b2890ce2322f38b61dfbb0ec521d7 Mon Sep 17 00:00:00 2001 From: Henrik Panhans Date: Thu, 7 Mar 2024 09:40:10 +0100 Subject: [PATCH 03/25] Remove obsolete properties Signed-off-by: Henrik Panhans --- .../SwiftFrameCore/Config/ConfigData.swift | 25 ------------------- .../Workers/ConfigProcessor.swift | 14 ----------- .../Utility/ConfigDataFixtures.swift | 8 ------ 3 files changed, 47 deletions(-) diff --git a/Sources/SwiftFrameCore/Config/ConfigData.swift b/Sources/SwiftFrameCore/Config/ConfigData.swift index f9a4c38..69ec6b7 100644 --- a/Sources/SwiftFrameCore/Config/ConfigData.swift +++ b/Sources/SwiftFrameCore/Config/ConfigData.swift @@ -20,8 +20,6 @@ struct ConfigData: Decodable, ConfigValidateable { let textColorSource: ColorSource let outputFormat: FileFormat let localesRegex: String? - let clearDirectories: Bool? - let outputWholeImage: Bool? @DecodableDefault.EmptyList var textGroups: [TextGroup] @@ -31,8 +29,6 @@ struct ConfigData: Decodable, ConfigValidateable { // MARK: - Coding Keys enum CodingKeys: String, CodingKey { - case clearDirectories - case outputWholeImage case deviceData case textGroups case stringsPath @@ -54,8 +50,6 @@ struct ConfigData: Decodable, ConfigValidateable { fontSource: FontSource, textColorSource: ColorSource, outputFormat: FileFormat, - clearDirectories: Bool?, - outputWholeImage: Bool?, deviceData: [DeviceData], localesRegex: String? = nil) { @@ -66,8 +60,6 @@ struct ConfigData: Decodable, ConfigValidateable { self.fontSource = fontSource self.textColorSource = textColorSource self.outputFormat = outputFormat - self.clearDirectories = clearDirectories - self.outputWholeImage = outputWholeImage self.deviceData = deviceData self.localesRegex = localesRegex } @@ -92,22 +84,6 @@ struct ConfigData: Decodable, ConfigValidateable { // MARK: - ConfigValidateable public func validate() throws { - if clearDirectories != nil { - let warningMessage = """ - Specifying clearDirectories in the config file is deprecated and will be ignored in a future version. - Please use the CLI argument --clear-directories instead. - """ - print(CommandLineFormatter.formatWarning(text: warningMessage)) - } - - if outputWholeImage != nil { - let warningMessage = """ - Specifying outputWholeImage in the config file is deprecated and will be ignored in a future version. - Please use the CLI argument --output-whole-image instead. - """ - print(CommandLineFormatter.formatWarning(text: warningMessage)) - } - guard !deviceData.isEmpty else { throw NSError( description: "No screenshot data was supplied", @@ -125,7 +101,6 @@ struct ConfigData: Decodable, ConfigValidateable { public func printSummary(insetByTabs tabs: Int) { ky_print("### Config Summary Start", insetByTabs: tabs) - CommandLineFormatter.printKeyValue("Outputs whole image as well in addition to slices", value: outputWholeImage) CommandLineFormatter.printKeyValue("Title Color", value: textColorSource.hexString, insetBy: tabs) CommandLineFormatter.printKeyValue("Title Font", value: try? fontSource.font().fontName, insetBy: tabs) CommandLineFormatter.printKeyValue("Title Max Font Size", value: maxFontSize, insetBy: tabs) diff --git a/Sources/SwiftFrameCore/Workers/ConfigProcessor.swift b/Sources/SwiftFrameCore/Workers/ConfigProcessor.swift index ec27b04..8abe1cc 100644 --- a/Sources/SwiftFrameCore/Workers/ConfigProcessor.swift +++ b/Sources/SwiftFrameCore/Workers/ConfigProcessor.swift @@ -38,13 +38,6 @@ public class ConfigProcessor: VerbosePrintable { public func validate() throws { try process() try data.validate() - - if data.outputWholeImage != nil { - printDeprecationWarning(for: "ouputWholeImage") - } - if data.clearDirectories != nil { - printDeprecationWarning(for: "clearDirectories") - } } private func process() throws { @@ -146,11 +139,4 @@ public class ConfigProcessor: VerbosePrintable { group.wait() } - // MARK: - Helpers - - private func printDeprecationWarning(for configProperty: String) { - let warningMessage = "\(configProperty) was specified in the config file, which is deprecated. The value will be ignored" - print(CommandLineFormatter.formatWarning(text: warningMessage)) - } - } diff --git a/Tests/SwiftFrameTests/Utility/ConfigDataFixtures.swift b/Tests/SwiftFrameTests/Utility/ConfigDataFixtures.swift index ec9ff32..e92f060 100644 --- a/Tests/SwiftFrameTests/Utility/ConfigDataFixtures.swift +++ b/Tests/SwiftFrameTests/Utility/ConfigDataFixtures.swift @@ -12,8 +12,6 @@ extension ConfigData { fontSource: .nsFont(.systemFont(ofSize: 20)), textColorSource: try! ColorSource(hexString: "#ff00ff"), outputFormat: .png, - clearDirectories: true, - outputWholeImage: true, deviceData: [.goodData] ) @@ -25,8 +23,6 @@ extension ConfigData { fontSource: .nsFont(.systemFont(ofSize: 20)), textColorSource: try! ColorSource(hexString: "#ff00ff"), outputFormat: .png, - clearDirectories: true, - outputWholeImage: true, deviceData: [.goodData], localesRegex: "^(?!en|fr$)\\w*$" ) @@ -39,8 +35,6 @@ extension ConfigData { fontSource: .nsFont(.systemFont(ofSize: 20)), textColorSource: try! ColorSource(hexString: "#ff00ff"), outputFormat: .png, - clearDirectories: true, - outputWholeImage: true, deviceData: [.goodData], localesRegex: "en" ) @@ -53,8 +47,6 @@ extension ConfigData { fontSource: .nsFont(.systemFont(ofSize: 20)), textColorSource: try! ColorSource(hexString: "#ff00ff"), outputFormat: .png, - clearDirectories: true, - outputWholeImage: true, deviceData: [.invalidData] ) From 14058cde28aed07716f02c3c7c218eed52a59684 Mon Sep 17 00:00:00 2001 From: Henrik Panhans Date: Thu, 7 Mar 2024 11:13:15 +0100 Subject: [PATCH 04/25] Create slice size calculation logic and add tests Signed-off-by: Henrik Panhans --- Sources/SwiftFrame/Render.swift | 10 ++- Sources/SwiftFrame/SwiftFrame.swift | 2 +- .../SwiftFrameCore/Config/DeviceData.swift | 76 ++++++------------- .../Helper Types/DecodableSize.swift | 13 ---- .../Workers/ConfigProcessor.swift | 15 ++-- .../Workers/ScreenshotRenderer.swift | 2 +- .../Workers/SliceSizeCalculator.swift | 13 ++++ .../Config Tests/DeviceDataTests.swift | 10 --- .../SliceSizeCalculatorTests.swift | 28 +++++++ Tests/SwiftFrameTests/Utility/BaseTest.swift | 22 ++---- .../Utility/DeviceDataFixtures.swift | 15 +--- 11 files changed, 89 insertions(+), 117 deletions(-) delete mode 100644 Sources/SwiftFrameCore/Helper Types/DecodableSize.swift create mode 100644 Sources/SwiftFrameCore/Workers/SliceSizeCalculator.swift create mode 100644 Tests/SwiftFrameTests/SliceSizeCalculatorTests.swift diff --git a/Sources/SwiftFrame/Render.swift b/Sources/SwiftFrame/Render.swift index 027a2b1..b458410 100644 --- a/Sources/SwiftFrame/Render.swift +++ b/Sources/SwiftFrame/Render.swift @@ -34,15 +34,17 @@ struct Render: ParsableCommand { @Flag( name: .long, + inversion: .prefixedNo, help: "Disables any colored output. Useful when running in CI" ) - var noColorOutput = false + var colorOutput = true @Flag( name: .long, + inversion: .prefixedNo, help: "Disables clearing the output directories before writing images to them" ) - var noClearDirectories = false + var clearDirectories = true // MARK: - Run @@ -55,8 +57,8 @@ struct Render: ParsableCommand { verbose: verbose, shouldValidateManually: manualValidation, shouldOutputWholeImage: outputWholeImage, - shouldClearDirectories: !noClearDirectories, - shouldColorOutput: !noColorOutput + shouldClearDirectories: clearDirectories, + shouldColorOutput: colorOutput ) try processor.validate() try processor.run() diff --git a/Sources/SwiftFrame/SwiftFrame.swift b/Sources/SwiftFrame/SwiftFrame.swift index 07d56b5..e6de05c 100644 --- a/Sources/SwiftFrame/SwiftFrame.swift +++ b/Sources/SwiftFrame/SwiftFrame.swift @@ -8,7 +8,7 @@ struct SwiftFrame: ParsableCommand { static let configuration = CommandConfiguration( commandName: "swiftframe", abstract: "CLI application for speedy screenshot framing", - version: "5.0.2", + version: "6.0.0", subcommands: [Render.self, Scaffold.self], defaultSubcommand: Render.self, helpNames: .shortAndLong diff --git a/Sources/SwiftFrameCore/Config/DeviceData.swift b/Sources/SwiftFrameCore/Config/DeviceData.swift index 9733f6d..3d788fa 100644 --- a/Sources/SwiftFrameCore/Config/DeviceData.swift +++ b/Sources/SwiftFrameCore/Config/DeviceData.swift @@ -9,8 +9,8 @@ public struct DeviceData: Decodable, ConfigValidateable { let outputSuffixes: [String] let templateImagePath: FileURL - private let screenshotsPath: FileURL - let sliceSizeOverride: DecodableSize? + let screenshotsPath: FileURL + let numberOfSlices: Int @DecodableDefault.IntZero var gapWidth: Int @@ -29,19 +29,19 @@ public struct DeviceData: Decodable, ConfigValidateable { case outputSuffixes case screenshotsPath = "screenshots" case templateImagePath = "templateFile" - case sliceSizeOverride case screenshotData case textData case gapWidth + case numberOfSlices } // MARK: - Init - internal init( + init( outputSuffixes: [String], templateImagePath: FileURL, screenshotsPath: FileURL, - sliceSizeOverride: DecodableSize? = nil, + numberOfSlices: Int, screenshotsGroupedByLocale: [String: [String: URL]]? = nil, templateImage: NSBitmapImageRep? = nil, screenshotData: [ScreenshotData] = [ScreenshotData](), @@ -51,7 +51,7 @@ public struct DeviceData: Decodable, ConfigValidateable { self.outputSuffixes = outputSuffixes self.templateImagePath = templateImagePath self.screenshotsPath = screenshotsPath - self.sliceSizeOverride = sliceSizeOverride + self.numberOfSlices = numberOfSlices self.screenshotsGroupedByLocale = screenshotsGroupedByLocale self.templateImage = templateImage self.screenshotData = screenshotData @@ -62,7 +62,7 @@ public struct DeviceData: Decodable, ConfigValidateable { // MARK: - Methods func makeProcessedData(localesRegex: NSRegularExpression?) throws -> DeviceData { - guard let rep = ImageLoader.loadRepresentation(at: templateImagePath.absoluteURL) else { + guard let templateImage = ImageLoader.loadRepresentation(at: templateImagePath.absoluteURL) else { throw NSError(description: "Error while loading template image at path \(templateImagePath.absoluteString)") } @@ -77,21 +77,22 @@ public struct DeviceData: Decodable, ConfigValidateable { parsedScreenshots[folder.lastPathComponent] = dictionary } - let processedTextData = try textData.map { try $0.makeProcessedData(size: rep.size) } + let processedTextData = try textData.map { try $0.makeProcessedData(size: templateImage.size) } let processedScreenshotData = screenshotData - .map { $0.makeProcessedData(size: rep.size) } + .map { $0.makeProcessedData(size: templateImage.size) } .sorted { $0.zIndex < $1.zIndex } return DeviceData( outputSuffixes: outputSuffixes, templateImagePath: templateImagePath, screenshotsPath: screenshotsPath, - sliceSizeOverride: sliceSizeOverride, + numberOfSlices: numberOfSlices, screenshotsGroupedByLocale: parsedScreenshots, - templateImage: rep, + templateImage: templateImage, screenshotData: processedScreenshotData, textData: processedTextData, - gapWidth: gapWidth) + gapWidth: gapWidth + ) } // MARK: - ConfigValidateable @@ -101,27 +102,12 @@ public struct DeviceData: Decodable, ConfigValidateable { throw NSError(description: "No screenshots were loaded, most likely caused by a faulty regular expression") } - try screenshotsGroupedByLocale.forEach { localeDict in - guard let first = localeDict.value.first?.value else { - return - } - try localeDict.value.forEach { - if let size = NSBitmapImageRep.ky_loadFromURL($0.value)?.ky_nativeSize, size != NSBitmapImageRep.ky_loadFromURL(first)?.ky_nativeSize { - throw NSError( - description: "Image file with mismatching resolution found in folder \"\(localeDict.key)\"", - expectation: "All screenshots should have the same resolution", - actualValue: "Screenshot with dimensions \(size)") - } - } - } - - // Now that we know all screenshots have the same resolution, we can validate that template image is multiple in width - // plus specified gap width in between - if let screenshotSize = sliceSizeOverride?.cgSize ?? NSBitmapImageRep.ky_loadFromURL(screenshotsGroupedByLocale.first?.value.first?.value)?.ky_nativeSize { - guard let templateImageSize = templateImage?.ky_nativeSize else { - throw NSError(description: "Template image for output suffixes \(suffixesStringRepresentation) could not be loaded for validation") - } - try validateSize(templateImageSize, screenshotSize: screenshotSize) + guard numberOfSlices > 0 else { + throw NSError( + description: "Invalid numberOfSlices value", + expectation: "numberOfSlices value should be >= 1", + actualValue: "numberOfSlices value is \(numberOfSlices)" + ) } try screenshotData.forEach { try $0.validate() } @@ -137,34 +123,16 @@ public struct DeviceData: Decodable, ConfigValidateable { } } - private func validateSize(_ templateSize: CGSize, screenshotSize: CGSize) throws { - let remainingPixels = templateSize.width.truncatingRemainder(dividingBy: screenshotSize.width) - if gapWidth == 0 { - guard remainingPixels == 0 else { - throw NSError( - description: "Template image for output suffixes \(suffixesStringRepresentation) is not a multiple in width as associated screenshot width", - expectation: "Width should be multiple of \(Int(screenshotSize.width))px", - actualValue: "\(Int(screenshotSize.width))px") - } - } else { - // Make sure there's at least one gap - guard remainingPixels.truncatingRemainder(dividingBy: CGFloat(gapWidth)) == 0 && remainingPixels != 0 else { - throw NSError( - description: "Template image for output suffixes \(suffixesStringRepresentation) is not a multiple in width as associated screenshot width", - expectation: "Template image width should be = (x * screenshot width) + (x - 1) * gap width", - actualValue: "Template image width: \(templateSize.width)px, screenshot width: \(screenshotSize.width), gap width: \(gapWidth)") - } - } - } - func printSummary(insetByTabs tabs: Int) { CommandLineFormatter.printKeyValue("Ouput suffixes", value: outputSuffixes.joined(separator: ", "), insetBy: tabs) CommandLineFormatter.printKeyValue("Template file path", value: templateImagePath.path, insetBy: tabs) + CommandLineFormatter.printKeyValue("Number of slices", value: numberOfSlices, insetBy: tabs) CommandLineFormatter.printKeyValue("Gap Width", value: gapWidth, insetBy: tabs) CommandLineFormatter.printKeyValue( "Screenshot folders", value: screenshotsGroupedByLocale.isEmpty ? "none" : screenshotsGroupedByLocale.keys.joined(separator: ", "), - insetBy: tabs) + insetBy: tabs + ) screenshotData.forEach { $0.printSummary(insetByTabs: tabs) } textData.forEach { $0.printSummary(insetByTabs: tabs) } } diff --git a/Sources/SwiftFrameCore/Helper Types/DecodableSize.swift b/Sources/SwiftFrameCore/Helper Types/DecodableSize.swift deleted file mode 100644 index 518ecd9..0000000 --- a/Sources/SwiftFrameCore/Helper Types/DecodableSize.swift +++ /dev/null @@ -1,13 +0,0 @@ -import Foundation - -/// Wrapper struct used to work around weird decoding behavior of `CGSize` -struct DecodableSize: Codable { - - let width: CGFloat - let height: CGFloat - - var cgSize: CGSize { - CGSize(width: width, height: height) - } - -} diff --git a/Sources/SwiftFrameCore/Workers/ConfigProcessor.swift b/Sources/SwiftFrameCore/Workers/ConfigProcessor.swift index 8abe1cc..6300c33 100644 --- a/Sources/SwiftFrameCore/Workers/ConfigProcessor.swift +++ b/Sources/SwiftFrameCore/Workers/ConfigProcessor.swift @@ -23,8 +23,8 @@ public class ConfigProcessor: VerbosePrintable { shouldValidateManually: Bool, shouldOutputWholeImage: Bool, shouldClearDirectories: Bool, - shouldColorOutput: Bool) throws - { + shouldColorOutput: Bool + ) throws { data = try DecodableParser.parseData(fromURL: configURL) self.verbose = verbose self.shouldValidateManually = shouldValidateManually @@ -103,14 +103,17 @@ public class ConfigProcessor: VerbosePrintable { try deviceData.screenshotsGroupedByLocale.forEach { locale, imageDict in group.enter() + defer { group.leave() } guard let templateImage = deviceData.templateImage else { throw NSError(description: "No template image found") } - guard let sliceSize = deviceData.sliceSizeOverride?.cgSize ?? NSBitmapImageRep.ky_loadFromURL(imageDict.first?.value)?.ky_nativeSize else { - throw NSError(description: "No screenshots supplied, so it's impossible to slice into the correct size") - } + let sliceSize = SliceSizeCalculator.calculateSliceSize( + templateImageSize: templateImage.ky_nativeSize, + numberOfSlices: deviceData.numberOfSlices, + gapWidth: deviceData.gapWidth + ) let composer = try ImageComposer(canvasSize: templateImage.ky_nativeSize) try composer.add(screenshots: imageDict, with: deviceData.screenshotData, for: locale) @@ -132,8 +135,6 @@ public class ConfigProcessor: VerbosePrintable { deviceData.outputSuffixes.forEach { suffix in print("Finished \(locale)-\(suffix)") } - - group.leave() } group.wait() diff --git a/Sources/SwiftFrameCore/Workers/ScreenshotRenderer.swift b/Sources/SwiftFrameCore/Workers/ScreenshotRenderer.swift index 360fce3..cf8fc3b 100644 --- a/Sources/SwiftFrameCore/Workers/ScreenshotRenderer.swift +++ b/Sources/SwiftFrameCore/Workers/ScreenshotRenderer.swift @@ -49,7 +49,7 @@ final class ScreenshotRenderer { screenshotData.topRight.y ] - // Can force-unwrap since the are never empty + // Can safely force-unwrap since we know the arrays are not empty let minX = xCoordinates.min()! let maxX = xCoordinates.max()! let minY = yCoordinates.min()! diff --git a/Sources/SwiftFrameCore/Workers/SliceSizeCalculator.swift b/Sources/SwiftFrameCore/Workers/SliceSizeCalculator.swift new file mode 100644 index 0000000..5b9753f --- /dev/null +++ b/Sources/SwiftFrameCore/Workers/SliceSizeCalculator.swift @@ -0,0 +1,13 @@ +import Foundation + +struct SliceSizeCalculator { + + static func calculateSliceSize(templateImageSize: CGSize, numberOfSlices: Int, gapWidth: Int?) -> CGSize { + // number of slices minus 1 because gaps are only in between, multiplied by gapWidth + let totalGapWidthIfAny = gapWidth.flatMap { (numberOfSlices - 1) * $0 } + let templateWidthSubstractingGaps = templateImageSize.width - CGFloat(totalGapWidthIfAny ?? 0) + // Resulting slice is remaining width divided by expected number of slices, height can just be forwarded + return CGSize(width: templateWidthSubstractingGaps / CGFloat(numberOfSlices), height: templateImageSize.height) + } + +} diff --git a/Tests/SwiftFrameTests/Config Tests/DeviceDataTests.swift b/Tests/SwiftFrameTests/Config Tests/DeviceDataTests.swift index fa82f15..b18319b 100644 --- a/Tests/SwiftFrameTests/Config Tests/DeviceDataTests.swift +++ b/Tests/SwiftFrameTests/Config Tests/DeviceDataTests.swift @@ -16,11 +16,6 @@ class DeviceDataTests: BaseTest { XCTAssertNoThrow(try data.validate()) } - func testGapDataInvalid() throws { - let data = try DeviceData.gapData.makeProcessedData(localesRegex: nil) - XCTAssertThrowsError(try data.validate()) - } - func testInvalidData() throws { let data = try DeviceData.invalidData.makeProcessedData(localesRegex: nil) XCTAssertThrowsError(try data.validate()) @@ -31,9 +26,4 @@ class DeviceDataTests: BaseTest { XCTAssertNoThrow(try data.validate()) } - func testFaultyMismatchingDeviceSizeData() throws { - let data = try DeviceData.faultyMismatchingDeviceSizeData.makeProcessedData(localesRegex: nil) - XCTAssertThrowsError(try data.validate()) - } - } diff --git a/Tests/SwiftFrameTests/SliceSizeCalculatorTests.swift b/Tests/SwiftFrameTests/SliceSizeCalculatorTests.swift new file mode 100644 index 0000000..43eb3d3 --- /dev/null +++ b/Tests/SwiftFrameTests/SliceSizeCalculatorTests.swift @@ -0,0 +1,28 @@ +import XCTest +@testable import SwiftFrameCore + +final class SliceSizeCalculatorTests: BaseTest { + + func testSliceSizeCalculator_ProducesFiveSlices_WhenNotUsingGapWidth() { + let templateSize = CGSize(width: 100, height: 50) + + let calculatedSliceSize = SliceSizeCalculator.calculateSliceSize( + templateImageSize: templateSize, + numberOfSlices: 5, + gapWidth: nil + ) + XCTAssertEqual(calculatedSliceSize, CGSize(width: 20, height: 50)) + } + + func testSliceSizeCalculator_ProducesFiveSlices_WhenUsingGapWidth() { + let templateSize = CGSize(width: 100, height: 50) + + let calculatedSliceSize = SliceSizeCalculator.calculateSliceSize( + templateImageSize: templateSize, + numberOfSlices: 5, + gapWidth: 5 + ) + XCTAssertEqual(calculatedSliceSize, CGSize(width: 16, height: 50)) + } + +} diff --git a/Tests/SwiftFrameTests/Utility/BaseTest.swift b/Tests/SwiftFrameTests/Utility/BaseTest.swift index 2c975c9..8e13f8e 100644 --- a/Tests/SwiftFrameTests/Utility/BaseTest.swift +++ b/Tests/SwiftFrameTests/Utility/BaseTest.swift @@ -3,24 +3,14 @@ import XCTest class BaseTest: XCTestCase { - override func setUp() { - super.setUp() - - do { - try TestingUtility.setupMockDirectoryWithScreenshots() - } catch let error { - XCTFail(error.localizedDescription) - } + override func setUpWithError() throws { + try TestingUtility.setupMockDirectoryWithScreenshots() + try super.setUpWithError() } - override func tearDown() { - super.tearDown() - - do { - try TestingUtility.clearTestingDirectory() - } catch let error { - XCTFail(error.localizedDescription) - } + override func tearDownWithError() throws { + try TestingUtility.clearTestingDirectory() + try super.tearDownWithError() } } diff --git a/Tests/SwiftFrameTests/Utility/DeviceDataFixtures.swift b/Tests/SwiftFrameTests/Utility/DeviceDataFixtures.swift index 981df7b..9b69a03 100644 --- a/Tests/SwiftFrameTests/Utility/DeviceDataFixtures.swift +++ b/Tests/SwiftFrameTests/Utility/DeviceDataFixtures.swift @@ -7,6 +7,7 @@ extension DeviceData { outputSuffixes: ["iPhone X"], templateImagePath: FileURL(path: "testing/templatefile-debug_device1.png"), screenshotsPath: FileURL(path: "testing/screenshots/"), + numberOfSlices: 4, screenshotData: [.goodData], textData: [.goodData] ) @@ -15,6 +16,7 @@ extension DeviceData { outputSuffixes: ["iPhone X"], templateImagePath: FileURL(path: "testing/templatefile-debug_device1.png"), screenshotsPath: FileURL(path: "testing/screenshots/"), + numberOfSlices: 4, screenshotData: [.goodData], textData: [.goodData], gapWidth: 16 @@ -24,6 +26,7 @@ extension DeviceData { outputSuffixes: ["iPhone X"], templateImagePath: FileURL(path: "testing/templatefile-debug_device1.png"), screenshotsPath: FileURL(path: "testing/screenshots/"), + numberOfSlices: 4, screenshotData: [.goodData], textData: [.invalidData] ) @@ -32,19 +35,9 @@ extension DeviceData { outputSuffixes: ["iPhone X"], templateImagePath: FileURL(path: "testing/templatefile-debug_device1.png"), screenshotsPath: FileURL(path: "testing/screenshots/"), - sliceSizeOverride: DecodableSize(width: 50, height: 100), + numberOfSlices: 4, screenshotData: [.goodData], textData: [.goodData] ) - static let faultyMismatchingDeviceSizeData = DeviceData( - outputSuffixes: ["iPhone X"], - templateImagePath: FileURL(path: "testing/templatefile-debug_device1.png"), - screenshotsPath: FileURL(path: "testing/screenshots/"), - sliceSizeOverride: DecodableSize(width: 50, height: 100), - screenshotData: [.goodData], - textData: [.goodData], - gapWidth: 2 - ) - } From ad2353946db64c7bc585b7648f2d80506159af62 Mon Sep 17 00:00:00 2001 From: Henrik Panhans Date: Thu, 7 Mar 2024 11:20:15 +0100 Subject: [PATCH 05/25] Revert some changes to reduce diff Signed-off-by: Henrik Panhans --- Package.swift | 2 +- Sources/SwiftFrameCore/Workers/ScreenshotRenderer.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 7c09840..3bb2cc5 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.7 +// swift-tools-version:5.4 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription diff --git a/Sources/SwiftFrameCore/Workers/ScreenshotRenderer.swift b/Sources/SwiftFrameCore/Workers/ScreenshotRenderer.swift index cf8fc3b..360fce3 100644 --- a/Sources/SwiftFrameCore/Workers/ScreenshotRenderer.swift +++ b/Sources/SwiftFrameCore/Workers/ScreenshotRenderer.swift @@ -49,7 +49,7 @@ final class ScreenshotRenderer { screenshotData.topRight.y ] - // Can safely force-unwrap since we know the arrays are not empty + // Can force-unwrap since the are never empty let minX = xCoordinates.min()! let maxX = xCoordinates.max()! let minY = yCoordinates.min()! From a3566d0109679602befbc356f977623a1a782ed9 Mon Sep 17 00:00:00 2001 From: Henrik Panhans Date: Thu, 7 Mar 2024 11:33:57 +0100 Subject: [PATCH 06/25] Nicer names for tests and more useful tests for DeviceData Signed-off-by: Henrik Panhans --- .../SwiftFrameCore/Config/DeviceData.swift | 8 ++++ .../Config Tests/ConfigDataTests.swift | 2 +- .../Config Tests/DeviceDataTests.swift | 27 ++++++++------ .../Config Tests/TextDataTests.swift | 2 +- Tests/SwiftFrameTests/ImageLoaderTests.swift | 2 +- .../SliceSizeCalculatorTests.swift | 2 +- .../{BaseTest.swift => BaseTestCase.swift} | 2 +- .../Utility/ConfigDataFixtures.swift | 8 ++-- .../Utility/DeviceDataFixtures.swift | 37 ++++++++++--------- 9 files changed, 52 insertions(+), 38 deletions(-) rename Tests/SwiftFrameTests/Utility/{BaseTest.swift => BaseTestCase.swift} (90%) diff --git a/Sources/SwiftFrameCore/Config/DeviceData.swift b/Sources/SwiftFrameCore/Config/DeviceData.swift index 3d788fa..852422d 100644 --- a/Sources/SwiftFrameCore/Config/DeviceData.swift +++ b/Sources/SwiftFrameCore/Config/DeviceData.swift @@ -110,6 +110,14 @@ public struct DeviceData: Decodable, ConfigValidateable { ) } + guard gapWidth >= 0 else { + throw NSError( + description: "Invalid gapWidth value", + expectation: "gapWidth value should be >= 0 or ommitted from config", + actualValue: "gapWdith value is \(gapWidth)" + ) + } + try screenshotData.forEach { try $0.validate() } try textData.forEach { try $0.validate() } diff --git a/Tests/SwiftFrameTests/Config Tests/ConfigDataTests.swift b/Tests/SwiftFrameTests/Config Tests/ConfigDataTests.swift index 03399ab..1289848 100644 --- a/Tests/SwiftFrameTests/Config Tests/ConfigDataTests.swift +++ b/Tests/SwiftFrameTests/Config Tests/ConfigDataTests.swift @@ -2,7 +2,7 @@ import Foundation import XCTest @testable import SwiftFrameCore -class ConfigDataTests: BaseTest { +class ConfigDataTests: BaseTestCase { func testValidData() throws { var data = ConfigData.goodData diff --git a/Tests/SwiftFrameTests/Config Tests/DeviceDataTests.swift b/Tests/SwiftFrameTests/Config Tests/DeviceDataTests.swift index b18319b..4eac2d0 100644 --- a/Tests/SwiftFrameTests/Config Tests/DeviceDataTests.swift +++ b/Tests/SwiftFrameTests/Config Tests/DeviceDataTests.swift @@ -2,28 +2,31 @@ import Foundation import XCTest @testable import SwiftFrameCore -class DeviceDataTests: BaseTest { +class DeviceDataTests: BaseTestCase { - func testValidData() throws { - let data = try DeviceData.goodData.makeProcessedData(localesRegex: nil) + func testDeviceData_IsValid_WhenAllDataIsValid() throws { + let data = try DeviceData.validData().makeProcessedData(localesRegex: nil) XCTAssertNoThrow(try data.validate()) } - func testGapDataValid() throws { - try TestingUtility.setupMockDirectoryWithScreenshots(gapWidth: 16) - - let data = try DeviceData.gapData.makeProcessedData(localesRegex: nil) + func testDeviceData_IsValid_WhenGapWidthIsPositive() throws { + let data = try DeviceData.validData(gapWidth: 16).makeProcessedData(localesRegex: nil) XCTAssertNoThrow(try data.validate()) } - func testInvalidData() throws { - let data = try DeviceData.invalidData.makeProcessedData(localesRegex: nil) + func testDeviceData_IsInvalid_WhenTextDataIsInvalid() throws { + let data = try DeviceData.invalidTextData.makeProcessedData(localesRegex: nil) XCTAssertThrowsError(try data.validate()) } - func testMismatchingDeviceSizeData() throws { - let data = try DeviceData.mismatchingDeviceSizeData.makeProcessedData(localesRegex: nil) - XCTAssertNoThrow(try data.validate()) + func testDeviceData_IsInvalid_WhenNumberOfSlicesIsZero() throws { + let data = try DeviceData.invalidNumberOfSlices.makeProcessedData(localesRegex: nil) + XCTAssertThrowsError(try data.validate()) + } + + func testDeviceData_IsInvalid_WhenGapWidthNegative() throws { + let data = try DeviceData.invalidGapWidth.makeProcessedData(localesRegex: nil) + XCTAssertThrowsError(try data.validate()) } } diff --git a/Tests/SwiftFrameTests/Config Tests/TextDataTests.swift b/Tests/SwiftFrameTests/Config Tests/TextDataTests.swift index 2d91672..34e2c57 100644 --- a/Tests/SwiftFrameTests/Config Tests/TextDataTests.swift +++ b/Tests/SwiftFrameTests/Config Tests/TextDataTests.swift @@ -2,7 +2,7 @@ import Foundation import XCTest @testable import SwiftFrameCore -class TextDataTests: BaseTest { +class TextDataTests: BaseTestCase { func testValidData() throws { let size = CGSize(width: 200, height: 200) diff --git a/Tests/SwiftFrameTests/ImageLoaderTests.swift b/Tests/SwiftFrameTests/ImageLoaderTests.swift index b015de4..e158866 100644 --- a/Tests/SwiftFrameTests/ImageLoaderTests.swift +++ b/Tests/SwiftFrameTests/ImageLoaderTests.swift @@ -2,7 +2,7 @@ import Foundation import XCTest @testable import SwiftFrameCore -class ImageLoaderTests: BaseTest { +class ImageLoaderTests: BaseTestCase { func testLoadImage() throws { let context = try GraphicsContext(size: .square100Pixels) diff --git a/Tests/SwiftFrameTests/SliceSizeCalculatorTests.swift b/Tests/SwiftFrameTests/SliceSizeCalculatorTests.swift index 43eb3d3..b31f288 100644 --- a/Tests/SwiftFrameTests/SliceSizeCalculatorTests.swift +++ b/Tests/SwiftFrameTests/SliceSizeCalculatorTests.swift @@ -1,7 +1,7 @@ import XCTest @testable import SwiftFrameCore -final class SliceSizeCalculatorTests: BaseTest { +final class SliceSizeCalculatorTests: BaseTestCase { func testSliceSizeCalculator_ProducesFiveSlices_WhenNotUsingGapWidth() { let templateSize = CGSize(width: 100, height: 50) diff --git a/Tests/SwiftFrameTests/Utility/BaseTest.swift b/Tests/SwiftFrameTests/Utility/BaseTestCase.swift similarity index 90% rename from Tests/SwiftFrameTests/Utility/BaseTest.swift rename to Tests/SwiftFrameTests/Utility/BaseTestCase.swift index 8e13f8e..27917f5 100644 --- a/Tests/SwiftFrameTests/Utility/BaseTest.swift +++ b/Tests/SwiftFrameTests/Utility/BaseTestCase.swift @@ -1,7 +1,7 @@ import Foundation import XCTest -class BaseTest: XCTestCase { +class BaseTestCase: XCTestCase { override func setUpWithError() throws { try TestingUtility.setupMockDirectoryWithScreenshots() diff --git a/Tests/SwiftFrameTests/Utility/ConfigDataFixtures.swift b/Tests/SwiftFrameTests/Utility/ConfigDataFixtures.swift index e92f060..0a20439 100644 --- a/Tests/SwiftFrameTests/Utility/ConfigDataFixtures.swift +++ b/Tests/SwiftFrameTests/Utility/ConfigDataFixtures.swift @@ -12,7 +12,7 @@ extension ConfigData { fontSource: .nsFont(.systemFont(ofSize: 20)), textColorSource: try! ColorSource(hexString: "#ff00ff"), outputFormat: .png, - deviceData: [.goodData] + deviceData: [.validData()] ) static let skippedLocaleData = ConfigData( @@ -23,7 +23,7 @@ extension ConfigData { fontSource: .nsFont(.systemFont(ofSize: 20)), textColorSource: try! ColorSource(hexString: "#ff00ff"), outputFormat: .png, - deviceData: [.goodData], + deviceData: [.validData()], localesRegex: "^(?!en|fr$)\\w*$" ) @@ -35,7 +35,7 @@ extension ConfigData { fontSource: .nsFont(.systemFont(ofSize: 20)), textColorSource: try! ColorSource(hexString: "#ff00ff"), outputFormat: .png, - deviceData: [.goodData], + deviceData: [.validData()], localesRegex: "en" ) @@ -47,7 +47,7 @@ extension ConfigData { fontSource: .nsFont(.systemFont(ofSize: 20)), textColorSource: try! ColorSource(hexString: "#ff00ff"), outputFormat: .png, - deviceData: [.invalidData] + deviceData: [.invalidTextData] ) } diff --git a/Tests/SwiftFrameTests/Utility/DeviceDataFixtures.swift b/Tests/SwiftFrameTests/Utility/DeviceDataFixtures.swift index 9b69a03..a6b94cb 100644 --- a/Tests/SwiftFrameTests/Utility/DeviceDataFixtures.swift +++ b/Tests/SwiftFrameTests/Utility/DeviceDataFixtures.swift @@ -3,41 +3,44 @@ import Foundation extension DeviceData { - static let goodData = DeviceData( - outputSuffixes: ["iPhone X"], - templateImagePath: FileURL(path: "testing/templatefile-debug_device1.png"), - screenshotsPath: FileURL(path: "testing/screenshots/"), - numberOfSlices: 4, - screenshotData: [.goodData], - textData: [.goodData] - ) + static func validData(gapWidth: Int = 0) -> DeviceData { + DeviceData( + outputSuffixes: ["iPhone X"], + templateImagePath: FileURL(path: "testing/templatefile-debug_device1.png"), + screenshotsPath: FileURL(path: "testing/screenshots/"), + numberOfSlices: 4, + screenshotData: [.goodData], + textData: [.goodData], + gapWidth: gapWidth + ) + } - static let gapData = DeviceData( + static let invalidTextData = DeviceData( outputSuffixes: ["iPhone X"], templateImagePath: FileURL(path: "testing/templatefile-debug_device1.png"), screenshotsPath: FileURL(path: "testing/screenshots/"), numberOfSlices: 4, screenshotData: [.goodData], - textData: [.goodData], - gapWidth: 16 + textData: [.invalidData] ) - static let invalidData = DeviceData( + static let invalidNumberOfSlices = DeviceData( outputSuffixes: ["iPhone X"], templateImagePath: FileURL(path: "testing/templatefile-debug_device1.png"), screenshotsPath: FileURL(path: "testing/screenshots/"), - numberOfSlices: 4, + numberOfSlices: 0, screenshotData: [.goodData], - textData: [.invalidData] + textData: [.goodData] ) - static let mismatchingDeviceSizeData = DeviceData( + static let invalidGapWidth = DeviceData( outputSuffixes: ["iPhone X"], templateImagePath: FileURL(path: "testing/templatefile-debug_device1.png"), screenshotsPath: FileURL(path: "testing/screenshots/"), - numberOfSlices: 4, + numberOfSlices: 5, screenshotData: [.goodData], - textData: [.goodData] + textData: [.goodData], + gapWidth: -10 ) } From c792021a167c36dee997e0926f7b20ad43fae4e0 Mon Sep 17 00:00:00 2001 From: Henrik Panhans Date: Thu, 7 Mar 2024 11:50:58 +0100 Subject: [PATCH 07/25] Remove a bunch of unused code and reduce access levels Signed-off-by: Henrik Panhans --- Sources/SwiftFrame/Scaffold.swift | 4 ---- Sources/SwiftFrameCore/Config/ConfigData.swift | 8 ++++---- Sources/SwiftFrameCore/Config/DeviceData.swift | 6 +----- Sources/SwiftFrameCore/Config/ScreenshotData.swift | 4 ++-- Sources/SwiftFrameCore/Config/TextGroup.swift | 4 +--- .../Extensions/FileManager+Extensions.swift | 9 --------- .../Extensions/NSError+Extensions.swift | 2 +- .../Extensions/String+Extensions.swift | 4 ---- .../Helper Types/DecodableDefault.swift | 11 +---------- Sources/SwiftFrameCore/Helper Types/Point.swift | 6 +----- .../Helper Types/VerbosePrintable.swift | 7 ------- .../Workers/CommandLineFormatter.swift | 12 ++++++------ Sources/SwiftFrameCore/Workers/ImageLoader.swift | 7 ------- Sources/SwiftFrameCore/Workers/ImageWriter.swift | 2 +- Sources/SwiftFrameCore/Workers/TextRenderer.swift | 4 ---- Tests/SwiftFrameTests/Utility/TestingUtility.swift | 2 +- Tests/SwiftFrameTests/Utility/TextDataFixtures.swift | 9 ++++++--- 17 files changed, 25 insertions(+), 76 deletions(-) diff --git a/Sources/SwiftFrame/Scaffold.swift b/Sources/SwiftFrame/Scaffold.swift index 9a216db..97afc75 100644 --- a/Sources/SwiftFrame/Scaffold.swift +++ b/Sources/SwiftFrame/Scaffold.swift @@ -86,10 +86,6 @@ struct Scaffold: ParsableCommand, VerbosePrintable { print("Created \(numberOfCreatedFiles) files".formattedGreen()) } - private func lowercasedDirectoryIfNeeded(_ string: String) -> String { - lowercasedDirectories ? string.lowercased() : string - } - private func makeScaffoldRootURL() -> URL { if let path = path { return URL(fileURLWithPath: path) diff --git a/Sources/SwiftFrameCore/Config/ConfigData.swift b/Sources/SwiftFrameCore/Config/ConfigData.swift index 69ec6b7..bbf6433 100644 --- a/Sources/SwiftFrameCore/Config/ConfigData.swift +++ b/Sources/SwiftFrameCore/Config/ConfigData.swift @@ -42,7 +42,7 @@ struct ConfigData: Decodable, ConfigValidateable { // MARK: - Init - public init( + init( textGroups: [TextGroup] = [], stringsPath: FileURL, maxFontSize: CGFloat, @@ -66,7 +66,7 @@ struct ConfigData: Decodable, ConfigValidateable { // MARK: - Processing - mutating public func process() throws { + mutating func process() throws { let regex: NSRegularExpression? if let pattern = localesRegex { regex = try NSRegularExpression(pattern: pattern, options: .caseInsensitive) @@ -83,7 +83,7 @@ struct ConfigData: Decodable, ConfigValidateable { // MARK: - ConfigValidateable - public func validate() throws { + func validate() throws { guard !deviceData.isEmpty else { throw NSError( description: "No screenshot data was supplied", @@ -99,7 +99,7 @@ struct ConfigData: Decodable, ConfigValidateable { try deviceData.forEach { try $0.validate() } } - public func printSummary(insetByTabs tabs: Int) { + func printSummary(insetByTabs tabs: Int) { ky_print("### Config Summary Start", insetByTabs: tabs) CommandLineFormatter.printKeyValue("Title Color", value: textColorSource.hexString, insetBy: tabs) CommandLineFormatter.printKeyValue("Title Font", value: try? fontSource.font().fontName, insetBy: tabs) diff --git a/Sources/SwiftFrameCore/Config/DeviceData.swift b/Sources/SwiftFrameCore/Config/DeviceData.swift index 852422d..3dbe7e1 100644 --- a/Sources/SwiftFrameCore/Config/DeviceData.swift +++ b/Sources/SwiftFrameCore/Config/DeviceData.swift @@ -1,7 +1,7 @@ import AppKit import Foundation -public struct DeviceData: Decodable, ConfigValidateable { +struct DeviceData: Decodable, ConfigValidateable { // MARK: - Properties @@ -19,10 +19,6 @@ public struct DeviceData: Decodable, ConfigValidateable { private(set) var screenshotData = [ScreenshotData]() private(set) var textData = [TextData]() - private var suffixesStringRepresentation: String { - outputSuffixes.map { "\"\($0)\"" }.joined(separator: ", ") - } - // MARK: - Coding Keys enum CodingKeys: String, CodingKey { diff --git a/Sources/SwiftFrameCore/Config/ScreenshotData.swift b/Sources/SwiftFrameCore/Config/ScreenshotData.swift index 01e8b55..52a075b 100644 --- a/Sources/SwiftFrameCore/Config/ScreenshotData.swift +++ b/Sources/SwiftFrameCore/Config/ScreenshotData.swift @@ -1,6 +1,6 @@ import Foundation -public struct ScreenshotData: Decodable, ConfigValidateable, Equatable { +struct ScreenshotData: Decodable, ConfigValidateable, Equatable { // MARK: - Properties @@ -14,7 +14,7 @@ public struct ScreenshotData: Decodable, ConfigValidateable, Equatable { // MARK: - Init - internal init(screenshotName: String, bottomLeft: Point, bottomRight: Point, topLeft: Point, topRight: Point, zIndex: Int?) { + init(screenshotName: String, bottomLeft: Point, bottomRight: Point, topLeft: Point, topRight: Point, zIndex: Int?) { self.screenshotName = screenshotName self.bottomLeft = bottomLeft self.bottomRight = bottomRight diff --git a/Sources/SwiftFrameCore/Config/TextGroup.swift b/Sources/SwiftFrameCore/Config/TextGroup.swift index d8d2b86..c5c7741 100644 --- a/Sources/SwiftFrameCore/Config/TextGroup.swift +++ b/Sources/SwiftFrameCore/Config/TextGroup.swift @@ -1,9 +1,7 @@ import AppKit import Foundation -private let kNumTitleLines = 3 - -public struct TextGroup: Decodable, ConfigValidateable, Hashable { +struct TextGroup: Decodable, ConfigValidateable, Hashable { // MARK: - Properties diff --git a/Sources/SwiftFrameCore/Extensions/FileManager+Extensions.swift b/Sources/SwiftFrameCore/Extensions/FileManager+Extensions.swift index aa58665..d621644 100644 --- a/Sources/SwiftFrameCore/Extensions/FileManager+Extensions.swift +++ b/Sources/SwiftFrameCore/Extensions/FileManager+Extensions.swift @@ -7,15 +7,6 @@ extension FileManager { .filter { pathExtension == nil ? true : $0.pathExtension == pathExtension } } - // Doesn't throw if the directory doesn't exist or another error occurred - func ky_unsafeFilesAtPath(_ url: URL) -> [URL] { - do { - return try ky_filesAtPath(url) - } catch { - return [] - } - } - func ky_subDirectoriesAtPath(_ url: URL) -> [URL] { guard let urls = try? contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: .skipsHiddenFiles) else { return [] diff --git a/Sources/SwiftFrameCore/Extensions/NSError+Extensions.swift b/Sources/SwiftFrameCore/Extensions/NSError+Extensions.swift index 966117f..d3d95b9 100644 --- a/Sources/SwiftFrameCore/Extensions/NSError+Extensions.swift +++ b/Sources/SwiftFrameCore/Extensions/NSError+Extensions.swift @@ -26,7 +26,7 @@ public extension NSError { } -public func ky_executeOrExit(verbose: Bool = false, _ work: () throws -> T) -> T { +func ky_executeOrExit(verbose: Bool = false, _ work: () throws -> T) -> T { do { return try work() } catch let error as NSError { diff --git a/Sources/SwiftFrameCore/Extensions/String+Extensions.swift b/Sources/SwiftFrameCore/Extensions/String+Extensions.swift index a22d4ed..9b40e41 100644 --- a/Sources/SwiftFrameCore/Extensions/String+Extensions.swift +++ b/Sources/SwiftFrameCore/Extensions/String+Extensions.swift @@ -6,10 +6,6 @@ extension String { public func formattedGreen() -> String { CommandLineFormatter.formatWithColorIfNeeded(self, color: .green) } - - func formattedRed() -> String { - CommandLineFormatter.formatWithColorIfNeeded(self, color: .red) - } func ky_containsHTMLTags() -> Bool { let regex = try! NSRegularExpression(pattern: "<(.*)>.*?|<(.*)/>") diff --git a/Sources/SwiftFrameCore/Helper Types/DecodableDefault.swift b/Sources/SwiftFrameCore/Helper Types/DecodableDefault.swift index 48e674b..c0ff381 100644 --- a/Sources/SwiftFrameCore/Helper Types/DecodableDefault.swift +++ b/Sources/SwiftFrameCore/Helper Types/DecodableDefault.swift @@ -10,7 +10,7 @@ protocol DecodableDefaultSource { } -public enum DecodableDefault {} +enum DecodableDefault {} extension DecodableDefault { @@ -98,12 +98,3 @@ extension DecodableDefault { extension DecodableDefault.Wrapper: Equatable where Value: Equatable {} extension DecodableDefault.Wrapper: Hashable where Value: Hashable {} - -extension DecodableDefault.Wrapper: Encodable where Value: Encodable { - - func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - try container.encode(wrappedValue) - } - -} diff --git a/Sources/SwiftFrameCore/Helper Types/Point.swift b/Sources/SwiftFrameCore/Helper Types/Point.swift index 4e28ee5..2355dfe 100644 --- a/Sources/SwiftFrameCore/Helper Types/Point.swift +++ b/Sources/SwiftFrameCore/Helper Types/Point.swift @@ -12,13 +12,9 @@ struct Point: Codable, Equatable { CIVector(x: CGFloat(x), y: CGFloat(y)) } - var cgPoint: CGPoint { - CGPoint(x: x, y: y) - } - // MARK: - Coordinate space conversion - public func convertingToBottomLeftOrigin(withSize size: CGSize) -> Point { + func convertingToBottomLeftOrigin(withSize size: CGSize) -> Point { let newY = Int(size.height) - y return Point(x: x, y: newY) } diff --git a/Sources/SwiftFrameCore/Helper Types/VerbosePrintable.swift b/Sources/SwiftFrameCore/Helper Types/VerbosePrintable.swift index 8bfbb29..2409e41 100644 --- a/Sources/SwiftFrameCore/Helper Types/VerbosePrintable.swift +++ b/Sources/SwiftFrameCore/Helper Types/VerbosePrintable.swift @@ -25,11 +25,4 @@ public extension VerbosePrintable { printVerbose(CommandLineFormatter.formatTimeMeasurement(messageString)) } - @inlinable func performAndPrintElapsedTime(_ name: @autoclosure () -> String, work: () throws -> T) rethrows -> T { - let startTime = CFAbsoluteTimeGetCurrent() - let result = try work() - printElapsedTime(name(), startTime: startTime) - return result - } - } diff --git a/Sources/SwiftFrameCore/Workers/CommandLineFormatter.swift b/Sources/SwiftFrameCore/Workers/CommandLineFormatter.swift index c96ab7a..2d9ff4e 100644 --- a/Sources/SwiftFrameCore/Workers/CommandLineFormatter.swift +++ b/Sources/SwiftFrameCore/Workers/CommandLineFormatter.swift @@ -1,6 +1,6 @@ import Foundation -public final class CommandLineFormatter { +final class CommandLineFormatter { // MARK: - Nested Types @@ -19,17 +19,17 @@ public final class CommandLineFormatter { // MARK: - Message Formatting - public class func formatWarning(title: String = "WARNING", text: String) -> String { + class func formatWarning(title: String = "WARNING", text: String) -> String { let message = "[\(title)] \(text)" return formatWithColorIfNeeded(message, color: .yellow) } - public class func formatError(_ text: String) -> String { + class func formatError(_ text: String) -> String { let message = "[ERROR] \(text)" return formatWithColorIfNeeded(message, color: .red) } - public class func formatTimeMeasurement(_ text: String) -> String { + class func formatTimeMeasurement(_ text: String) -> String { let message = "[TIME] \(text)" return formatWithColorIfNeeded(message, color: .green) } @@ -44,7 +44,7 @@ public final class CommandLineFormatter { // MARK: - Key-Value Formatting - public class func printKeyValue(_ key: String, value: Any?, insetBy tabs: Int = 0) { + class func printKeyValue(_ key: String, value: Any?, insetBy tabs: Int = 0) { guard let value = value else { return } @@ -59,7 +59,7 @@ public final class CommandLineFormatter { } -public func ky_print(_ objects: Any..., insetByTabs tabs: Int) { +func ky_print(_ objects: Any..., insetByTabs tabs: Int) { let tabsString = String(repeating: CommandLineFormatter.tabsString, count: tabs) let arguments = objects.count == 1 ? String(describing: objects[0]) diff --git a/Sources/SwiftFrameCore/Workers/ImageLoader.swift b/Sources/SwiftFrameCore/Workers/ImageLoader.swift index c2c7a01..69c97a1 100644 --- a/Sources/SwiftFrameCore/Workers/ImageLoader.swift +++ b/Sources/SwiftFrameCore/Workers/ImageLoader.swift @@ -5,13 +5,6 @@ final class ImageLoader { // MARK: - Image Loading - func loadImage(atURL url: URL) throws -> NSImage { - guard let image = NSImage(contentsOf: url) else { - throw NSError(description: "Could not load image at \(url.absoluteString)") - } - return image - } - func loadImage(atPath path: String) throws -> NSImage { guard let image = NSImage(contentsOfFile: path) else { throw NSError(description: "Could not load image at \(path)") diff --git a/Sources/SwiftFrameCore/Workers/ImageWriter.swift b/Sources/SwiftFrameCore/Workers/ImageWriter.swift index e13631d..c51b0fb 100644 --- a/Sources/SwiftFrameCore/Workers/ImageWriter.swift +++ b/Sources/SwiftFrameCore/Workers/ImageWriter.swift @@ -1,7 +1,7 @@ import AppKit import Foundation -public final class ImageWriter { +final class ImageWriter { // MARK: - Exporting diff --git a/Sources/SwiftFrameCore/Workers/TextRenderer.swift b/Sources/SwiftFrameCore/Workers/TextRenderer.swift index 118b320..64bd903 100644 --- a/Sources/SwiftFrameCore/Workers/TextRenderer.swift +++ b/Sources/SwiftFrameCore/Workers/TextRenderer.swift @@ -158,10 +158,6 @@ final class TextRenderer { } -private func >=(lhs: CGSize, rhs: CGSize) -> Bool { - lhs.width >= rhs.width && lhs.height >= rhs.height -} - private func <=(lhs: CGSize, rhs: CGSize) -> Bool { lhs.width <= rhs.width && lhs.height <= rhs.height } diff --git a/Tests/SwiftFrameTests/Utility/TestingUtility.swift b/Tests/SwiftFrameTests/Utility/TestingUtility.swift index 38c6b01..feee375 100644 --- a/Tests/SwiftFrameTests/Utility/TestingUtility.swift +++ b/Tests/SwiftFrameTests/Utility/TestingUtility.swift @@ -1,7 +1,7 @@ import Foundation @testable import SwiftFrameCore -public typealias JSONDictionary = [String: Encodable] +typealias JSONDictionary = [String: Encodable] struct TestingUtility { diff --git a/Tests/SwiftFrameTests/Utility/TextDataFixtures.swift b/Tests/SwiftFrameTests/Utility/TextDataFixtures.swift index b38070c..459d0f7 100644 --- a/Tests/SwiftFrameTests/Utility/TextDataFixtures.swift +++ b/Tests/SwiftFrameTests/Utility/TextDataFixtures.swift @@ -13,7 +13,8 @@ extension TextData { groupIdentifier: nil, topLeft: Point(x: 10, y: 5), bottomRight: Point(x: 15, y: 20), - textColorOverride: nil) + textColorOverride: nil + ) static let invalidData = TextData( titleIdentifier: "someId", @@ -24,7 +25,8 @@ extension TextData { groupIdentifier: nil, topLeft: Point(x: 10, y: 5), bottomRight: Point(x: 8, y: 20), - textColorOverride: nil) + textColorOverride: nil + ) static let invertedData = TextData( titleIdentifier: "someId", @@ -35,6 +37,7 @@ extension TextData { groupIdentifier: nil, topLeft: Point(x: 10, y: 20), bottomRight: Point(x: 15, y: 5), - textColorOverride: nil) + textColorOverride: nil + ) } From 61452ad1556e9156fff4715e4df4e3e5bda72ff8 Mon Sep 17 00:00:00 2001 From: Henrik Panhans Date: Thu, 7 Mar 2024 12:00:05 +0100 Subject: [PATCH 08/25] More easy tests Signed-off-by: Henrik Panhans --- Tests/SwiftFrameTests/PointTests.swift | 33 +++++++++++++++++++ .../Utility/TextDataFixtures.swift | 6 ++-- .../Utility/TextGroupFixtures.swift | 2 +- 3 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 Tests/SwiftFrameTests/PointTests.swift diff --git a/Tests/SwiftFrameTests/PointTests.swift b/Tests/SwiftFrameTests/PointTests.swift new file mode 100644 index 0000000..d0c7edb --- /dev/null +++ b/Tests/SwiftFrameTests/PointTests.swift @@ -0,0 +1,33 @@ +import Foundation +import XCTest +@testable import SwiftFrameCore + +class PointTests: XCTestCase { + + func testPoint_DecodesCorrectly_WhenJSONIsWellFormed() throws { + let expectedX = 10 + let expectedY = 5 + + let jsonString = "{ \"x\": \(expectedX), \"y\": \(expectedY) }" + let jsonData = try XCTUnwrap(jsonString.data(using: .utf8)) + + let decodedPoint = try JSONDecoder().decode(Point.self, from: jsonData) + XCTAssertEqual(decodedPoint, Point(x: expectedX, y: expectedY)) + } + + func testPoint_FailsToDecode_WhenJSONIsMalformed() throws { + let jsonString = #"{ "x": 10, "yCoord": 5 }"# + let jsonData = try XCTUnwrap(jsonString.data(using: .utf8)) + + XCTAssertThrowsError(try JSONDecoder().decode(Point.self, from: jsonData)) + } + + func testPoint_ConvertsToBottomLeftOrigin() throws { + let point = Point(x: 10, y: 5) + let size = CGSize(width: 30, height: 30) + + let convertedPoint = point.convertingToBottomLeftOrigin(withSize: size) + XCTAssertEqual(convertedPoint, Point(x: 10, y: 25)) + } + +} diff --git a/Tests/SwiftFrameTests/Utility/TextDataFixtures.swift b/Tests/SwiftFrameTests/Utility/TextDataFixtures.swift index 459d0f7..8d0a322 100644 --- a/Tests/SwiftFrameTests/Utility/TextDataFixtures.swift +++ b/Tests/SwiftFrameTests/Utility/TextDataFixtures.swift @@ -5,7 +5,7 @@ import Foundation extension TextData { static let goodData = TextData( - titleIdentifier: "someId", + titleIdentifier: "someID", textAlignment: TextAlignment(horizontal: .center, vertical: .top), maxFontSizeOverride: nil, fontOverride: nil, @@ -17,7 +17,7 @@ extension TextData { ) static let invalidData = TextData( - titleIdentifier: "someId", + titleIdentifier: "someID", textAlignment: TextAlignment(horizontal: .center, vertical: .top), maxFontSizeOverride: nil, fontOverride: nil, @@ -29,7 +29,7 @@ extension TextData { ) static let invertedData = TextData( - titleIdentifier: "someId", + titleIdentifier: "someID", textAlignment: TextAlignment(horizontal: .center, vertical: .top), maxFontSizeOverride: nil, fontOverride: nil, diff --git a/Tests/SwiftFrameTests/Utility/TextGroupFixtures.swift b/Tests/SwiftFrameTests/Utility/TextGroupFixtures.swift index b93989e..0c04a67 100644 --- a/Tests/SwiftFrameTests/Utility/TextGroupFixtures.swift +++ b/Tests/SwiftFrameTests/Utility/TextGroupFixtures.swift @@ -4,6 +4,6 @@ import Foundation extension TextGroup { - static let goodData = TextGroup(identifier: "SomeID", maxFontSize: 200.00) + static let goodData = TextGroup(identifier: "someID", maxFontSize: 200.00) } From 3025001b3f6300ddacad1ab2c1989793ad20aa06 Mon Sep 17 00:00:00 2001 From: Henrik Panhans Date: Thu, 7 Mar 2024 12:50:56 +0100 Subject: [PATCH 09/25] A lot more cleanup Signed-off-by: Henrik Panhans --- .periphery.yml | 4 +++ Package.swift | 4 +-- Sources/SwiftFrame/Scaffold.swift | 2 +- .../SwiftFrameCore/Config/ConfigData.swift | 9 +++---- .../SwiftFrameCore/Config/DeviceData.swift | 13 +++++++++- .../Config/ScreenshotData.swift | 2 +- Sources/SwiftFrameCore/Config/TextData.swift | 7 ++--- .../Extensions/CGSize+Extensions.swift | 9 +++++++ .../Extensions/Collection+Extensions.swift | 6 ++--- .../NSAttributedString+Extensions.swift | 2 +- .../NSBitmapImageRep+Extensions.swift | 2 +- .../Helper Types/DecodableDefault.swift | 26 ------------------- .../Workers/CommandLineFormatter.swift | 2 +- .../Workers/ConfigProcessor.swift | 4 +-- .../Config Tests/TextDataTests.swift | 10 +++---- .../SwiftFrameTests/ImageComposerTests.swift | 4 +-- Tests/SwiftFrameTests/ImageLoaderTests.swift | 2 +- Tests/SwiftFrameTests/RegexMatchTests.swift | 6 ++--- .../Utility/DeviceDataFixtures.swift | 8 +++--- .../Utility/StringFilesContainer.swift | 1 - .../SwiftFrameTests/Utility/TestHelpers.swift | 9 ------- .../Utility/TextDataFixtures.swift | 16 ++---------- 22 files changed, 62 insertions(+), 86 deletions(-) create mode 100644 .periphery.yml create mode 100644 Sources/SwiftFrameCore/Extensions/CGSize+Extensions.swift diff --git a/.periphery.yml b/.periphery.yml new file mode 100644 index 0000000..17826b8 --- /dev/null +++ b/.periphery.yml @@ -0,0 +1,4 @@ +targets: +- SwiftFrame +- SwiftFrameCore +- SwiftFrameTests diff --git a/Package.swift b/Package.swift index 3bb2cc5..4fbce1d 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.4 +// swift-tools-version:5.8 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -6,7 +6,7 @@ import PackageDescription let package = Package( name: "SwiftFrame", platforms: [ - .macOS(.v10_15) + .macOS(.v13) ], products: [ .executable(name: "swiftframe", targets: ["SwiftFrame"]) diff --git a/Sources/SwiftFrame/Scaffold.swift b/Sources/SwiftFrame/Scaffold.swift index 97afc75..c16f2e3 100644 --- a/Sources/SwiftFrame/Scaffold.swift +++ b/Sources/SwiftFrame/Scaffold.swift @@ -87,7 +87,7 @@ struct Scaffold: ParsableCommand, VerbosePrintable { } private func makeScaffoldRootURL() -> URL { - if let path = path { + if let path { return URL(fileURLWithPath: path) } else { return URL(fileURLWithPath: FileManager.default.currentDirectoryPath) diff --git a/Sources/SwiftFrameCore/Config/ConfigData.swift b/Sources/SwiftFrameCore/Config/ConfigData.swift index bbf6433..abb602f 100644 --- a/Sources/SwiftFrameCore/Config/ConfigData.swift +++ b/Sources/SwiftFrameCore/Config/ConfigData.swift @@ -67,11 +67,10 @@ struct ConfigData: Decodable, ConfigValidateable { // MARK: - Processing mutating func process() throws { - let regex: NSRegularExpression? - if let pattern = localesRegex { - regex = try NSRegularExpression(pattern: pattern, options: .caseInsensitive) + let regex: Regex? = if let localesRegex, !localesRegex.isEmpty { + try Regex(localesRegex) } else { - regex = nil + nil } deviceData = try deviceData.map { try $0.makeProcessedData(localesRegex: regex) } @@ -101,7 +100,7 @@ struct ConfigData: Decodable, ConfigValidateable { func printSummary(insetByTabs tabs: Int) { ky_print("### Config Summary Start", insetByTabs: tabs) - CommandLineFormatter.printKeyValue("Title Color", value: textColorSource.hexString, insetBy: tabs) + CommandLineFormatter.printKeyValue("Title Color", value: textColorSource.hexString.uppercased(), insetBy: tabs) CommandLineFormatter.printKeyValue("Title Font", value: try? fontSource.font().fontName, insetBy: tabs) CommandLineFormatter.printKeyValue("Title Max Font Size", value: maxFontSize, insetBy: tabs) CommandLineFormatter.printKeyValue( diff --git a/Sources/SwiftFrameCore/Config/DeviceData.swift b/Sources/SwiftFrameCore/Config/DeviceData.swift index 3dbe7e1..0110563 100644 --- a/Sources/SwiftFrameCore/Config/DeviceData.swift +++ b/Sources/SwiftFrameCore/Config/DeviceData.swift @@ -57,7 +57,7 @@ struct DeviceData: Decodable, ConfigValidateable { // MARK: - Methods - func makeProcessedData(localesRegex: NSRegularExpression?) throws -> DeviceData { + func makeProcessedData(localesRegex: Regex?) throws -> DeviceData { guard let templateImage = ImageLoader.loadRepresentation(at: templateImagePath.absoluteURL) else { throw NSError(description: "Error while loading template image at path \(templateImagePath.absoluteString)") } @@ -132,11 +132,22 @@ struct DeviceData: Decodable, ConfigValidateable { CommandLineFormatter.printKeyValue("Template file path", value: templateImagePath.path, insetBy: tabs) CommandLineFormatter.printKeyValue("Number of slices", value: numberOfSlices, insetBy: tabs) CommandLineFormatter.printKeyValue("Gap Width", value: gapWidth, insetBy: tabs) + + if let templateImage { + let sliceSize = SliceSizeCalculator.calculateSliceSize( + templateImageSize: templateImage.ky_nativeSize, + numberOfSlices: numberOfSlices, + gapWidth: gapWidth + ) + CommandLineFormatter.printKeyValue("Output slice size", value: sliceSize.configValidationRepresentation, insetBy: tabs) + } + CommandLineFormatter.printKeyValue( "Screenshot folders", value: screenshotsGroupedByLocale.isEmpty ? "none" : screenshotsGroupedByLocale.keys.joined(separator: ", "), insetBy: tabs ) + screenshotData.forEach { $0.printSummary(insetByTabs: tabs) } textData.forEach { $0.printSummary(insetByTabs: tabs) } } diff --git a/Sources/SwiftFrameCore/Config/ScreenshotData.swift b/Sources/SwiftFrameCore/Config/ScreenshotData.swift index 52a075b..6ef4a34 100644 --- a/Sources/SwiftFrameCore/Config/ScreenshotData.swift +++ b/Sources/SwiftFrameCore/Config/ScreenshotData.swift @@ -45,6 +45,6 @@ struct ScreenshotData: Decodable, ConfigValidateable, Equatable { CommandLineFormatter.printKeyValue("Bottom Right", value: bottomRight, insetBy: tabs + 1) CommandLineFormatter.printKeyValue("Top Left", value: topLeft, insetBy: tabs + 1) CommandLineFormatter.printKeyValue("Top Right", value: topRight, insetBy: tabs + 1) - CommandLineFormatter.printKeyValue("Z Index", value: zIndex, insetBy: tabs + 1) + CommandLineFormatter.printKeyValue("Z-Index", value: zIndex, insetBy: tabs + 1) } } diff --git a/Sources/SwiftFrameCore/Config/TextData.swift b/Sources/SwiftFrameCore/Config/TextData.swift index e7b4192..153259a 100644 --- a/Sources/SwiftFrameCore/Config/TextData.swift +++ b/Sources/SwiftFrameCore/Config/TextData.swift @@ -87,7 +87,8 @@ struct TextData: Decodable, ConfigValidateable { throw NSError( description: "Bad text bounds for identifier \"\(titleIdentifier)\"", expectation: "Top Left coordinates should have smaller x coordinates and smaller y coordinates than bottom right", - actualValue: "Top Left: \(topLeft), Bottom Right: \(bottomRight)") + actualValue: "Top Left: \(topLeft), Bottom Right: \(bottomRight)" + ) } } @@ -104,8 +105,8 @@ struct TextData: Decodable, ConfigValidateable { CommandLineFormatter.printKeyValue("Max Point Size", value: ptSize, insetBy: tabs + 1) } - if let textColorOverride = textColorOverride { - CommandLineFormatter.printKeyValue("Custom color", value: textColorOverride.ky_hexString, insetBy: tabs + 1) + if let textColorOverride { + CommandLineFormatter.printKeyValue("Custom color", value: textColorOverride.ky_hexString.uppercased(), insetBy: tabs + 1) } } } diff --git a/Sources/SwiftFrameCore/Extensions/CGSize+Extensions.swift b/Sources/SwiftFrameCore/Extensions/CGSize+Extensions.swift new file mode 100644 index 0000000..f5ddd7b --- /dev/null +++ b/Sources/SwiftFrameCore/Extensions/CGSize+Extensions.swift @@ -0,0 +1,9 @@ +import Foundation + +extension CGSize { + + var configValidationRepresentation: String { + "Size(width: \(width), height: \(height))" + } + +} diff --git a/Sources/SwiftFrameCore/Extensions/Collection+Extensions.swift b/Sources/SwiftFrameCore/Extensions/Collection+Extensions.swift index 9cf8ca9..dedbea5 100644 --- a/Sources/SwiftFrameCore/Extensions/Collection+Extensions.swift +++ b/Sources/SwiftFrameCore/Extensions/Collection+Extensions.swift @@ -2,13 +2,13 @@ import Foundation extension Array where Element == URL { - func filterByFileOrFoldername(regex: NSRegularExpression?) throws -> Self { - guard let regex = regex else { + func filterByFileOrFoldername(regex: Regex?) throws -> Self { + guard let regex else { return self } return self.filter { url in let lastComponent = url.deletingPathExtension().lastPathComponent - return regex.matches(lastComponent) + return !lastComponent.matches(of: regex).isEmpty } } diff --git a/Sources/SwiftFrameCore/Extensions/NSAttributedString+Extensions.swift b/Sources/SwiftFrameCore/Extensions/NSAttributedString+Extensions.swift index 6818861..83a2a5b 100644 --- a/Sources/SwiftFrameCore/Extensions/NSAttributedString+Extensions.swift +++ b/Sources/SwiftFrameCore/Extensions/NSAttributedString+Extensions.swift @@ -13,7 +13,7 @@ extension NSMutableAttributedString { options: [.characterEncoding: String.Encoding.utf8.rawValue], documentAttributes: nil ) - if let string = string { + if let string { return string } else { throw NSError(description: "Could not parse HTML string") diff --git a/Sources/SwiftFrameCore/Extensions/NSBitmapImageRep+Extensions.swift b/Sources/SwiftFrameCore/Extensions/NSBitmapImageRep+Extensions.swift index fc70701..ce980f8 100644 --- a/Sources/SwiftFrameCore/Extensions/NSBitmapImageRep+Extensions.swift +++ b/Sources/SwiftFrameCore/Extensions/NSBitmapImageRep+Extensions.swift @@ -13,7 +13,7 @@ extension NSBitmapImageRep { } static func ky_loadFromURL(_ url: URL?) -> NSBitmapImageRep? { - guard let url = url else { + guard let url else { return nil } return ImageLoader.loadRepresentation(at: url) diff --git a/Sources/SwiftFrameCore/Helper Types/DecodableDefault.swift b/Sources/SwiftFrameCore/Helper Types/DecodableDefault.swift index c0ff381..9e6805b 100644 --- a/Sources/SwiftFrameCore/Helper Types/DecodableDefault.swift +++ b/Sources/SwiftFrameCore/Helper Types/DecodableDefault.swift @@ -45,37 +45,16 @@ extension DecodableDefault { typealias Source = DecodableDefaultSource typealias List = Decodable & ExpressibleByArrayLiteral - typealias Map = Decodable & ExpressibleByDictionaryLiteral enum Sources { - enum True: Source { - static var defaultValue: Bool { true } - } - - enum False: Source { - static var defaultValue: Bool { false } - } - - enum EmptyString: Source { - static var defaultValue: String { "" } - } - enum EmptyList: Source { static var defaultValue: T { [] } } - enum EmptyMap: Source { - static var defaultValue: T { [:] } - } - enum IntZero: Source { static var defaultValue: Swift.Int { 0 } } - enum DoubleZero: Source { - static var defaultValue: Swift.Double { 0.00 } - } - enum CGFloatZero: Source { static var defaultValue: CGFloat { 0.00 } } @@ -85,13 +64,8 @@ extension DecodableDefault { extension DecodableDefault { - typealias True = Wrapper - typealias False = Wrapper - typealias EmptyString = Wrapper typealias EmptyList = Wrapper> - typealias EmptyMap = Wrapper> typealias IntZero = Wrapper - typealias DoubleZero = Wrapper typealias CGFloatZero = Wrapper } diff --git a/Sources/SwiftFrameCore/Workers/CommandLineFormatter.swift b/Sources/SwiftFrameCore/Workers/CommandLineFormatter.swift index 2d9ff4e..c563694 100644 --- a/Sources/SwiftFrameCore/Workers/CommandLineFormatter.swift +++ b/Sources/SwiftFrameCore/Workers/CommandLineFormatter.swift @@ -45,7 +45,7 @@ final class CommandLineFormatter { // MARK: - Key-Value Formatting class func printKeyValue(_ key: String, value: Any?, insetBy tabs: Int = 0) { - guard let value = value else { + guard let value else { return } print(CommandLineFormatter.formatKeyValue(key, value: value, insetBy: tabs)) diff --git a/Sources/SwiftFrameCore/Workers/ConfigProcessor.swift b/Sources/SwiftFrameCore/Workers/ConfigProcessor.swift index 6300c33..f9d5f90 100644 --- a/Sources/SwiftFrameCore/Workers/ConfigProcessor.swift +++ b/Sources/SwiftFrameCore/Workers/ConfigProcessor.swift @@ -47,7 +47,7 @@ public class ConfigProcessor: VerbosePrintable { public func run() throws { if shouldValidateManually { data.printSummary(insetByTabs: 0) - print("Press return key to continue") + print("Press any key to continue") _ = readLine() } @@ -87,7 +87,7 @@ public class ConfigProcessor: VerbosePrintable { DispatchQueue.concurrentPerform(iterations: data.deviceData.count) { index in ky_executeOrExit(verbose: verbose) { [weak self] in - guard let `self` = self else { + guard let `self` else { throw NSError(description: "Could not reference weak self") } try self.process(deviceData: self.data.deviceData[index]) diff --git a/Tests/SwiftFrameTests/Config Tests/TextDataTests.swift b/Tests/SwiftFrameTests/Config Tests/TextDataTests.swift index 34e2c57..26df444 100644 --- a/Tests/SwiftFrameTests/Config Tests/TextDataTests.swift +++ b/Tests/SwiftFrameTests/Config Tests/TextDataTests.swift @@ -4,15 +4,15 @@ import XCTest class TextDataTests: BaseTestCase { - func testValidData() throws { + func testTextData_IsValid() throws { let size = CGSize(width: 200, height: 200) - let data = try TextData.goodData.makeProcessedData(size: size) + let processedData = try TextData.validData.makeProcessedData(size: size) - XCTAssertNoThrow(try data.validate()) + XCTAssertNoThrow(try processedData.validate()) } - func testInvalidData() throws { - XCTAssertThrowsError(try TextData.invalidData.validate()) + func testTextData_IsInvalid_WhenTextBoundsAreInvalid() throws { + XCTAssertThrowsError(try TextData.invalidTextBounds.validate()) } } diff --git a/Tests/SwiftFrameTests/ImageComposerTests.swift b/Tests/SwiftFrameTests/ImageComposerTests.swift index 4a81bd6..be3d7ad 100644 --- a/Tests/SwiftFrameTests/ImageComposerTests.swift +++ b/Tests/SwiftFrameTests/ImageComposerTests.swift @@ -10,7 +10,7 @@ class ImageComposerTests: XCTestCase { let composer = try ImageComposer(canvasSize: size) try composer.addTemplateImage(templateFile) - let image = try ky_unwrap(composer.context.cg.makeImage()) + let image = try XCTUnwrap(composer.context.cg.makeImage()) XCTAssertEqual(image.width, Int(size.width)) XCTAssertEqual(image.height, Int(size.height)) } @@ -21,7 +21,7 @@ class ImageComposerTests: XCTestCase { let composer = try ImageComposer(canvasSize: size) try composer.addTemplateImage(templateFile) - let image = try ky_unwrap(composer.context.cg.makeImage()) + let image = try XCTUnwrap(composer.context.cg.makeImage()) let slices = try ImageWriter.sliceImage(image, with: NSSize(width: 20, height: 50), gapWidth: 0) XCTAssertEqual(slices.count, 5) for slice in slices { diff --git a/Tests/SwiftFrameTests/ImageLoaderTests.swift b/Tests/SwiftFrameTests/ImageLoaderTests.swift index e158866..1f77c5a 100644 --- a/Tests/SwiftFrameTests/ImageLoaderTests.swift +++ b/Tests/SwiftFrameTests/ImageLoaderTests.swift @@ -7,7 +7,7 @@ class ImageLoaderTests: BaseTestCase { func testLoadImage() throws { let context = try GraphicsContext(size: .square100Pixels) let rep = context.cg.makePlainWhiteImageRep() - let cgImage = try ky_unwrap(rep.cgImage) + let cgImage = try XCTUnwrap(rep.cgImage) let url = URL(fileURLWithPath: "testing/en/en-testing_device.png") diff --git a/Tests/SwiftFrameTests/RegexMatchTests.swift b/Tests/SwiftFrameTests/RegexMatchTests.swift index 1d55447..49d93be 100644 --- a/Tests/SwiftFrameTests/RegexMatchTests.swift +++ b/Tests/SwiftFrameTests/RegexMatchTests.swift @@ -19,7 +19,7 @@ class RegexMatchTests: XCTestCase { } func testFranceFilteredOut() throws { - let regex = try NSRegularExpression(pattern: "^(?!fr$)\\w*$", options: .caseInsensitive) + let regex = try Regex("^(?!fr$)\\w*$") let urls = try RegexMatchTests.urls.filterByFileOrFoldername(regex: regex) guard ky_assertEqual(urls.count, 3) else { @@ -31,7 +31,7 @@ class RegexMatchTests: XCTestCase { } func testFranceAndRussiaFilteredOut() throws { - let regex = try NSRegularExpression(pattern: "^(?!fr|ru$)\\w*$", options: .caseInsensitive) + let regex = try Regex("^(?!fr|ru$)\\w*$") let urls = try RegexMatchTests.urls.filterByFileOrFoldername(regex: regex) guard ky_assertEqual(urls.count, 2) else { @@ -42,7 +42,7 @@ class RegexMatchTests: XCTestCase { } func testOnlyRussiaAndFrance() throws { - let regex = try NSRegularExpression(pattern: "ru|fr", options: .caseInsensitive) + let regex = try Regex("ru|fr") let urls = try RegexMatchTests.urls.filterByFileOrFoldername(regex: regex) guard ky_assertEqual(urls.count, 2) else { diff --git a/Tests/SwiftFrameTests/Utility/DeviceDataFixtures.swift b/Tests/SwiftFrameTests/Utility/DeviceDataFixtures.swift index a6b94cb..8be829e 100644 --- a/Tests/SwiftFrameTests/Utility/DeviceDataFixtures.swift +++ b/Tests/SwiftFrameTests/Utility/DeviceDataFixtures.swift @@ -10,7 +10,7 @@ extension DeviceData { screenshotsPath: FileURL(path: "testing/screenshots/"), numberOfSlices: 4, screenshotData: [.goodData], - textData: [.goodData], + textData: [.validData], gapWidth: gapWidth ) } @@ -21,7 +21,7 @@ extension DeviceData { screenshotsPath: FileURL(path: "testing/screenshots/"), numberOfSlices: 4, screenshotData: [.goodData], - textData: [.invalidData] + textData: [.invalidTextBounds] ) static let invalidNumberOfSlices = DeviceData( @@ -30,7 +30,7 @@ extension DeviceData { screenshotsPath: FileURL(path: "testing/screenshots/"), numberOfSlices: 0, screenshotData: [.goodData], - textData: [.goodData] + textData: [.validData] ) static let invalidGapWidth = DeviceData( @@ -39,7 +39,7 @@ extension DeviceData { screenshotsPath: FileURL(path: "testing/screenshots/"), numberOfSlices: 5, screenshotData: [.goodData], - textData: [.goodData], + textData: [.validData], gapWidth: -10 ) diff --git a/Tests/SwiftFrameTests/Utility/StringFilesContainer.swift b/Tests/SwiftFrameTests/Utility/StringFilesContainer.swift index 0c5d9bf..909d9de 100644 --- a/Tests/SwiftFrameTests/Utility/StringFilesContainer.swift +++ b/Tests/SwiftFrameTests/Utility/StringFilesContainer.swift @@ -3,6 +3,5 @@ import Foundation struct StringFilesContainer { static let goodData = ["\"someID\"": "\"Some interesting title\""] - static let wrongKeyData = ["\"someIdentifier\"": "\"Some interesting title\""] } diff --git a/Tests/SwiftFrameTests/Utility/TestHelpers.swift b/Tests/SwiftFrameTests/Utility/TestHelpers.swift index 2c0eb29..a68adf6 100644 --- a/Tests/SwiftFrameTests/Utility/TestHelpers.swift +++ b/Tests/SwiftFrameTests/Utility/TestHelpers.swift @@ -40,12 +40,3 @@ extension CGContext { } } - -/// Since `XCTUnwrap` is currently unavailable when calling `swift test` from the command line, we use a custom wrapper -/// See https://bugs.swift.org/browse/SR-11501 -func ky_unwrap(_ value: T?) throws -> T { - guard let value = value else { - throw NSError(description: "Value of type \(T.self) was nil") - } - return value -} diff --git a/Tests/SwiftFrameTests/Utility/TextDataFixtures.swift b/Tests/SwiftFrameTests/Utility/TextDataFixtures.swift index 8d0a322..abb94aa 100644 --- a/Tests/SwiftFrameTests/Utility/TextDataFixtures.swift +++ b/Tests/SwiftFrameTests/Utility/TextDataFixtures.swift @@ -4,7 +4,7 @@ import Foundation extension TextData { - static let goodData = TextData( + static let validData = TextData( titleIdentifier: "someID", textAlignment: TextAlignment(horizontal: .center, vertical: .top), maxFontSizeOverride: nil, @@ -16,7 +16,7 @@ extension TextData { textColorOverride: nil ) - static let invalidData = TextData( + static let invalidTextBounds = TextData( titleIdentifier: "someID", textAlignment: TextAlignment(horizontal: .center, vertical: .top), maxFontSizeOverride: nil, @@ -28,16 +28,4 @@ extension TextData { textColorOverride: nil ) - static let invertedData = TextData( - titleIdentifier: "someID", - textAlignment: TextAlignment(horizontal: .center, vertical: .top), - maxFontSizeOverride: nil, - fontOverride: nil, - textColorOverrideString: nil, - groupIdentifier: nil, - topLeft: Point(x: 10, y: 20), - bottomRight: Point(x: 15, y: 5), - textColorOverride: nil - ) - } From c6c0475b8f3dbe87fcf69d7f72f91205d10b819d Mon Sep 17 00:00:00 2001 From: Henrik Panhans Date: Thu, 7 Mar 2024 12:52:22 +0100 Subject: [PATCH 10/25] Remove unused method Signed-off-by: Henrik Panhans --- .../Extensions/Collection+Extensions.swift | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/Sources/SwiftFrameCore/Extensions/Collection+Extensions.swift b/Sources/SwiftFrameCore/Extensions/Collection+Extensions.swift index dedbea5..ad27e50 100644 --- a/Sources/SwiftFrameCore/Extensions/Collection+Extensions.swift +++ b/Sources/SwiftFrameCore/Extensions/Collection+Extensions.swift @@ -1,6 +1,6 @@ import Foundation -extension Array where Element == URL { +extension [URL] { func filterByFileOrFoldername(regex: Regex?) throws -> Self { guard let regex else { @@ -13,12 +13,3 @@ extension Array where Element == URL { } } - -extension NSRegularExpression { - - func matches(_ string: String) -> Bool { - let range = NSRange(location: 0, length: (string as NSString).length) - return firstMatch(in: string, options: [], range: range) != nil - } - -} From 22bd938be7f1f8fcf1f8c08c150439013a7acd75 Mon Sep 17 00:00:00 2001 From: Henrik Panhans Date: Thu, 7 Mar 2024 12:53:23 +0100 Subject: [PATCH 11/25] Update example config files Signed-off-by: Henrik Panhans --- Example/example.config | 2 ++ Example/example.yaml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Example/example.config b/Example/example.config index b18d02d..47af0e1 100644 --- a/Example/example.config +++ b/Example/example.config @@ -12,6 +12,7 @@ "outputSuffixes": ["iPhone X 5.8 inches"], "screenshots": "Example/Screenshots/iPhone X/", "templateFile": "Example/Template Files/iPhone X TemplateFile.png", + "numberOfSlices": 5, "screenshotData": [ { "screenshotName": "launchscreen.png", @@ -91,6 +92,7 @@ "outputSuffixes": ["ipadPro", "ipadPro129"], "screenshots": "Example/Screenshots/iPad Pro/", "templateFile": "Example/Template Files/iPad Pro TemplateFile.png", + "numberOfSlices": 4, "screenshotData": [ { "screenshotName": "launchscreen.png", diff --git a/Example/example.yaml b/Example/example.yaml index 33ac3b6..f96fe0e 100644 --- a/Example/example.yaml +++ b/Example/example.yaml @@ -11,6 +11,7 @@ deviceData: - iPhone X 5.8 inches screenshots: Example/Screenshots/iPhone X/ templateFile: Example/Template Files/iPhone X TemplateFile.png + numberOfSlices: 4 screenshotData: - screenshotName: launchscreen.png zIndex: 12 @@ -66,6 +67,7 @@ deviceData: - iPad Pro 12.9 inch screenshots: Example/Screenshots/iPad Pro/ templateFile: Example/Template Files/iPad Pro TemplateFile.png + numberOfSlices: 5 screenshotData: - screenshotName: launchscreen.png zIndex: 12 From ef848b49ed3897a4fa80e6cb13fa5f121061b79a Mon Sep 17 00:00:00 2001 From: Henrik Panhans Date: Thu, 7 Mar 2024 12:53:35 +0100 Subject: [PATCH 12/25] Use safer shebang in install script Signed-off-by: Henrik Panhans --- install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.sh b/install.sh index 522e2bb..505cef3 100755 --- a/install.sh +++ b/install.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e From 836c2e618dc62b659df22729fe8fe24c4f9a5f30 Mon Sep 17 00:00:00 2001 From: Henrik Panhans Date: Thu, 7 Mar 2024 12:58:39 +0100 Subject: [PATCH 13/25] Make benchmark script independent of external libs Signed-off-by: Henrik Panhans --- benchmark.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/benchmark.py b/benchmark.py index 5d8b560..873ee47 100755 --- a/benchmark.py +++ b/benchmark.py @@ -4,7 +4,7 @@ import subprocess import sys import time -from distutils.dir_util import copy_tree +from shutil import copytree from os import path from pathlib import Path from shutil import copy2, rmtree @@ -34,7 +34,7 @@ def exit_handler(): # Copy template files TEMPLATE_FILES_FOLDER = "Example/Template Files/" -copy_tree(TEMPLATE_FILES_FOLDER, "Benchmark/Template Files/") +copytree(TEMPLATE_FILES_FOLDER, "Benchmark/Template Files/") # Copying over screenshots and titles for locale in LOCALES: @@ -42,7 +42,7 @@ def exit_handler(): for device in ["iPhone X", "iPad Pro"]: screenshot_source_folder = f"Example/Screenshots/{device}/en" screenshot_target_folder = f"Benchmark/Screenshots/{device}/{locale}" - copy_tree(screenshot_source_folder, screenshot_target_folder) + copytree(screenshot_source_folder, screenshot_target_folder) STRING_SOURCE_FILE = "Example/Strings/en.strings" STRING_DESTINATION_FILE = f"Benchmark/Strings/{locale}.strings" @@ -56,7 +56,7 @@ def exit_handler(): compile_process = subprocess.run("swift build -c release", shell=True, check=True) if compile_process.returncode != 0: - exit(compile_process.returncode) + sys.exit(compile_process.returncode) # Running benchmark benchmark_start = time.time() From a225d45070cbc48cca74df46a7f138d777b5cd5e Mon Sep 17 00:00:00 2001 From: Henrik Panhans Date: Thu, 7 Mar 2024 12:58:52 +0100 Subject: [PATCH 14/25] Remove accidentaly committed periphery file Signed-off-by: Henrik Panhans --- .periphery.yml | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 .periphery.yml diff --git a/.periphery.yml b/.periphery.yml deleted file mode 100644 index 17826b8..0000000 --- a/.periphery.yml +++ /dev/null @@ -1,4 +0,0 @@ -targets: -- SwiftFrame -- SwiftFrameCore -- SwiftFrameTests From c0ee0faa5e6bd0580810800f902237a503ac047c Mon Sep 17 00:00:00 2001 From: Henrik Panhans Date: Thu, 7 Mar 2024 13:03:02 +0100 Subject: [PATCH 15/25] Fix GH Actions --- .github/workflows/swift.yml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index 1e08389..fe52f37 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -7,8 +7,13 @@ jobs: runs-on: macos-latest steps: - - uses: actions/checkout@v2 - - name: Build - run: swift build -v - - name: Run tests - run: swift test -v \ No newline at end of file + - name: Checkout repository + uses: actions/checkout@v2 + - name: Configure Swift version + uses: swift-actions/setup-swift@v1 + with: + swift-version: "5.8" + - name: Build + run: swift build -v + - name: Run tests + run: swift test -v From 683498b106bdd3c1b11ab898a83260d1d46c87b3 Mon Sep 17 00:00:00 2001 From: Henrik Panhans Date: Thu, 7 Mar 2024 13:07:12 +0100 Subject: [PATCH 16/25] Upgrade to Swift 5.9 Signed-off-by: Henrik Panhans --- .github/workflows/swift.yml | 2 +- Package.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index fe52f37..1526f3d 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -12,7 +12,7 @@ jobs: - name: Configure Swift version uses: swift-actions/setup-swift@v1 with: - swift-version: "5.8" + swift-version: "5.9" - name: Build run: swift build -v - name: Run tests diff --git a/Package.swift b/Package.swift index 4fbce1d..26f7c75 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.8 +// swift-tools-version:5.9 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription From 5ba29bfd6a8d9a002cbab3aa4bbd863d5e91082b Mon Sep 17 00:00:00 2001 From: Henrik Panhans Date: Thu, 7 Mar 2024 14:16:43 +0100 Subject: [PATCH 17/25] Set Xcode version instead of Swift version Signed-off-by: Henrik Panhans --- .github/workflows/swift.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index 1526f3d..e8e3728 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -9,10 +9,10 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v2 - - name: Configure Swift version - uses: swift-actions/setup-swift@v1 + - name: Configure Xcode version + uses: maxim-lobanov/setup-xcode@v1 with: - swift-version: "5.9" + xcode-version: latest-stable - name: Build run: swift build -v - name: Run tests From 1262765d78dcfc02caa58ac1d2407587dfa1821a Mon Sep 17 00:00:00 2001 From: Henrik Panhans Date: Thu, 7 Mar 2024 14:19:41 +0100 Subject: [PATCH 18/25] Use specif Xcode version Signed-off-by: Henrik Panhans --- .github/workflows/swift.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index e8e3728..27783ed 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -12,7 +12,7 @@ jobs: - name: Configure Xcode version uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: latest-stable + xcode-version: "15.2" - name: Build run: swift build -v - name: Run tests From 0f722b4bb40ae1de2f41cc75f392c23af7ffc17d Mon Sep 17 00:00:00 2001 From: Henrik Panhans Date: Thu, 7 Mar 2024 14:29:33 +0100 Subject: [PATCH 19/25] Maybe using Swift 5.7 it works Signed-off-by: Henrik Panhans --- .github/workflows/swift.yml | 2 +- Package.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index 27783ed..e8e3728 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -12,7 +12,7 @@ jobs: - name: Configure Xcode version uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: "15.2" + xcode-version: latest-stable - name: Build run: swift build -v - name: Run tests diff --git a/Package.swift b/Package.swift index 26f7c75..addda0d 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.9 +// swift-tools-version:5.7 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription From 017ab5b60cbaf205abd99bdca79c8100d13e9c1d Mon Sep 17 00:00:00 2001 From: Henrik Panhans Date: Thu, 7 Mar 2024 14:32:38 +0100 Subject: [PATCH 20/25] Fix Swift 5.7 incompatible syntax Signed-off-by: Henrik Panhans --- Sources/SwiftFrameCore/Config/ConfigData.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftFrameCore/Config/ConfigData.swift b/Sources/SwiftFrameCore/Config/ConfigData.swift index abb602f..b429de2 100644 --- a/Sources/SwiftFrameCore/Config/ConfigData.swift +++ b/Sources/SwiftFrameCore/Config/ConfigData.swift @@ -67,10 +67,11 @@ struct ConfigData: Decodable, ConfigValidateable { // MARK: - Processing mutating func process() throws { - let regex: Regex? = if let localesRegex, !localesRegex.isEmpty { - try Regex(localesRegex) + let regex: Regex? + if let localesRegex, !localesRegex.isEmpty { + regex = try Regex(localesRegex) } else { - nil + regex = nil } deviceData = try deviceData.map { try $0.makeProcessedData(localesRegex: regex) } From 052763b618b4b880cafd393eeb6e0b72e8915f11 Mon Sep 17 00:00:00 2001 From: Henrik Panhans Date: Thu, 7 Mar 2024 14:41:08 +0100 Subject: [PATCH 21/25] Revert Regex changes to hopefully fix GH Actions Signed-off-by: Henrik Panhans --- Sources/SwiftFrameCore/Config/ConfigData.swift | 4 ++-- Sources/SwiftFrameCore/Config/DeviceData.swift | 2 +- .../Extensions/Collection+Extensions.swift | 15 ++++++++++++--- Tests/SwiftFrameTests/RegexMatchTests.swift | 6 +++--- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/Sources/SwiftFrameCore/Config/ConfigData.swift b/Sources/SwiftFrameCore/Config/ConfigData.swift index b429de2..13b2608 100644 --- a/Sources/SwiftFrameCore/Config/ConfigData.swift +++ b/Sources/SwiftFrameCore/Config/ConfigData.swift @@ -67,9 +67,9 @@ struct ConfigData: Decodable, ConfigValidateable { // MARK: - Processing mutating func process() throws { - let regex: Regex? + let regex: NSRegularExpression? if let localesRegex, !localesRegex.isEmpty { - regex = try Regex(localesRegex) + regex = try NSRegularExpression(pattern: localesRegex) } else { regex = nil } diff --git a/Sources/SwiftFrameCore/Config/DeviceData.swift b/Sources/SwiftFrameCore/Config/DeviceData.swift index 0110563..a54173d 100644 --- a/Sources/SwiftFrameCore/Config/DeviceData.swift +++ b/Sources/SwiftFrameCore/Config/DeviceData.swift @@ -57,7 +57,7 @@ struct DeviceData: Decodable, ConfigValidateable { // MARK: - Methods - func makeProcessedData(localesRegex: Regex?) throws -> DeviceData { + func makeProcessedData(localesRegex: NSRegularExpression?) throws -> DeviceData { guard let templateImage = ImageLoader.loadRepresentation(at: templateImagePath.absoluteURL) else { throw NSError(description: "Error while loading template image at path \(templateImagePath.absoluteString)") } diff --git a/Sources/SwiftFrameCore/Extensions/Collection+Extensions.swift b/Sources/SwiftFrameCore/Extensions/Collection+Extensions.swift index ad27e50..d33acd2 100644 --- a/Sources/SwiftFrameCore/Extensions/Collection+Extensions.swift +++ b/Sources/SwiftFrameCore/Extensions/Collection+Extensions.swift @@ -1,15 +1,24 @@ import Foundation -extension [URL] { +extension Array where Element == URL { - func filterByFileOrFoldername(regex: Regex?) throws -> Self { + func filterByFileOrFoldername(regex: NSRegularExpression?) throws -> Self { guard let regex else { return self } return self.filter { url in let lastComponent = url.deletingPathExtension().lastPathComponent - return !lastComponent.matches(of: regex).isEmpty + return regex.matches(lastComponent) } } } + +extension NSRegularExpression { + + func matches(_ string: String) -> Bool { + let range = NSRange(location: 0, length: (string as NSString).length) + return firstMatch(in: string, options: [], range: range) != nil + } + +} diff --git a/Tests/SwiftFrameTests/RegexMatchTests.swift b/Tests/SwiftFrameTests/RegexMatchTests.swift index 49d93be..1d55447 100644 --- a/Tests/SwiftFrameTests/RegexMatchTests.swift +++ b/Tests/SwiftFrameTests/RegexMatchTests.swift @@ -19,7 +19,7 @@ class RegexMatchTests: XCTestCase { } func testFranceFilteredOut() throws { - let regex = try Regex("^(?!fr$)\\w*$") + let regex = try NSRegularExpression(pattern: "^(?!fr$)\\w*$", options: .caseInsensitive) let urls = try RegexMatchTests.urls.filterByFileOrFoldername(regex: regex) guard ky_assertEqual(urls.count, 3) else { @@ -31,7 +31,7 @@ class RegexMatchTests: XCTestCase { } func testFranceAndRussiaFilteredOut() throws { - let regex = try Regex("^(?!fr|ru$)\\w*$") + let regex = try NSRegularExpression(pattern: "^(?!fr|ru$)\\w*$", options: .caseInsensitive) let urls = try RegexMatchTests.urls.filterByFileOrFoldername(regex: regex) guard ky_assertEqual(urls.count, 2) else { @@ -42,7 +42,7 @@ class RegexMatchTests: XCTestCase { } func testOnlyRussiaAndFrance() throws { - let regex = try Regex("ru|fr") + let regex = try NSRegularExpression(pattern: "ru|fr", options: .caseInsensitive) let urls = try RegexMatchTests.urls.filterByFileOrFoldername(regex: regex) guard ky_assertEqual(urls.count, 2) else { From e4f193bdf314f025d982e4e8bca13dfb7fed2cf5 Mon Sep 17 00:00:00 2001 From: Henrik Panhans Date: Mon, 11 Mar 2024 10:26:32 +0100 Subject: [PATCH 22/25] Use newest checkout version and also pin Xcode version --- .github/workflows/swift.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index e8e3728..7f37e3f 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -4,15 +4,15 @@ on: [push] jobs: build: - runs-on: macos-latest + runs-on: macos-14 steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Configure Xcode version uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: latest-stable + xcode-version: "15.2" - name: Build run: swift build -v - name: Run tests From 8c2949e2a0a9160e910b839bc518c6d1697a6f70 Mon Sep 17 00:00:00 2001 From: Henrik Panhans Date: Mon, 11 Mar 2024 10:37:50 +0100 Subject: [PATCH 23/25] Reapply all the changes made to satisfy GH Actions This reverts commit 052763b618b4b880cafd393eeb6e0b72e8915f11. Signed-off-by: Henrik Panhans --- Package.swift | 2 +- .../SwiftFrameCore/Config/ConfigData.swift | 7 ++- .../SwiftFrameCore/Config/DeviceData.swift | 2 +- .../Extensions/Collection+Extensions.swift | 15 ++---- Tests/SwiftFrameTests/RegexMatchTests.swift | 50 +++++++++---------- 5 files changed, 32 insertions(+), 44 deletions(-) diff --git a/Package.swift b/Package.swift index addda0d..26f7c75 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.7 +// swift-tools-version:5.9 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription diff --git a/Sources/SwiftFrameCore/Config/ConfigData.swift b/Sources/SwiftFrameCore/Config/ConfigData.swift index 13b2608..abb602f 100644 --- a/Sources/SwiftFrameCore/Config/ConfigData.swift +++ b/Sources/SwiftFrameCore/Config/ConfigData.swift @@ -67,11 +67,10 @@ struct ConfigData: Decodable, ConfigValidateable { // MARK: - Processing mutating func process() throws { - let regex: NSRegularExpression? - if let localesRegex, !localesRegex.isEmpty { - regex = try NSRegularExpression(pattern: localesRegex) + let regex: Regex? = if let localesRegex, !localesRegex.isEmpty { + try Regex(localesRegex) } else { - regex = nil + nil } deviceData = try deviceData.map { try $0.makeProcessedData(localesRegex: regex) } diff --git a/Sources/SwiftFrameCore/Config/DeviceData.swift b/Sources/SwiftFrameCore/Config/DeviceData.swift index a54173d..0110563 100644 --- a/Sources/SwiftFrameCore/Config/DeviceData.swift +++ b/Sources/SwiftFrameCore/Config/DeviceData.swift @@ -57,7 +57,7 @@ struct DeviceData: Decodable, ConfigValidateable { // MARK: - Methods - func makeProcessedData(localesRegex: NSRegularExpression?) throws -> DeviceData { + func makeProcessedData(localesRegex: Regex?) throws -> DeviceData { guard let templateImage = ImageLoader.loadRepresentation(at: templateImagePath.absoluteURL) else { throw NSError(description: "Error while loading template image at path \(templateImagePath.absoluteString)") } diff --git a/Sources/SwiftFrameCore/Extensions/Collection+Extensions.swift b/Sources/SwiftFrameCore/Extensions/Collection+Extensions.swift index d33acd2..ad27e50 100644 --- a/Sources/SwiftFrameCore/Extensions/Collection+Extensions.swift +++ b/Sources/SwiftFrameCore/Extensions/Collection+Extensions.swift @@ -1,24 +1,15 @@ import Foundation -extension Array where Element == URL { +extension [URL] { - func filterByFileOrFoldername(regex: NSRegularExpression?) throws -> Self { + func filterByFileOrFoldername(regex: Regex?) throws -> Self { guard let regex else { return self } return self.filter { url in let lastComponent = url.deletingPathExtension().lastPathComponent - return regex.matches(lastComponent) + return !lastComponent.matches(of: regex).isEmpty } } } - -extension NSRegularExpression { - - func matches(_ string: String) -> Bool { - let range = NSRange(location: 0, length: (string as NSString).length) - return firstMatch(in: string, options: [], range: range) != nil - } - -} diff --git a/Tests/SwiftFrameTests/RegexMatchTests.swift b/Tests/SwiftFrameTests/RegexMatchTests.swift index 1d55447..11fab52 100644 --- a/Tests/SwiftFrameTests/RegexMatchTests.swift +++ b/Tests/SwiftFrameTests/RegexMatchTests.swift @@ -4,52 +4,50 @@ import XCTest class RegexMatchTests: XCTestCase { - static let urls: [URL] = { - [ - URL(fileURLWithPath: "strings/en.strings"), - URL(fileURLWithPath: "strings/de.strings"), - URL(fileURLWithPath: "strings/fr.strings"), - URL(fileURLWithPath: "strings/ru.strings") - ] - }() + private let urls: [URL] = [ + URL(fileURLWithPath: "strings/en.strings"), + URL(fileURLWithPath: "strings/de.strings"), + URL(fileURLWithPath: "strings/fr.strings"), + URL(fileURLWithPath: "strings/ru.strings") + ] func testAllURLs() throws { - let urls = try RegexMatchTests.urls.filterByFileOrFoldername(regex: nil) - XCTAssertEqual(urls, RegexMatchTests.urls) + let filteredURLs = try urls.filterByFileOrFoldername(regex: nil) + XCTAssertEqual(filteredURLs, urls) } func testFranceFilteredOut() throws { - let regex = try NSRegularExpression(pattern: "^(?!fr$)\\w*$", options: .caseInsensitive) - let urls = try RegexMatchTests.urls.filterByFileOrFoldername(regex: regex) + let regex = try Regex("^(?!fr$)\\w*$") + let filteredURLs = try urls.filterByFileOrFoldername(regex: regex) - guard ky_assertEqual(urls.count, 3) else { + guard ky_assertEqual(filteredURLs.count, 3) else { return } - XCTAssertTrue(urls[0].absoluteString.hasSuffix("en.strings")) - XCTAssertTrue(urls[1].absoluteString.hasSuffix("de.strings")) - XCTAssertTrue(urls[2].absoluteString.hasSuffix("ru.strings")) + XCTAssertTrue(filteredURLs[0].absoluteString.hasSuffix("en.strings")) + XCTAssertTrue(filteredURLs[1].absoluteString.hasSuffix("de.strings")) + XCTAssertTrue(filteredURLs[2].absoluteString.hasSuffix("ru.strings")) } func testFranceAndRussiaFilteredOut() throws { - let regex = try NSRegularExpression(pattern: "^(?!fr|ru$)\\w*$", options: .caseInsensitive) - let urls = try RegexMatchTests.urls.filterByFileOrFoldername(regex: regex) + let regex = try Regex("^(?!fr|ru$)\\w*$") + let filteredURLs = try urls.filterByFileOrFoldername(regex: regex) - guard ky_assertEqual(urls.count, 2) else { + guard ky_assertEqual(filteredURLs.count, 2) else { return } - XCTAssertTrue(urls[0].absoluteString.hasSuffix("en.strings")) - XCTAssertTrue(urls[1].absoluteString.hasSuffix("de.strings")) + XCTAssertTrue(filteredURLs[0].absoluteString.hasSuffix("en.strings")) + XCTAssertTrue(filteredURLs[1].absoluteString.hasSuffix("de.strings")) } func testOnlyRussiaAndFrance() throws { - let regex = try NSRegularExpression(pattern: "ru|fr", options: .caseInsensitive) - let urls = try RegexMatchTests.urls.filterByFileOrFoldername(regex: regex) + let regex = try Regex("ru|fr") + let filteredURLs = try urls.filterByFileOrFoldername(regex: regex) - guard ky_assertEqual(urls.count, 2) else { + guard ky_assertEqual(filteredURLs.count, 2) else { return } - XCTAssertEqual(urls[0].lastPathComponent, "fr.strings") - XCTAssertEqual(urls[1].lastPathComponent, "ru.strings") + XCTAssertEqual(filteredURLs[0].lastPathComponent, "fr.strings") + XCTAssertEqual(filteredURLs[1].lastPathComponent, "ru.strings") } } From b6c1749f9b18fa184a00141e52b950afebb36dbe Mon Sep 17 00:00:00 2001 From: Henrik Panhans Date: Mon, 11 Mar 2024 11:29:27 +0100 Subject: [PATCH 24/25] Even safer validation of slice size Negative sizes are not possible anymore Signed-off-by: Henrik Panhans --- .../SwiftFrameCore/Config/DeviceData.swift | 12 ++++++++-- .../Extensions/NSError+Extensions.swift | 3 ++- .../Helper Types/Pluralizer.swift | 17 ++++++++++++++ .../Workers/ConfigProcessor.swift | 8 ++----- .../Workers/SliceSizeCalculator.swift | 15 +++++++++++- Tests/SwiftFrameTests/PluralizerTests.swift | 23 +++++++++++++++++++ .../SliceSizeCalculatorTests.swift | 20 ++++++++++++---- 7 files changed, 84 insertions(+), 14 deletions(-) create mode 100644 Sources/SwiftFrameCore/Helper Types/Pluralizer.swift create mode 100644 Tests/SwiftFrameTests/PluralizerTests.swift diff --git a/Sources/SwiftFrameCore/Config/DeviceData.swift b/Sources/SwiftFrameCore/Config/DeviceData.swift index 0110563..6b6f609 100644 --- a/Sources/SwiftFrameCore/Config/DeviceData.swift +++ b/Sources/SwiftFrameCore/Config/DeviceData.swift @@ -114,6 +114,14 @@ struct DeviceData: Decodable, ConfigValidateable { ) } + if let templateImage { + _ = try SliceSizeCalculator.calculateSliceSize( + templateImageSize: templateImage.ky_nativeSize, + numberOfSlices: numberOfSlices, + gapWidth: gapWidth + ) + } + try screenshotData.forEach { try $0.validate() } try textData.forEach { try $0.validate() } @@ -134,12 +142,12 @@ struct DeviceData: Decodable, ConfigValidateable { CommandLineFormatter.printKeyValue("Gap Width", value: gapWidth, insetBy: tabs) if let templateImage { - let sliceSize = SliceSizeCalculator.calculateSliceSize( + let sliceSize = try? SliceSizeCalculator.calculateSliceSize( templateImageSize: templateImage.ky_nativeSize, numberOfSlices: numberOfSlices, gapWidth: gapWidth ) - CommandLineFormatter.printKeyValue("Output slice size", value: sliceSize.configValidationRepresentation, insetBy: tabs) + CommandLineFormatter.printKeyValue("Output slice size", value: sliceSize?.configValidationRepresentation, insetBy: tabs) } CommandLineFormatter.printKeyValue( diff --git a/Sources/SwiftFrameCore/Extensions/NSError+Extensions.swift b/Sources/SwiftFrameCore/Extensions/NSError+Extensions.swift index d3d95b9..425a49d 100644 --- a/Sources/SwiftFrameCore/Extensions/NSError+Extensions.swift +++ b/Sources/SwiftFrameCore/Extensions/NSError+Extensions.swift @@ -13,7 +13,8 @@ public extension NSError { NSLocalizedDescriptionKey: description, NSError.kExpectationKey: expectation as Any, NSError.kActualValueKey: actualValue as Any - ]) + ] + ) } var expectation: String? { diff --git a/Sources/SwiftFrameCore/Helper Types/Pluralizer.swift b/Sources/SwiftFrameCore/Helper Types/Pluralizer.swift new file mode 100644 index 0000000..e702b0c --- /dev/null +++ b/Sources/SwiftFrameCore/Helper Types/Pluralizer.swift @@ -0,0 +1,17 @@ +import Foundation + +enum Pluralizer { + + static func pluralize(_ value: Int, singular: String, plural: String, zero: String? = nil) -> String { + let absoluteValue = abs(value) + return switch absoluteValue { + case 0: + "\(value) \(zero ?? plural)" + case 1: + "\(value) \(singular)" + default: + "\(value) \(plural)" + } + } + +} diff --git a/Sources/SwiftFrameCore/Workers/ConfigProcessor.swift b/Sources/SwiftFrameCore/Workers/ConfigProcessor.swift index f9d5f90..766e1ae 100644 --- a/Sources/SwiftFrameCore/Workers/ConfigProcessor.swift +++ b/Sources/SwiftFrameCore/Workers/ConfigProcessor.swift @@ -36,12 +36,8 @@ public class ConfigProcessor: VerbosePrintable { // MARK: - Methods public func validate() throws { - try process() - try data.validate() - } - - private func process() throws { try data.process() + try data.validate() } public func run() throws { @@ -109,7 +105,7 @@ public class ConfigProcessor: VerbosePrintable { throw NSError(description: "No template image found") } - let sliceSize = SliceSizeCalculator.calculateSliceSize( + let sliceSize = try SliceSizeCalculator.calculateSliceSize( templateImageSize: templateImage.ky_nativeSize, numberOfSlices: deviceData.numberOfSlices, gapWidth: deviceData.gapWidth diff --git a/Sources/SwiftFrameCore/Workers/SliceSizeCalculator.swift b/Sources/SwiftFrameCore/Workers/SliceSizeCalculator.swift index 5b9753f..aa28d72 100644 --- a/Sources/SwiftFrameCore/Workers/SliceSizeCalculator.swift +++ b/Sources/SwiftFrameCore/Workers/SliceSizeCalculator.swift @@ -2,10 +2,23 @@ import Foundation struct SliceSizeCalculator { - static func calculateSliceSize(templateImageSize: CGSize, numberOfSlices: Int, gapWidth: Int?) -> CGSize { + static func calculateSliceSize(templateImageSize: CGSize, numberOfSlices: Int, gapWidth: Int?) throws -> CGSize { + guard numberOfSlices > 0 else { + throw NSError(description: "Number of slices must be larger than 0") + } // number of slices minus 1 because gaps are only in between, multiplied by gapWidth let totalGapWidthIfAny = gapWidth.flatMap { (numberOfSlices - 1) * $0 } let templateWidthSubstractingGaps = templateImageSize.width - CGFloat(totalGapWidthIfAny ?? 0) + + guard Int(templateWidthSubstractingGaps) >= numberOfSlices else { + let minimumTemplateWidth = numberOfSlices + (totalGapWidthIfAny ?? 0) + throw NSError( + description: "Template image is not wide enough to accommodate \(Pluralizer.pluralize(numberOfSlices, singular: "slice", plural: "slices"))", + expectation: "Template image should be at least \(minimumTemplateWidth) pixels wide", + actualValue: "Template image is \(templateImageSize.width) pixels wide" + ) + } + // Resulting slice is remaining width divided by expected number of slices, height can just be forwarded return CGSize(width: templateWidthSubstractingGaps / CGFloat(numberOfSlices), height: templateImageSize.height) } diff --git a/Tests/SwiftFrameTests/PluralizerTests.swift b/Tests/SwiftFrameTests/PluralizerTests.swift new file mode 100644 index 0000000..b03fb52 --- /dev/null +++ b/Tests/SwiftFrameTests/PluralizerTests.swift @@ -0,0 +1,23 @@ +import Foundation +import XCTest +@testable import SwiftFrameCore + +class PluralizerTests: XCTestCase { + + func testPluralizer_ProducesSingularString_WhenSpecifyingOne() { + XCTAssertEqual(Pluralizer.pluralize(1, singular: "slice", plural: "slices"), "1 slice") + } + + func testPluralizer_ProducesPluralString_WhenSpecifyingBigNumber() { + XCTAssertEqual(Pluralizer.pluralize(32, singular: "slice", plural: "slices"), "32 slices") + } + + func testPluralizer_ProducesPluralString_WhenSpecifyingZero() { + XCTAssertEqual(Pluralizer.pluralize(0, singular: "slice", plural: "slices"), "0 slices") + } + + func testPluralizer_ProducesZeroString_WhenSpecifyingZero() { + XCTAssertEqual(Pluralizer.pluralize(0, singular: "slice", plural: "slices", zero: "bogus"), "0 bogus") + } + +} diff --git a/Tests/SwiftFrameTests/SliceSizeCalculatorTests.swift b/Tests/SwiftFrameTests/SliceSizeCalculatorTests.swift index b31f288..1150c6c 100644 --- a/Tests/SwiftFrameTests/SliceSizeCalculatorTests.swift +++ b/Tests/SwiftFrameTests/SliceSizeCalculatorTests.swift @@ -3,10 +3,10 @@ import XCTest final class SliceSizeCalculatorTests: BaseTestCase { - func testSliceSizeCalculator_ProducesFiveSlices_WhenNotUsingGapWidth() { + func testSliceSizeCalculator_ProducesFiveSlices_WhenNotUsingGapWidth() throws { let templateSize = CGSize(width: 100, height: 50) - let calculatedSliceSize = SliceSizeCalculator.calculateSliceSize( + let calculatedSliceSize = try SliceSizeCalculator.calculateSliceSize( templateImageSize: templateSize, numberOfSlices: 5, gapWidth: nil @@ -14,10 +14,10 @@ final class SliceSizeCalculatorTests: BaseTestCase { XCTAssertEqual(calculatedSliceSize, CGSize(width: 20, height: 50)) } - func testSliceSizeCalculator_ProducesFiveSlices_WhenUsingGapWidth() { + func testSliceSizeCalculator_ProducesFiveSlices_WhenUsingGapWidth() throws { let templateSize = CGSize(width: 100, height: 50) - let calculatedSliceSize = SliceSizeCalculator.calculateSliceSize( + let calculatedSliceSize = try SliceSizeCalculator.calculateSliceSize( templateImageSize: templateSize, numberOfSlices: 5, gapWidth: 5 @@ -25,4 +25,16 @@ final class SliceSizeCalculatorTests: BaseTestCase { XCTAssertEqual(calculatedSliceSize, CGSize(width: 16, height: 50)) } + func testSliceSizeCalculator_ThrowsError_WhenTotalWidthIsNotEnough() { + let templateSize = CGSize(width: 24, height: 50) + + XCTAssertThrowsError( + try SliceSizeCalculator.calculateSliceSize( + templateImageSize: templateSize, + numberOfSlices: 7, + gapWidth: 6 + ) + ) + } + } From 79384660613d088688137773b067b564ee5346d1 Mon Sep 17 00:00:00 2001 From: Henrik Panhans Date: Tue, 26 Mar 2024 14:30:19 +0100 Subject: [PATCH 25/25] Simplify error printing Signed-off-by: Henrik Panhans --- Sources/SwiftFrameCore/Extensions/NSError+Extensions.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Sources/SwiftFrameCore/Extensions/NSError+Extensions.swift b/Sources/SwiftFrameCore/Extensions/NSError+Extensions.swift index 425a49d..6e71001 100644 --- a/Sources/SwiftFrameCore/Extensions/NSError+Extensions.swift +++ b/Sources/SwiftFrameCore/Extensions/NSError+Extensions.swift @@ -37,10 +37,7 @@ func ky_executeOrExit(verbose: Bool = false, _ work: () throws -> T) -> T { public func ky_exitWithError(_ error: Error, verbose: Bool = false) -> Never { let error = error as NSError - let errorMessage = verbose - ? CommandLineFormatter.formatError(error.description) - : CommandLineFormatter.formatError(error.localizedDescription) - print(errorMessage) + print(CommandLineFormatter.formatError(verbose ? error.description : error.localizedDescription)) error.expectation.flatMap { print(CommandLineFormatter.formatWarning(title: "EXPECTATION", text: $0)) } error.actualValue.flatMap { print(CommandLineFormatter.formatWarning(title: "ACTUAL", text: $0)) }