diff --git a/Core/Generator/FileGeneratorExtension.swift b/Core/Generator/FileGeneratorExtension.swift index 393c056..d540ad3 100644 --- a/Core/Generator/FileGeneratorExtension.swift +++ b/Core/Generator/FileGeneratorExtension.swift @@ -33,8 +33,19 @@ extension FileGenerator { if modelFile.type == .classType { content = content.replacingOccurrences(of: "{REQUIRED}", with: "required ") + if modelFile.configuration?.shouldGenerateInitMethod == true { + let assignment = modelFile.component.initialiserFunctionComponent.map { doubleTab + $0.assignmentString }.joined(separator: "\n") + let functionParameters = modelFile.component.initialiserFunctionComponent.map { $0.functionParameter }.joined(separator: ", ") + let initialiserFunctionStatement = "\n\(singleTab)init (\(functionParameters)) {" + content = content.replacingOccurrences(of: "{INITIALIZER_FUNCTION_DECLRATION}", with: initialiserFunctionStatement) + content = content.replacingOccurrences(of: "{INITIALISER_FUNCTION_ASSIGNMENT}", with: assignment) + content = content.replacingOccurrences(of: "{INITIALISER_FUNCTION_END}", with: "\(singleTab)}\n") + } } else { content = content.replacingOccurrences(of: "{REQUIRED}", with: "") + content = content.replacingOccurrences(of: "{INITIALIZER_FUNCTION_DECLRATION}", with: "") + content = content.replacingOccurrences(of: "{INITIALISER_FUNCTION_ASSIGNMENT}", with: "") + content = content.replacingOccurrences(of: "{INITIALISER_FUNCTION_END}", with: "") } return content } diff --git a/Core/Generator/Model-File-Components/ModelComponent.swift b/Core/Generator/Model-File-Components/ModelComponent.swift index 296b7ad..9dc952d 100644 --- a/Core/Generator/Model-File-Components/ModelComponent.swift +++ b/Core/Generator/Model-File-Components/ModelComponent.swift @@ -16,11 +16,19 @@ internal struct ModelComponent { var stringConstants: [String] /// Initialisers for the properties. var initialisers: [String] + // Initialiser function's assignment and function parameters for classes. + var initialiserFunctionComponent: [InitialiserFunctionComponent] /// Initialise a blank model component structure. init() { declarations = [] stringConstants = [] initialisers = [] + initialiserFunctionComponent = [] } } + +internal struct InitialiserFunctionComponent { + var functionParameter: String + var assignmentString: String +} diff --git a/Core/Generator/Model-File-Components/SwiftJSONModelFile.swift b/Core/Generator/Model-File-Components/SwiftJSONModelFile.swift index 85f2fcc..5602d61 100644 --- a/Core/Generator/Model-File-Components/SwiftJSONModelFile.swift +++ b/Core/Generator/Model-File-Components/SwiftJSONModelFile.swift @@ -43,6 +43,7 @@ struct SwiftJSONModelFile: ModelFile { component.stringConstants.append(genStringConstant(property.constantName, property.key)) component.declarations.append(genVariableDeclaration(property.name, type, isArray, isOptional)) component.initialisers.append(genInitializerForVariable(name: property.name, type: property.type, constantName: property.constantName, isOptional: isOptional, isArray: isArray, isObject: isObject)) + component.initialiserFunctionComponent.append(genInitaliserFunctionAssignmentAndParams(property.name, type, isArray, isOptional)) case .nullType: // Currently we do not deal with null values. break @@ -77,6 +78,13 @@ struct SwiftJSONModelFile: ModelFile { return genPrimitiveVariableDeclaration(name, internalType, isOptional) } + func genPrimitiveVariableDeclaration(_ name: String, _ type: String, _ isOptional: Bool) -> String { + if isOptional { + return "var \(name): \(type)?" + } + return "var \(name): \(type)" + } + /// Generate the variable declaration string /// /// - Parameters: @@ -84,11 +92,19 @@ struct SwiftJSONModelFile: ModelFile { /// - type: variable type to use /// - isArray: Is the value an object /// - Returns: A string to use as the declration - func genPrimitiveVariableDeclaration(_ name: String, _ type: String, _ isOptional: Bool) -> String { + func genInitaliserFunctionAssignmentAndParams(_ name: String, _ type: String, _ isArray: Bool, _ isOptional: Bool) -> InitialiserFunctionComponent { + var result = InitialiserFunctionComponent(functionParameter: "", assignmentString: "") + result.assignmentString = "self.\(name) = \(name)" + + var typeString = type + if isArray { + typeString = "[\(typeString)]" + } if isOptional { - return "var \(name): \(type)?" + typeString = "\(typeString)?" } - return "var \(name): \(type)" + result.functionParameter = "\(name): \(typeString)" + return result } func genInitializerForVariable(name: String, type: String, constantName: String, isOptional: Bool, isArray: Bool, isObject _: Bool) -> String { diff --git a/Core/Generator/ModelGenerationConfiguration.swift b/Core/Generator/ModelGenerationConfiguration.swift index 2d4627b..e51e77d 100644 --- a/Core/Generator/ModelGenerationConfiguration.swift +++ b/Core/Generator/ModelGenerationConfiguration.swift @@ -28,6 +28,8 @@ struct ModelGenerationConfiguration { var separateCodingKeys: Bool /// Should header be included. var variablesOptional: Bool + /// Should generate a init method for the class (applicable only to class). + var shouldGenerateInitMethod: Bool mutating func defaultConfig() { variablesOptional = true @@ -37,5 +39,6 @@ struct ModelGenerationConfiguration { prefix = "" filePath = "" baseClassName = "" + shouldGenerateInitMethod = true } } diff --git a/Core/Generator/MultipleModelGenerator.swift b/Core/Generator/MultipleModelGenerator.swift index a580fb3..cbb8f9a 100644 --- a/Core/Generator/MultipleModelGenerator.swift +++ b/Core/Generator/MultipleModelGenerator.swift @@ -149,6 +149,9 @@ struct MultipleModelGenerator { if let type = fromJSON["construct_type"].string, type == "struct" { constructType = ConstructType.structType } + + let initialiserParameter = fromJSON["initaliser_needed"].bool + let initialisersNeeded = initialiserParameter != nil ? initialiserParameter! : true let jsonLibrary = JSONMappingMethod.swiftNormal let config = ModelGenerationConfiguration(filePath: fromJSON["destination_path"].string ?? "", baseClassName: "", @@ -158,7 +161,8 @@ struct MultipleModelGenerator { constructType: constructType, modelMappingLibrary: jsonLibrary, separateCodingKeys: fromJSON["separate_coding_keys"].boolValue, - variablesOptional: fromJSON["variable_option"].boolValue) + variablesOptional: fromJSON["variable_option"].boolValue, + shouldGenerateInitMethod: initialisersNeeded) return config } diff --git a/Core/Template/BaseTemplate.txt b/Core/Template/BaseTemplate.txt index 80b72fa..0763d6a 100644 --- a/Core/Template/BaseTemplate.txt +++ b/Core/Template/BaseTemplate.txt @@ -14,7 +14,9 @@ import Foundation } {DECLARATION} - +{INITIALIZER_FUNCTION_DECLRATION} +{INITIALISER_FUNCTION_ASSIGNMENT} +{INITIALISER_FUNCTION_END} {REQUIRED}init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) {INITIALIZER} diff --git a/README.md b/README.md index 014b3a2..358c9be 100644 --- a/README.md +++ b/README.md @@ -7,20 +7,23 @@ Status](https://travis-ci.org/insanoid/SwiftyJSONAccelerator.svg?branch=master)] ![codecov](https://codecov.io/gh/insanoid/SwiftyJSONAccelerator/branch/master/graph/badge.svg) -**Version v2.1 Released! (Swift 5)** +**Version v2.2** + +- Generate initializer function for classes +- **Application Download:** [Download the .app (v2.2.0)](https://github.com/insanoid/SwiftyJSONAccelerator/releases/download/v2.2.0/SwiftyJSONAccelerator.app.zip) + +**Version v2.1** - Tests are back - major parts of the code is covered. - Multiple file model generator is working again. -**Version v2.0 Released! (Swift 5)** +**Version v2.0 (Swift 5)** - Generates Swift 5 `Codeable` version along with `CodingKeys`. - Allows support to switch between `Optional` and non-optional variations. - Temporarily support for CLI and tests have been removed. - UI now supports Dark mode! -- **Application Download:** [Download the .app (v2.1.0)](https://github.com/insanoid/SwiftyJSONAccelerator/releases/download/v2.1.0/SwiftyJSONAccelerator.app.zip) - ## Installing & Building - **Building:** @@ -30,7 +33,7 @@ Status](https://travis-ci.org/insanoid/SwiftyJSONAccelerator.svg?branch=master)] You will also need to install `SwiftFormat` with `brew install swiftformat` and `SwiftLint` with `brew install swiftlint`. -- **Application Only:** [Download the .app (v2.1.0)](https://github.com/insanoid/SwiftyJSONAccelerator/releases/download/v2.1.0/SwiftyJSONAccelerator.app.zip) +- **Application Only:** [Download the .app (v2.2.0)](https://github.com/insanoid/SwiftyJSONAccelerator/releases/download/v2.2.0/SwiftyJSONAccelerator.app.zip) ## Features diff --git a/SwiftyJSONAccelerator.xcodeproj/project.pbxproj b/SwiftyJSONAccelerator.xcodeproj/project.pbxproj index 06936fc..4497c9c 100644 --- a/SwiftyJSONAccelerator.xcodeproj/project.pbxproj +++ b/SwiftyJSONAccelerator.xcodeproj/project.pbxproj @@ -713,6 +713,7 @@ CODE_SIGN_ENTITLEMENTS = SwiftyJSONAccelerator/Support/SwiftyJSONAccelerator.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 14; DEVELOPMENT_TEAM = UYGU8PDBPS; INFOPLIST_FILE = SwiftyJSONAccelerator/Support/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -720,6 +721,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.11; + MARKETING_VERSION = 2.2; PRODUCT_BUNDLE_IDENTIFIER = com.karthik.SwiftyJSONAccelerator; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; @@ -734,6 +736,7 @@ CODE_SIGN_ENTITLEMENTS = SwiftyJSONAccelerator/Support/SwiftyJSONAccelerator.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 14; DEVELOPMENT_TEAM = UYGU8PDBPS; INFOPLIST_FILE = SwiftyJSONAccelerator/Support/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -741,6 +744,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.11; + MARKETING_VERSION = 2.2; PRODUCT_BUNDLE_IDENTIFIER = com.karthik.SwiftyJSONAccelerator; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; diff --git a/SwiftyJSONAccelerator/Support/AppDelegate.swift b/SwiftyJSONAccelerator/Support/AppDelegate.swift index 4398eb3..7d9050b 100644 --- a/SwiftyJSONAccelerator/Support/AppDelegate.swift +++ b/SwiftyJSONAccelerator/Support/AppDelegate.swift @@ -14,9 +14,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele NSUserNotificationCenter.default.delegate = self } - func applicationWillTerminate(_: Notification) { - // Insert code here to tear down your application - } + func applicationWillTerminate(_: Notification) {} func userNotificationCenter(_: NSUserNotificationCenter, shouldPresent _: NSUserNotification) -> Bool { // Since our notification is to be shown when app is in focus, this function always returns true. @@ -27,7 +25,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele guard let pathString = notification.userInfo![Constants.filePathKey] as? String else { return } - // Open the path for the notification. + // Open the path mentioned in the notification. let urlPath = URL(fileURLWithPath: pathString, isDirectory: true) if notification.activationType == .actionButtonClicked { NSWorkspace.shared.activateFileViewerSelecting([urlPath]) diff --git a/SwiftyJSONAccelerator/Support/Base.lproj/Main.storyboard b/SwiftyJSONAccelerator/Support/Base.lproj/Main.storyboard index 557f01d..a7bf716 100644 --- a/SwiftyJSONAccelerator/Support/Base.lproj/Main.storyboard +++ b/SwiftyJSONAccelerator/Support/Base.lproj/Main.storyboard @@ -1,8 +1,8 @@ - + - + @@ -335,9 +335,12 @@ + + + + + @@ -446,25 +460,27 @@ - + + + - - + + @@ -476,6 +492,7 @@ + diff --git a/SwiftyJSONAccelerator/Support/Info.plist b/SwiftyJSONAccelerator/Support/Info.plist index c40dafd..a897098 100644 --- a/SwiftyJSONAccelerator/Support/Info.plist +++ b/SwiftyJSONAccelerator/Support/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2.0 + $(MARKETING_VERSION) CFBundleVersion - 13 + $(CURRENT_PROJECT_VERSION) LSApplicationCategoryType public.app-category.developer-tools LSMinimumSystemVersion diff --git a/SwiftyJSONAccelerator/UI/SJEditorViewController.swift b/SwiftyJSONAccelerator/UI/SJEditorViewController.swift index f8b5c75..5c1ee1e 100644 --- a/SwiftyJSONAccelerator/UI/SJEditorViewController.swift +++ b/SwiftyJSONAccelerator/UI/SJEditorViewController.swift @@ -20,6 +20,7 @@ class SJEditorViewController: NSViewController, NSTextViewDelegate { @IBOutlet var authorNameTextField: NSTextField! @IBOutlet var variablesOptionalCheckbox: NSButton! @IBOutlet var separateCodingKeysCheckbox: NSButton! + @IBOutlet var generateInitialiserFunctionCheckbox: NSButton! @IBOutlet var librarySelector: NSPopUpButton! @IBOutlet var modelTypeSelectorSegment: NSSegmentedControl! @@ -44,6 +45,8 @@ class SJEditorViewController: NSViewController, NSTextViewDelegate { modelTypeSelectorSegment.selectSegment(withTag: 0) variablesOptionalCheckbox.state = .on separateCodingKeysCheckbox.state = .on + generateInitialiserFunctionCheckbox.state = .on + generateInitialiserFunctionCheckbox.isEnabled = false } /// Validate and updates the textview @@ -124,9 +127,11 @@ extension SJEditorViewController { } /// When switching between versions of code being generated - @IBAction func librarySwitched(sender: Any) { - if let menu = sender as? NSPopUpButton { - librarySelector.title = menu.selectedItem!.title + @IBAction func modelTypeSwitched(sender _: Any) { + if modelTypeSelectorSegment.selectedSegment == 0 { + generateInitialiserFunctionCheckbox.isEnabled = false + } else { + generateInitialiserFunctionCheckbox.isEnabled = true } } @@ -150,6 +155,7 @@ extension SJEditorViewController { } func notify(fileCount: Int, path: String) { + NSUserNotificationCenter.default.removeAllDeliveredNotifications() let notification = NSUserNotification() notification.identifier = "SwiftyJSONAccelerator-" + UUID().uuidString notification.title = "SwiftyJSONAccelerator" @@ -192,12 +198,13 @@ extension SJEditorViewController { } let parserResponse = JSONHelper.convertToObject(textView?.string) + let generateInitialiserFunction = modelTypeSelectorSegment.selectedSegment == 1 ? generateInitialiserFunctionCheckbox.state == .on : false // Checks for validity of the content, else can cause crashes. if parserResponse.parsedObject != nil { let destinationPath = filePath!.appending("/") - let variablesOptional = variablesOptionalCheckbox.state.rawValue == 1 - let separateCodingKeys = separateCodingKeysCheckbox.state.rawValue == 1 + let variablesOptional = variablesOptionalCheckbox.state == .on + let separateCodingKeys = separateCodingKeysCheckbox.state == .on let constructType = modelTypeSelectorSegment.selectedSegment == 0 ? ConstructType.structType : ConstructType.classType let libraryType = mappingMethodForIndex(librarySelector.indexOfSelectedItem) let configuration = ModelGenerationConfiguration( @@ -209,7 +216,8 @@ extension SJEditorViewController { constructType: constructType, modelMappingLibrary: libraryType, separateCodingKeys: separateCodingKeys, - variablesOptional: variablesOptional + variablesOptional: variablesOptional, + shouldGenerateInitMethod: generateInitialiserFunction ) let modelGenerator = ModelGenerator(JSON(parserResponse.parsedObject!), configuration) let filesGenerated = modelGenerator.generate() diff --git a/SwiftyJSONAcceleratorTests/ModelGeneratorTests.swift b/SwiftyJSONAcceleratorTests/ModelGeneratorTests.swift index 37850f7..bbf9851 100644 --- a/SwiftyJSONAcceleratorTests/ModelGeneratorTests.swift +++ b/SwiftyJSONAcceleratorTests/ModelGeneratorTests.swift @@ -59,7 +59,8 @@ class ModelGeneratorTests: XCTestCase { constructType: .structType, modelMappingLibrary: library, separateCodingKeys: true, - variablesOptional: optional + variablesOptional: optional, + shouldGenerateInitMethod: true ) } @@ -119,7 +120,8 @@ class ModelGeneratorTests: XCTestCase { authorName: "A3", companyName: "A4", prefix: "A5", constructType: .classType, modelMappingLibrary: .swiftCodeExtended, - separateCodingKeys: true, variablesOptional: true) + separateCodingKeys: true, variablesOptional: true, + shouldGenerateInitMethod: true) XCTAssertEqual(modelConfig.filePath, "A1") XCTAssertEqual(modelConfig.baseClassName, "A2") @@ -157,6 +159,7 @@ class ModelGeneratorTests: XCTestCase { validateDeclarations(filename: file.fileName, declarations: file.component.declarations, optional: optional) validateKeys(filename: file.fileName, stringKeys: file.component.stringConstants) validateInitialiser(filename: file.fileName, initialisers: file.component.initialisers, optional: optional) + validateInitialiserFunctionComponents(filename: file.fileName, initialiserFunctionComponents: file.component.initialiserFunctionComponent, optional: optional) let content = FileGenerator.generateFileContentWith(file, configuration: config) let name = file.fileName let path = "/tmp/sj/" @@ -230,6 +233,32 @@ class ModelGeneratorTests: XCTestCase { } } + func validateInitialiserFunctionComponents(filename: String, initialiserFunctionComponents: [InitialiserFunctionComponent], optional: Bool) { + let possibleAssignmentValues = ["ACBaseClass": ["self.valueOne = valueOne", "self.valueThree = valueThree", "self.valueSeven = valueSeven", "self.valueEight = valueEight", "self.valueFour = valueFour", "self.valueFive = valueFive", "self.valueSix = valueSix", "self.valueTwo = valueTwo"], + "ACValueSeven": ["self.subValueFive = subValueFive", "self.doubleValue = doubleValue", "self.subValueThird = subValueThird", "self.internalProperty = internalProperty", "self.subValueFour = subValueFour"], + "ACSubValueFive": ["self.twoLevelDown = twoLevelDown"], + "ACValueSix": ["self.subValue = subValue", "self.subValueSecond = subValueSecond"]] + + var possibleFunctionParamValues = ["ACBaseClass": ["valueSeven: [ACValueSeven]", "valueThree: Bool", "valueFive: [String]", "valueSix: ACValueSix", "valueEight: Any", "valueTwo: Int", "valueFour: Float", "valueOne: String"], + "ACValueSeven": ["subValueFive: ACSubValueFive", "subValueFour: String", "internalProperty: String", "subValueThird: Float", "doubleValue: Float"], + "ACSubValueFive": ["twoLevelDown: String"], + "ACValueSix": ["subValue: String", "subValueSecond: Bool"]] + + if optional == true { + possibleFunctionParamValues = ["ACBaseClass": ["valueSeven: [ACValueSeven]?", "valueThree: Bool?", "valueFive: [String]?", "valueSix: ACValueSix?", "valueEight: Any?", "valueTwo: Int?", "valueFour: Float?", "valueOne: String?"], + "ACValueSeven": ["subValueFive: ACSubValueFive?", "subValueFour: String?", "internalProperty: String?", "subValueThird: Float?", "doubleValue: Float?"], + "ACSubValueFive": ["twoLevelDown: String?"], + "ACValueSix": ["subValue: String?", "subValueSecond: Bool?"]] + } + + XCTAssertEqual(possibleAssignmentValues[filename]?.count, initialiserFunctionComponents.count) + XCTAssertEqual(possibleFunctionParamValues[filename]?.count, initialiserFunctionComponents.count) + for initialiserFunctionComponent in initialiserFunctionComponents { + XCTAssert(possibleAssignmentValues[filename]!.contains(initialiserFunctionComponent.assignmentString)) + XCTAssert(possibleFunctionParamValues[filename]!.contains(initialiserFunctionComponent.functionParameter)) + } + } + func testClassModelGenerator() { var config = defaultConfiguration(library: .swiftNormal, optional: true) config.constructType = .classType