From e64e8f294a4ab45d602689d88fd52893d4871af7 Mon Sep 17 00:00:00 2001 From: Fumiya Tanaka Date: Sat, 21 Sep 2024 17:14:25 +0900 Subject: [PATCH] chore: Apply swift format (#56) --- .swift-format | 70 ++++ .../Csv2ImageApp.xcodeproj/project.pbxproj | 8 +- .../Csv2ImageApp/Csv2ImageAppApp.swift | 21 +- Csv2ImageApp/Csv2ImageApp/Ex/Binding+Ex.swift | 4 +- .../Csv2ImageApp/Ex/UIApplication+Ex.swift | 18 +- .../Generated/CsvConfig+CoreDataClass.swift | 2 +- .../CsvConfig+CoreDataProperties.swift | 5 +- .../Generated/CsvOutput+CoreDataClass.swift | 2 +- .../CsvOutput+CoreDataProperties.swift | 5 +- .../Generated/XCAssets+Generated.swift | 141 +++---- .../Csv2ImageApp/Models/HistoryModel.swift | 7 +- .../States/GenerateOutputState.swift | 21 +- .../States/SelectedCsvState.swift | 1 - Csv2ImageApp/Csv2ImageApp/Type.swift | 24 +- Csv2ImageApp/Csv2ImageApp/Views/CButton.swift | 20 +- Csv2ImageApp/Csv2ImageApp/Views/CText.swift | 5 +- .../GenerateOutputModel.swift | 185 +++++---- .../GenerateOutputView+iOS.swift | 213 ++++++----- .../GenerateOutputView+macOS.swift | 176 ++------- .../GenerateOutputView.swift | 35 +- .../GeneratePreviewView.swift | 70 ++-- .../PdfDocumentView/SwiftUI+PdfDocument.swift | 45 ++- .../Views/SelectCsvView/SelectCsvModel.swift | 106 ++--- .../SelectCsvView/SelectCsvView+iOS.swift | 89 ++--- .../SelectCsvView/SelectCsvView+macOS.swift | 73 ++-- .../Views/SelectCsvView/SelectCsvView.swift | 13 +- .../Csv2ImageAppTests/Csv2ImageAppTests.swift | 1 + .../CsvBuilderExample/ContentView.swift | 11 +- .../CsvCompositionExample.swift | 2 +- .../CsvBuilderExample/SecondContentView.swift | 15 +- Package.swift | 4 +- Sources/Csv2Img/Csv.swift | 166 ++++---- Sources/Csv2Img/CsvColumn.swift | 22 +- Sources/Csv2Img/CsvColumnStyle+Ex.swift | 7 +- Sources/Csv2Img/CsvError.swift | 3 +- Sources/Csv2Img/CsvRow.swift | 3 +- Sources/Csv2Img/Image+Data.swift | 38 +- Sources/Csv2Img/ImageMaker.swift | 240 ++++++------ Sources/Csv2Img/Maker.swift | 2 +- Sources/Csv2Img/PdfMaker.swift | 362 +++++++++--------- Sources/Csv2Img/PdfSize.swift | 53 +-- Sources/Csv2Img/String+Ex.swift | 49 ++- Sources/Csv2Img/TypeConvertible.swift | 6 +- Sources/Csv2ImgCmd/command.swift | 32 +- Sources/CsvBuilder/CsvBuilder.swift | 56 +-- Sources/CsvBuilder/CsvComposition.swift | 4 +- .../CsvBuilder/CsvCompositionElement.swift | 3 +- Sources/CsvBuilder/CsvCompositionParser.swift | 86 +++-- Sources/CsvBuilder/Example.swift | 6 +- Sources/CsvBuilder/NewCsvComposition.swift | 10 +- Sources/CsvBuilder/RowWrapper.swift | 3 +- Tests/Csv2ImgTests/Csv2ImgTests.swift | 1 + Tests/CsvBuilderTests/CsvBuilderTests.swift | 2 +- 53 files changed, 1277 insertions(+), 1269 deletions(-) create mode 100644 .swift-format diff --git a/.swift-format b/.swift-format new file mode 100644 index 0000000..dc1eb7f --- /dev/null +++ b/.swift-format @@ -0,0 +1,70 @@ +{ + "fileScopedDeclarationPrivacy" : { + "accessLevel" : "private" + }, + "indentation" : { + "spaces" : 4 + }, + "indentConditionalCompilationBlocks" : true, + "indentSwitchCaseLabels" : false, + "lineBreakAroundMultilineExpressionChainComponents" : false, + "lineBreakBeforeControlFlowKeywords" : false, + "lineBreakBeforeEachArgument" : false, + "lineBreakBeforeEachGenericRequirement" : false, + "lineLength" : 100, + "maximumBlankLines" : 1, + "multiElementCollectionTrailingCommas" : true, + "noAssignmentInExpressions" : { + "allowedFunctions" : [ + "XCTAssertNoThrow" + ] + }, + "prioritizeKeepingFunctionOutputTogether" : false, + "respectsExistingLineBreaks" : true, + "rules" : { + "AllPublicDeclarationsHaveDocumentation" : false, + "AlwaysUseLiteralForEmptyCollectionInit" : false, + "AlwaysUseLowerCamelCase" : true, + "AmbiguousTrailingClosureOverload" : true, + "BeginDocumentationCommentWithOneLineSummary" : false, + "DoNotUseSemicolons" : true, + "DontRepeatTypeInStaticProperties" : true, + "FileScopedDeclarationPrivacy" : true, + "FullyIndirectEnum" : true, + "GroupNumericLiterals" : true, + "IdentifiersMustBeASCII" : true, + "NeverForceUnwrap" : false, + "NeverUseForceTry" : false, + "NeverUseImplicitlyUnwrappedOptionals" : false, + "NoAccessLevelOnExtensionDeclaration" : true, + "NoAssignmentInExpressions" : true, + "NoBlockComments" : true, + "NoCasesWithOnlyFallthrough" : true, + "NoEmptyTrailingClosureParentheses" : true, + "NoLabelsInCasePatterns" : true, + "NoLeadingUnderscores" : false, + "NoParensAroundConditions" : true, + "NoPlaygroundLiterals" : true, + "NoVoidReturnOnFunctionSignature" : true, + "OmitExplicitReturns" : false, + "OneCasePerLine" : true, + "OneVariableDeclarationPerLine" : true, + "OnlyOneTrailingClosureArgument" : true, + "OrderedImports" : true, + "ReplaceForEachWithForLoop" : true, + "ReturnVoidInsteadOfEmptyTuple" : true, + "TypeNamesShouldBeCapitalized" : true, + "UseEarlyExits" : false, + "UseExplicitNilCheckInConditions" : true, + "UseLetInEveryBoundCaseVariable" : true, + "UseShorthandTypeNames" : true, + "UseSingleLinePropertyGetter" : true, + "UseSynthesizedInitializer" : true, + "UseTripleSlashForDocumentationComments" : true, + "UseWhereClausesInForLoops" : false, + "ValidateDocumentationComments" : false + }, + "spacesAroundRangeFormationOperators" : false, + "tabWidth" : 8, + "version" : 1 +} diff --git a/Csv2ImageApp/Csv2ImageApp.xcodeproj/project.pbxproj b/Csv2ImageApp/Csv2ImageApp.xcodeproj/project.pbxproj index 4025cf0..15f1aaf 100644 --- a/Csv2ImageApp/Csv2ImageApp.xcodeproj/project.pbxproj +++ b/Csv2ImageApp/Csv2ImageApp.xcodeproj/project.pbxproj @@ -635,12 +635,12 @@ INFOPLIST_KEY_UIRequiresFullScreen = NO; INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; INFOPLIST_PREPROCESS = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 13.0; + MACOSX_DEPLOYMENT_TARGET = 14.0; MARKETING_VERSION = 1.5; PRODUCT_BUNDLE_IDENTIFIER = dev.fummicc1.Csv2ImageApp_debug; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -680,12 +680,12 @@ INFOPLIST_KEY_UIRequiresFullScreen = NO; INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; INFOPLIST_PREPROCESS = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 13.0; + MACOSX_DEPLOYMENT_TARGET = 14.0; MARKETING_VERSION = 1.5; PRODUCT_BUNDLE_IDENTIFIER = dev.fummicc1.Csv2ImageApp; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/Csv2ImageApp/Csv2ImageApp/Csv2ImageAppApp.swift b/Csv2ImageApp/Csv2ImageApp/Csv2ImageAppApp.swift index e0a61b3..38df049 100644 --- a/Csv2ImageApp/Csv2ImageApp/Csv2ImageAppApp.swift +++ b/Csv2ImageApp/Csv2ImageApp/Csv2ImageAppApp.swift @@ -5,9 +5,8 @@ // Created by Fumiya Tanaka on 2022/06/07. // -import SwiftUI import CoreData - +import SwiftUI enum CsvImageAppError: Swift.Error { case invalidNetworkURL(url: String) @@ -26,7 +25,6 @@ enum CsvImageAppError: Swift.Error { } } - @main struct Csv2ImageAppApp: App { @@ -62,13 +60,16 @@ struct Csv2ImageAppApp: App { .environment( \.managedObjectContext, persistentController.viewContext ) - #if os(macOS) - .onReceive(NotificationCenter.default.publisher(for: Application.willUpdateNotification), perform: { _ in - for window in Application.shared.windows { - window.standardWindowButton(.zoomButton)?.isEnabled = false - } - }) - #endif + #if os(macOS) + .onReceive( + NotificationCenter.default.publisher( + for: Application.willUpdateNotification), + perform: { _ in + for window in Application.shared.windows { + window.standardWindowButton(.zoomButton)?.isEnabled = false + } + }) + #endif } } } diff --git a/Csv2ImageApp/Csv2ImageApp/Ex/Binding+Ex.swift b/Csv2ImageApp/Csv2ImageApp/Ex/Binding+Ex.swift index ae68238..7a16407 100644 --- a/Csv2ImageApp/Csv2ImageApp/Ex/Binding+Ex.swift +++ b/Csv2ImageApp/Csv2ImageApp/Ex/Binding+Ex.swift @@ -9,7 +9,7 @@ import Foundation import SwiftUI extension Binding { - func isNotNil() -> Binding where Value == Optional { + func isNotNil() -> Binding where Value == V? { Binding { self.wrappedValue != nil } set: { v, _ in @@ -19,7 +19,7 @@ extension Binding { } } - func isNil() -> Binding where Value == Optional { + func isNil() -> Binding where Value == V? { Binding { self.wrappedValue == nil } set: { v, _ in diff --git a/Csv2ImageApp/Csv2ImageApp/Ex/UIApplication+Ex.swift b/Csv2ImageApp/Csv2ImageApp/Ex/UIApplication+Ex.swift index 825ec4f..f76da32 100644 --- a/Csv2ImageApp/Csv2ImageApp/Ex/UIApplication+Ex.swift +++ b/Csv2ImageApp/Csv2ImageApp/Ex/UIApplication+Ex.swift @@ -8,14 +8,14 @@ import Foundation #if os(iOS) -import UIKit -extension Application { - var activeRootViewController: UIViewController? { - self.connectedScenes - .filter { $0.activationState == .foregroundActive } - .compactMap { $0 as? UIWindowScene } - .compactMap { $0.keyWindow }.first? - .rootViewController + import UIKit + extension Application { + var activeRootViewController: UIViewController? { + self.connectedScenes + .filter { $0.activationState == .foregroundActive } + .compactMap { $0 as? UIWindowScene } + .compactMap { $0.keyWindow }.first? + .rootViewController + } } -} #endif diff --git a/Csv2ImageApp/Csv2ImageApp/Generated/CsvConfig+CoreDataClass.swift b/Csv2ImageApp/Csv2ImageApp/Generated/CsvConfig+CoreDataClass.swift index f1c1f0b..4dba825 100644 --- a/Csv2ImageApp/Csv2ImageApp/Generated/CsvConfig+CoreDataClass.swift +++ b/Csv2ImageApp/Csv2ImageApp/Generated/CsvConfig+CoreDataClass.swift @@ -6,8 +6,8 @@ // // -import Foundation import CoreData +import Foundation @objc(CsvConfig) public class CsvConfig: NSManagedObject { diff --git a/Csv2ImageApp/Csv2ImageApp/Generated/CsvConfig+CoreDataProperties.swift b/Csv2ImageApp/Csv2ImageApp/Generated/CsvConfig+CoreDataProperties.swift index ca28b3f..f1f44c6 100644 --- a/Csv2ImageApp/Csv2ImageApp/Generated/CsvConfig+CoreDataProperties.swift +++ b/Csv2ImageApp/Csv2ImageApp/Generated/CsvConfig+CoreDataProperties.swift @@ -6,9 +6,8 @@ // // -import Foundation import CoreData - +import Foundation extension CsvConfig { @@ -21,6 +20,6 @@ extension CsvConfig { } -extension CsvConfig : Identifiable { +extension CsvConfig: Identifiable { } diff --git a/Csv2ImageApp/Csv2ImageApp/Generated/CsvOutput+CoreDataClass.swift b/Csv2ImageApp/Csv2ImageApp/Generated/CsvOutput+CoreDataClass.swift index 13cfdfb..f357707 100644 --- a/Csv2ImageApp/Csv2ImageApp/Generated/CsvOutput+CoreDataClass.swift +++ b/Csv2ImageApp/Csv2ImageApp/Generated/CsvOutput+CoreDataClass.swift @@ -6,8 +6,8 @@ // // -import Foundation import CoreData +import Foundation @objc(CsvOutput) public class CsvOutput: NSManagedObject { diff --git a/Csv2ImageApp/Csv2ImageApp/Generated/CsvOutput+CoreDataProperties.swift b/Csv2ImageApp/Csv2ImageApp/Generated/CsvOutput+CoreDataProperties.swift index d25182e..3f92d4a 100644 --- a/Csv2ImageApp/Csv2ImageApp/Generated/CsvOutput+CoreDataProperties.swift +++ b/Csv2ImageApp/Csv2ImageApp/Generated/CsvOutput+CoreDataProperties.swift @@ -6,9 +6,8 @@ // // -import Foundation import CoreData - +import Foundation extension CsvOutput { @@ -23,6 +22,6 @@ extension CsvOutput { } -extension CsvOutput : Identifiable { +extension CsvOutput: Identifiable { } diff --git a/Csv2ImageApp/Csv2ImageApp/Generated/XCAssets+Generated.swift b/Csv2ImageApp/Csv2ImageApp/Generated/XCAssets+Generated.swift index 94afb1d..084eb90 100644 --- a/Csv2ImageApp/Csv2ImageApp/Generated/XCAssets+Generated.swift +++ b/Csv2ImageApp/Csv2ImageApp/Generated/XCAssets+Generated.swift @@ -2,18 +2,21 @@ // Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen #if os(macOS) - import AppKit + import AppKit #elseif os(iOS) - import UIKit + import UIKit #elseif os(tvOS) || os(watchOS) - import UIKit + import UIKit #endif #if canImport(SwiftUI) - import SwiftUI + import SwiftUI #endif // Deprecated typealiases -@available(*, deprecated, renamed: "ColorAsset.Color", message: "This typealias will be removed in SwiftGen 7.0") +@available( + *, deprecated, renamed: "ColorAsset.Color", + message: "This typealias will be removed in SwiftGen 7.0" +) internal typealias AssetColorTypeAlias = ColorAsset.Color // swiftlint:disable superfluous_disable_command file_length implicit_return @@ -22,89 +25,89 @@ internal typealias AssetColorTypeAlias = ColorAsset.Color // swiftlint:disable identifier_name line_length nesting type_body_length type_name internal enum Asset { - internal static let accentColor = ColorAsset(name: "AccentColor") - internal static let backgroundColor = ColorAsset(name: "BackgroundColor") - internal static let lightAccentColor = ColorAsset(name: "LightAccentColor") - internal static let secondaryBackgroundColor = ColorAsset(name: "SecondaryBackgroundColor") - internal static let secondaryColor = ColorAsset(name: "SecondaryColor") - internal static let textColor = ColorAsset(name: "TextColor") + internal static let accentColor = ColorAsset(name: "AccentColor") + internal static let backgroundColor = ColorAsset(name: "BackgroundColor") + internal static let lightAccentColor = ColorAsset(name: "LightAccentColor") + internal static let secondaryBackgroundColor = ColorAsset(name: "SecondaryBackgroundColor") + internal static let secondaryColor = ColorAsset(name: "SecondaryColor") + internal static let textColor = ColorAsset(name: "TextColor") } // swiftlint:enable identifier_name line_length nesting type_body_length type_name // MARK: - Implementation Details internal final class ColorAsset { - internal fileprivate(set) var name: String + internal fileprivate(set) var name: String - #if os(macOS) - internal typealias Color = NSColor - #elseif os(iOS) || os(tvOS) || os(watchOS) - internal typealias Color = UIColor - #endif + #if os(macOS) + internal typealias Color = NSColor + #elseif os(iOS) || os(tvOS) || os(watchOS) + internal typealias Color = UIColor + #endif - @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) - internal private(set) lazy var color: Color = { - guard let color = Color(asset: self) else { - fatalError("Unable to load color asset named \(name).") - } - return color - }() + @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) + internal private(set) lazy var color: Color = { + guard let color = Color(asset: self) else { + fatalError("Unable to load color asset named \(name).") + } + return color + }() - #if os(iOS) || os(tvOS) - @available(iOS 11.0, tvOS 11.0, *) - internal func color(compatibleWith traitCollection: UITraitCollection) -> Color { - let bundle = BundleToken.bundle - guard let color = Color(named: name, in: bundle, compatibleWith: traitCollection) else { - fatalError("Unable to load color asset named \(name).") - } - return color - } - #endif + #if os(iOS) || os(tvOS) + @available(iOS 11.0, tvOS 11.0, *) + internal func color(compatibleWith traitCollection: UITraitCollection) -> Color { + let bundle = BundleToken.bundle + guard let color = Color(named: name, in: bundle, compatibleWith: traitCollection) else { + fatalError("Unable to load color asset named \(name).") + } + return color + } + #endif - #if canImport(SwiftUI) - @available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) - internal private(set) lazy var swiftUIColor: SwiftUI.Color = { - SwiftUI.Color(asset: self) - }() - #endif + #if canImport(SwiftUI) + @available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) + internal private(set) lazy var swiftUIColor: SwiftUI.Color = { + SwiftUI.Color(asset: self) + }() + #endif - fileprivate init(name: String) { - self.name = name - } + fileprivate init(name: String) { + self.name = name + } } -internal extension ColorAsset.Color { - @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) - convenience init?(asset: ColorAsset) { - let bundle = BundleToken.bundle - #if os(iOS) || os(tvOS) - self.init(named: asset.name, in: bundle, compatibleWith: nil) - #elseif os(macOS) - self.init(named: NSColor.Name(asset.name), bundle: bundle) - #elseif os(watchOS) - self.init(named: asset.name) - #endif - } +extension ColorAsset.Color { + @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) + convenience init?(asset: ColorAsset) { + let bundle = BundleToken.bundle + #if os(iOS) || os(tvOS) + self.init(named: asset.name, in: bundle, compatibleWith: nil) + #elseif os(macOS) + self.init(named: NSColor.Name(asset.name), bundle: bundle) + #elseif os(watchOS) + self.init(named: asset.name) + #endif + } } #if canImport(SwiftUI) -@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) -internal extension SwiftUI.Color { - init(asset: ColorAsset) { - let bundle = BundleToken.bundle - self.init(asset.name, bundle: bundle) - } -} + @available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) + extension SwiftUI.Color { + init(asset: ColorAsset) { + let bundle = BundleToken.bundle + self.init(asset.name, bundle: bundle) + } + } #endif // swiftlint:disable convenience_type private final class BundleToken { - static let bundle: Bundle = { - #if SWIFT_PACKAGE - return Bundle.module - #else - return Bundle(for: BundleToken.self) - #endif - }() + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() } // swiftlint:enable convenience_type diff --git a/Csv2ImageApp/Csv2ImageApp/Models/HistoryModel.swift b/Csv2ImageApp/Csv2ImageApp/Models/HistoryModel.swift index 490b2b5..b94e34f 100644 --- a/Csv2ImageApp/Csv2ImageApp/Models/HistoryModel.swift +++ b/Csv2ImageApp/Csv2ImageApp/Models/HistoryModel.swift @@ -5,8 +5,8 @@ // Created by Fumiya Tanaka on 2022/06/20. // -import Foundation import CoreData +import Foundation class HistoryModel: ObservableObject { let context: NSManagedObjectContext @@ -37,7 +37,9 @@ class HistoryModel: ObservableObject { if let updated = notification.userInfo?[NSUpdatedObjectsKey] as? Set { for updatedElement in updated { Task { - if let i = await self.histories.firstIndex(where: { $0.objectID == updatedElement.objectID }) { + if let i = await self.histories.firstIndex(where: { + $0.objectID == updatedElement.objectID + }) { await MainActor.run(body: { self.update(index: i, value: updatedElement) }) @@ -57,5 +59,4 @@ class HistoryModel: ObservableObject { try context.save() } - } diff --git a/Csv2ImageApp/Csv2ImageApp/States/GenerateOutputState.swift b/Csv2ImageApp/Csv2ImageApp/States/GenerateOutputState.swift index 01ed6f7..43d6f7d 100644 --- a/Csv2ImageApp/Csv2ImageApp/States/GenerateOutputState.swift +++ b/Csv2ImageApp/Csv2ImageApp/States/GenerateOutputState.swift @@ -5,10 +5,9 @@ // Created by Fumiya Tanaka on 2022/08/07. // +import Csv2Img import Foundation import PDFKit -import Csv2Img - struct GenerateOutputState: Hashable, Equatable { let url: URL @@ -61,18 +60,16 @@ struct GenerateOutputState: Hashable, Equatable { } } - static func ==(lhs: GenerateOutputState, rhs: GenerateOutputState) -> Bool { - lhs.url == rhs.url && - lhs.exportType == rhs.exportType && - lhs.fileType == rhs.fileType && - lhs.encoding == rhs.encoding && - lhs.cgImage?.convertToData() == rhs.cgImage?.convertToData() && - lhs.pdfDocument?.dataRepresentation() == rhs.pdfDocument?.dataRepresentation() + static func == (lhs: GenerateOutputState, rhs: GenerateOutputState) -> Bool { + lhs.url == rhs.url && lhs.exportType == rhs.exportType && lhs.fileType == rhs.fileType + && lhs.encoding == rhs.encoding + && lhs.cgImage?.convertToData() == rhs.cgImage?.convertToData() + && lhs.pdfDocument?.dataRepresentation() == rhs.pdfDocument?.dataRepresentation() } } extension String.Encoding { -// static func ==(lhs: String.Encoding, rhs: String.Encoding) -> Bool { -// lhs.id == rhs.id -// } + // static func ==(lhs: String.Encoding, rhs: String.Encoding) -> Bool { + // lhs.id == rhs.id + // } } diff --git a/Csv2ImageApp/Csv2ImageApp/States/SelectedCsvState.swift b/Csv2ImageApp/Csv2ImageApp/States/SelectedCsvState.swift index 5b5d7ef..2dfca63 100644 --- a/Csv2ImageApp/Csv2ImageApp/States/SelectedCsvState.swift +++ b/Csv2ImageApp/Csv2ImageApp/States/SelectedCsvState.swift @@ -7,7 +7,6 @@ import Foundation - enum FileURLType { case local case network diff --git a/Csv2ImageApp/Csv2ImageApp/Type.swift b/Csv2ImageApp/Csv2ImageApp/Type.swift index b8b7741..baee165 100644 --- a/Csv2ImageApp/Csv2ImageApp/Type.swift +++ b/Csv2ImageApp/Csv2ImageApp/Type.swift @@ -9,17 +9,17 @@ import Foundation import SwiftUI #if os(macOS) -import AppKit -typealias Application = NSApplication -typealias ApplicationDelegate = NSApplicationDelegate -typealias ApplicationDelegateAdaptor = NSApplicationDelegateAdaptor -typealias Responder = NSResponder -typealias ViewRepresentable = NSViewRepresentable + import AppKit + typealias Application = NSApplication + typealias ApplicationDelegate = NSApplicationDelegate + typealias ApplicationDelegateAdaptor = NSApplicationDelegateAdaptor + typealias Responder = NSResponder + typealias ViewRepresentable = NSViewRepresentable #elseif os(iOS) -import UIKit -typealias Application = UIApplication -typealias ApplicationDelegate = UIApplicationDelegate -typealias ApplicationDelegateAdaptor = UIApplicationDelegateAdaptor -typealias Responder = UIResponder -typealias ViewRepresentable = UIViewRepresentable + import UIKit + typealias Application = UIApplication + typealias ApplicationDelegate = UIApplicationDelegate + typealias ApplicationDelegateAdaptor = UIApplicationDelegateAdaptor + typealias Responder = UIResponder + typealias ViewRepresentable = UIViewRepresentable #endif diff --git a/Csv2ImageApp/Csv2ImageApp/Views/CButton.swift b/Csv2ImageApp/Csv2ImageApp/Views/CButton.swift index 445bdde..f5d4778 100644 --- a/Csv2ImageApp/Csv2ImageApp/Views/CButton.swift +++ b/Csv2ImageApp/Csv2ImageApp/Views/CButton.swift @@ -27,12 +27,26 @@ struct CButton: View { } let content: () -> AnyView - var hPadding: CGFloat = 12 - var vPadding: CGFloat = 8 + var hPadding: CGFloat + var vPadding: CGFloat var role: Role = .normal let onPressed: () -> Void + init( + content: @escaping () -> AnyView, + hPadding: CGFloat = 12, + vPadding: CGFloat = 8, + role: Role, + onPressed: @escaping () -> Void + ) { + self.content = content + self.hPadding = hPadding + self.vPadding = vPadding + self.role = role + self.onPressed = onPressed + } + var body: some View { Button { onPressed() @@ -92,6 +106,6 @@ struct CButton: View { struct CButton_Previews: PreviewProvider { static var previews: some View { - CButton.labeled("Hello", onPressed: { }) + CButton.labeled("Hello", onPressed: {}) } } diff --git a/Csv2ImageApp/Csv2ImageApp/Views/CText.swift b/Csv2ImageApp/Csv2ImageApp/Views/CText.swift index 8fcef95..58816ad 100644 --- a/Csv2ImageApp/Csv2ImageApp/Views/CText.swift +++ b/Csv2ImageApp/Csv2ImageApp/Views/CText.swift @@ -14,7 +14,10 @@ struct CText: View { let font: Font let foregroundColor: Color - init(_ text: String, isBold: Bool = true, font: Font = .body, foregroundColor: Color = Asset.textColor.swiftUIColor) { + init( + _ text: String, isBold: Bool = true, font: Font = .body, + foregroundColor: Color = Asset.textColor.swiftUIColor + ) { self.text = text self.isBold = isBold self.font = font diff --git a/Csv2ImageApp/Csv2ImageApp/Views/GenerateOutputView/GenerateOutputModel.swift b/Csv2ImageApp/Csv2ImageApp/Views/GenerateOutputView/GenerateOutputModel.swift index 859f405..626b70a 100644 --- a/Csv2ImageApp/Csv2ImageApp/Views/GenerateOutputView/GenerateOutputModel.swift +++ b/Csv2ImageApp/Csv2ImageApp/Views/GenerateOutputView/GenerateOutputModel.swift @@ -5,22 +5,38 @@ // Created by Fumiya Tanaka on 2022/08/07. // -import Foundation +import Combine import Csv2Img +import Foundation import PDFKit import SwiftUI -import Combine - enum GenerateOutputModelError: Error { } +@globalActor +actor CsvGlobalActor { + static var shared: CsvGlobalActor = .init() +} + +@Observable class GenerateOutputModel: ObservableObject { - @Published @MainActor private(set) var state: GenerateOutputState - @Published @MainActor private(set) var savedURL: URL? + private(set) var state: GenerateOutputState { + didSet { + if oldValue.exportType == state.exportType && oldValue.encoding == state.encoding + && oldValue.size == state.size && oldValue.orientation == state.orientation + { + return + } + Task { @CsvGlobalActor in + await updateCachedCsv() + } + } + } + private(set) var savedURL: URL? - @Published private var cachedCsv: Csv? { + private var cachedCsv: Csv? { didSet { guard let cachedCsv else { return @@ -34,8 +50,6 @@ class GenerateOutputModel: ObservableObject { } } - private var cancellables: Set = [] - private let queue = DispatchQueue(label: "dev.fummicc1.csv2imgapp.generate-output-model", attributes: .concurrent) private var csvTask: Task? deinit { @@ -43,14 +57,19 @@ class GenerateOutputModel: ObservableObject { } @MainActor - init(url: URL, urlType: FileURLType, encoding: String.Encoding = .utf8, exportMode: Csv.ExportType = .pdf) { + init( + url: URL, + urlType: FileURLType, + encoding: String.Encoding = .utf8, + exportMode: Csv.ExportType = .pdf + ) { self.state = .init( url: url, fileType: urlType, encoding: encoding, exportType: exportMode ) - Task { + Task { @CsvGlobalActor in await updateCachedCsv() } } @@ -60,44 +79,14 @@ class GenerateOutputModel: ObservableObject { state[keyPath: keyPath] = value } - @MainActor - func onAppear() async { - Publishers.CombineLatest4( - _state.projectedValue - .map( - \.exportType - ).removeDuplicates(), - _state.projectedValue - .map( - \.encoding - ).removeDuplicates(), - _state.projectedValue - .map( - \.size - ).removeDuplicates(), - _state.projectedValue - .map( - \.orientation - ) - .removeDuplicates() - ) - .share() - .receive(on: queue) - .sink { (_, _, _, _) in - Task { - await self.updateCachedCsv() - } - } - .store(in: &cancellables) - } - + @CsvGlobalActor func updateCachedCsv() async { - let exportMode = await state.exportType - let encoding = await state.encoding - let pdfSize = await state.size - let pdfOrientation = await state.orientation - let url = await state.url - let fileType = await state.fileType + let exportMode = state.exportType + let encoding = state.encoding + let pdfSize = state.size + let pdfOrientation = state.orientation + let url = state.url + let fileType = state.fileType let csv: Csv? do { switch fileType { @@ -131,7 +120,8 @@ class GenerateOutputModel: ObservableObject { ) let exportable = try await csv.generate(exportType: exportMode) if type(of: exportable.base) == PDFDocument.self { - await self.update(keyPath: \.pdfDocument, value: (exportable.base as! PDFDocument)) + await self.update( + keyPath: \.pdfDocument, value: (exportable.base as! PDFDocument)) } else { await self.update(keyPath: \.cgImage, value: (exportable.base as! CGImage)) } @@ -165,70 +155,73 @@ class GenerateOutputModel: ObservableObject { @discardableResult func save() -> Bool { #if os(macOS) - save_macOS() + save_macOS() #elseif os(iOS) - save_iOS() + save_iOS() #endif } } #if os(macOS) -extension GenerateOutputModel { - @MainActor - private func save_macOS() -> Bool { - let panel = NSSavePanel() - panel.nameFieldStringValue = state.url.lastPathComponent - panel.allowedContentTypes = [state.exportType.utType] - let result = panel.runModal() - if result == .OK { - guard let url = panel.url else { - return false - } - do { - if let pdf = state.pdfDocument { - if pdf.write(to: url) { + extension GenerateOutputModel { + @MainActor + private func save_macOS() -> Bool { + let panel = NSSavePanel() + panel.nameFieldStringValue = state.url.lastPathComponent + panel.allowedContentTypes = [state.exportType.utType] + let result = panel.runModal() + if result == .OK { + guard let url = panel.url else { + return false + } + do { + if let pdf = state.pdfDocument { + if pdf.write(to: url) { + savedURL = url + return true + } + } else if let imgData = state.cgImage?.convertToData() { + try imgData.write(to: url) savedURL = url return true } - } else if let imgData = state.cgImage?.convertToData() { - try imgData.write(to: url) - savedURL = url - return true + } catch { + print(error) } - } catch { - print(error) } + return false } - return false } -} #elseif os(iOS) -extension GenerateOutputModel { - @MainActor - private func save_iOS() -> Bool { - guard var url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last else { - return false - } - guard let fileName = state.url.lastPathComponent.split(separator: ".").first else { - return false - } - url.appendPathComponent(String(fileName), conformingTo: state.exportType.utType) - if let pdf = state.pdfDocument, state.exportType == .pdf { - if pdf.write(to: url) { - savedURL = url - return true + extension GenerateOutputModel { + @MainActor + private func save_iOS() -> Bool { + guard + var url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) + .last + else { + return false } - } else if let image = state.cgImage, state.exportType == .png { - do { - try image.convertToData()?.write(to: url) - savedURL = url - return true - } catch { - print(error) + guard let fileName = state.url.lastPathComponent.split(separator: ".").first else { + return false + } + url.appendPathComponent(String(fileName), conformingTo: state.exportType.utType) + if let pdf = state.pdfDocument, state.exportType == .pdf { + if pdf.write(to: url) { + savedURL = url + return true + } + } else if let image = state.cgImage, state.exportType == .png { + do { + try image.convertToData()?.write(to: url) + savedURL = url + return true + } catch { + print(error) + } } - } - return false + return false + } } -} #endif diff --git a/Csv2ImageApp/Csv2ImageApp/Views/GenerateOutputView/GenerateOutputView+iOS.swift b/Csv2ImageApp/Csv2ImageApp/Views/GenerateOutputView/GenerateOutputView+iOS.swift index 15aefcb..a168481 100644 --- a/Csv2ImageApp/Csv2ImageApp/Views/GenerateOutputView/GenerateOutputView+iOS.swift +++ b/Csv2ImageApp/Csv2ImageApp/Views/GenerateOutputView/GenerateOutputView+iOS.swift @@ -5,141 +5,144 @@ // Created by Fumiya Tanaka on 2022/08/07. // -import SwiftUI import Csv2Img - +import SwiftUI #if os(iOS) -struct GenerateOutputView_iOS: View { + struct GenerateOutputView_iOS: View { - @StateObject var model: GenerateOutputModel - @Binding var backToPreviousPage: Bool - @State private var succeedSavingOutput: Bool = false + @StateObject var model: GenerateOutputModel + @Binding var backToPreviousPage: Bool + @State private var succeedSavingOutput: Bool = false - private let availableEncodingType: [String.Encoding] = [ - .utf8, - .utf16, - .utf32, - .shiftJIS, - .ascii, - ] + private let availableEncodingType: [String.Encoding] = [ + .utf8, + .utf16, + .utf32, + .shiftJIS, + .ascii, + ] - var body: some View { - NavigationStack { - loadedContent.id(model.state.isLoading) - .toolbar { - ToolbarItem(placement: .primaryAction) { - Button("Save") { - succeedSavingOutput = model.save() - } - } - } - .toolbar { - ToolbarItem(placement: .topBarLeading) { - Button("Back") { - backToPreviousPage = true + var body: some View { + NavigationStack { + loadedContent.id(model.state.isLoading) + .toolbar { + ToolbarItem(placement: .primaryAction) { + Button("Save") { + succeedSavingOutput = model.save() + } + } } - } - } - .alert("Complete Saving!", isPresented: $succeedSavingOutput) { - CButton.labeled("Back") { - withAnimation { - backToPreviousPage = true + .toolbar { + ToolbarItem(placement: .topBarLeading) { + Button("Back") { + backToPreviousPage = true + } + } } - } - if let savedURL = model.savedURL, Application.shared.canOpenURL(savedURL) { - CButton.labeled("Open") { - Application.shared.open(savedURL) + .alert("Complete Saving!", isPresented: $succeedSavingOutput) { + CButton.labeled("Back") { + withAnimation { + backToPreviousPage = true + } + } + if let savedURL = model.savedURL, Application.shared.canOpenURL(savedURL) { + CButton.labeled("Open") { + Application.shared.open(savedURL) + } + } } - } } } - } - var loadedContent: some View { - VStack { - List { - Section("Export Type") { - Picker(selection: Binding(get: { - model.state.exportType - }, set: { exportType in - model.update(keyPath: \.exportType, value: exportType) - })) { - CText("PDF") - .tag(Csv.ExportType.pdf) - CText("PNG") - .tag(Csv.ExportType.png) - } label: { - EmptyView() + var loadedContent: some View { + VStack { + List { + Section("Export Type") { + Picker( + selection: Binding( + get: { + model.state.exportType + }, + set: { exportType in + model.update(keyPath: \.exportType, value: exportType) + }) + ) { + CText("PDF") + .tag(Csv.ExportType.pdf) + CText("PNG") + .tag(Csv.ExportType.png) + } label: { + EmptyView() + } + .pickerStyle(.segmented) } - .pickerStyle(.segmented) - } - Section("Encoding") { - Menu(model.state.encoding.description) { - ForEach(availableEncodingType, id: \.self) { encoding in - Button { - model.update(keyPath: \.encoding, value: encoding) - } label: { - Text(encoding.description) + Section("Encoding") { + Menu(model.state.encoding.description) { + ForEach(availableEncodingType, id: \.self) { encoding in + Button { + model.update(keyPath: \.encoding, value: encoding) + } label: { + Text(encoding.description) + } } } + .fixedSize() } - .fixedSize() - } - Section("PDF Size") { - Menu(model.state.size.rawValue) { - ForEach(PdfSize.allCases.indices, id: \.self) { index in - let size = PdfSize.allCases[index] - Button { - model.update(keyPath: \.size, value: size) - } label: { - Text(size.rawValue) + Section("PDF Size") { + Menu(model.state.size.rawValue) { + ForEach(PdfSize.allCases.indices, id: \.self) { index in + let size = PdfSize.allCases[index] + Button { + model.update(keyPath: \.size, value: size) + } label: { + Text(size.rawValue) + } } } + .fixedSize() } - .fixedSize() - } - Section("PDF Orientation") { - Menu(model.state.orientation.rawValue) { - ForEach(PdfSize.Orientation.allCases.indices, id: \.self) { index in - let orientation = PdfSize.Orientation.allCases[index] - Button { - model.update(keyPath: \.orientation, value: orientation) - } label: { - Text(orientation.rawValue) + Section("PDF Orientation") { + Menu(model.state.orientation.rawValue) { + ForEach(PdfSize.Orientation.allCases.indices, id: \.self) { index in + let orientation = PdfSize.Orientation.allCases[index] + Button { + model.update(keyPath: \.orientation, value: orientation) + } label: { + Text(orientation.rawValue) + } } } + .fixedSize() } - .fixedSize() } - } - .background(Asset.lightAccentColor.swiftUIColor) - .frame(maxHeight: 200) + .background(Asset.lightAccentColor.swiftUIColor) + .frame(maxHeight: 200) - GeometryReader { proxy in - VStack(alignment: .center) { - GeneratePreviewView( - model: model, - size: .constant( - CGSize( - width: proxy.size.width, - height: proxy.size.height + GeometryReader { proxy in + VStack(alignment: .center) { + GeneratePreviewView( + model: model, + size: .constant( + CGSize( + width: proxy.size.width, + height: proxy.size.height + ) ) ) - ) - } + } + } + .background(Asset.lightAccentColor.swiftUIColor) } - .background(Asset.lightAccentColor.swiftUIColor) } - } - var loadingContent: some View { - ProgressView { - CText("Loading...", font: .largeTitle) + var loadingContent: some View { + ProgressView { + CText("Loading...", font: .largeTitle) + } + .padding() + .progressViewStyle(.linear) } - .padding() - .progressViewStyle(.linear) } -} #endif diff --git a/Csv2ImageApp/Csv2ImageApp/Views/GenerateOutputView/GenerateOutputView+macOS.swift b/Csv2ImageApp/Csv2ImageApp/Views/GenerateOutputView/GenerateOutputView+macOS.swift index ced8df3..f2a9c78 100644 --- a/Csv2ImageApp/Csv2ImageApp/Views/GenerateOutputView/GenerateOutputView+macOS.swift +++ b/Csv2ImageApp/Csv2ImageApp/Views/GenerateOutputView/GenerateOutputView+macOS.swift @@ -5,160 +5,58 @@ // Created by Fumiya Tanaka on 2022/08/07. // -import SwiftUI import Csv2Img import PDFKit +import SwiftUI #if os(macOS) -struct GenerateOutputView_macOS: View { + struct GenerateOutputView_macOS: View { - @StateObject var model: GenerateOutputModel - @Binding var backToPreviousPage: Bool + @StateObject var model: GenerateOutputModel + @Binding var backToPreviousPage: Bool - private let availableEncodingType: [String.Encoding] = [ - .utf8, - .utf16, - .utf32, - .shiftJIS, - .ascii, - ] + private let availableEncodingType: [String.Encoding] = [ + .utf8, + .utf16, + .utf32, + .shiftJIS, + .ascii, + ] - @Environment(\.dismiss) var dismiss + @Environment(\.dismiss) var dismiss - var body: some View { - NavigationSplitView(sidebar: { - List { - Section("Export Type") { - Picker(selection: Binding(get: { - model.state.exportType - }, set: { exportType, _ in - model.update(keyPath: \.exportType, value: exportType) - })) { - CText("png").tag(Csv.ExportType.png) - CText("pdf").tag(Csv.ExportType.pdf) - } label: { - EmptyView() - } - .pickerStyle(.radioGroup) - } - Section("Encoding") { - let encodingInfo = model.state.encoding.description - Menu(encodingInfo) { - ForEach(availableEncodingType) { encoding in - Button { - model.update(keyPath: \.encoding, value: encoding) - } label: { - Text(encoding.description) - } - } - } - .fixedSize() - } - Section("PDF Size") { - let encodingInfo = model.state.size.rawValue - Menu(encodingInfo) { - ForEach(PdfSize.allCases.indices, id: \.self) { index in - let size = PdfSize.allCases[index] - Button { - model.update(keyPath: \.size, value: size) - } label: { - Text(size.rawValue) - } - } - } - .fixedSize() - } - Section("PDF Orientation") { - let orientation = model.state.orientation.rawValue - Menu(orientation) { - ForEach(PdfSize.Orientation.allCases.indices, id: \.self) { index in - let orientation = PdfSize.Orientation.allCases[index] - Button { - model.update(keyPath: \.orientation, value: orientation) - } label: { - Text(orientation.rawValue) - } - } - } - .fixedSize() - } - } + var body: some View { VStack { - CButton.labeled("Save", role: .primary) { - model.save() - } - }.padding() - }, detail: { - if model.state.isLoading { - loadingContent - } else { - VStack(alignment: .center) { - GeometryReader { proxy in - GeneratePreviewView( - model: model, - size: .constant( - CGSize( - width: proxy.size.width, - height: proxy.size.height - ) - ) + GeneratePreviewView( + model: model, + size: .constant( + .init( + width: 320, + height: 240 ) - } - } + ) + ) } - }) - .background(Asset.lightAccentColor.swiftUIColor) - - } - - var loadingContent: some View { - VStack { - Spacer() - ProgressView(value: model.state.progress) { - CText("Loading...", font: .largeTitle) + .toolbar { + CButton.labeled( + "cancel", + onPressed: { + dismiss() + } + ) } - .padding() - .progressViewStyle(.linear) - Spacer() } - .background(Asset.lightAccentColor.swiftUIColor) } - var errorContent: some View { - VStack { - Spacer() - VStack { - CText(model.state.errorMessage ?? "", foregroundColor: .red) - .padding() - } - .fixedSize(horizontal: false, vertical: true) - .background(Asset.secondaryBackgroundColor.swiftUIColor) - .cornerRadius(12) - .padding() + struct GenerateOutputView_macOS_Previews: PreviewProvider { + static var previews: some View { + GenerateOutputView_macOS( + model: GenerateOutputModel( + url: URL(string: "https://via.placeholder.com/150")!, + urlType: .network + ), + backToPreviousPage: .constant(false) + ) } - .opacity(model.state.errorMessage != nil ? 1 : 0) - .animation(.easeInOut, value: model.state.errorMessage) - .onChange(of: model.state.errorMessage, perform: { errorMessage in - if errorMessage != nil { - DispatchQueue.main.asyncAfter(deadline: .now() + 3) { - withAnimation { - model.clearError() - } - } - } - }) - } -} - -struct GenerateOutputView_macOS_Previews: PreviewProvider { - static var previews: some View { - GenerateOutputView_macOS( - model: GenerateOutputModel( - url: URL(string: "https://via.placeholder.com/150")!, - urlType: .network - ), - backToPreviousPage: .constant(false) - ) } -} #endif diff --git a/Csv2ImageApp/Csv2ImageApp/Views/GenerateOutputView/GenerateOutputView.swift b/Csv2ImageApp/Csv2ImageApp/Views/GenerateOutputView/GenerateOutputView.swift index d975979..ba00069 100644 --- a/Csv2ImageApp/Csv2ImageApp/Views/GenerateOutputView/GenerateOutputView.swift +++ b/Csv2ImageApp/Csv2ImageApp/Views/GenerateOutputView/GenerateOutputView.swift @@ -7,31 +7,24 @@ import SwiftUI - struct GenerateOutputView: View { - + @StateObject var model: GenerateOutputModel @Binding var backToPreviousPage: Bool - + var body: some View { -#if os(iOS) - GenerateOutputView_iOS( - model: model, - backToPreviousPage: _backToPreviousPage - ) - .task { - await model.onAppear() - } -#elseif os(macOS) - GenerateOutputView_macOS( - model: model, - backToPreviousPage: _backToPreviousPage - ) - .task { - await model.onAppear() - } -#endif - + #if os(iOS) + GenerateOutputView_iOS( + model: model, + backToPreviousPage: _backToPreviousPage + ) + #elseif os(macOS) + GenerateOutputView_macOS( + model: model, + backToPreviousPage: _backToPreviousPage + ) + #endif + } } diff --git a/Csv2ImageApp/Csv2ImageApp/Views/GenerateOutputView/GeneratePreviewView.swift b/Csv2ImageApp/Csv2ImageApp/Views/GenerateOutputView/GeneratePreviewView.swift index 50376b3..f15829d 100644 --- a/Csv2ImageApp/Csv2ImageApp/Views/GenerateOutputView/GeneratePreviewView.swift +++ b/Csv2ImageApp/Csv2ImageApp/Views/GenerateOutputView/GeneratePreviewView.swift @@ -6,10 +6,11 @@ // import SwiftUI + #if os(iOS) -import UIKit + import UIKit #elseif os(macOS) -import AppKit + import AppKit #endif struct GeneratePreviewView: View { @@ -18,47 +19,50 @@ struct GeneratePreviewView: View { @Binding var size: CGSize #if os(iOS) - var body: some View { - Group { - if let cgImage = model.state.cgImage, model.state.exportType == .png { - let image = UIImage(cgImage: cgImage) - ScrollView { - ScrollView(.horizontal, content: { - Image(uiImage: image) - .resizable() - .aspectRatio(contentMode: .fit) - }) + var body: some View { + Group { + if let cgImage = model.state.cgImage, model.state.exportType == .png { + let image = UIImage(cgImage: cgImage) + ScrollView { + ScrollView( + .horizontal, + content: { + Image(uiImage: image) + .resizable() + .aspectRatio(contentMode: .fit) + }) + } + .frame(width: size.width, height: size.height) + } else if let document = model.state.pdfDocument, model.state.exportType == .pdf { + PdfDocumentView(document: document, size: _size) } - .frame(width: size.width, height: size.height) - } else if let document = model.state.pdfDocument, model.state.exportType == .pdf { - PdfDocumentView(document: document, size: _size) } } - } #elseif os(macOS) - var body: some View { - Group { - if let cgImage = model.state.cgImage, model.state.exportType == .png { - let image = NSImage( - cgImage: cgImage, - size: CGSize(width: cgImage.width, height: cgImage.height) - ) - ScrollView(content: { - ScrollView(.horizontal, content: { - Image(nsImage: image) - .resizable() - .aspectRatio(contentMode: .fit) + var body: some View { + Group { + if let cgImage = model.state.cgImage, model.state.exportType == .png { + let image = NSImage( + cgImage: cgImage, + size: CGSize(width: cgImage.width, height: cgImage.height) + ) + ScrollView(content: { + ScrollView( + .horizontal, + content: { + Image(nsImage: image) + .resizable() + .aspectRatio(contentMode: .fit) + }) }) - }) - } else if let document = model.state.pdfDocument, model.state.exportType == .pdf { - PdfDocumentView(document: document, size: _size) + } else if let document = model.state.pdfDocument, model.state.exportType == .pdf { + PdfDocumentView(document: document, size: _size) + } } } - } #endif } - extension String.Encoding: Identifiable { public var id: UInt { self.rawValue diff --git a/Csv2ImageApp/Csv2ImageApp/Views/PdfDocumentView/SwiftUI+PdfDocument.swift b/Csv2ImageApp/Csv2ImageApp/Views/PdfDocumentView/SwiftUI+PdfDocument.swift index fad5e54..d6ebb23 100644 --- a/Csv2ImageApp/Csv2ImageApp/Views/PdfDocumentView/SwiftUI+PdfDocument.swift +++ b/Csv2ImageApp/Csv2ImageApp/Views/PdfDocumentView/SwiftUI+PdfDocument.swift @@ -5,9 +5,8 @@ // Created by Fumiya Tanaka on 2022/06/22. // -import SwiftUI import PDFKit - +import SwiftUI struct PdfDocumentView: ViewRepresentable { @@ -17,28 +16,28 @@ struct PdfDocumentView: ViewRepresentable { private let view: PDFView = .init() #if os(macOS) - typealias NSViewType = PDFView - func makeNSView(context: Context) -> PDFView { - view.document = document - view.setFrameSize(size) - view.displayMode = .twoUpContinuous - return view - } - - func updateNSView(_ nsView: PDFView, context: Context) { - } + typealias NSViewType = PDFView + func makeNSView(context: Context) -> PDFView { + view.document = document + view.setFrameSize(size) + view.displayMode = .twoUpContinuous + return view + } + + func updateNSView(_ nsView: PDFView, context: Context) { + } #elseif os(iOS) - typealias UIViewType = PDFView - - func makeUIView(context: Context) -> PDFView { - view.document = document - view.frame.size = size - view.displayMode = .singlePage - view.usePageViewController(true, withViewOptions: nil) - return view - } - func updateUIView(_ uiView: PDFView, context: Context) { - } + typealias UIViewType = PDFView + + func makeUIView(context: Context) -> PDFView { + view.document = document + view.frame.size = size + view.displayMode = .singlePage + view.usePageViewController(true, withViewOptions: nil) + return view + } + func updateUIView(_ uiView: PDFView, context: Context) { + } #endif } diff --git a/Csv2ImageApp/Csv2ImageApp/Views/SelectCsvView/SelectCsvModel.swift b/Csv2ImageApp/Csv2ImageApp/Views/SelectCsvView/SelectCsvModel.swift index 8a7d156..ede165a 100644 --- a/Csv2ImageApp/Csv2ImageApp/Views/SelectCsvView/SelectCsvModel.swift +++ b/Csv2ImageApp/Csv2ImageApp/Views/SelectCsvView/SelectCsvModel.swift @@ -23,9 +23,9 @@ class SelectCsvModel: NSObject, ObservableObject { @MainActor func selectFileOnDisk() async { #if os(macOS) - await selectFileOnDisk_macOS() + await selectFileOnDisk_macOS() #elseif os(iOS) - await selectFileOnDisk_iOS() + await selectFileOnDisk_iOS() #endif } @@ -54,64 +54,68 @@ class SelectCsvModel: NSObject, ObservableObject { } } - #if os(macOS) -import AppKit -extension SelectCsvModel { - @MainActor - private func selectFileOnDisk_macOS() async { - let panel = NSOpenPanel() - panel.allowedContentTypes = [.commaSeparatedText] - let result = panel.runModal() - if result == .OK { - guard let url = panel.url else { - error = "\(SelectCsvModelError.fileNotFound)" - return - } - withAnimation { - selectedCsv = SelectedCsvState(fileType: .local, url: url) + import AppKit + extension SelectCsvModel { + @MainActor + private func selectFileOnDisk_macOS() async { + let panel = NSOpenPanel() + panel.allowedContentTypes = [.commaSeparatedText] + let result = panel.runModal() + if result == .OK { + guard let url = panel.url else { + error = "\(SelectCsvModelError.fileNotFound)" + return + } + withAnimation { + selectedCsv = SelectedCsvState(fileType: .local, url: url) + } } } } -} #elseif os(iOS) -import UIKit -extension SelectCsvModel: UIDocumentPickerDelegate { - @MainActor - private func selectFileOnDisk_iOS() async { - let viewController = UIDocumentPickerViewController( - forOpeningContentTypes: [.commaSeparatedText] - ) - viewController.delegate = self - Application.shared.activeRootViewController?.present( - viewController, animated: true - ) - } - - func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { - guard let url = urls.first else { - return - } - withAnimation { - selectedCsv = .init(fileType: .local, url: url) + import UIKit + extension SelectCsvModel: UIDocumentPickerDelegate { + @MainActor + private func selectFileOnDisk_iOS() async { + let viewController = UIDocumentPickerViewController( + forOpeningContentTypes: [.commaSeparatedText] + ) + viewController.delegate = self + Application.shared.activeRootViewController?.present( + viewController, animated: true + ) } - } - - func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { - selectedCsv = nil - } - @MainActor func openFolderApp() { - guard var urlPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last?.absoluteString else { - return + func documentPicker( + _ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL] + ) { + guard let url = urls.first else { + return + } + withAnimation { + selectedCsv = .init(fileType: .local, url: url) + } } - let newPath = urlPath.replacingOccurrences(of: "file://", with: "shareddocuments://") - guard let url = URL(string: newPath) else { - return + + func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { + selectedCsv = nil } - if Application.shared.canOpenURL(url) { - Application.shared.open(url) + + @MainActor func openFolderApp() { + guard + var urlPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) + .last?.absoluteString + else { + return + } + let newPath = urlPath.replacingOccurrences(of: "file://", with: "shareddocuments://") + guard let url = URL(string: newPath) else { + return + } + if Application.shared.canOpenURL(url) { + Application.shared.open(url) + } } } -} #endif diff --git a/Csv2ImageApp/Csv2ImageApp/Views/SelectCsvView/SelectCsvView+iOS.swift b/Csv2ImageApp/Csv2ImageApp/Views/SelectCsvView/SelectCsvView+iOS.swift index da6c7b2..3ecc318 100644 --- a/Csv2ImageApp/Csv2ImageApp/Views/SelectCsvView/SelectCsvView+iOS.swift +++ b/Csv2ImageApp/Csv2ImageApp/Views/SelectCsvView/SelectCsvView+iOS.swift @@ -8,60 +8,63 @@ import SwiftUI #if os(iOS) -struct SelectCsvView_iOS: View { + struct SelectCsvView_iOS: View { - @StateObject var model: SelectCsvModel + @StateObject var model: SelectCsvModel - var body: some View { - BrandingFrameView { - VStack { - Spacer() - CButton.labeled("Select Csv File") { - Task { - await model.selectFileOnDisk() - } - } - Divider().padding() - CText("Alternately, please input csv file url on the Internet.") - HStack { - TextField("example: https://bit.ly/3c2leMC", text: $model.networkUrlText) - .textFieldStyle(.roundedBorder) + var body: some View { + BrandingFrameView { + VStack { Spacer() - if !model.networkUrlText.isEmpty { - CButton.icon(systemName: "xmark") { - model.networkUrlText = "" + CButton.labeled("Select Csv File") { + Task { + await model.selectFileOnDisk() } } - CButton.labeled("OK") { - Task { - await model.selectFileOnTheInternet() + Divider().padding() + CText("Alternately, please input csv file url on the Internet.") + HStack { + TextField("example: https://bit.ly/3c2leMC", text: $model.networkUrlText) + .textFieldStyle(.roundedBorder) + Spacer() + if !model.networkUrlText.isEmpty { + CButton.icon(systemName: "xmark") { + model.networkUrlText = "" + } + } + CButton.labeled("OK") { + Task { + await model.selectFileOnTheInternet() + } } + Spacer().frame(width: 16) } - Spacer().frame(width: 16) - } - Spacer() - Divider() - CText("Saved data is stored in Folder App.", isBold: true) - CButton.labeled("Open Folder App") { - model.openFolderApp() + Spacer() + Divider() + CText("Saved data is stored in Folder App.", isBold: true) + CButton.labeled("Open Folder App") { + model.openFolderApp() + } + Spacer().frame(height: 40) } - Spacer().frame(height: 40) + .padding() } - .padding() + .alert( + "Error", isPresented: $model.error.isNotNil(), + actions: { + Button("Close") { + model.error = nil + } + }, + message: { + Text(model.error ?? "") + }) } - .alert("Error", isPresented: $model.error.isNotNil(), actions: { - Button("Close") { - model.error = nil - } - }, message: { - Text(model.error ?? "") - }) } -} -struct SelectCsvView_iOS_Previews: PreviewProvider { - static var previews: some View { - SelectCsvView_iOS(model: SelectCsvModel()) + struct SelectCsvView_iOS_Previews: PreviewProvider { + static var previews: some View { + SelectCsvView_iOS(model: SelectCsvModel()) + } } -} #endif diff --git a/Csv2ImageApp/Csv2ImageApp/Views/SelectCsvView/SelectCsvView+macOS.swift b/Csv2ImageApp/Csv2ImageApp/Views/SelectCsvView/SelectCsvView+macOS.swift index 0e935fe..b4b6edf 100644 --- a/Csv2ImageApp/Csv2ImageApp/Views/SelectCsvView/SelectCsvView+macOS.swift +++ b/Csv2ImageApp/Csv2ImageApp/Views/SelectCsvView/SelectCsvView+macOS.swift @@ -9,54 +9,57 @@ import SwiftUI import UniformTypeIdentifiers #if os(macOS) -struct SelectCsvView_macOS: View { + struct SelectCsvView_macOS: View { - @State private var isTargeted: Bool = false - @StateObject var model: SelectCsvModel + @State private var isTargeted: Bool = false + @StateObject var model: SelectCsvModel - var body: some View { - BrandingFrameView { - VStack { - CText("Drop csv file here", font: .largeTitle) + var body: some View { + BrandingFrameView { + VStack { + CText("Drop csv file here", font: .largeTitle) - Spacer().frame(height: 32) - CButton.labeled("Alternatively, Choose from Finder") { - Task { - await model.selectFileOnDisk() + Spacer().frame(height: 32) + CButton.labeled("Alternatively, Choose from Finder") { + Task { + await model.selectFileOnDisk() + } } } } - } - .onDrop(of: [.fileURL], isTargeted: $isTargeted) { providers in - guard let provider = providers.first else { - return false - } - provider.loadItem(forTypeIdentifier: UTType.fileURL.identifier, options: nil) { data, error in - if let error = error { - print(error) - return - } - guard let data = data as? Data, let url = URL(dataRepresentation: data, relativeTo: nil, isAbsolute: true) else { - return + .onDrop(of: [.fileURL], isTargeted: $isTargeted) { providers in + guard let provider = providers.first else { + return false } - if url.lastPathComponent.contains(".csv") { - DispatchQueue.main.async { - withAnimation { - model.selectedCsv = SelectedCsvState(fileType: .local, url: url) + provider.loadItem(forTypeIdentifier: UTType.fileURL.identifier, options: nil) { + data, error in + if let error = error { + print(error) + return + } + guard let data = data as? Data, + let url = URL(dataRepresentation: data, relativeTo: nil, isAbsolute: true) + else { + return + } + if url.lastPathComponent.contains(".csv") { + DispatchQueue.main.async { + withAnimation { + model.selectedCsv = SelectedCsvState(fileType: .local, url: url) + } } } } + return true } - return true } } -} -struct SelectCsvView_macOS_Previews: PreviewProvider { - static var previews: some View { - SelectCsvView_macOS( - model: SelectCsvModel() - ) + struct SelectCsvView_macOS_Previews: PreviewProvider { + static var previews: some View { + SelectCsvView_macOS( + model: SelectCsvModel() + ) + } } -} #endif diff --git a/Csv2ImageApp/Csv2ImageApp/Views/SelectCsvView/SelectCsvView.swift b/Csv2ImageApp/Csv2ImageApp/Views/SelectCsvView/SelectCsvView.swift index a7cea5b..501b6c6 100644 --- a/Csv2ImageApp/Csv2ImageApp/Views/SelectCsvView/SelectCsvView.swift +++ b/Csv2ImageApp/Csv2ImageApp/Views/SelectCsvView/SelectCsvView.swift @@ -5,9 +5,8 @@ // Created by Fumiya Tanaka on 2022/06/07. // -import SwiftUI import Csv2Img - +import SwiftUI struct SelectCsvView: View { @@ -16,11 +15,11 @@ struct SelectCsvView: View { var body: some View { Group { -#if os(macOS) - SelectCsvView_macOS(model: model) -#elseif os(iOS) - SelectCsvView_iOS(model: model) -#endif + #if os(macOS) + SelectCsvView_macOS(model: model) + #elseif os(iOS) + SelectCsvView_iOS(model: model) + #endif } .onReceive(model.$selectedCsv) { selectedCsv in self.selectedCsv = selectedCsv diff --git a/Csv2ImageApp/Csv2ImageAppTests/Csv2ImageAppTests.swift b/Csv2ImageApp/Csv2ImageAppTests/Csv2ImageAppTests.swift index 195c869..73f5a52 100644 --- a/Csv2ImageApp/Csv2ImageAppTests/Csv2ImageAppTests.swift +++ b/Csv2ImageApp/Csv2ImageAppTests/Csv2ImageAppTests.swift @@ -6,6 +6,7 @@ // import XCTest + @testable import Csv2ImageApp class Csv2ImageAppTests: XCTestCase { diff --git a/Examples/CsvBuilderExample/CsvBuilderExample/ContentView.swift b/Examples/CsvBuilderExample/CsvBuilderExample/ContentView.swift index 687f5ef..80b19b7 100644 --- a/Examples/CsvBuilderExample/CsvBuilderExample/ContentView.swift +++ b/Examples/CsvBuilderExample/CsvBuilderExample/ContentView.swift @@ -1,5 +1,5 @@ -import SwiftUI import CsvBuilder +import SwiftUI struct ContentView: View { @@ -13,9 +13,12 @@ struct ContentView: View { .foregroundColor(.accentColor) Text("Hello, world!") if let image = image { - Image(nsImage: NSImage(cgImage: image, size: CGSize(width: image.width, height: image.height))) - .resizable() - .aspectRatio(contentMode: .fit) + Image( + nsImage: NSImage( + cgImage: image, size: CGSize(width: image.width, height: image.height)) + ) + .resizable() + .aspectRatio(contentMode: .fit) } } .padding() diff --git a/Examples/CsvBuilderExample/CsvBuilderExample/CsvCompositionExample.swift b/Examples/CsvBuilderExample/CsvBuilderExample/CsvCompositionExample.swift index 2c5815f..359e3c6 100644 --- a/Examples/CsvBuilderExample/CsvBuilderExample/CsvCompositionExample.swift +++ b/Examples/CsvBuilderExample/CsvBuilderExample/CsvCompositionExample.swift @@ -7,5 +7,5 @@ struct CsvCompositionExample: CsvComposition { @CsvRows(column: "name") var names: [String] - init() { } + init() {} } diff --git a/Examples/CsvBuilderExample/CsvBuilderExample/SecondContentView.swift b/Examples/CsvBuilderExample/CsvBuilderExample/SecondContentView.swift index db60c18..25dc9ed 100644 --- a/Examples/CsvBuilderExample/CsvBuilderExample/SecondContentView.swift +++ b/Examples/CsvBuilderExample/CsvBuilderExample/SecondContentView.swift @@ -1,7 +1,6 @@ -import SwiftUI import Csv2Img import CsvBuilder - +import SwiftUI struct SecondContentView: View { @State private var composition: CsvCompositionExample = .init() @@ -14,9 +13,12 @@ struct SecondContentView: View { .foregroundColor(.accentColor) Text("Hello, world!") if let image = image { - Image(nsImage: NSImage(cgImage: image, size: CGSize(width: image.width, height: image.height))) - .resizable() - .aspectRatio(contentMode: .fit) + Image( + nsImage: NSImage( + cgImage: image, size: CGSize(width: image.width, height: image.height)) + ) + .resizable() + .aspectRatio(contentMode: .fit) } } .padding() @@ -24,7 +26,8 @@ struct SecondContentView: View { let yamada = Csv.Row(index: 0, values: ["98", "Yamada"]) let tanaka = Csv.Row(index: 0, values: ["99", "Tanaka"]) let sato = Csv.Row(index: 0, values: ["100", "Sato"]) - let csv = try! CsvCompositionParser.parse(type: CsvCompositionExample.self, rows: [yamada, tanaka, sato,]) + let csv = try! CsvCompositionParser.parse( + type: CsvCompositionExample.self, rows: [yamada, tanaka, sato]) let data = try! await csv.generate(fontSize: 20, exportType: .png) self.image = data.base as! CGImage } diff --git a/Package.swift b/Package.swift index 9f0c6fc..5dc5d99 100644 --- a/Package.swift +++ b/Package.swift @@ -31,7 +31,7 @@ let package = Package( .executable( name: "Csv2ImgCmd", targets: ["Csv2ImgCmd"] - ) + ), ], dependencies: [ // Dependencies declare other packages that this package depends on. @@ -69,7 +69,7 @@ let package = Package( .product( name: "ArgumentParser", package: "swift-argument-parser" - ) + ), ] ), ] diff --git a/Sources/Csv2Img/Csv.swift b/Sources/Csv2Img/Csv.swift index e91ea4f..d62d0e5 100644 --- a/Sources/Csv2Img/Csv.swift +++ b/Sources/Csv2Img/Csv.swift @@ -1,8 +1,8 @@ -import Foundation +import Combine import CoreGraphics -import UniformTypeIdentifiers +import Foundation import PDFKit -import Combine +import UniformTypeIdentifiers /** Csv data structure @@ -37,7 +37,7 @@ public actor Csv { /// `exportType` is value of ``ExportType`` with default value `png`. /// `pdfMetadata` is value of ``PDFMetadata`` with default value `nil`. public init( - separator: String=",", + separator: String = ",", rawString: String? = nil, encoding: String.Encoding = .utf8, columns: [Csv.Column] = [], @@ -49,12 +49,14 @@ public actor Csv { maximumRowCount: maximumRowCount, fontSize: 12 ) - self.pdfMetadata = pdfMetadata ?? PDFMetadata( - author: "Author", - title: "Title", - size: .a4, - orientation: .portrait - ) + self.pdfMetadata = + pdfMetadata + ?? PDFMetadata( + author: "Author", + title: "Title", + size: .a4, + orientation: .portrait + ) self.pdfMarker = PdfMaker( maximumRowCount: maximumRowCount, fontSize: 12, @@ -68,9 +70,7 @@ public actor Csv { self.exportType = exportType } - private( - set - ) public var encoding: String.Encoding + private(set) public var encoding: String.Encoding /// A flag whether ``Csv`` is loading contents or not public var isLoading: Bool { @@ -78,21 +78,23 @@ public actor Csv { } /// A `Publisher` to send ``isLoading``. - nonisolated public var isLoadingPublisher: AnyPublisher< - Bool, - Never - > { + nonisolated public var isLoadingPublisher: + AnyPublisher< + Bool, + Never + > + { isLoadingSubject.eraseToAnyPublisher() } /// `CurrentValueSubject` to store ``isLoading``. - private let isLoadingSubject: CurrentValueSubject< - Bool, - Never - > = .init( - false - ) - + private let isLoadingSubject: + CurrentValueSubject< + Bool, + Never + > = .init( + false + ) /// progress stores current completeFraction of convert /// Value is in `0...1` with `Double` type @@ -101,20 +103,23 @@ public actor Csv { } /// A `Publisher` to send ``progress``. - nonisolated public var progressPublisher: AnyPublisher< - Double, - Never - > { + nonisolated public var progressPublisher: + AnyPublisher< + Double, + Never + > + { progressSubject.eraseToAnyPublisher() } /// `CurrentValueSubject` to store ``progress``. - private let progressSubject: CurrentValueSubject< - Double, - Never - > = .init( - 0 - ) + private let progressSubject: + CurrentValueSubject< + Double, + Never + > = .init( + 0 + ) /// an separator applied to each row and column public var separator: String @@ -170,10 +175,11 @@ public actor Csv { func update( columnStyles: [Column.Style] ) { - columnStyles.enumerated().forEach { ( - i, - style - ) in + columnStyles.enumerated().forEach { + ( + i, + style + ) in columns[i].style = style } } @@ -276,7 +282,8 @@ extension Csv { maxLength: Int? = nil, exportType: ExportType = .png ) -> Csv { - var lines = str + var lines = + str .components( separatedBy: CharacterSet( charactersIn: "\r\n" @@ -297,9 +304,7 @@ extension Csv { omittingEmptySubsequences: false ) .count - let columns = ( - 0.. maxLength { - str = String( - item.prefix( - maxLength - ) - ) + "..." + str = + String( + item.prefix( + maxLength + ) + ) + "..." } else { str = item } @@ -427,35 +436,30 @@ extension Csv { exportType: ExportType = .png ) throws -> Csv { // https://www.hackingwithswift.com/forums/swift/accessing-files-from-the-files-app/8203 - let canAccess = file.startAccessingSecurityScopedResource() + _ = file.startAccessingSecurityScopedResource() defer { file.stopAccessingSecurityScopedResource() } - if canAccess { - let data = try Data( - contentsOf: file - ) - let str: String - if let _str = String( + let data = try Data( + contentsOf: file + ) + let str: String + if let _str = String( + data: data, + encoding: encoding + ) { + str = _str + } else { + throw Error.invalidLocalResource( + url: file.absoluteString, data: data, encoding: encoding - ) { - str = _str - } else { - throw Error.invalidLocalResource( - url: file.absoluteString, - data: data, - encoding: encoding - ) - } - return Csv.loadFromString( - str, - encoding: encoding, - separator: separator ) } - throw Error.cannotAccessFile( - url: file.absoluteString + return Csv.loadFromString( + str, + encoding: encoding, + separator: separator ) } @@ -504,7 +508,8 @@ extension Csv { fontSize: fontSize ) } - let exportable: any CsvExportable = try await withCheckedThrowingContinuation { continuation in + let exportable: any CsvExportable = try await withCheckedThrowingContinuation { + continuation in queue.async { [weak self] in guard let self = self else { continuation.resume( @@ -544,7 +549,8 @@ extension Csv { fontSize: fontSize ) } - let exportable: PDFDocument = try await withCheckedThrowingContinuation { continuation in + let exportable: PDFDocument = try await withCheckedThrowingContinuation { + continuation in queue.async { [weak self] in guard let self = self else { continuation.resume( diff --git a/Sources/Csv2Img/CsvColumn.swift b/Sources/Csv2Img/CsvColumn.swift index ea14b31..7cace15 100644 --- a/Sources/Csv2Img/CsvColumn.swift +++ b/Sources/Csv2Img/CsvColumn.swift @@ -1,13 +1,12 @@ // // CsvColumn.swift -// +// // // Created by Fumiya Tanaka on 2022/08/26. // -import Foundation import CoreGraphics - +import Foundation extension Csv { /// Column (a head line) @@ -46,7 +45,7 @@ extension Csv.Column { /// `applyOnlyColumn` determines whether this style affects both `Column` and `Row` or not. /// Default value of `applyOnlyColumn` is false, which means ``Style`` is also applied to ``Row``. public var applyOnlyColumn: Bool - + public init( color: CGColor, applyOnlyColumn: Bool = false @@ -54,7 +53,7 @@ extension Csv.Column { self.color = color self.applyOnlyColumn = applyOnlyColumn } - + public static func random( count: Int ) -> [Style] { @@ -64,12 +63,11 @@ extension Csv.Column { var hue: Double = 0.5 for _ in 0.. Style { random( count: 1 diff --git a/Sources/Csv2Img/CsvColumnStyle+Ex.swift b/Sources/Csv2Img/CsvColumnStyle+Ex.swift index f4d8064..ca71377 100644 --- a/Sources/Csv2Img/CsvColumnStyle+Ex.swift +++ b/Sources/Csv2Img/CsvColumnStyle+Ex.swift @@ -1,20 +1,19 @@ // // CsvColumnStyle+Ex.swift -// +// // // Created by Fumiya Tanaka on 2022/08/26. // import Foundation - extension Csv.Column.Style { func normalColor() -> Any { #if os(macOS) - let color: Any = Color.labelColor + let color: Any = Color.labelColor #elseif os(iOS) - let color: Any = Color.label.cgColor + let color: Any = Color.label.cgColor #endif return color } diff --git a/Sources/Csv2Img/CsvError.swift b/Sources/Csv2Img/CsvError.swift index 67925ba..9288799 100644 --- a/Sources/Csv2Img/CsvError.swift +++ b/Sources/Csv2Img/CsvError.swift @@ -1,13 +1,12 @@ // // CsvError.swift -// +// // // Created by Fumiya Tanaka on 2022/08/26. // import Foundation - extension Csv { /// `Error` related with Csv implmentation. diff --git a/Sources/Csv2Img/CsvRow.swift b/Sources/Csv2Img/CsvRow.swift index 0017690..871620b 100644 --- a/Sources/Csv2Img/CsvRow.swift +++ b/Sources/Csv2Img/CsvRow.swift @@ -1,13 +1,12 @@ // // CsvRow.swift -// +// // // Created by Fumiya Tanaka on 2022/08/26. // import Foundation - extension Csv { /// Row (a line) /// diff --git a/Sources/Csv2Img/Image+Data.swift b/Sources/Csv2Img/Image+Data.swift index 28e9180..37a415c 100644 --- a/Sources/Csv2Img/Image+Data.swift +++ b/Sources/Csv2Img/Image+Data.swift @@ -2,26 +2,26 @@ import CoreGraphics import Foundation #if canImport(AppKit) -import AppKit -extension CGImage { - public func convertToData() -> Data? { - let rep = NSBitmapImageRep( - cgImage: self - ) - return rep.representation( - using: .png, - properties: [:] - ) + import AppKit + extension CGImage { + public func convertToData() -> Data? { + let rep = NSBitmapImageRep( + cgImage: self + ) + return rep.representation( + using: .png, + properties: [:] + ) + } } -} #elseif canImport(UIKit) -import UIKit -extension CGImage { - public func convertToData() -> Data? { - let img = UIImage( - cgImage: self - ) - return img.pngData() + import UIKit + extension CGImage { + public func convertToData() -> Data? { + let img = UIImage( + cgImage: self + ) + return img.pngData() + } } -} #endif diff --git a/Sources/Csv2Img/ImageMaker.swift b/Sources/Csv2Img/ImageMaker.swift index 46a6903..7ce711c 100644 --- a/Sources/Csv2Img/ImageMaker.swift +++ b/Sources/Csv2Img/ImageMaker.swift @@ -1,19 +1,18 @@ -#if os(macOS) -import AppKit -typealias Image = NSImage -typealias Color = NSColor -typealias Rect = NSRect -#elseif os(iOS) -import UIKit -typealias Image = UIImage -typealias Color = UIColor -typealias Rect = CGRect -#endif - -import Foundation import CoreGraphics import CoreText +import Foundation +#if os(macOS) + import AppKit + typealias Image = NSImage + typealias Color = NSColor + typealias Rect = NSRect +#elseif os(iOS) + import UIKit + typealias Image = UIImage + typealias Color = UIColor + typealias Rect = CGRect +#endif public enum ImageMakingError: Error { /// Failed to get current `CGContext` @@ -35,9 +34,9 @@ protocol ImageMakerType: Maker { /// `ImageMarker` generate png-image from ``Csv``. final class ImageMaker: ImageMakerType { - + typealias Exportable = CGImage - + init( maximumRowCount: Int?, fontSize: Double @@ -45,19 +44,19 @@ final class ImageMaker: ImageMakerType { self.maximumRowCount = maximumRowCount self.fontSize = fontSize } - + var maximumRowCount: Int? - + var fontSize: Double - + var latestOutput: CGImage? - + func set( fontSize size: Double ) { self.fontSize = size } - + /// generate png-image data from ``Csv``. func make( columns: [Csv.Column], @@ -66,7 +65,7 @@ final class ImageMaker: ImageMakerType { Double ) -> Void ) throws -> CGImage { - + let length = min( maximumRowCount ?? rows.count, rows.count @@ -74,11 +73,11 @@ final class ImageMaker: ImageMakerType { let rows = rows[.. Void ) throws -> PDFDocument { - // NOTE: Anchor is bottom-left. - let horizontalSpace: Double = 8 - let verticalSpace: Double = 12 + // NOTE: Anchor is bottom-left. + let horizontalSpace: Double = 8 + let verticalSpace: Double = 12 let pageSize = pdfSize.size( orientation: orientation @@ -358,7 +356,7 @@ final class PdfMaker: PdfMakerType { let rows = rows[.. CGSize { - let landscape = switch self { - case .a0: - CGSize(width: 3370, height: 2384) - case .a1: - CGSize(width: 2384, height: 1684) - case .a2: - CGSize(width: 1684, height: 1191) - case .a3: - CGSize(width: 1191, height: 842) - case .a4: - CGSize(width: 842, height: 595) - case .a5: - CGSize(width: 595, height: 420) - case .b0: - CGSize(width: 4127, height: 2920) - case .b1: - CGSize(width: 2920, height: 2064) - case .b2: - CGSize(width: 2064, height: 1460) - case .b3: - CGSize(width: 1460, height: 1032) - case .b4: - CGSize(width: 1032, height: 729) - case .b5: - CGSize(width: 729, height: 516) - } + let landscape = + switch self { + case .a0: + CGSize(width: 3370, height: 2384) + case .a1: + CGSize(width: 2384, height: 1684) + case .a2: + CGSize(width: 1684, height: 1191) + case .a3: + CGSize(width: 1191, height: 842) + case .a4: + CGSize(width: 842, height: 595) + case .a5: + CGSize(width: 595, height: 420) + case .b0: + CGSize(width: 4127, height: 2920) + case .b1: + CGSize(width: 2920, height: 2064) + case .b2: + CGSize(width: 2064, height: 1460) + case .b3: + CGSize(width: 1460, height: 1032) + case .b4: + CGSize(width: 1032, height: 729) + case .b5: + CGSize(width: 729, height: 516) + } if orientation == .landscape { return landscape } diff --git a/Sources/Csv2Img/String+Ex.swift b/Sources/Csv2Img/String+Ex.swift index 7f621c8..b800c62 100644 --- a/Sources/Csv2Img/String+Ex.swift +++ b/Sources/Csv2Img/String+Ex.swift @@ -1,27 +1,26 @@ +import Foundation + #if canImport(AppKit) -import AppKit -typealias Font = NSFont + import AppKit + typealias Font = NSFont #elseif canImport(UIKit) -import UIKit -typealias Font = UIFont + import UIKit + typealias Font = UIFont #endif -import Foundation extension String { func getSize( fontSize: Double ) -> CGSize { - ( - self as NSString - ) - .size( - withAttributes: [ - .font: Font.systemFont( - ofSize: fontSize, - weight: .bold - ) - ] - ) + (self as NSString) + .size( + withAttributes: [ + .font: Font.systemFont( + ofSize: fontSize, + weight: .bold + ) + ] + ) } } @@ -29,14 +28,14 @@ extension NSAttributedString { func _draw( at rect: Rect ) { -#if os(macOS) - draw( - with: rect - ) -#elseif os(iOS) - draw( - in: rect - ) -#endif + #if os(macOS) + draw( + with: rect + ) + #elseif os(iOS) + draw( + in: rect + ) + #endif } } diff --git a/Sources/Csv2Img/TypeConvertible.swift b/Sources/Csv2Img/TypeConvertible.swift index 10bbc0c..dd3c58b 100644 --- a/Sources/Csv2Img/TypeConvertible.swift +++ b/Sources/Csv2Img/TypeConvertible.swift @@ -17,13 +17,13 @@ public final class AnyCsvExportable: CsvExportable { } extension CGImage: CsvExportable { - + } extension Data: CsvExportable { - + } extension PDFDocument: @unchecked Sendable { } extension PDFDocument: CsvExportable { - + } diff --git a/Sources/Csv2ImgCmd/command.swift b/Sources/Csv2ImgCmd/command.swift index b4191da..d036ea1 100644 --- a/Sources/Csv2ImgCmd/command.swift +++ b/Sources/Csv2ImgCmd/command.swift @@ -1,10 +1,9 @@ -import Foundation import ArgumentParser import CoreImage import Csv2Img +import Foundation import PDFKit - /// Csv resource type public enum InputType: EnumerableFlag { /// The local input-type @@ -35,20 +34,19 @@ public enum InputType: EnumerableFlag { .customLong( "local" ), - .short + .short, ] case .network: return [ .customLong( "network" ), - .short + .short, ] } } } - /// Coomand line interface `Csv2Img` /// /// @@ -75,33 +73,33 @@ public struct Csv2Img: AsyncParsableCommand { shouldDisplay: true, helpNames: [ .long, - .short + .short, ] ) } - + @Flag( help: "Csv file type. Choose either `local` or `network`" ) public var inputType: InputType - + @Option public var exportType: Csv.ExportType = .pdf - + @Argument( help: "Input. csv absolute-path or url on the internet" ) public var input: String - + @Argument( help: "Output. Specify local path." ) public var output: String public init() { - + } - + public func run() async throws { let csv: Csv switch inputType { @@ -112,9 +110,11 @@ public struct Csv2Img: AsyncParsableCommand { ) ) case .network: - guard let url = URL( - string: input - ) else { + guard + let url = URL( + string: input + ) + else { print( "Invalid URL: \(input)." ) @@ -124,7 +124,7 @@ public struct Csv2Img: AsyncParsableCommand { url ) } - await csv.update(pdfMetadata: .init(size: .b3, orientation: .landscape)) + await csv.update(pdfMetadata: .init(size: .b3, orientation: .landscape)) let exportable = try await csv.generate( fontSize: 12, exportType: exportType, diff --git a/Sources/CsvBuilder/CsvBuilder.swift b/Sources/CsvBuilder/CsvBuilder.swift index 16171e5..9d0e853 100644 --- a/Sources/CsvBuilder/CsvBuilder.swift +++ b/Sources/CsvBuilder/CsvBuilder.swift @@ -1,12 +1,12 @@ // // CsvBuilder.swift -// +// // // Created by Fumiya Tanaka on 2022/08/25. // -import Foundation import Csv2Img +import Foundation public enum CsvBuilderError: Error { } @@ -22,18 +22,18 @@ public enum CsvBuilder { reflecting: composition ) let children = mirror.children - + var elements: [CsvCompositionElement] = [] var rowSize: Int = 0 - + for child in children { let value = String( describing: child.value ) - guard let _ = child.label else { + guard child.label != nil else { continue } - + let rowExp = try NSRegularExpression( pattern: rowRegex ) @@ -51,12 +51,14 @@ public enum CsvBuilder { at: 1 ), in: value - ), let columnRange = Range( - result.range( - at: 2 - ), - in: value - ) { + ), + let columnRange = Range( + result.range( + at: 2 + ), + in: value + ) + { let columnName = String( value[columnRange] ) @@ -86,7 +88,8 @@ public enum CsvBuilder { let styles = Csv.Column.Style.random( count: elements.count ) - let columns: [Csv.Column] = elements + let columns: [Csv.Column] = + elements .map( \.columnName ) @@ -97,7 +100,7 @@ public enum CsvBuilder { style: styles[$0.offset] ) } - + var rows: [Csv.Row] = [] let flattedRows = elements.map( \.rows @@ -118,7 +121,7 @@ public enum CsvBuilder { row ) } - + return Csv( separator: ",", columns: columns, @@ -126,14 +129,15 @@ public enum CsvBuilder { exportType: .pdf ) } - + static func trim( str: String ) -> [CsvCompositionElement.Row] { var str = str let head = "\"" let tail = "," - str = str + str = + str .replacingOccurrences( of: "[", with: "" @@ -153,17 +157,17 @@ public enum CsvBuilder { let rows = str.split( separator: "\n" ) - .enumerated() - .map { - CsvCompositionElement.Row( - index: $0.offset, - value: String( - $0.element.trimmingCharacters( - in: .whitespaces - ) + .enumerated() + .map { + CsvCompositionElement.Row( + index: $0.offset, + value: String( + $0.element.trimmingCharacters( + in: .whitespaces ) ) - } + ) + } return rows } } diff --git a/Sources/CsvBuilder/CsvComposition.swift b/Sources/CsvBuilder/CsvComposition.swift index 3e3736d..345cda4 100644 --- a/Sources/CsvBuilder/CsvComposition.swift +++ b/Sources/CsvBuilder/CsvComposition.swift @@ -1,12 +1,12 @@ // // CsvComposition.swift -// +// // // Created by Fumiya Tanaka on 2022/08/25. // -import Foundation import Csv2Img +import Foundation public protocol CsvComposition { func build() throws -> Csv diff --git a/Sources/CsvBuilder/CsvCompositionElement.swift b/Sources/CsvBuilder/CsvCompositionElement.swift index 784ec4c..2f893fb 100644 --- a/Sources/CsvBuilder/CsvCompositionElement.swift +++ b/Sources/CsvBuilder/CsvCompositionElement.swift @@ -1,13 +1,12 @@ // // CsvCompositionElement.swift -// +// // // Created by Fumiya Tanaka on 2022/08/25. // import Foundation - struct CsvCompositionElement { var columnName: String var rows: [Row] diff --git a/Sources/CsvBuilder/CsvCompositionParser.swift b/Sources/CsvBuilder/CsvCompositionParser.swift index 7e007ca..0fa5cce 100644 --- a/Sources/CsvBuilder/CsvCompositionParser.swift +++ b/Sources/CsvBuilder/CsvCompositionParser.swift @@ -1,7 +1,7 @@ +import Csv2Img import Foundation -import SwiftSyntax import SwiftParser -import Csv2Img +import SwiftSyntax public struct CsvCompositionParser { @@ -11,7 +11,7 @@ public struct CsvCompositionParser { ) case failedToDecodeWithUtf8 } - + public static func parse( type: Composition.Type, rows: [Csv.Row] = [] @@ -19,10 +19,12 @@ public struct CsvCompositionParser { let compositionType = String( describing: type ) - guard let file = Bundle.main.url( - forResource: compositionType, - withExtension: "txt" - ) else { + guard + let file = Bundle.main.url( + forResource: compositionType, + withExtension: "txt" + ) + else { throw Error.fileNotFound( type: compositionType ) @@ -30,10 +32,12 @@ public struct CsvCompositionParser { let content = try Data( contentsOf: file ) - guard let source = String( - data: content, - encoding: .utf8 - ) else { + guard + let source = String( + data: content, + encoding: .utf8 + ) + else { throw Error.failedToDecodeWithUtf8 } let syntax: SourceFileSyntax = Parser.parse( @@ -45,7 +49,7 @@ public struct CsvCompositionParser { rows: rows ) } - + static func parseIntoCsv( type: C.Type, source: SourceFileSyntax, @@ -58,9 +62,11 @@ public struct CsvCompositionParser { case .decl( let decl ): - guard let decl = decl.as( - StructDeclSyntax.self - ) else { + guard + let decl = decl.as( + StructDeclSyntax.self + ) + else { break } if !validateInheritedType( @@ -73,12 +79,12 @@ public struct CsvCompositionParser { type: C.self, members: members ) - .map { - Csv.Column.init( - name: $0, - style: .random() - ) - } + .map { + Csv.Column.init( + name: $0, + style: .random() + ) + } allColumns.append( contentsOf: columns ) @@ -93,7 +99,7 @@ public struct CsvCompositionParser { exportType: .pdf ) } - + static func validateInheritedType( decl: StructDeclSyntax ) -> Bool { @@ -114,7 +120,7 @@ public struct CsvCompositionParser { ) }) != nil } - + static func extractColumns( _ type: C.Type, variableDecl decl: VariableDeclSyntax @@ -123,23 +129,29 @@ public struct CsvCompositionParser { return attributes.compactMap { attribute in var columns: [String] = [] for attr in attributes { - guard let attr = attr.as( - AttributeSyntax.self - ) else { + guard + let attr = attr.as( + AttributeSyntax.self + ) + else { continue } - let hasCsvRowsAttr = attr.attributeName.as( - IdentifierTypeSyntax.self - )?.name.text == "CsvRows" + let hasCsvRowsAttr = + attr.attributeName.as( + IdentifierTypeSyntax.self + )?.name.text == "CsvRows" if !hasCsvRowsAttr { continue } - guard let tokens = attr.arguments?.tokens( - viewMode: .all - ) else { + guard + let tokens = attr.arguments?.tokens( + viewMode: .all + ) + else { continue } - let column = tokens + let column = + tokens .compactMap { if case let TokenKind.stringSegment( column @@ -173,9 +185,11 @@ extension StructDeclSyntax { ) -> [String] { var columns: [String] = [] for member in members { - guard let decl = member.decl.as( - VariableDeclSyntax.self - ) else { + guard + let decl = member.decl.as( + VariableDeclSyntax.self + ) + else { continue } let c = CsvCompositionParser.extractColumns( diff --git a/Sources/CsvBuilder/Example.swift b/Sources/CsvBuilder/Example.swift index 5e060c4..bbebe39 100644 --- a/Sources/CsvBuilder/Example.swift +++ b/Sources/CsvBuilder/Example.swift @@ -4,13 +4,13 @@ struct ExampleComposition: CsvComposition { column: "age" ) var ages: [String] - + @CsvRows( column: "name" ) var names: [String] - + init() { - + } } diff --git a/Sources/CsvBuilder/NewCsvComposition.swift b/Sources/CsvBuilder/NewCsvComposition.swift index 8c34781..6c893f9 100644 --- a/Sources/CsvBuilder/NewCsvComposition.swift +++ b/Sources/CsvBuilder/NewCsvComposition.swift @@ -1,6 +1,6 @@ // // File.swift -// +// // // Created by Fumiya Tanaka on 2023/02/01. // @@ -14,16 +14,16 @@ public struct NewCsvComposition< > { var columns: [Column] var rows: Rows - + @dynamicMemberLookup public struct Rows { public typealias Value = [Column: [Row]] var value: Value = [:] - + subscript( dynamicMember keyPath: KeyPath< - Rows, - [Rows] + Rows, + [Rows] > ) -> Value { fatalError() diff --git a/Sources/CsvBuilder/RowWrapper.swift b/Sources/CsvBuilder/RowWrapper.swift index 54fb25d..1e6ac72 100644 --- a/Sources/CsvBuilder/RowWrapper.swift +++ b/Sources/CsvBuilder/RowWrapper.swift @@ -1,13 +1,12 @@ // // CsvRows.swift -// +// // // Created by Fumiya Tanaka on 2022/08/24. // import Foundation - @propertyWrapper public struct CsvRows { public var wrappedValue: [String] diff --git a/Tests/Csv2ImgTests/Csv2ImgTests.swift b/Tests/Csv2ImgTests/Csv2ImgTests.swift index 91cc8d6..2be992f 100644 --- a/Tests/Csv2ImgTests/Csv2ImgTests.swift +++ b/Tests/Csv2ImgTests/Csv2ImgTests.swift @@ -1,4 +1,5 @@ import XCTest + @testable import Csv2Img final class Csv2ImgTests: XCTestCase { diff --git a/Tests/CsvBuilderTests/CsvBuilderTests.swift b/Tests/CsvBuilderTests/CsvBuilderTests.swift index 50a3b59..d49b294 100644 --- a/Tests/CsvBuilderTests/CsvBuilderTests.swift +++ b/Tests/CsvBuilderTests/CsvBuilderTests.swift @@ -1,6 +1,6 @@ // // CsvBuilderTests.swift -// +// // // Created by Fumiya Tanaka on 2022/08/24. //