├── .commitlintrc.json ├── .github ├── CODE_OF_CONDUCT.md ├── ISSUE_TEMPLATE ├── contributing.md ├── dependabot.yml ├── pull_request_template.md ├── release-drafter.yml └── workflows │ ├── create_release.yml │ └── release_drafter.yml ├── .gitignore ├── .releaserc ├── .swift-version ├── .swiftformat ├── .swiftlint.yml ├── .travis.yml ├── Core ├── Constants.swift ├── Generator │ ├── FileGenerator.swift │ ├── FileGeneratorExtension.swift │ ├── ModalGenerator.swift │ ├── Model-File-Components │ │ ├── ModelComponent.swift │ │ ├── ModelFile.swift │ │ ├── PropertyComponent.swift │ │ └── SwiftJSONModelFile.swift │ ├── ModelGenerationConfiguration.swift │ ├── MultipleModelGenerator.swift │ └── NameGenerator.swift ├── Helpers │ ├── JSONHelper.swift │ └── StringExtensions.swift └── Template │ └── BaseTemplate.txt ├── Gemfile ├── Gemfile.lock ├── Graphics ├── SwiftyJSON.png └── SwiftyJSON.psd ├── LICENSE.md ├── Makefile ├── Podfile ├── README.md ├── SwiftyJSONAccelerator.xcodeproj ├── project.pbxproj └── xcshareddata │ └── xcschemes │ └── SwiftyJSONAccelerator.xcscheme ├── SwiftyJSONAccelerator ├── Support │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon_128x128.png │ │ │ ├── Icon_128x128@2x.png │ │ │ ├── Icon_16x16.png │ │ │ ├── Icon_16x16@2x.png │ │ │ ├── Icon_256x256.png │ │ │ ├── Icon_256x256@2x.png │ │ │ ├── Icon_32x32.png │ │ │ ├── Icon_32x32@2x.png │ │ │ ├── Icon_512x512.png │ │ │ └── Icon_512x512@2x.png │ │ ├── Contents.json │ │ ├── failure.imageset │ │ │ ├── Contents.json │ │ │ ├── failure.png │ │ │ ├── failure@2x.png │ │ │ └── failure@3x.png │ │ └── success.imageset │ │ │ ├── Contents.json │ │ │ ├── success.png │ │ │ ├── success@2x.png │ │ │ └── success@3x.png │ ├── Base.lproj │ │ └── Main.storyboard │ ├── Info.plist │ └── SwiftyJSONAccelerator.entitlements └── UI │ ├── External-Libraries │ └── LineNumberRulerView.swift │ ├── SJEditorViewController.swift │ └── SJTextView.swift ├── SwiftyJSONAcceleratorTests ├── FileGeneratorTests.swift ├── Info.plist ├── JSONHelperTests.swift ├── ModelGeneratorTests.swift ├── MultipleModelGeneratorTests.swift ├── StringTests.swift └── Support Files │ ├── Info.plist │ ├── MultipleModelTests │ ├── album.json │ ├── community.json │ ├── photo.json │ ├── test_config.json │ └── user.json │ └── TestJSONFile.sample ├── codecov.yml └── preview.png /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"] 3 | } 4 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | education, socio-economic status, nationality, personal appearance, race, 10 | religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at {{ email }}. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | title: "[Bug]: " 4 | labels: ["bug", "triage"] 5 | assignees: 6 | - octocat 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | Thanks for taking the time to fill out this bug report! 12 | - type: input 13 | id: contact 14 | attributes: 15 | label: Contact Details 16 | description: How can we get in touch with you if we need more info? 17 | placeholder: ex. email@example.com 18 | validations: 19 | required: false 20 | - type: textarea 21 | id: what-happened 22 | attributes: 23 | label: What happened? 24 | description: Also tell us, what did you expect to happen? 25 | placeholder: Tell us what you see! 26 | value: "A bug happened!" 27 | validations: 28 | required: true 29 | - type: input 30 | id: version 31 | attributes: 32 | label: Version 33 | description: What version of the app are you running? 34 | validations: 35 | required: true 36 | - type: input 37 | id: macOS-version 38 | attributes: 39 | label: macOS Version 40 | description: What version of macOS are you running? 41 | validations: 42 | required: true 43 | - type: dropdown 44 | id: build-it-self 45 | attributes: 46 | label: Did you build the application or download a pre-built version? 47 | multiple: true 48 | options: 49 | - Built it with Xcode / main branch 50 | - Built it with Xcode / dev branch 51 | - Downloaded .app file 52 | - type: dropdown 53 | id: apple-silicon 54 | attributes: 55 | label: Are you using an apple silicon machine (M1-x, M2) 56 | multiple: true 57 | options: 58 | - Yes 59 | - No 60 | - type: textarea 61 | id: logs 62 | attributes: 63 | label: Relevant log output 64 | description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. 65 | render: shell 66 | - type: checkboxes 67 | id: terms 68 | attributes: 69 | label: Code of Conduct 70 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/insanoid/SwiftyJSONAcceleratorblob/master/.github/CONTRIBUTING.md) 71 | options: 72 | - label: I agree to follow this project's Code of Conduct 73 | required: true 74 | -------------------------------------------------------------------------------- /.github/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing Guide 2 | - All improvements are welcome in the form of pull-requests. More so now than before, as I no longer work primarily on swift. Any suggestions regarding code quality of the app, generated code's quality, Swift related improvements and pull requests are all very welcome. 3 | - If you find bugs please raise them as issue tickets with specifics. Bug is when the feature is not working as it was designed or the app is broken. 4 | - If there are interesting use-cases that you would like to have in the application that would be useful for everyone - would be happy to add them to the app, either raise them as pull-requests or if isn't possible for you to build it raise them as feature request for others to contribute. 5 | 6 | - When making any changes please assess them: 7 | - Is this change relevant for you or do others need it as well - adding features that you/your organisation needs sometimes is a niche and can be done in your branched version. Would be happy to consider but I usually merge things which I see as a more broadly used feature. 8 | - When changing existing functionality (e.g., changing generated variables from `var` to `let`) assess if this is an opinion or a standard way 9 | - When contributing please open your pull-requests to `dev` branch as there will be automation built to create releases. 10 | - Please ensure you add tests for new code you write and fix any tests you break. 11 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: bundler 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "04:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Proposed changes 2 | 3 | Describe the big picture of your changes here to communicate to the maintainers why we should accept this pull request. If it fixes a bug or resolves a feature request, be sure to link to that issue. 4 | 5 | ## Types of changes 6 | 7 | What types of changes does your code introduce to SwiftyJSONAccelerator? 8 | _Put an `x` in the boxes that apply_ 9 | 10 | - [ ] Bugfix (non-breaking change which fixes an issue) 11 | - [ ] New feature (non-breaking change which adds functionality) 12 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 13 | - [ ] Documentation Update (if none of the other choices apply) 14 | 15 | ## Checklist 16 | 17 | _Put an `x` in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code._ 18 | 19 | - [ ] I have read the [CONTRIBUTING](https://github.com/insanoid/SwiftyJSONAccelerator/blob/main/.github/CONTRIBUTING.md) doc 20 | - [ ] Lint and unit tests pass locally with my changes 21 | - [ ] I have added tests that prove my fix is effective or that my feature works 22 | - [ ] I have added necessary documentation (if appropriate) 23 | 24 | ## Further comments 25 | 26 | If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution you did and what alternatives you considered, etc... 27 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | template: | 2 | ## What’s Changed 3 | 4 | $CHANGES 5 | -------------------------------------------------------------------------------- /.github/workflows/create_release.yml: -------------------------------------------------------------------------------- 1 | name: Create Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | # We create a new version if the branch is master. 10 | version_and_publish: 11 | name: Create Release 12 | if: github.ref == 'refs/heads/master' 13 | runs-on: ubuntu-latest 14 | outputs: 15 | release_version: ${{ steps.generate_version.outputs.release_version }} 16 | steps: 17 | - name: Checkout Repository 18 | uses: actions/checkout@v2 19 | with: 20 | fetch-depth: 0 21 | # In future we should run tests to ensure if it's worth making a release. 22 | - name: Install semantic-release 23 | run: | 24 | npm set registry https://registry.npmjs.org/ 25 | sudo npm install -g \ 26 | semantic-release \ 27 | @semantic-release/commit-analyzer \ 28 | @semantic-release/github \ 29 | @semantic-release/exec \ 30 | @semantic-release/release-notes-generator \ 31 | conventional-changelog-conventionalcommits 32 | 33 | - name: Generate version 34 | id: generate_version 35 | env: 36 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} 37 | NPM_TOKEN: ${{ secrets.GH_TOKEN }} 38 | run: | 39 | npx semantic-release --ci 40 | 41 | - name: No Releases Done 42 | if: "!steps.generate_version.outputs.release_version" 43 | run: | 44 | echo "No Release was done either due to wrong commit or no major changes." 45 | -------------------------------------------------------------------------------- /.github/workflows/release_drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | # branches to consider in the event; optional, defaults to all 6 | branches: 7 | - dev 8 | pull_request: 9 | types: [opened, reopened, synchronize] 10 | 11 | permissions: 12 | contents: read 13 | 14 | jobs: 15 | update_release_draft: 16 | permissions: 17 | contents: write 18 | pull-requests: write 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: release-drafter/release-drafter@v5 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | ## Build generated 5 | build/ 6 | DerivedData 7 | 8 | ## Various settings 9 | *.pbxuser 10 | !default.pbxuser 11 | *.mode1v3 12 | !default.mode1v3 13 | *.mode2v3 14 | !default.mode2v3 15 | *.perspectivev3 16 | !default.perspectivev3 17 | xcuserdata 18 | 19 | ## Other 20 | *.xccheckout 21 | *.moved-aside 22 | *.xcuserstate 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | 29 | # CocoaPods 30 | Pods/ 31 | Podfile.lock 32 | 33 | # Carthage 34 | # 35 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 36 | # Carthage/Checkouts 37 | 38 | Carthage/Build 39 | 40 | # Clang and OCLint 41 | compile_commands.json 42 | xcodebuild.log 43 | 44 | 45 | #appcode 46 | .idea 47 | 48 | #Code Coverage in Xcode7 49 | *.gcda 50 | *.gcno -------------------------------------------------------------------------------- /.releaserc: -------------------------------------------------------------------------------- 1 | { 2 | "branches": ["master"], 3 | "tagFormat": "${version}", 4 | "plugins": [ 5 | [ 6 | "@semantic-release/commit-analyzer", { 7 | "preset": "conventionalcommits", 8 | "releaseRules": [ 9 | {"type": "doc", "release": false}, 10 | {"type": "test", "release": false}, 11 | {"type": "chore", "release": "patch"}, 12 | {"type": "devx", "release": false} 13 | ], 14 | "parserOpts": { 15 | "noteKeywords": ["BREAKING CHANGE", "BREAKING CHANGES", "BREAKING"] 16 | } 17 | } 18 | ], 19 | "@semantic-release/github", 20 | [ 21 | "@semantic-release/exec", { 22 | "publishCmd": "echo \"::set-output name=release_version::${nextRelease.version}\"" 23 | } 24 | ], 25 | [ 26 | "@semantic-release/release-notes-generator", { 27 | "preset": "conventionalcommits", 28 | "presetConfig": { 29 | "types": [ 30 | {"type": "feat", "section": "Features"}, 31 | {"type": "fix", "section": "Bug Fixes"}, 32 | {"type": "doc", "section": "Documentation", "hidden": true}, 33 | {"type": "test", "section": "Tests", "hidden": true}, 34 | {"type": "chore", "section": "Chore / Improvements"}, 35 | {"type": "devx", "section": "Improved Developer Experience", "hidden": false} 36 | ] 37 | }, 38 | "parserOpts": { 39 | "noteKeywords": ["BREAKING CHANGE", "BREAKING CHANGES", "BREAKING"] 40 | }, 41 | "writerOpts": { 42 | "commitsSort": ["subject", "scope"] 43 | } 44 | } 45 | ] 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 5.0 -------------------------------------------------------------------------------- /.swiftformat: -------------------------------------------------------------------------------- 1 | # file options 2 | 3 | --allman false 4 | --binarygrouping 4,8 5 | --commas always 6 | --comments indent 7 | --decimalgrouping 3,6 8 | --elseposition same-line 9 | --empty void 10 | --exponentcase lowercase 11 | --exponentgrouping disabled 12 | --fractiongrouping disabled 13 | --header ignore 14 | --hexgrouping 4,8 15 | --hexliteralcase uppercase 16 | --ifdef indent 17 | --indent 4 18 | --indentcase false 19 | --importgrouping testable-bottom 20 | --linebreaks lf 21 | --octalgrouping 4,8 22 | --operatorfunc spaced 23 | --patternlet hoist 24 | --ranges spaced 25 | --self remove 26 | --semicolons inline 27 | --stripunusedargs always 28 | --trimwhitespace always 29 | --wraparguments preserve 30 | --wrapcollections preserve 31 | 32 | # rules 33 | 34 | --enable isEmpty 35 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - trailing_whitespace 3 | - line_length 4 | - compiler_protocol_init 5 | - force_try 6 | opt_in_rules: 7 | - empty_count 8 | - empty_string 9 | excluded: 10 | - Pods 11 | - SwiftyJSONAccelerator/UI/External-Libraries 12 | function_body_length: 13 | warning: 300 14 | error: 500 15 | function_parameter_count: 16 | warning: 6 17 | error: 8 18 | type_body_length: 19 | warning: 300 20 | error: 500 21 | file_length: 22 | warning: 1000 23 | error: 1500 24 | ignore_comment_only_lines: true 25 | cyclomatic_complexity: 26 | warning: 15 27 | error: 25 28 | reporter: "xcode" 29 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: swift 2 | 3 | osx_image: xcode10.2 4 | xcode_workspace: SwiftyJSONAccelerator.xcworkspace 5 | xcode_scheme: SwiftyJSONAccelerator 6 | xcode_sdk: macosx10.14 7 | 8 | before_install: 9 | - gem install xcpretty 10 | - gem install slather 11 | - gem install cocoapods 12 | - gem install xcov 13 | 14 | addons: 15 | homebrew: 16 | packages: 17 | - swiftlint 18 | - swiftformat 19 | update: true 20 | 21 | script: 22 | - xcodebuild -workspace SwiftyJSONAccelerator.xcworkspace -scheme "SwiftyJSONAccelerator" CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO test 23 | 24 | after_success: 25 | - xcov -w SwiftyJSONAccelerator.xcworkspace -s SwiftyJSONAccelerator xcov_output --coveralls_service_name travis-ci --coveralls_service_job_id $TRAVIS_JOB_ID 26 | - bash <(curl -s https://codecov.io/bash) -J 'SwiftyJSONAccelerator' 27 | -------------------------------------------------------------------------------- /Core/Constants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.swift 3 | // SwiftyJSONAccelerator 4 | // 5 | // Created by Karthikeya Udupa on 24/07/2019. 6 | // Copyright © 2019 Karthikeya Udupa. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Various supported variable types 12 | enum VariableType: String { 13 | case string = "String" 14 | case int = "Int" 15 | case float = "Float" 16 | case double = "Double" 17 | case bool = "Bool" 18 | case array = "[]" 19 | case object = "{OBJ}" 20 | case null = "Any" 21 | } 22 | 23 | /// Various types of construct that can be generated. 24 | /// 25 | /// - classType: Model with construct type class. 26 | /// - structType: Model with construct type struct. 27 | enum ConstructType: String { 28 | case classType = "class" 29 | case structType = "struct" 30 | } 31 | 32 | /// Various types of access control modifiers that can be applied to objects and properties. 33 | enum AccessControl: String, CaseIterable { 34 | case `internal` 35 | case `private` 36 | case `public` 37 | 38 | /// The prefix to be applied to objects and properties' declarations. 39 | var declarationPrefix: String { 40 | switch self { 41 | case .internal: 42 | // The default access control type, no need to explicitly set it 43 | return "" 44 | default: 45 | return rawValue 46 | } 47 | } 48 | } 49 | 50 | /// JSON mapping options available in the UI 51 | /// 52 | /// - Swift: Pure Swift 5 Codeable 53 | /// - SwiftCodeExtended: Codeextended along with Swift 5 - https://github.com/JohnSundell/Codextended 54 | enum JSONMappingMethod: String { 55 | case swiftNormal = "swiftCodingVanilla" 56 | case swiftCodeExtended 57 | } 58 | 59 | /// Types of property. 60 | /// 61 | /// - Value: Value type like String, Integer, Float etc. 62 | /// - ValueArray: Array of Value 63 | /// - Object: Object type 64 | /// - ObjectArray: Array of object 65 | /// - emptyArray: An empty array 66 | /// - Null: Null value 67 | enum PropertyType: String { 68 | case valueType 69 | case valueTypeArray 70 | case objectType 71 | case objectTypeArray 72 | case emptyArray 73 | case nullType 74 | } 75 | 76 | /// Place to store actual constants that don't fit in classes. 77 | enum Constants { 78 | static let filePathKey: String = "path" 79 | } 80 | -------------------------------------------------------------------------------- /Core/Generator/FileGenerator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileGenerator.swift 3 | // SwiftyJSONAccelerator 4 | // 5 | // Created by Karthik on 09/07/2016. 6 | // Copyright © 2016 Karthikeya Udupa K M. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct FileGenerator { 12 | /// Fetch the template for creating model.swift files. 13 | /// 14 | /// - Parameter filename: Name of the file to be loaded 15 | /// - Returns: String containing the template. 16 | static func loadFileWith(_ filename: String) throws -> String { 17 | let bundle = Bundle.main 18 | guard let path = bundle.path(forResource: filename, ofType: "txt") else { 19 | return "" 20 | } 21 | let content = try String(contentsOfFile: path) 22 | return content 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Core/Generator/FileGeneratorExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileGenerator.swift 3 | // SwiftyJSONAccelerator 4 | // 5 | // Created by Karthik on 27/12/2016. 6 | // Copyright © 2016 Karthikeya Udupa K M. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension FileGenerator { 12 | static func generateFileContentWith(_ modelFile: ModelFile, configuration: ModelGenerationConfiguration) -> String { 13 | var content = try! loadFileWith("BaseTemplate") 14 | let singleTab = " ", doubleTab = " " 15 | let accessPrefix = modelFile.accessControl.declarationPrefix 16 | content = content.replacingOccurrences(of: "{OBJECT_NAME}", with: modelFile.fileName) 17 | content = content.replacingOccurrences(of: "{DATE}", with: todayDateString()) 18 | content = content.replacingOccurrences(of: "{OBJECT_KIND}", with: modelFile.type.rawValue) 19 | content = content.replacingOccurrences(of: "{ACCESS_CONTROL}", with: accessPrefix) 20 | 21 | if let authorName = configuration.authorName { 22 | content = content.replacingOccurrences(of: "__NAME__", with: authorName) 23 | } 24 | if let companyName = configuration.companyName { 25 | content = content.replacingOccurrences(of: "__MyCompanyName__", with: companyName) 26 | } 27 | 28 | let stringConstants = modelFile.component.stringConstants.map { doubleTab + $0 }.joined(separator: "\n") 29 | let declarations = modelFile.component.declarations.map { singleTab + $0 }.joined(separator: "\n") 30 | let initialisers = modelFile.component.initialisers.map { doubleTab + $0 }.joined(separator: "\n") 31 | 32 | content = content.replacingOccurrences(of: "{STRING_CONSTANT}", with: stringConstants) 33 | content = content.replacingOccurrences(of: "{DECLARATION}", with: declarations) 34 | content = content.replacingOccurrences(of: "{INITIALIZER}", with: initialisers) 35 | 36 | if modelFile.type == .classType { 37 | content = content.replacingOccurrences(of: "{REQUIRED}", with: "required ") 38 | if modelFile.configuration?.shouldGenerateInitMethod == true { 39 | let assignment = modelFile.component.initialiserFunctionComponent.map { doubleTab + $0.assignmentString }.joined(separator: "\n") 40 | let functionParameters = modelFile.component.initialiserFunctionComponent.map { $0.functionParameter }.joined(separator: ", ") 41 | let initialiserFunctionStatement = "\n\(singleTab)\(accessPrefix)init (\(functionParameters)) {" 42 | content = content.replacingOccurrences(of: "{INITIALIZER_FUNCTION_DECLRATION}", with: initialiserFunctionStatement) 43 | content = content.replacingOccurrences(of: "{INITIALISER_FUNCTION_ASSIGNMENT}", with: assignment) 44 | content = content.replacingOccurrences(of: "{INITIALISER_FUNCTION_END}", with: "\(singleTab)}\n") 45 | } 46 | } else { 47 | content = content.replacingOccurrences(of: "{REQUIRED}", with: "") 48 | content = content.replacingOccurrences(of: "{INITIALIZER_FUNCTION_DECLRATION}", with: "") 49 | content = content.replacingOccurrences(of: "{INITIALISER_FUNCTION_ASSIGNMENT}", with: "") 50 | content = content.replacingOccurrences(of: "{INITIALISER_FUNCTION_END}", with: "") 51 | } 52 | return content 53 | } 54 | 55 | /** 56 | Write the given content to a file at the mentioned path. 57 | 58 | - parameter name: The name of the file. 59 | - parameter content: Content that has to be written on the file. 60 | - parameter path: Path where the file has to be created. 61 | 62 | - returns: Boolean indicating if the process was successful. 63 | */ 64 | static func writeToFileWith(_ name: String, content: String, path: String) throws { 65 | let filename = path.appendingFormat("%@", name + ".swift") 66 | try FileManager.default.createDirectory(at: URL(fileURLWithPath: path), 67 | withIntermediateDirectories: true, 68 | attributes: nil) 69 | try content.write(toFile: filename, atomically: true, encoding: String.Encoding.utf8) 70 | } 71 | 72 | fileprivate static func todayDateString() -> String { 73 | let formatter = DateFormatter() 74 | formatter.dateStyle = .short 75 | return formatter.string(from: Date()) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Core/Generator/ModalGenerator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ModalGenerator.swift 3 | // SwiftyJSONAccelerator 4 | // 5 | // Created by Karthikeya Udupa on 25/07/2019. 6 | // Copyright © 2019 Karthikeya Udupa. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | 12 | /// Model generator responsible for creation of models based on the JSON, needs to be initialised with all properties before proceeding. 13 | public struct ModelGenerator { 14 | /// Configuration for generation of the model. 15 | var configuration: ModelGenerationConfiguration 16 | /// JSON content that has to be processed. 17 | var baseContent: JSON 18 | 19 | /// Initialise the structure with the JSON and configuration 20 | /// 21 | /// - Parameters: 22 | /// - baseContent: Base content JSON that has to be used to generate the model. 23 | /// - configuration: Configuration to generate the the model. 24 | init(_ baseContent: JSON, _ configuration: ModelGenerationConfiguration) { 25 | self.baseContent = baseContent 26 | self.configuration = configuration 27 | } 28 | 29 | /// Generate the models for the structure based on the set configuration and content. 30 | /// 31 | /// - Returns: An array of files that were generated. 32 | func generate() -> [ModelFile] { 33 | return generateModelForJSON(baseContent, configuration.baseClassName, true) 34 | } 35 | 36 | /** 37 | Generate a set model files for the given JSON object. 38 | 39 | - parameter object: Object that has to be parsed. 40 | - parameter defaultClassName: Default Classname for the object. 41 | - parameter isTopLevelObject: Is the current object the root object in the JSON. 42 | 43 | - returns: Model files for the current object and sub objects. 44 | */ 45 | func generateModelForJSON(_ object: JSON, _ defaultClassName: String, _ isTopLevelObject: Bool) -> [ModelFile] { 46 | let className = NameGenerator.fixClassName(defaultClassName, configuration.prefix, isTopLevelObject) 47 | var modelFiles: [ModelFile] = [] 48 | 49 | // Incase the object was NOT a dictionary. (this would only happen in case of the top level 50 | // object, since internal objects are handled within the function and do not pass an array here) 51 | if let rootObject = object.array, let firstObject = rootObject.first { 52 | let subClassType = firstObject.detailedValueType() 53 | // If the type of the first item is an object then make it the base class and generate 54 | // stuff. However, currently it does not make a base file to handle the array. 55 | if subClassType == .object { 56 | return generateModelForJSON(JSONHelper.reduce(rootObject), defaultClassName, isTopLevelObject) 57 | } 58 | return [] 59 | } 60 | 61 | if let rootObject = object.dictionary { 62 | // A model file to store the current model. 63 | var currentModel = SwiftJSONModelFile() 64 | currentModel.setInfo(className, configuration) 65 | currentModel.sourceJSON = object 66 | 67 | for (key, value) in rootObject { 68 | /// basic information, name, type and the constant to store the key. 69 | let variableName = NameGenerator.fixVariableName(key) 70 | let variableType = value.detailedValueType() 71 | let stringConstantName = NameGenerator.variableKey(className, variableName) 72 | 73 | switch variableType { 74 | case .array: 75 | if value.arrayValue.isEmpty { 76 | currentModel.generateAndAddComponentsFor(PropertyComponent(variableName, VariableType.array.rawValue, stringConstantName, key, .emptyArray)) 77 | } else { 78 | let subClassType = value.arrayValue.first!.detailedValueType() 79 | if subClassType == .object { 80 | let models = generateModelForJSON(JSONHelper.reduce(value.arrayValue), variableName, false) 81 | modelFiles += models 82 | let model = models.first 83 | let classname = model?.fileName 84 | currentModel.generateAndAddComponentsFor(PropertyComponent(variableName, classname!, stringConstantName, key, .objectTypeArray)) 85 | } else { 86 | currentModel.generateAndAddComponentsFor(PropertyComponent(variableName, subClassType.rawValue, stringConstantName, key, .valueTypeArray)) 87 | } 88 | } 89 | case .object: 90 | let models = generateModelForJSON(value, variableName, false) 91 | let model = models.first 92 | let typeName = model?.fileName 93 | currentModel.generateAndAddComponentsFor(PropertyComponent(variableName, typeName!, stringConstantName, key, .objectType)) 94 | modelFiles += models 95 | case .null: 96 | currentModel.generateAndAddComponentsFor(PropertyComponent(variableName, VariableType.null.rawValue, stringConstantName, key, .nullType)) 97 | default: 98 | currentModel.generateAndAddComponentsFor(PropertyComponent(variableName, variableType.rawValue, stringConstantName, key, .valueType)) 99 | } 100 | } 101 | 102 | modelFiles = [currentModel] + modelFiles 103 | } 104 | 105 | // at the end we return the collection of files. 106 | return modelFiles 107 | } 108 | 109 | /** 110 | Generates the notification message for the model files returned. 111 | 112 | - parameter modelFiles: Array of model files that were generated. 113 | 114 | - returns: Notification tht was generated. 115 | */ 116 | func generateNotificationFor(_ modelFiles: [ModelFile]) -> NSUserNotification { 117 | let notification = NSUserNotification() 118 | notification.title = NSLocalizedString("SwiftyJSONAccelerator", comment: "") 119 | if !modelFiles.isEmpty { 120 | let firstModel = (modelFiles.first)! 121 | notification.subtitle = String(format: NSLocalizedString("Completed - %@.swift", comment: ""), firstModel.fileName) 122 | } else { 123 | notification.subtitle = NSLocalizedString("No files were generated, cannot model arrays inside arrays.", comment: "") 124 | } 125 | return notification 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /Core/Generator/Model-File-Components/ModelComponent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ModelComponent.swift 3 | // SwiftyJSONAccelerator 4 | // 5 | // Created by Karthik on 09/07/2016. 6 | // Copyright © 2016 Karthikeya Udupa K M. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// A strcture to store the various components of a model file. 12 | internal struct ModelComponent { 13 | /// Declaration of properties. 14 | var declarations: [String] 15 | /// String constants to store the keys. 16 | var stringConstants: [String] 17 | /// Initialisers for the properties. 18 | var initialisers: [String] 19 | // Initialiser function's assignment and function parameters for classes. 20 | var initialiserFunctionComponent: [InitialiserFunctionComponent] 21 | 22 | /// Initialise a blank model component structure. 23 | init() { 24 | declarations = [] 25 | stringConstants = [] 26 | initialisers = [] 27 | initialiserFunctionComponent = [] 28 | } 29 | } 30 | 31 | internal struct InitialiserFunctionComponent { 32 | var functionParameter: String 33 | var assignmentString: String 34 | } 35 | -------------------------------------------------------------------------------- /Core/Generator/Model-File-Components/ModelFile.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ModelFile.swift 3 | // SwiftyJSONAccelerator 4 | // 5 | // Created by Karthikeya Udupa on 01/06/16. 6 | // Copyright © 2016 Karthikeya Udupa K M. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | 12 | /// A protocol defining the structure of the model file. 13 | protocol ModelFile { 14 | /// Filename for the model. 15 | var fileName: String { get set } 16 | 17 | /// Original JSON source file used for generating this model. 18 | var sourceJSON: JSON { get set } 19 | 20 | /// Type of the the object, if a structure or a class. 21 | var type: ConstructType { get } 22 | 23 | /// Type of access control for object and properties. 24 | var accessControl: AccessControl { get } 25 | 26 | /// Storage for various components of the model, it is used to store the intermediate data. 27 | var component: ModelComponent { get } 28 | 29 | /// Configuration to be used for this model file's generation. 30 | var configuration: ModelGenerationConfiguration? { get set } 31 | 32 | /// Set the basic information for the given model file. 33 | /// 34 | /// - Parameters: 35 | /// - fileName: Name of the model file. 36 | /// - configuration: Configuration for the model file. 37 | mutating func setInfo(_ fileName: String, _ configuration: ModelGenerationConfiguration) 38 | 39 | /// Generate various required components for the given property. 40 | /// 41 | /// - Parameter property: Property for which components are to be generated. 42 | mutating func generateAndAddComponentsFor(_ property: PropertyComponent) 43 | } 44 | -------------------------------------------------------------------------------- /Core/Generator/Model-File-Components/PropertyComponent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PropertyComponent.swift 3 | // SwiftyJSONAccelerator 4 | // 5 | // Created by Karthik on 09/07/2016. 6 | // Copyright © 2016 Karthikeya Udupa K M. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | /// A strucutre to store various attributes related to a single property. 11 | struct PropertyComponent { 12 | /// Name of the property. 13 | var name: String 14 | /// Type of the property. 15 | var type: String 16 | /// Constant name that is to be used to encode, decode and read from JSON. 17 | var constantName: String 18 | /// Original key in the JSON file. 19 | var key: String 20 | /// Nature of the property, if it is a value type, an array of a value type or an object. 21 | var propertyType: PropertyType 22 | 23 | /// Initialise a property component. 24 | /// 25 | /// - Parameters: 26 | /// - name: Name of the property. 27 | /// - type: Type of the property. 28 | /// - constantName: Constant name that is to be used to encode, decode and read from JSON. 29 | /// - key: Original key in the JSON file. 30 | /// - propertyType: Nature of the property, if it is a value type, an array of a value type or an object. 31 | init(_ name: String, _ type: String, _ constantName: String, _ key: String, _ propertyType: PropertyType) { 32 | self.name = name 33 | self.type = type 34 | self.constantName = constantName 35 | self.key = key 36 | self.propertyType = propertyType 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Core/Generator/Model-File-Components/SwiftJSONModelFile.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftJSONModelFile.swift 3 | // SwiftyJSONAccelerator 4 | // 5 | // Created by Karthikeya Udupa on 25/07/2019. 6 | // Copyright © 2019 Karthikeya Udupa. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | 12 | /// Provides support for SwiftyJSON library. 13 | struct SwiftJSONModelFile: ModelFile { 14 | var fileName: String 15 | var type: ConstructType 16 | var accessControl: AccessControl 17 | var component: ModelComponent 18 | var sourceJSON: JSON 19 | var configuration: ModelGenerationConfiguration? 20 | 21 | // MARK: - Initialisers. 22 | 23 | init() { 24 | fileName = "" 25 | type = ConstructType.structType 26 | component = ModelComponent() 27 | sourceJSON = JSON([]) 28 | accessControl = .internal 29 | } 30 | 31 | mutating func setInfo(_ fileName: String, _ configuration: ModelGenerationConfiguration) { 32 | self.fileName = fileName 33 | type = configuration.constructType 34 | accessControl = configuration.accessControl 35 | self.configuration = configuration 36 | } 37 | 38 | mutating func generateAndAddComponentsFor(_ property: PropertyComponent) { 39 | let isOptional = configuration!.variablesOptional 40 | let isArray = property.propertyType == .valueTypeArray || property.propertyType == .objectTypeArray 41 | let isObject = property.propertyType == .objectType || property.propertyType == .objectTypeArray 42 | let type = property.propertyType == .emptyArray ? "Any" : property.type 43 | 44 | switch property.propertyType { 45 | case .valueType, .valueTypeArray, .objectType, .objectTypeArray, .emptyArray: 46 | component.stringConstants.append(genStringConstant(property.constantName, property.key)) 47 | component.declarations.append(genVariableDeclaration(property.name, type, isArray, isOptional)) 48 | component.initialisers.append(genInitializerForVariable(name: property.name, type: property.type, constantName: property.constantName, isOptional: isOptional, isArray: isArray, isObject: isObject)) 49 | component.initialiserFunctionComponent.append(genInitaliserFunctionAssignmentAndParams(property.name, type, isArray, isOptional)) 50 | case .nullType: 51 | // Currently we do not deal with null values. 52 | break 53 | } 54 | } 55 | 56 | /// Format the incoming string is in the case format. 57 | /// 58 | /// - Parameters: 59 | /// - constantName: Constant value to represent the variable. 60 | /// - value: Value for the key that is used in the JSON. 61 | /// - Returns: Returns `case = "value"`. 62 | func genStringConstant(_ constantName: String, _ value: String) -> String { 63 | let component = constantName.components(separatedBy: ".") 64 | let caseName = component.last! 65 | return "case \(caseName)" + (caseName == value ? "" : " = \"\(value)\"") 66 | } 67 | 68 | /// Generate the variable declaration string 69 | /// 70 | /// - Parameters: 71 | /// - name: variable name to be used 72 | /// - type: variable type to use 73 | /// - isArray: Is the value an object 74 | /// - isOptional: Is optional variable kind 75 | /// - Returns: A string to use as the declration 76 | func genVariableDeclaration(_ name: String, _ type: String, _ isArray: Bool, _ isOptional: Bool) -> String { 77 | var internalType = type 78 | if isArray { 79 | internalType = "[\(type)]" 80 | } 81 | return genPrimitiveVariableDeclaration(name, internalType, isOptional) 82 | } 83 | 84 | func genPrimitiveVariableDeclaration(_ name: String, _ type: String, _ isOptional: Bool) -> String { 85 | let optionalSuffix = isOptional ? "?" : "" 86 | var declrationPrefix = "" 87 | if !accessControl.declarationPrefix.isEmpty { 88 | declrationPrefix = "\(accessControl.declarationPrefix) " 89 | } 90 | return "\(declrationPrefix)var \(name): \(type)\(optionalSuffix)" 91 | } 92 | 93 | /// Generate the variable declaration string 94 | /// 95 | /// - Parameters: 96 | /// - name: variable name to be used 97 | /// - type: variable type to use 98 | /// - isArray: Is the value an object 99 | /// - Returns: A string to use as the declration 100 | func genInitaliserFunctionAssignmentAndParams(_ name: String, _ type: String, _ isArray: Bool, _ isOptional: Bool) -> InitialiserFunctionComponent { 101 | var result = InitialiserFunctionComponent(functionParameter: "", assignmentString: "") 102 | result.assignmentString = "self.\(name) = \(name)" 103 | 104 | var typeString = type 105 | if isArray { 106 | typeString = "[\(typeString)]" 107 | } 108 | if isOptional { 109 | typeString = "\(typeString)?" 110 | } 111 | result.functionParameter = "\(name): \(typeString)" 112 | return result 113 | } 114 | 115 | func genInitializerForVariable(name: String, type: String, constantName: String, isOptional: Bool, isArray: Bool, isObject _: Bool) -> String { 116 | var variableType = type 117 | if isArray { 118 | variableType = "[\(type)]" 119 | } 120 | let component = constantName.components(separatedBy: ".") 121 | let decodeMethod = isOptional ? "decodeIfPresent" : "decode" 122 | return "\(name) = try container.\(decodeMethod)(\(variableType).self, forKey: .\(component.last!))" 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /Core/Generator/ModelGenerationConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ModelGenerationConfiguration.swift 3 | // SwiftyJSONAccelerator 4 | // 5 | // Created by Karthikeya Udupa on 01/06/16. 6 | // Copyright © 2016 Karthikeya Udupa K M. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Structure to store the configuration for the model generation. 12 | struct ModelGenerationConfiguration { 13 | /// Path where the generated files have to be stored. 14 | var filePath: String 15 | /// Name of the root level class for the provided JSON. 16 | var baseClassName: String 17 | /// The author name that has to be put in the file's header comments. 18 | var authorName: String? 19 | /// Company name that has to be put into the file's header. 20 | var companyName: String? 21 | /// A namespace prefix for the file (not recommended for Swift but people might want it) 22 | var prefix: String? 23 | /// Type of the object that have to be generated. 24 | var constructType: ConstructType 25 | /// Access control for object and properties. 26 | var accessControl: AccessControl 27 | /// Model mapping library to be used. 28 | var modelMappingLibrary: JSONMappingMethod 29 | /// Separate coding keys into an enum and not use string. 30 | var separateCodingKeys: Bool 31 | /// Should header be included. 32 | var variablesOptional: Bool 33 | /// Should generate a init method for the class (applicable only to class). 34 | var shouldGenerateInitMethod: Bool 35 | 36 | mutating func defaultConfig() { 37 | variablesOptional = true 38 | separateCodingKeys = true 39 | modelMappingLibrary = .swiftNormal 40 | constructType = .classType 41 | accessControl = .internal 42 | prefix = "" 43 | filePath = "" 44 | baseClassName = "" 45 | shouldGenerateInitMethod = true 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Core/Generator/MultipleModelGenerator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MultipleModelGenerator.swift 3 | // SwiftyJSONAccelerator 4 | // 5 | // Created by Karthik on 24/12/2016. 6 | // Copyright © 2016 Karthikeya Udupa K M. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | 12 | /// An enumeration for handling various kinds of errors generated from multiple model generator. 13 | /// 14 | /// - noJSONFiles: No JSON file was found at the location. 15 | /// - noConfigFile: No configuration file was found at the location. 16 | /// - configInvalid: Configuration provided is invalid. 17 | /// - invalidJSONFile: Invalid JSON file. 18 | /// - invalidConfigJSON: JSON file for config is invalid. 19 | /// - invalidPath: The filepath is invalid. 20 | enum MultipleModelGeneratorError: Error, Equatable { 21 | case noJSONFiles 22 | case noConfigFile 23 | case configInvalid(rule: String) 24 | case invalidJSONFile(filename: String) 25 | case invalidConfigJSON 26 | case invalidPath 27 | 28 | /// Generate an error message for the error case. 29 | /// 30 | /// - Returns: Error message for the current case. 31 | func errorMessage() -> String { 32 | switch self { 33 | case .noJSONFiles: 34 | return "No JSON file at the given path." 35 | case .noConfigFile: 36 | return "No .config.json was found at the path." 37 | case let .configInvalid(rule): 38 | return rule 39 | case let .invalidJSONFile(filename): 40 | return "The file \(filename) is invalid." 41 | case .invalidConfigJSON: 42 | return "The .config.json has an invalid JSON." 43 | case .invalidPath: 44 | return "The path is invalid." 45 | } 46 | } 47 | 48 | static func == (lhs: MultipleModelGeneratorError, rhs: MultipleModelGeneratorError) -> Bool { 49 | return lhs.errorMessage() == rhs.errorMessage() 50 | } 51 | } 52 | 53 | /// A structure to generate multiple mdoels from JSON files at once. 54 | enum MultipleModelGenerator { 55 | /// Generate models for the JSON files in the given path. Use the `.config.json` to load config. 56 | /// 57 | /// - Parameter forPath: Path with the JSON files. 58 | /// - Returns: An array of model files. 59 | static func generate(forPath: String) throws -> (modelFiles: [ModelFile], configuration: ModelGenerationConfiguration) { 60 | let fileManager = FileManager.default 61 | var isDir: ObjCBool = true 62 | guard fileManager.fileExists(atPath: forPath, isDirectory: &isDir) else { 63 | throw MultipleModelGeneratorError.invalidPath 64 | } 65 | 66 | let response = filesIn(path: forPath) 67 | if response.files.isEmpty { 68 | throw MultipleModelGeneratorError.noJSONFiles 69 | } 70 | 71 | guard let configPath = response.configFile else { 72 | throw MultipleModelGeneratorError.noConfigFile 73 | } 74 | 75 | guard let configJSON = loadJSON(fromFile: configPath) else { 76 | throw MultipleModelGeneratorError.invalidConfigJSON 77 | } 78 | 79 | /// The final configuration for the models (without filename) 80 | var finalConfiguration = loadConfiguration(fromJSON: configJSON) 81 | 82 | finalConfiguration.filePath = generatePathToSave(fromBasePath: forPath, destinationPath: finalConfiguration.filePath) 83 | var models = [ModelFile]() 84 | for file in response.files { 85 | let url = URL(fileURLWithPath: file) 86 | let fileName = url.lastPathComponent.replacingOccurrences(of: ".json", with: "") 87 | if let json = loadJSON(fromFile: file) { 88 | finalConfiguration.baseClassName = fileName 89 | let model = ModelGenerator(json, finalConfiguration) 90 | models.append(contentsOf: model.generate()) 91 | } else { 92 | throw MultipleModelGeneratorError.invalidJSONFile(filename: url.lastPathComponent) 93 | } 94 | } 95 | return (merge(models: models), finalConfiguration) 96 | } 97 | 98 | /// If there is no file path, put the default path in, if it is relative fix it using the the file path. 99 | /// Additionally fix the path by adding `/` at the end. 100 | /// 101 | /// - Parameter fromBasePath: base path, which the json files are fetched from. 102 | /// - Returns: Path provided in the config file. 103 | static func generatePathToSave(fromBasePath: String, destinationPath: String) -> String { 104 | var finalPath = destinationPath 105 | if destinationPath.isEmpty { 106 | finalPath = fromBasePath 107 | } else if destinationPath.hasPrefix("/") == false { 108 | var basePath = fromBasePath 109 | if basePath.hasSuffix("/") == false { 110 | basePath += "/" 111 | } 112 | finalPath = basePath + destinationPath 113 | } 114 | 115 | if finalPath.hasSuffix("/") == false { 116 | finalPath += "/" 117 | } 118 | return finalPath 119 | } 120 | 121 | /// Fetch the files in the path, both normal JSON file and config files. 122 | /// 123 | /// - Parameter path: Path which has to be scanned. 124 | /// - Returns: An array of JSON files and a configuration file. 125 | static func filesIn(path: String) -> (files: [String], configFile: String?) { 126 | let fileManager = FileManager.default 127 | let enumerator = fileManager.enumerator(atPath: path) 128 | var jsonFiles = [String]() 129 | var configFile: String? 130 | while let element = enumerator?.nextObject() as? String { 131 | if element.hasSuffix("json") { 132 | if element == ".config.json" || element == "test_config.json" { 133 | configFile = path + "/" + element 134 | } else { 135 | jsonFiles.append(path + "/" + element) 136 | } 137 | } 138 | } 139 | return (jsonFiles, configFile) 140 | } 141 | 142 | /// Load configuration file from the provided JSON. 143 | /// 144 | /// - Parameter fromJSON: JSON file with configuration properties. 145 | /// - Returns: Configuration model. 146 | /// - Throws: `MultipleModelGeneratorError.configInvalid` error. 147 | static func loadConfiguration(fromJSON: JSON) -> ModelGenerationConfiguration { 148 | var constructType = ConstructType.classType 149 | if let type = fromJSON["construct_type"].string, type == "struct" { 150 | constructType = ConstructType.structType 151 | } 152 | var accessControl = AccessControl.internal 153 | if let string = fromJSON["access_control"].string, let value = AccessControl(rawValue: string) { 154 | accessControl = value 155 | } 156 | 157 | let initialiserParameter = fromJSON["initaliser_needed"].bool 158 | let initialisersNeeded = initialiserParameter != nil ? initialiserParameter! : true 159 | let jsonLibrary = JSONMappingMethod.swiftNormal 160 | let config = ModelGenerationConfiguration(filePath: fromJSON["destination_path"].string ?? "", 161 | baseClassName: "", 162 | authorName: fromJSON["author_name"].string, 163 | companyName: fromJSON["company_name"].string, 164 | prefix: fromJSON["prefix"].string, 165 | constructType: constructType, 166 | accessControl: accessControl, 167 | modelMappingLibrary: jsonLibrary, 168 | separateCodingKeys: fromJSON["separate_coding_keys"].boolValue, 169 | variablesOptional: fromJSON["variable_option"].boolValue, 170 | shouldGenerateInitMethod: initialisersNeeded) 171 | return config 172 | } 173 | 174 | /// Merge the models into sensible models. 175 | /// 176 | /// - Parameter models: List of suggested models. 177 | /// - Returns: Reduced set of fisible models. 178 | static func merge(models: [ModelFile]) -> [ModelFile] { 179 | // If there are no models or a single model we do not care. 180 | if models.count <= 1 { 181 | return models 182 | } 183 | 184 | // This is an array to keep a track of the models we need to return. 185 | var modelsToReturn = [ModelFile]() 186 | 187 | // We need to group models by their filename to simplify merging. 188 | let groupedModels = groupByName(models: models) 189 | 190 | for models in groupedModels { 191 | if models.count <= 1 { 192 | modelsToReturn.append(contentsOf: models) 193 | } else { 194 | var sourceJSON = [JSON]() 195 | for model in models { 196 | sourceJSON.append(model.sourceJSON) 197 | } 198 | // Take the JSON of the files and merge the models (this might generate further dependencies) 199 | let combinedJSON = JSONHelper.reduce(sourceJSON) 200 | 201 | let currentConfig = (models.first?.configuration)! 202 | var fileName = (models.first?.fileName)! 203 | 204 | // When being merged take out the prefixes to get the name without prefix for the generator. 205 | if let prefix = currentConfig.prefix, let range = fileName.range(of: prefix) { 206 | fileName = fileName.replacingOccurrences(of: prefix, with: "", options: .literal, range: range) 207 | } 208 | 209 | let model = ModelGenerator(combinedJSON, (models.first?.configuration)!) 210 | let newModels = model.generateModelForJSON(combinedJSON, fileName, false) 211 | for newModel in newModels where newModel.fileName == (models.first?.fileName)! { 212 | modelsToReturn.append(newModel) 213 | break 214 | } 215 | } 216 | } 217 | return modelsToReturn 218 | } 219 | 220 | /// Load a JSON file from the file at the given path. 221 | /// 222 | /// - Parameter fromFile: Filepath for the JSON file. 223 | /// - Returns: JSON object or nil. 224 | static func loadJSON(fromFile: String) -> JSON? { 225 | let fileManager = FileManager.default 226 | if fileManager.fileExists(atPath: fromFile) == false { 227 | return nil 228 | } 229 | let url = URL(fileURLWithPath: fromFile) 230 | do { 231 | let jsonData = try Data(contentsOf: url, options: Data.ReadingOptions.uncached) 232 | return try JSON(data: jsonData) 233 | } catch { 234 | return nil 235 | } 236 | } 237 | 238 | static func groupByName(models: [ModelFile]) -> [[ModelFile]] { 239 | var modelGroups = [String: [ModelFile]]() 240 | for model in models { 241 | let key = model.fileName 242 | if modelGroups.index(forKey: key) != nil { 243 | modelGroups[key]?.append(model) 244 | } else { 245 | modelGroups[key] = [model] 246 | } 247 | } 248 | return modelGroups.compactMap { $1 } 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /Core/Generator/NameGenerator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NameGenerator.swift 3 | // SwiftyJSONAccelerator 4 | // 5 | // Created by Karthikeya Udupa on 01/06/16. 6 | // Copyright © 2016 Karthikeya Udupa K M. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// A structure to store the various kinds of string name generation functions for classes and variables. 12 | struct NameGenerator { 13 | /// Generates/fixes a classname based on the string and suffix e.g. "KT"+"ClassNameSentenceCase". Replaces invalid characters. 14 | /// 15 | /// - Parameters: 16 | /// - className: Name of the class, will be converted to sentence case. 17 | /// - prefix: Suffix that has to be appended to the class. 18 | /// - isTopLevelObject: Indicates if the object is the root of the JSON. 19 | /// - Returns: A generated string representing the name of the class in the model. 20 | static func fixClassName(_ className: String, _ prefix: String?, _ isTopLevelObject: Bool) -> String { 21 | // If it is not a top level object, it is already formatted (since it is a property) 22 | var formattedClassName = isTopLevelObject ? fixVariableName(className) : className 23 | formattedClassName.uppercaseFirst() 24 | formattedClassName.appendPrefix(prefix) 25 | return formattedClassName 26 | } 27 | 28 | /// Generates/fixes a variable name in sentence case with the first letter as lowercase. Replaces invalid names and swift keywords. 29 | /// Ensures all caps are maintained if previously set in the name. 30 | /// 31 | /// - Parameter variableName: Name of the variable in the JSON 32 | /// - Returns: A generated string representation of the variable name. 33 | static func fixVariableName(_ variableName: String) -> String { 34 | var tmpVariableName = replaceKeywords(variableName) 35 | tmpVariableName.replaceOccurrencesOfStringsWithString(["-", "_"], " ") 36 | tmpVariableName.trim() 37 | 38 | var finalVariableName = "" 39 | for (index, var element) in tmpVariableName.components(separatedBy: " ").enumerated() { 40 | index == 0 ? element.lowercaseFirst() : element.uppercaseFirst() 41 | finalVariableName.append(element) 42 | } 43 | return finalVariableName 44 | } 45 | 46 | /// Cross checks the current name against a possible set of keywords, this list is no where 47 | /// extensive, but it is not meant to be, user should be able to do this in the unlikely 48 | /// case it happens. 49 | /// 50 | /// - Parameter currentName: The current name which has to be checked. 51 | /// - Returns: New name for the variable. 52 | static func replaceKeywords(_ currentName: String) -> String { 53 | /// Swift keywords from https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html#ID413 as of 2020-10-10 54 | /// Does not include the "sometimes" keywords 55 | /// Thanks to a PR from @TheMadBug. 56 | let swiftKeywords: Set = [ 57 | "associatedtype", "class", "deinit", 58 | "enum", "extension", "fileprivate", 59 | "func", "import", "init", 60 | "inout", "internal", "let", 61 | "open", "operator", "private", 62 | "protocol", "public", "rethrows", 63 | "static", "struct", "subscript", 64 | "typealias", "var", 65 | "break", "case", "continue", 66 | "default", "defer", "do", 67 | "else", "fallthrough", "for", 68 | "guard", "if", "in", 69 | "repeat", "return", "switch", 70 | "where", "while", 71 | "as", "Any", "catch", 72 | "false", "is", "nil", 73 | "super", "self", "Self", 74 | "throw", "throws", "true", 75 | "try", 76 | ] 77 | 78 | var keywordsWithReplacements: [String: String] = [:] 79 | for keyword in swiftKeywords { 80 | keywordsWithReplacements[keyword] = "\(keyword)Value" 81 | } 82 | 83 | if let value = keywordsWithReplacements[currentName] { 84 | return value 85 | } 86 | return currentName 87 | } 88 | 89 | /// Generate the key for the given variable. 90 | /// 91 | /// - Parameters: 92 | /// - _: Name of the class. 93 | /// - variableName: Name of the Varible. 94 | /// - Returns: The name for the key for the variable in the given class. 95 | static func variableKey(_: String, _ variableName: String) -> String { 96 | return "SerializationKeys.\(variableName)" 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Core/Helpers/JSONHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSONHelper.swift 3 | // SwiftyJSONAccelerator 4 | // 5 | // Created by Karthik on 16/10/2015. 6 | // Copyright © 2015 Karthikeya Udupa K M. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | 12 | /// A structure to store JSON parsed response in a systematic manner 13 | struct JSONParserResponse { 14 | let parsedObject: AnyObject? 15 | let error: NSError? 16 | 17 | /// Provides a easy way to know if the response is valid or not. 18 | var isValid: Bool { 19 | return parsedObject != nil 20 | } 21 | } 22 | 23 | /// Provide helpers to handle JSON content that the user provided. 24 | enum JSONHelper { 25 | /// Validate if the string that is provided can be converted into a valid JSON. 26 | /// 27 | /// - Parameter jsonString: Input string that is to be checked as JSON. 28 | /// - Returns: Bool indicating if it is a JSON or NSError with the error about the validation. 29 | static func isStringValidJSON(_ jsonString: String?) -> JSONParserResponse { 30 | return convertToObject(jsonString) 31 | } 32 | 33 | /// Convert the given string into an object. 34 | /// 35 | /// - Parameter jsonString: Input string that needs to be converted. 36 | /// - Returns: `JSONParserResponse` which contains the parsed object or the error. 37 | static func convertToObject(_ jsonString: String?) -> JSONParserResponse { 38 | guard let jsonValueString = jsonString else { return JSONParserResponse(parsedObject: nil, error: nil) } 39 | let jsonData = jsonValueString.data(using: String.Encoding.utf8)! 40 | do { 41 | let object = try JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions.allowFragments) 42 | return JSONParserResponse(parsedObject: object as AnyObject?, error: nil) 43 | } catch let error as NSError { 44 | return JSONParserResponse(parsedObject: nil, error: error) 45 | } 46 | } 47 | 48 | /// Formats the given string into beautiful JSON with indentation. 49 | /// 50 | /// - Parameter jsonString: JSON string that has to be formatted. 51 | /// - Returns: String with JSON but well formatted. 52 | static func prettyJSON(_ jsonString: String?) -> String? { 53 | let response = convertToObject(jsonString) 54 | if response.isValid { 55 | return prettyJSON(object: response.parsedObject) 56 | } 57 | return nil 58 | } 59 | 60 | /// Format the given Object into beautiful JSON with indentation. 61 | /// 62 | /// - Parameter passedObject: Object that has to be formatted. 63 | /// - Returns: String with JSON but well formatted. 64 | static func prettyJSON(object passedObject: AnyObject?) -> String? { 65 | guard let object = passedObject else { return nil } 66 | 67 | do { 68 | let data = try JSONSerialization.data(withJSONObject: object, options: JSONSerialization.WritingOptions.prettyPrinted) 69 | return String(data: data, encoding: String.Encoding.utf8) 70 | } catch { 71 | return nil 72 | } 73 | } 74 | 75 | /// Reduce an array of JSON objects to a single JSON object with all possible keys (merge all keys into one single object). 76 | /// 77 | /// - Parameter items: An array of JSON items that have to be reduced. 78 | /// - Returns: Reduced JSON with the common key/value pairs. 79 | static func reduce(_ items: [JSON]) -> JSON { 80 | return items.reduce([:]) { source, item -> JSON in 81 | var finalObject = source 82 | for (key, jsonValue) in item { 83 | if let newValue = jsonValue.dictionary { 84 | finalObject[key] = reduce([JSON(newValue), finalObject[key]]) 85 | } else if let newValue = jsonValue.array, newValue.first != nil && (newValue.first!.dictionary != nil || newValue.first!.array != nil) { 86 | finalObject[key] = JSON([reduce(newValue + finalObject[key].arrayValue)]) 87 | // swiftlint:disable all 88 | // swift-format-ignore 89 | } else if jsonValue != JSON.null || !finalObject[key].exists() { 90 | finalObject[key] = jsonValue 91 | } 92 | // swiftlint:enable all 93 | } 94 | return finalObject 95 | } 96 | } 97 | } 98 | 99 | // Helper methods for JSON Object 100 | extension JSON { 101 | /// Extensive value types with differentiation between the number types. 102 | /// 103 | /// - Returns: Value type of the JSON value 104 | func detailedValueType() -> VariableType { 105 | switch type { 106 | case .string: 107 | return .string 108 | case .bool: 109 | return .bool 110 | case .array: 111 | return .array 112 | case .number: 113 | switch CFNumberGetType(numberValue as CFNumber) { 114 | case .sInt8Type, 115 | .sInt16Type, 116 | .sInt32Type, 117 | .sInt64Type, 118 | .charType, 119 | .shortType, 120 | .intType, 121 | .longType, 122 | .longLongType, 123 | .cfIndexType, 124 | .nsIntegerType: 125 | return .int 126 | case .float32Type, 127 | .float64Type, 128 | .floatType, 129 | .cgFloatType, 130 | .doubleType: 131 | return .float 132 | // Covers any future types for CFNumber. 133 | @unknown default: 134 | return .float 135 | } 136 | case .null: 137 | return .null 138 | default: 139 | return .object 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /Core/Helpers/StringExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+Helpers.swift 3 | // SwiftyJSONAccelerator 4 | // 5 | // Created by Karthikeya Udupa on 01/06/16. 6 | // Copyright © 2016 Karthikeya Udupa K M. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // A structure to manage the position of the character in a string-line. 12 | public struct CharacterPosition { 13 | var character: String 14 | var line: Int 15 | var column: Int 16 | } 17 | 18 | // Extension for string to provide helper method to generate names. 19 | public extension String { 20 | /// Fetches the first character of the string. 21 | var firstChar: String { 22 | return String(self.prefix(1)) 23 | } 24 | 25 | /// Makes the first character of the string uppercase. 26 | mutating func uppercaseFirst() { 27 | self = firstChar.uppercased() + String(dropFirst()) 28 | } 29 | 30 | /// Makes the first character of the string lowercase. 31 | mutating func lowercaseFirst() { 32 | self = firstChar.lowercased() + String(dropFirst()) 33 | } 34 | 35 | /// Replace occurrence of multiple strings with a single string. 36 | /// 37 | /// - Parameters: 38 | /// - strings: String to replace. 39 | /// - replacementString: String to replace with. 40 | mutating func replaceOccurrencesOfStringsWithString(_ strings: [String], _ replacementString: String) { 41 | for string in strings { 42 | self = replacingOccurrences(of: string, with: replacementString) 43 | } 44 | } 45 | 46 | /// Removes whitespace and newline at the ends. 47 | mutating func trim() { 48 | self = trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) 49 | } 50 | 51 | /// Appends an optional to the string. 52 | /// 53 | /// - Parameter prefix: String to append. 54 | mutating func appendPrefix(_ prefix: String?) { 55 | if let checkedPrefix = prefix { 56 | self = checkedPrefix + self 57 | } 58 | } 59 | 60 | func characterRowAndLineAt(position: Int) -> CharacterPosition { 61 | var lineNumber = 0 62 | var characterPosition = 0 63 | for line in components(separatedBy: "\n") { 64 | lineNumber += 1 65 | var columnNumber = 0 66 | for column in line { 67 | characterPosition += 1 68 | columnNumber += 1 69 | if characterPosition == position { 70 | return CharacterPosition(character: String(column), line: lineNumber, column: columnNumber) 71 | } 72 | } 73 | characterPosition += 1 74 | if characterPosition == position { 75 | return CharacterPosition(character: "\n", line: lineNumber, column: columnNumber + 1) 76 | } 77 | } 78 | return CharacterPosition(character: "", line: 0, column: 0) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Core/Template/BaseTemplate.txt: -------------------------------------------------------------------------------- 1 | // 2 | // {OBJECT_NAME}.swift 3 | // 4 | // Created by __NAME__ on {DATE} 5 | // Copyright (c) __MyCompanyName__. All rights reserved. 6 | // 7 | 8 | import Foundation 9 | 10 | {ACCESS_CONTROL}{OBJECT_KIND} {OBJECT_NAME}: Codable { 11 | 12 | enum CodingKeys: String, CodingKey { 13 | {STRING_CONSTANT} 14 | } 15 | 16 | {DECLARATION} 17 | {INITIALIZER_FUNCTION_DECLRATION} 18 | {INITIALISER_FUNCTION_ASSIGNMENT} 19 | {INITIALISER_FUNCTION_END} 20 | {ACCESS_CONTROL}{REQUIRED}init(from decoder: Decoder) throws { 21 | let container = try decoder.container(keyedBy: CodingKeys.self) 22 | {INITIALIZER} 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'cocoapods' 4 | gem 'synx' 5 | gem 'xcpretty' 6 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.5) 5 | rexml 6 | activesupport (6.1.7) 7 | concurrent-ruby (~> 1.0, >= 1.0.2) 8 | i18n (>= 1.6, < 2) 9 | minitest (>= 5.1) 10 | tzinfo (~> 2.0) 11 | zeitwerk (~> 2.3) 12 | addressable (2.8.1) 13 | public_suffix (>= 2.0.2, < 6.0) 14 | algoliasearch (1.27.5) 15 | httpclient (~> 2.8, >= 2.8.3) 16 | json (>= 1.5.1) 17 | atomos (0.1.3) 18 | claide (1.1.0) 19 | clamp (0.6.5) 20 | cocoapods (1.11.3) 21 | addressable (~> 2.8) 22 | claide (>= 1.0.2, < 2.0) 23 | cocoapods-core (= 1.11.3) 24 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 25 | cocoapods-downloader (>= 1.4.0, < 2.0) 26 | cocoapods-plugins (>= 1.0.0, < 2.0) 27 | cocoapods-search (>= 1.0.0, < 2.0) 28 | cocoapods-trunk (>= 1.4.0, < 2.0) 29 | cocoapods-try (>= 1.1.0, < 2.0) 30 | colored2 (~> 3.1) 31 | escape (~> 0.0.4) 32 | fourflusher (>= 2.3.0, < 3.0) 33 | gh_inspector (~> 1.0) 34 | molinillo (~> 0.8.0) 35 | nap (~> 1.0) 36 | ruby-macho (>= 1.0, < 3.0) 37 | xcodeproj (>= 1.21.0, < 2.0) 38 | cocoapods-core (1.11.3) 39 | activesupport (>= 5.0, < 7) 40 | addressable (~> 2.8) 41 | algoliasearch (~> 1.0) 42 | concurrent-ruby (~> 1.1) 43 | fuzzy_match (~> 2.0.4) 44 | nap (~> 1.0) 45 | netrc (~> 0.11) 46 | public_suffix (~> 4.0) 47 | typhoeus (~> 1.0) 48 | cocoapods-deintegrate (1.0.5) 49 | cocoapods-downloader (1.6.3) 50 | cocoapods-plugins (1.0.0) 51 | nap 52 | cocoapods-search (1.0.1) 53 | cocoapods-trunk (1.6.0) 54 | nap (>= 0.8, < 2.0) 55 | netrc (~> 0.11) 56 | cocoapods-try (1.2.0) 57 | colored2 (3.1.2) 58 | colorize (0.8.1) 59 | concurrent-ruby (1.1.10) 60 | escape (0.0.4) 61 | ethon (0.15.0) 62 | ffi (>= 1.15.0) 63 | ffi (1.15.5) 64 | fourflusher (2.3.1) 65 | fuzzy_match (2.0.4) 66 | gh_inspector (1.1.3) 67 | httpclient (2.8.3) 68 | i18n (1.12.0) 69 | concurrent-ruby (~> 1.0) 70 | json (2.6.2) 71 | minitest (5.16.3) 72 | molinillo (0.8.0) 73 | nanaimo (0.3.0) 74 | nap (1.1.0) 75 | netrc (0.11.0) 76 | public_suffix (4.0.7) 77 | rexml (3.2.5) 78 | rouge (2.0.7) 79 | ruby-macho (2.5.1) 80 | synx (0.2.1) 81 | clamp (~> 0.6) 82 | colorize (~> 0.7) 83 | xcodeproj (~> 1.0) 84 | typhoeus (1.4.0) 85 | ethon (>= 0.9.0) 86 | tzinfo (2.0.5) 87 | concurrent-ruby (~> 1.0) 88 | xcodeproj (1.22.0) 89 | CFPropertyList (>= 2.3.3, < 4.0) 90 | atomos (~> 0.1.3) 91 | claide (>= 1.0.2, < 2.0) 92 | colored2 (~> 3.1) 93 | nanaimo (~> 0.3.0) 94 | rexml (~> 3.2.4) 95 | xcpretty (0.3.0) 96 | rouge (~> 2.0.7) 97 | zeitwerk (2.6.1) 98 | 99 | PLATFORMS 100 | ruby 101 | 102 | DEPENDENCIES 103 | cocoapods 104 | synx 105 | xcpretty 106 | 107 | BUNDLED WITH 108 | 2.0.2 109 | -------------------------------------------------------------------------------- /Graphics/SwiftyJSON.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insanoid/SwiftyJSONAccelerator/af702ae3fdf691bda8813c3a890bf1ea7bd14a6f/Graphics/SwiftyJSON.png -------------------------------------------------------------------------------- /Graphics/SwiftyJSON.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insanoid/SwiftyJSONAccelerator/af702ae3fdf691bda8813c3a890bf1ea7bd14a6f/Graphics/SwiftyJSON.psd -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | ## Copyright (c) Karthikeya Udupa ([karthikeyaudupa@gmail.com](mailto:karthikeyaudupa@gmail.com)) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | WORKSPACE = SwiftyJSONAccelerator.xcworkspace 2 | PROJECT = SwiftyJSONAccelerator.xcodeproj 3 | TEMPORARY_FOLDER?=/tmp/SwiftyJSONAccelerator.dst/ 4 | BUILD_TOOL?=xcodebuild 5 | PIPE_FAIL = set -o pipefail && 6 | XCPRETTY = | xcpretty -s 7 | 8 | APP_SCHEME = "SwiftyJSONAccelerator" 9 | APP_NAME = SwiftyJSONAccelerator.app 10 | APP_INSTALLATION_PATH = /Applications/ 11 | 12 | XCODEFLAGS_APP=-workspace $(WORKSPACE) \ 13 | -scheme $(APP_SCHEME) \ 14 | CODE_SIGN_IDENTITY="" \ 15 | CODE_SIGNING_REQUIRED=NO \ 16 | CONFIGURATION_BUILD_DIR=$(TEMPORARY_FOLDER) 17 | 18 | # Format the folder structure. 19 | synxify: 20 | synx -p \ 21 | -e "SwiftyJSONAccelerator/UI/External-Libraries" \ 22 | $(PROJECT) 23 | 24 | # Format the folder structure. 25 | lint: 26 | swiftlint autocorrect 27 | swiftlint 28 | swiftformat . 29 | 30 | # Clean the projects. 31 | clean: 32 | @rm -rf "$(TEMPORARY_FOLDER)" 33 | @$(BUILD_TOOL) $(XCODEFLAGS_APP) -configuration Debug clean $(XCPRETTY) 34 | @$(BUILD_TOOL) $(XCODEFLAGS_APP) -configuration Release clean $(XCPRETTY) 35 | @$(BUILD_TOOL) $(XCODEFLAGS_APP) -configuration Test clean $(XCPRETTY) 36 | 37 | # Run test for the app. 38 | test: 39 | @$(BUILD_TOOL) $(XCODEFLAGS_APP) -configuration Test | tee xcodebuild.log $(XCPRETTY) 40 | @slather coverage --show --html --scheme $(APP_SCHEME) $(PROJECT) 41 | @rm xcodebuild.log 42 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.14' 2 | use_frameworks! 3 | 4 | target 'SwiftyJSONAccelerator' do 5 | pod 'SwiftyJSON', '~> 4.0' 6 | target 'SwiftyJSONAcceleratorTests' do 7 | inherit! :search_paths 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Logo](https://raw.githubusercontent.com/insanoid/SwiftyJSONAccelerator/master/SwiftyJSONAccelerator/Support/Assets.xcassets/AppIcon.appiconset/Icon_32x32%402x.png) 2 | 3 | # SwiftyJSONAccelerator - MacOS app `Codeable` Model file Generator For Swift 5 4 | 5 | [![Build 6 | Status](https://travis-ci.org/insanoid/SwiftyJSONAccelerator.svg?branch=master)](https://travis-ci.org/insanoid/SwiftyJSONAccelerator) 7 | ![codecov](https://codecov.io/gh/insanoid/SwiftyJSONAccelerator/branch/master/graph/badge.svg) 8 | 9 | ## Installing & Building 10 | 11 | - **Building:** 12 | 13 | ``` 14 | pod install 15 | ``` 16 | 17 | You will also need to install `SwiftFormat` with `brew install swiftformat` and `SwiftLint` with `brew install swiftlint`. 18 | 19 | - **Download dmg:** [Download the .app (v2.2.0)](https://github.com/insanoid/SwiftyJSONAccelerator/releases/download/v2.2.0/SwiftyJSONAccelerator.app.zip) 20 | 21 | ## Common Issues 22 | 23 | - **`SwiftyJSONAccelerator` can't be opened because Apple cannot check it for malicious software.**: Run the following command `xattr -d com.apple.quarantine `. 24 | 25 | 26 | ## Features 27 | 28 | ![Logo](https://github.com/insanoid/SwiftyJSONAccelerator/blob/master/preview.png) 29 | 30 | A Swift model generator like the Objective-C [JSONAccelerator](http://nerdery.com/json-accelerator). Formats and generates models for the given JSON and also breaks them into files making it easy to manage and share between several models. 31 | 32 | - The models that are generated depend Swift's inbuilt `Codeable` feature making encoding and decoding objects a thing of the past. 33 | - Allows to opt for either optional or non-optional variables. 34 | - Allows an array of a certain object type with different properties to be merged into a single model with all properties. 35 | - Click `Load folder with JSON files + Config` to generate all possible models for given folder with JSON files, note this needs a `.config.json` as this uses the CLI logic internally. 36 | 37 | ## Contributions and Requests 38 | 39 | Any suggestions regarding code quality of the app, generated code's quality, Swift related improvements and pull requests are all very welcome. Please make sure you submit the pull request to the next release branch and not the master branch. 40 | 41 | - [Contributing Guidelines](.github/contributing.md) 42 | - [Code of Conduct](.github/CODE_OF_CONDUCT.md) 43 | - [MIT License](LICENSE.md) / [Karthikeya Udupa](https://karthikeya.co.uk) 44 | -------------------------------------------------------------------------------- /SwiftyJSONAccelerator.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 54; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1B83234B615A1E2C083165BC /* Pods_SwiftyJSONAcceleratorTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DD79F3F4CA34990ED57C901 /* Pods_SwiftyJSONAcceleratorTests.framework */; }; 11 | 233771D54E74BB0A23429F8A /* Pods_SwiftyJSONAccelerator.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9C761827F34F7ABFE85AF9E1 /* Pods_SwiftyJSONAccelerator.framework */; }; 12 | 93050DA222F54ADB00FBCA6B /* TestJSONFile.sample in Resources */ = {isa = PBXBuildFile; fileRef = 93050DA122F54ADB00FBCA6B /* TestJSONFile.sample */; }; 13 | 93050DA422F54B2800FBCA6B /* ModelGeneratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93050DA322F54B2800FBCA6B /* ModelGeneratorTests.swift */; }; 14 | 9341B64822F98EE30092D16D /* FileGeneratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9341B64722F98EE30092D16D /* FileGeneratorTests.swift */; }; 15 | 9341B64A22F9BC240092D16D /* MultipleModelGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9341B64922F9BC240092D16D /* MultipleModelGenerator.swift */; }; 16 | 9341B64B22F9BC550092D16D /* MultipleModelGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9341B64922F9BC240092D16D /* MultipleModelGenerator.swift */; }; 17 | 9341B64D22F9C43D0092D16D /* MultipleModelGeneratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9341B64C22F9C3950092D16D /* MultipleModelGeneratorTests.swift */; }; 18 | 9341B64E22F9C6350092D16D /* album.json in Resources */ = {isa = PBXBuildFile; fileRef = 93050D9D22F54AD100FBCA6B /* album.json */; }; 19 | 9341B64F22F9C6350092D16D /* community.json in Resources */ = {isa = PBXBuildFile; fileRef = 93050D9E22F54AD100FBCA6B /* community.json */; }; 20 | 9341B65022F9C6350092D16D /* photo.json in Resources */ = {isa = PBXBuildFile; fileRef = 93050D9F22F54AD100FBCA6B /* photo.json */; }; 21 | 9341B65122F9C6350092D16D /* test_config.json in Resources */ = {isa = PBXBuildFile; fileRef = 93050D9B22F54AD100FBCA6B /* test_config.json */; }; 22 | 9341B65222F9C6350092D16D /* user.json in Resources */ = {isa = PBXBuildFile; fileRef = 93050D9C22F54AD100FBCA6B /* user.json */; }; 23 | 937D9DF822E89CAC00A83D84 /* JSONHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 937D9DF522E89CAC00A83D84 /* JSONHelper.swift */; }; 24 | 937D9DF922E89CAC00A83D84 /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 937D9DF622E89CAC00A83D84 /* StringExtensions.swift */; }; 25 | 937D9DFC22E89F4800A83D84 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 937D9DFB22E89F4800A83D84 /* Constants.swift */; }; 26 | 93846D3A22E8BAE20051D564 /* StringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93846D3922E8BAE20051D564 /* StringTests.swift */; }; 27 | 93E0F10822E859D5008D3B16 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93E0F10722E859D5008D3B16 /* AppDelegate.swift */; }; 28 | 93E0F10A22E859D5008D3B16 /* SJEditorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93E0F10922E859D5008D3B16 /* SJEditorViewController.swift */; }; 29 | 93E0F10C22E859D6008D3B16 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 93E0F10B22E859D6008D3B16 /* Assets.xcassets */; }; 30 | 93E0F10F22E859D6008D3B16 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 93E0F10D22E859D6008D3B16 /* Main.storyboard */; }; 31 | 93EA46FC22E8896000A7B0DB /* SJTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93EA46FB22E8896000A7B0DB /* SJTextView.swift */; }; 32 | 93EA470022E8908A00A7B0DB /* LineNumberRulerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93EA46FF22E8908A00A7B0DB /* LineNumberRulerView.swift */; }; 33 | 93FBA66D22E9B8EA00015EFD /* ModalGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93FBA66C22E9B8EA00015EFD /* ModalGenerator.swift */; }; 34 | 93FBA67022E9B97B00015EFD /* ModelGenerationConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93FBA66F22E9B97A00015EFD /* ModelGenerationConfiguration.swift */; }; 35 | 93FBA67222E9BB4800015EFD /* ModelFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93FBA67122E9BB4800015EFD /* ModelFile.swift */; }; 36 | 93FBA67422E9BC5200015EFD /* PropertyComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93FBA67322E9BC5200015EFD /* PropertyComponent.swift */; }; 37 | 93FBA67622E9BCA100015EFD /* ModelComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93FBA67522E9BCA100015EFD /* ModelComponent.swift */; }; 38 | 93FBA67822E9BDA200015EFD /* NameGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93FBA67722E9BDA200015EFD /* NameGenerator.swift */; }; 39 | 93FBA67A22E9C2D700015EFD /* FileGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93FBA67922E9C2D700015EFD /* FileGenerator.swift */; }; 40 | 93FBA68422E9E5B100015EFD /* SwiftJSONModelFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93FBA68322E9E5B100015EFD /* SwiftJSONModelFile.swift */; }; 41 | 93FBA68622E9F40F00015EFD /* FileGeneratorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93FBA68522E9F40E00015EFD /* FileGeneratorExtension.swift */; }; 42 | 93FBA68722E9F70A00015EFD /* BaseTemplate.txt in Resources */ = {isa = PBXBuildFile; fileRef = 93FBA68222E9E05100015EFD /* BaseTemplate.txt */; }; 43 | 93FDFB4322F23942007D5498 /* FileGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93FBA67922E9C2D700015EFD /* FileGenerator.swift */; }; 44 | 93FDFB4422F23942007D5498 /* FileGeneratorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93FBA68522E9F40E00015EFD /* FileGeneratorExtension.swift */; }; 45 | 93FDFB4522F23942007D5498 /* ModalGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93FBA66C22E9B8EA00015EFD /* ModalGenerator.swift */; }; 46 | 93FDFB4622F23942007D5498 /* ModelGenerationConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93FBA66F22E9B97A00015EFD /* ModelGenerationConfiguration.swift */; }; 47 | 93FDFB4722F23942007D5498 /* NameGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93FBA67722E9BDA200015EFD /* NameGenerator.swift */; }; 48 | 93FDFB4822F23946007D5498 /* ModelComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93FBA67522E9BCA100015EFD /* ModelComponent.swift */; }; 49 | 93FDFB4922F23946007D5498 /* ModelFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93FBA67122E9BB4800015EFD /* ModelFile.swift */; }; 50 | 93FDFB4A22F23946007D5498 /* PropertyComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93FBA67322E9BC5200015EFD /* PropertyComponent.swift */; }; 51 | 93FDFB4B22F23946007D5498 /* SwiftJSONModelFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93FBA68322E9E5B100015EFD /* SwiftJSONModelFile.swift */; }; 52 | 93FDFB4C22F2394B007D5498 /* JSONHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 937D9DF522E89CAC00A83D84 /* JSONHelper.swift */; }; 53 | 93FDFB4D22F2394B007D5498 /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 937D9DF622E89CAC00A83D84 /* StringExtensions.swift */; }; 54 | 93FDFB4E22F23960007D5498 /* BaseTemplate.txt in Resources */ = {isa = PBXBuildFile; fileRef = 93FBA68222E9E05100015EFD /* BaseTemplate.txt */; }; 55 | 93FDFB4F22F23962007D5498 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 937D9DFB22E89F4800A83D84 /* Constants.swift */; }; 56 | 93FDFB5122F2BECD007D5498 /* JSONHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93FDFB5022F2BECD007D5498 /* JSONHelperTests.swift */; }; 57 | /* End PBXBuildFile section */ 58 | 59 | /* Begin PBXContainerItemProxy section */ 60 | 93E0F11722E859D6008D3B16 /* PBXContainerItemProxy */ = { 61 | isa = PBXContainerItemProxy; 62 | containerPortal = 93E0F0FC22E859D5008D3B16 /* Project object */; 63 | proxyType = 1; 64 | remoteGlobalIDString = 93E0F10322E859D5008D3B16; 65 | remoteInfo = SwiftyJSONAccelerator; 66 | }; 67 | /* End PBXContainerItemProxy section */ 68 | 69 | /* Begin PBXFileReference section */ 70 | 139667DBBC1C031005A38ECC /* Pods-SwiftyJSONAcceleratorTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftyJSONAcceleratorTests.release.xcconfig"; path = "Target Support Files/Pods-SwiftyJSONAcceleratorTests/Pods-SwiftyJSONAcceleratorTests.release.xcconfig"; sourceTree = ""; }; 71 | 2ABC7D6BA5C84B6E9C5E8547 /* Pods-SwiftyJSONAccelerator.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftyJSONAccelerator.debug.xcconfig"; path = "Target Support Files/Pods-SwiftyJSONAccelerator/Pods-SwiftyJSONAccelerator.debug.xcconfig"; sourceTree = ""; }; 72 | 5DD79F3F4CA34990ED57C901 /* Pods_SwiftyJSONAcceleratorTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftyJSONAcceleratorTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 73 | 93050D9B22F54AD100FBCA6B /* test_config.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = test_config.json; sourceTree = ""; }; 74 | 93050D9C22F54AD100FBCA6B /* user.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = user.json; sourceTree = ""; }; 75 | 93050D9D22F54AD100FBCA6B /* album.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = album.json; sourceTree = ""; }; 76 | 93050D9E22F54AD100FBCA6B /* community.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = community.json; sourceTree = ""; }; 77 | 93050D9F22F54AD100FBCA6B /* photo.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = photo.json; sourceTree = ""; }; 78 | 93050DA022F54AD100FBCA6B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 79 | 93050DA122F54ADB00FBCA6B /* TestJSONFile.sample */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = TestJSONFile.sample; sourceTree = ""; }; 80 | 93050DA322F54B2800FBCA6B /* ModelGeneratorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelGeneratorTests.swift; sourceTree = ""; }; 81 | 9341B64722F98EE30092D16D /* FileGeneratorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileGeneratorTests.swift; sourceTree = ""; }; 82 | 9341B64922F9BC240092D16D /* MultipleModelGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultipleModelGenerator.swift; sourceTree = ""; }; 83 | 9341B64C22F9C3950092D16D /* MultipleModelGeneratorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultipleModelGeneratorTests.swift; sourceTree = ""; }; 84 | 937D9DF522E89CAC00A83D84 /* JSONHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONHelper.swift; sourceTree = ""; }; 85 | 937D9DF622E89CAC00A83D84 /* StringExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringExtensions.swift; sourceTree = ""; }; 86 | 937D9DFB22E89F4800A83D84 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 87 | 93846D3922E8BAE20051D564 /* StringTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringTests.swift; sourceTree = ""; }; 88 | 93E0F10422E859D5008D3B16 /* SwiftyJSONAccelerator.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftyJSONAccelerator.app; sourceTree = BUILT_PRODUCTS_DIR; }; 89 | 93E0F10722E859D5008D3B16 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 90 | 93E0F10922E859D5008D3B16 /* SJEditorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SJEditorViewController.swift; sourceTree = ""; }; 91 | 93E0F10B22E859D6008D3B16 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 92 | 93E0F10E22E859D6008D3B16 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 93 | 93E0F11022E859D6008D3B16 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 94 | 93E0F11122E859D6008D3B16 /* SwiftyJSONAccelerator.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SwiftyJSONAccelerator.entitlements; sourceTree = ""; }; 95 | 93E0F11622E859D6008D3B16 /* SwiftyJSONAcceleratorTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftyJSONAcceleratorTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 96 | 93E0F11C22E859D6008D3B16 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 97 | 93EA46FB22E8896000A7B0DB /* SJTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SJTextView.swift; sourceTree = ""; }; 98 | 93EA46FF22E8908A00A7B0DB /* LineNumberRulerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineNumberRulerView.swift; sourceTree = ""; }; 99 | 93FBA66C22E9B8EA00015EFD /* ModalGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalGenerator.swift; sourceTree = ""; }; 100 | 93FBA66F22E9B97A00015EFD /* ModelGenerationConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModelGenerationConfiguration.swift; sourceTree = ""; }; 101 | 93FBA67122E9BB4800015EFD /* ModelFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelFile.swift; sourceTree = ""; }; 102 | 93FBA67322E9BC5200015EFD /* PropertyComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PropertyComponent.swift; sourceTree = ""; }; 103 | 93FBA67522E9BCA100015EFD /* ModelComponent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModelComponent.swift; sourceTree = ""; }; 104 | 93FBA67722E9BDA200015EFD /* NameGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NameGenerator.swift; sourceTree = ""; }; 105 | 93FBA67922E9C2D700015EFD /* FileGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileGenerator.swift; sourceTree = ""; }; 106 | 93FBA68222E9E05100015EFD /* BaseTemplate.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = BaseTemplate.txt; sourceTree = ""; }; 107 | 93FBA68322E9E5B100015EFD /* SwiftJSONModelFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftJSONModelFile.swift; sourceTree = ""; }; 108 | 93FBA68522E9F40E00015EFD /* FileGeneratorExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileGeneratorExtension.swift; sourceTree = ""; }; 109 | 93FDFB5022F2BECD007D5498 /* JSONHelperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONHelperTests.swift; sourceTree = ""; }; 110 | 9C761827F34F7ABFE85AF9E1 /* Pods_SwiftyJSONAccelerator.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftyJSONAccelerator.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 111 | 9C98DCCC52F206DFAE6F188A /* Pods-SwiftyJSONAcceleratorTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftyJSONAcceleratorTests.debug.xcconfig"; path = "Target Support Files/Pods-SwiftyJSONAcceleratorTests/Pods-SwiftyJSONAcceleratorTests.debug.xcconfig"; sourceTree = ""; }; 112 | DA85A8FD9057653442961237 /* Pods-SwiftyJSONAccelerator.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftyJSONAccelerator.release.xcconfig"; path = "Target Support Files/Pods-SwiftyJSONAccelerator/Pods-SwiftyJSONAccelerator.release.xcconfig"; sourceTree = ""; }; 113 | /* End PBXFileReference section */ 114 | 115 | /* Begin PBXFrameworksBuildPhase section */ 116 | 93E0F10122E859D5008D3B16 /* Frameworks */ = { 117 | isa = PBXFrameworksBuildPhase; 118 | buildActionMask = 2147483647; 119 | files = ( 120 | 233771D54E74BB0A23429F8A /* Pods_SwiftyJSONAccelerator.framework in Frameworks */, 121 | ); 122 | runOnlyForDeploymentPostprocessing = 0; 123 | }; 124 | 93E0F11322E859D6008D3B16 /* Frameworks */ = { 125 | isa = PBXFrameworksBuildPhase; 126 | buildActionMask = 2147483647; 127 | files = ( 128 | 1B83234B615A1E2C083165BC /* Pods_SwiftyJSONAcceleratorTests.framework in Frameworks */, 129 | ); 130 | runOnlyForDeploymentPostprocessing = 0; 131 | }; 132 | /* End PBXFrameworksBuildPhase section */ 133 | 134 | /* Begin PBXGroup section */ 135 | 432C91269AC646A242204E0B /* Pods */ = { 136 | isa = PBXGroup; 137 | children = ( 138 | 2ABC7D6BA5C84B6E9C5E8547 /* Pods-SwiftyJSONAccelerator.debug.xcconfig */, 139 | DA85A8FD9057653442961237 /* Pods-SwiftyJSONAccelerator.release.xcconfig */, 140 | 9C98DCCC52F206DFAE6F188A /* Pods-SwiftyJSONAcceleratorTests.debug.xcconfig */, 141 | 139667DBBC1C031005A38ECC /* Pods-SwiftyJSONAcceleratorTests.release.xcconfig */, 142 | ); 143 | path = Pods; 144 | sourceTree = ""; 145 | }; 146 | 93050D9822F54AD100FBCA6B /* Support Files */ = { 147 | isa = PBXGroup; 148 | children = ( 149 | 93050D9A22F54AD100FBCA6B /* MultipleModelTests */, 150 | 93050DA022F54AD100FBCA6B /* Info.plist */, 151 | 93050DA122F54ADB00FBCA6B /* TestJSONFile.sample */, 152 | ); 153 | path = "Support Files"; 154 | sourceTree = ""; 155 | }; 156 | 93050D9A22F54AD100FBCA6B /* MultipleModelTests */ = { 157 | isa = PBXGroup; 158 | children = ( 159 | 93050D9D22F54AD100FBCA6B /* album.json */, 160 | 93050D9E22F54AD100FBCA6B /* community.json */, 161 | 93050D9F22F54AD100FBCA6B /* photo.json */, 162 | 93050D9B22F54AD100FBCA6B /* test_config.json */, 163 | 93050D9C22F54AD100FBCA6B /* user.json */, 164 | ); 165 | path = MultipleModelTests; 166 | sourceTree = ""; 167 | }; 168 | 935F7E4822F38B650003F787 /* AppDelegate */ = { 169 | isa = PBXGroup; 170 | children = ( 171 | ); 172 | path = AppDelegate; 173 | sourceTree = ""; 174 | }; 175 | 937D9DF322E89CA200A83D84 /* Helpers */ = { 176 | isa = PBXGroup; 177 | children = ( 178 | 937D9DF522E89CAC00A83D84 /* JSONHelper.swift */, 179 | 937D9DF622E89CAC00A83D84 /* StringExtensions.swift */, 180 | ); 181 | path = Helpers; 182 | sourceTree = ""; 183 | }; 184 | 937D9DFA22E89F3000A83D84 /* Core */ = { 185 | isa = PBXGroup; 186 | children = ( 187 | 93FBA66A22E9B8A000015EFD /* Generator */, 188 | 937D9DF322E89CA200A83D84 /* Helpers */, 189 | 93FBA68022E9DFFC00015EFD /* Template */, 190 | 937D9DFB22E89F4800A83D84 /* Constants.swift */, 191 | ); 192 | path = Core; 193 | sourceTree = ""; 194 | }; 195 | 93E0F0FB22E859D5008D3B16 = { 196 | isa = PBXGroup; 197 | children = ( 198 | 937D9DFA22E89F3000A83D84 /* Core */, 199 | 9CC06C3DBF72A63524446DAC /* Frameworks */, 200 | 432C91269AC646A242204E0B /* Pods */, 201 | 93E0F10522E859D5008D3B16 /* Products */, 202 | 93E0F10622E859D5008D3B16 /* SwiftyJSONAccelerator */, 203 | 93E0F11922E859D6008D3B16 /* SwiftyJSONAcceleratorTests */, 204 | ); 205 | sourceTree = ""; 206 | }; 207 | 93E0F10522E859D5008D3B16 /* Products */ = { 208 | isa = PBXGroup; 209 | children = ( 210 | 93E0F10422E859D5008D3B16 /* SwiftyJSONAccelerator.app */, 211 | 93E0F11622E859D6008D3B16 /* SwiftyJSONAcceleratorTests.xctest */, 212 | ); 213 | name = Products; 214 | sourceTree = ""; 215 | }; 216 | 93E0F10622E859D5008D3B16 /* SwiftyJSONAccelerator */ = { 217 | isa = PBXGroup; 218 | children = ( 219 | 935F7E4822F38B650003F787 /* AppDelegate */, 220 | 93FBA66B22E9B8D100015EFD /* Support */, 221 | 93EA46F822E888F000A7B0DB /* UI */, 222 | ); 223 | path = SwiftyJSONAccelerator; 224 | sourceTree = ""; 225 | }; 226 | 93E0F11922E859D6008D3B16 /* SwiftyJSONAcceleratorTests */ = { 227 | isa = PBXGroup; 228 | children = ( 229 | 93050D9822F54AD100FBCA6B /* Support Files */, 230 | 9341B64722F98EE30092D16D /* FileGeneratorTests.swift */, 231 | 93E0F11C22E859D6008D3B16 /* Info.plist */, 232 | 93FDFB5022F2BECD007D5498 /* JSONHelperTests.swift */, 233 | 9341B64C22F9C3950092D16D /* MultipleModelGeneratorTests.swift */, 234 | 93050DA322F54B2800FBCA6B /* ModelGeneratorTests.swift */, 235 | 93846D3922E8BAE20051D564 /* StringTests.swift */, 236 | ); 237 | path = SwiftyJSONAcceleratorTests; 238 | sourceTree = ""; 239 | }; 240 | 93EA46F822E888F000A7B0DB /* UI */ = { 241 | isa = PBXGroup; 242 | children = ( 243 | 93EA470122E890A100A7B0DB /* External-Libraries */, 244 | 93E0F10922E859D5008D3B16 /* SJEditorViewController.swift */, 245 | 93EA46FB22E8896000A7B0DB /* SJTextView.swift */, 246 | ); 247 | path = UI; 248 | sourceTree = ""; 249 | }; 250 | 93EA470122E890A100A7B0DB /* External-Libraries */ = { 251 | isa = PBXGroup; 252 | children = ( 253 | 93EA46FF22E8908A00A7B0DB /* LineNumberRulerView.swift */, 254 | ); 255 | path = "External-Libraries"; 256 | sourceTree = ""; 257 | }; 258 | 93FBA66A22E9B8A000015EFD /* Generator */ = { 259 | isa = PBXGroup; 260 | children = ( 261 | 9341B64922F9BC240092D16D /* MultipleModelGenerator.swift */, 262 | 93FBA67B22E9C2FF00015EFD /* Model-File-Components */, 263 | 93FBA67922E9C2D700015EFD /* FileGenerator.swift */, 264 | 93FBA68522E9F40E00015EFD /* FileGeneratorExtension.swift */, 265 | 93FBA66C22E9B8EA00015EFD /* ModalGenerator.swift */, 266 | 93FBA66F22E9B97A00015EFD /* ModelGenerationConfiguration.swift */, 267 | 93FBA67722E9BDA200015EFD /* NameGenerator.swift */, 268 | ); 269 | path = Generator; 270 | sourceTree = ""; 271 | }; 272 | 93FBA66B22E9B8D100015EFD /* Support */ = { 273 | isa = PBXGroup; 274 | children = ( 275 | 93E0F10722E859D5008D3B16 /* AppDelegate.swift */, 276 | 93E0F10B22E859D6008D3B16 /* Assets.xcassets */, 277 | 93E0F11022E859D6008D3B16 /* Info.plist */, 278 | 93E0F10D22E859D6008D3B16 /* Main.storyboard */, 279 | 93E0F11122E859D6008D3B16 /* SwiftyJSONAccelerator.entitlements */, 280 | ); 281 | path = Support; 282 | sourceTree = ""; 283 | }; 284 | 93FBA67B22E9C2FF00015EFD /* Model-File-Components */ = { 285 | isa = PBXGroup; 286 | children = ( 287 | 93FBA67522E9BCA100015EFD /* ModelComponent.swift */, 288 | 93FBA67122E9BB4800015EFD /* ModelFile.swift */, 289 | 93FBA67322E9BC5200015EFD /* PropertyComponent.swift */, 290 | 93FBA68322E9E5B100015EFD /* SwiftJSONModelFile.swift */, 291 | ); 292 | path = "Model-File-Components"; 293 | sourceTree = ""; 294 | }; 295 | 93FBA68022E9DFFC00015EFD /* Template */ = { 296 | isa = PBXGroup; 297 | children = ( 298 | 93FBA68222E9E05100015EFD /* BaseTemplate.txt */, 299 | ); 300 | path = Template; 301 | sourceTree = ""; 302 | }; 303 | 9CC06C3DBF72A63524446DAC /* Frameworks */ = { 304 | isa = PBXGroup; 305 | children = ( 306 | 9C761827F34F7ABFE85AF9E1 /* Pods_SwiftyJSONAccelerator.framework */, 307 | 5DD79F3F4CA34990ED57C901 /* Pods_SwiftyJSONAcceleratorTests.framework */, 308 | ); 309 | name = Frameworks; 310 | sourceTree = ""; 311 | }; 312 | /* End PBXGroup section */ 313 | 314 | /* Begin PBXNativeTarget section */ 315 | 93E0F10322E859D5008D3B16 /* SwiftyJSONAccelerator */ = { 316 | isa = PBXNativeTarget; 317 | buildConfigurationList = 93E0F11F22E859D6008D3B16 /* Build configuration list for PBXNativeTarget "SwiftyJSONAccelerator" */; 318 | buildPhases = ( 319 | 9D410F870EFF22F4E4BEE9C9 /* [CP] Check Pods Manifest.lock */, 320 | 93E0F10022E859D5008D3B16 /* Sources */, 321 | 93E0F10122E859D5008D3B16 /* Frameworks */, 322 | 93E0F10222E859D5008D3B16 /* Resources */, 323 | 935F7E4722F3852E0003F787 /* Swiftlint */, 324 | 937D9DF122E8962F00A83D84 /* Swiftformat */, 325 | A802EAF639CE91FCE6AB21CD /* [CP] Embed Pods Frameworks */, 326 | ); 327 | buildRules = ( 328 | ); 329 | dependencies = ( 330 | ); 331 | name = SwiftyJSONAccelerator; 332 | productName = SwiftyJSONAccelerator; 333 | productReference = 93E0F10422E859D5008D3B16 /* SwiftyJSONAccelerator.app */; 334 | productType = "com.apple.product-type.application"; 335 | }; 336 | 93E0F11522E859D6008D3B16 /* SwiftyJSONAcceleratorTests */ = { 337 | isa = PBXNativeTarget; 338 | buildConfigurationList = 93E0F12222E859D6008D3B16 /* Build configuration list for PBXNativeTarget "SwiftyJSONAcceleratorTests" */; 339 | buildPhases = ( 340 | 3C651344C29B072C5A2C8417 /* [CP] Check Pods Manifest.lock */, 341 | 93E0F11222E859D6008D3B16 /* Sources */, 342 | 93E0F11322E859D6008D3B16 /* Frameworks */, 343 | 93E0F11422E859D6008D3B16 /* Resources */, 344 | ); 345 | buildRules = ( 346 | ); 347 | dependencies = ( 348 | 93E0F11822E859D6008D3B16 /* PBXTargetDependency */, 349 | ); 350 | name = SwiftyJSONAcceleratorTests; 351 | productName = SwiftyJSONAcceleratorTests; 352 | productReference = 93E0F11622E859D6008D3B16 /* SwiftyJSONAcceleratorTests.xctest */; 353 | productType = "com.apple.product-type.bundle.unit-test"; 354 | }; 355 | /* End PBXNativeTarget section */ 356 | 357 | /* Begin PBXProject section */ 358 | 93E0F0FC22E859D5008D3B16 /* Project object */ = { 359 | isa = PBXProject; 360 | attributes = { 361 | LastSwiftUpdateCheck = 1030; 362 | LastUpgradeCheck = 1340; 363 | ORGANIZATIONNAME = "Karthikeya Udupa"; 364 | TargetAttributes = { 365 | 93E0F10322E859D5008D3B16 = { 366 | CreatedOnToolsVersion = 10.3; 367 | }; 368 | 93E0F11522E859D6008D3B16 = { 369 | CreatedOnToolsVersion = 10.3; 370 | TestTargetID = 93E0F10322E859D5008D3B16; 371 | }; 372 | }; 373 | }; 374 | buildConfigurationList = 93E0F0FF22E859D5008D3B16 /* Build configuration list for PBXProject "SwiftyJSONAccelerator" */; 375 | compatibilityVersion = "Xcode 9.3"; 376 | developmentRegion = en; 377 | hasScannedForEncodings = 0; 378 | knownRegions = ( 379 | en, 380 | Base, 381 | ); 382 | mainGroup = 93E0F0FB22E859D5008D3B16; 383 | productRefGroup = 93E0F10522E859D5008D3B16 /* Products */; 384 | projectDirPath = ""; 385 | projectRoot = ""; 386 | targets = ( 387 | 93E0F10322E859D5008D3B16 /* SwiftyJSONAccelerator */, 388 | 93E0F11522E859D6008D3B16 /* SwiftyJSONAcceleratorTests */, 389 | ); 390 | }; 391 | /* End PBXProject section */ 392 | 393 | /* Begin PBXResourcesBuildPhase section */ 394 | 93E0F10222E859D5008D3B16 /* Resources */ = { 395 | isa = PBXResourcesBuildPhase; 396 | buildActionMask = 2147483647; 397 | files = ( 398 | 93E0F10C22E859D6008D3B16 /* Assets.xcassets in Resources */, 399 | 93FBA68722E9F70A00015EFD /* BaseTemplate.txt in Resources */, 400 | 93E0F10F22E859D6008D3B16 /* Main.storyboard in Resources */, 401 | ); 402 | runOnlyForDeploymentPostprocessing = 0; 403 | }; 404 | 93E0F11422E859D6008D3B16 /* Resources */ = { 405 | isa = PBXResourcesBuildPhase; 406 | buildActionMask = 2147483647; 407 | files = ( 408 | 9341B65222F9C6350092D16D /* user.json in Resources */, 409 | 9341B65022F9C6350092D16D /* photo.json in Resources */, 410 | 9341B65122F9C6350092D16D /* test_config.json in Resources */, 411 | 93FDFB4E22F23960007D5498 /* BaseTemplate.txt in Resources */, 412 | 9341B64E22F9C6350092D16D /* album.json in Resources */, 413 | 93050DA222F54ADB00FBCA6B /* TestJSONFile.sample in Resources */, 414 | 9341B64F22F9C6350092D16D /* community.json in Resources */, 415 | ); 416 | runOnlyForDeploymentPostprocessing = 0; 417 | }; 418 | /* End PBXResourcesBuildPhase section */ 419 | 420 | /* Begin PBXShellScriptBuildPhase section */ 421 | 3C651344C29B072C5A2C8417 /* [CP] Check Pods Manifest.lock */ = { 422 | isa = PBXShellScriptBuildPhase; 423 | buildActionMask = 2147483647; 424 | files = ( 425 | ); 426 | inputFileListPaths = ( 427 | ); 428 | inputPaths = ( 429 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 430 | "${PODS_ROOT}/Manifest.lock", 431 | ); 432 | name = "[CP] Check Pods Manifest.lock"; 433 | outputFileListPaths = ( 434 | ); 435 | outputPaths = ( 436 | "$(DERIVED_FILE_DIR)/Pods-SwiftyJSONAcceleratorTests-checkManifestLockResult.txt", 437 | ); 438 | runOnlyForDeploymentPostprocessing = 0; 439 | shellPath = /bin/sh; 440 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 441 | showEnvVarsInLog = 0; 442 | }; 443 | 935F7E4722F3852E0003F787 /* Swiftlint */ = { 444 | isa = PBXShellScriptBuildPhase; 445 | alwaysOutOfDate = 1; 446 | buildActionMask = 12; 447 | files = ( 448 | ); 449 | inputFileListPaths = ( 450 | ); 451 | inputPaths = ( 452 | ); 453 | name = Swiftlint; 454 | outputFileListPaths = ( 455 | ); 456 | outputPaths = ( 457 | ); 458 | runOnlyForDeploymentPostprocessing = 0; 459 | shellPath = /bin/sh; 460 | shellScript = "if which swiftlint >/dev/null; then\nswiftlint autocorrect --config .swiftlint.yml\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; 461 | }; 462 | 937D9DF122E8962F00A83D84 /* Swiftformat */ = { 463 | isa = PBXShellScriptBuildPhase; 464 | buildActionMask = 12; 465 | files = ( 466 | ); 467 | inputFileListPaths = ( 468 | ); 469 | inputPaths = ( 470 | ); 471 | name = Swiftformat; 472 | outputFileListPaths = ( 473 | ); 474 | outputPaths = ( 475 | ); 476 | runOnlyForDeploymentPostprocessing = 0; 477 | shellPath = /bin/sh; 478 | shellScript = "if which swiftformat >/dev/null; then\nswiftformat .\nelse\necho \"warning: SwiftFormat not installed, download from https://github.com/nicklockwood/SwiftFormat\"\nfi\n"; 479 | }; 480 | 9D410F870EFF22F4E4BEE9C9 /* [CP] Check Pods Manifest.lock */ = { 481 | isa = PBXShellScriptBuildPhase; 482 | buildActionMask = 2147483647; 483 | files = ( 484 | ); 485 | inputFileListPaths = ( 486 | ); 487 | inputPaths = ( 488 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 489 | "${PODS_ROOT}/Manifest.lock", 490 | ); 491 | name = "[CP] Check Pods Manifest.lock"; 492 | outputFileListPaths = ( 493 | ); 494 | outputPaths = ( 495 | "$(DERIVED_FILE_DIR)/Pods-SwiftyJSONAccelerator-checkManifestLockResult.txt", 496 | ); 497 | runOnlyForDeploymentPostprocessing = 0; 498 | shellPath = /bin/sh; 499 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 500 | showEnvVarsInLog = 0; 501 | }; 502 | A802EAF639CE91FCE6AB21CD /* [CP] Embed Pods Frameworks */ = { 503 | isa = PBXShellScriptBuildPhase; 504 | buildActionMask = 2147483647; 505 | files = ( 506 | ); 507 | inputFileListPaths = ( 508 | "${PODS_ROOT}/Target Support Files/Pods-SwiftyJSONAccelerator/Pods-SwiftyJSONAccelerator-frameworks-${CONFIGURATION}-input-files.xcfilelist", 509 | ); 510 | name = "[CP] Embed Pods Frameworks"; 511 | outputFileListPaths = ( 512 | "${PODS_ROOT}/Target Support Files/Pods-SwiftyJSONAccelerator/Pods-SwiftyJSONAccelerator-frameworks-${CONFIGURATION}-output-files.xcfilelist", 513 | ); 514 | runOnlyForDeploymentPostprocessing = 0; 515 | shellPath = /bin/sh; 516 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SwiftyJSONAccelerator/Pods-SwiftyJSONAccelerator-frameworks.sh\"\n"; 517 | showEnvVarsInLog = 0; 518 | }; 519 | /* End PBXShellScriptBuildPhase section */ 520 | 521 | /* Begin PBXSourcesBuildPhase section */ 522 | 93E0F10022E859D5008D3B16 /* Sources */ = { 523 | isa = PBXSourcesBuildPhase; 524 | buildActionMask = 2147483647; 525 | files = ( 526 | 93FBA67022E9B97B00015EFD /* ModelGenerationConfiguration.swift in Sources */, 527 | 93FBA67A22E9C2D700015EFD /* FileGenerator.swift in Sources */, 528 | 93EA46FC22E8896000A7B0DB /* SJTextView.swift in Sources */, 529 | 93E0F10A22E859D5008D3B16 /* SJEditorViewController.swift in Sources */, 530 | 93FBA67222E9BB4800015EFD /* ModelFile.swift in Sources */, 531 | 9341B64A22F9BC240092D16D /* MultipleModelGenerator.swift in Sources */, 532 | 93FBA68422E9E5B100015EFD /* SwiftJSONModelFile.swift in Sources */, 533 | 937D9DF822E89CAC00A83D84 /* JSONHelper.swift in Sources */, 534 | 93FBA66D22E9B8EA00015EFD /* ModalGenerator.swift in Sources */, 535 | 93FBA67822E9BDA200015EFD /* NameGenerator.swift in Sources */, 536 | 93EA470022E8908A00A7B0DB /* LineNumberRulerView.swift in Sources */, 537 | 937D9DFC22E89F4800A83D84 /* Constants.swift in Sources */, 538 | 93FBA68622E9F40F00015EFD /* FileGeneratorExtension.swift in Sources */, 539 | 93E0F10822E859D5008D3B16 /* AppDelegate.swift in Sources */, 540 | 937D9DF922E89CAC00A83D84 /* StringExtensions.swift in Sources */, 541 | 93FBA67422E9BC5200015EFD /* PropertyComponent.swift in Sources */, 542 | 93FBA67622E9BCA100015EFD /* ModelComponent.swift in Sources */, 543 | ); 544 | runOnlyForDeploymentPostprocessing = 0; 545 | }; 546 | 93E0F11222E859D6008D3B16 /* Sources */ = { 547 | isa = PBXSourcesBuildPhase; 548 | buildActionMask = 2147483647; 549 | files = ( 550 | 93FDFB4D22F2394B007D5498 /* StringExtensions.swift in Sources */, 551 | 93FDFB4722F23942007D5498 /* NameGenerator.swift in Sources */, 552 | 93FDFB4B22F23946007D5498 /* SwiftJSONModelFile.swift in Sources */, 553 | 93846D3A22E8BAE20051D564 /* StringTests.swift in Sources */, 554 | 93FDFB4622F23942007D5498 /* ModelGenerationConfiguration.swift in Sources */, 555 | 9341B64B22F9BC550092D16D /* MultipleModelGenerator.swift in Sources */, 556 | 93FDFB4922F23946007D5498 /* ModelFile.swift in Sources */, 557 | 93FDFB4A22F23946007D5498 /* PropertyComponent.swift in Sources */, 558 | 93FDFB5122F2BECD007D5498 /* JSONHelperTests.swift in Sources */, 559 | 93FDFB4F22F23962007D5498 /* Constants.swift in Sources */, 560 | 93FDFB4322F23942007D5498 /* FileGenerator.swift in Sources */, 561 | 93FDFB4422F23942007D5498 /* FileGeneratorExtension.swift in Sources */, 562 | 93050DA422F54B2800FBCA6B /* ModelGeneratorTests.swift in Sources */, 563 | 9341B64D22F9C43D0092D16D /* MultipleModelGeneratorTests.swift in Sources */, 564 | 93FDFB4C22F2394B007D5498 /* JSONHelper.swift in Sources */, 565 | 9341B64822F98EE30092D16D /* FileGeneratorTests.swift in Sources */, 566 | 93FDFB4522F23942007D5498 /* ModalGenerator.swift in Sources */, 567 | 93FDFB4822F23946007D5498 /* ModelComponent.swift in Sources */, 568 | ); 569 | runOnlyForDeploymentPostprocessing = 0; 570 | }; 571 | /* End PBXSourcesBuildPhase section */ 572 | 573 | /* Begin PBXTargetDependency section */ 574 | 93E0F11822E859D6008D3B16 /* PBXTargetDependency */ = { 575 | isa = PBXTargetDependency; 576 | target = 93E0F10322E859D5008D3B16 /* SwiftyJSONAccelerator */; 577 | targetProxy = 93E0F11722E859D6008D3B16 /* PBXContainerItemProxy */; 578 | }; 579 | /* End PBXTargetDependency section */ 580 | 581 | /* Begin PBXVariantGroup section */ 582 | 93E0F10D22E859D6008D3B16 /* Main.storyboard */ = { 583 | isa = PBXVariantGroup; 584 | children = ( 585 | 93E0F10E22E859D6008D3B16 /* Base */, 586 | ); 587 | name = Main.storyboard; 588 | path = .; 589 | sourceTree = ""; 590 | }; 591 | /* End PBXVariantGroup section */ 592 | 593 | /* Begin XCBuildConfiguration section */ 594 | 93E0F11D22E859D6008D3B16 /* Debug */ = { 595 | isa = XCBuildConfiguration; 596 | buildSettings = { 597 | ALWAYS_SEARCH_USER_PATHS = NO; 598 | CLANG_ANALYZER_NONNULL = YES; 599 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 600 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 601 | CLANG_CXX_LIBRARY = "libc++"; 602 | CLANG_ENABLE_MODULES = YES; 603 | CLANG_ENABLE_OBJC_ARC = YES; 604 | CLANG_ENABLE_OBJC_WEAK = YES; 605 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 606 | CLANG_WARN_BOOL_CONVERSION = YES; 607 | CLANG_WARN_COMMA = YES; 608 | CLANG_WARN_CONSTANT_CONVERSION = YES; 609 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 610 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 611 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 612 | CLANG_WARN_EMPTY_BODY = YES; 613 | CLANG_WARN_ENUM_CONVERSION = YES; 614 | CLANG_WARN_INFINITE_RECURSION = YES; 615 | CLANG_WARN_INT_CONVERSION = YES; 616 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 617 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 618 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 619 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 620 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 621 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 622 | CLANG_WARN_STRICT_PROTOTYPES = YES; 623 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 624 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 625 | CLANG_WARN_UNREACHABLE_CODE = YES; 626 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 627 | CODE_SIGN_IDENTITY = "Mac Developer"; 628 | COPY_PHASE_STRIP = NO; 629 | DEBUG_INFORMATION_FORMAT = dwarf; 630 | ENABLE_STRICT_OBJC_MSGSEND = YES; 631 | ENABLE_TESTABILITY = YES; 632 | GCC_C_LANGUAGE_STANDARD = gnu11; 633 | GCC_DYNAMIC_NO_PIC = NO; 634 | GCC_NO_COMMON_BLOCKS = YES; 635 | GCC_OPTIMIZATION_LEVEL = 0; 636 | GCC_PREPROCESSOR_DEFINITIONS = ( 637 | "DEBUG=1", 638 | "$(inherited)", 639 | ); 640 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 641 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 642 | GCC_WARN_UNDECLARED_SELECTOR = YES; 643 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 644 | GCC_WARN_UNUSED_FUNCTION = YES; 645 | GCC_WARN_UNUSED_VARIABLE = YES; 646 | MACOSX_DEPLOYMENT_TARGET = 10.14; 647 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 648 | MTL_FAST_MATH = YES; 649 | ONLY_ACTIVE_ARCH = YES; 650 | SDKROOT = macosx; 651 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 652 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 653 | }; 654 | name = Debug; 655 | }; 656 | 93E0F11E22E859D6008D3B16 /* Release */ = { 657 | isa = XCBuildConfiguration; 658 | buildSettings = { 659 | ALWAYS_SEARCH_USER_PATHS = NO; 660 | CLANG_ANALYZER_NONNULL = YES; 661 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 662 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 663 | CLANG_CXX_LIBRARY = "libc++"; 664 | CLANG_ENABLE_MODULES = YES; 665 | CLANG_ENABLE_OBJC_ARC = YES; 666 | CLANG_ENABLE_OBJC_WEAK = YES; 667 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 668 | CLANG_WARN_BOOL_CONVERSION = YES; 669 | CLANG_WARN_COMMA = YES; 670 | CLANG_WARN_CONSTANT_CONVERSION = YES; 671 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 672 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 673 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 674 | CLANG_WARN_EMPTY_BODY = YES; 675 | CLANG_WARN_ENUM_CONVERSION = YES; 676 | CLANG_WARN_INFINITE_RECURSION = YES; 677 | CLANG_WARN_INT_CONVERSION = YES; 678 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 679 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 680 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 681 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 682 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 683 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 684 | CLANG_WARN_STRICT_PROTOTYPES = YES; 685 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 686 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 687 | CLANG_WARN_UNREACHABLE_CODE = YES; 688 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 689 | CODE_SIGN_IDENTITY = "Mac Developer"; 690 | COPY_PHASE_STRIP = NO; 691 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 692 | ENABLE_NS_ASSERTIONS = NO; 693 | ENABLE_STRICT_OBJC_MSGSEND = YES; 694 | GCC_C_LANGUAGE_STANDARD = gnu11; 695 | GCC_NO_COMMON_BLOCKS = YES; 696 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 697 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 698 | GCC_WARN_UNDECLARED_SELECTOR = YES; 699 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 700 | GCC_WARN_UNUSED_FUNCTION = YES; 701 | GCC_WARN_UNUSED_VARIABLE = YES; 702 | MACOSX_DEPLOYMENT_TARGET = 10.14; 703 | MTL_ENABLE_DEBUG_INFO = NO; 704 | MTL_FAST_MATH = YES; 705 | SDKROOT = macosx; 706 | SWIFT_COMPILATION_MODE = wholemodule; 707 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 708 | }; 709 | name = Release; 710 | }; 711 | 93E0F12022E859D6008D3B16 /* Debug */ = { 712 | isa = XCBuildConfiguration; 713 | baseConfigurationReference = 2ABC7D6BA5C84B6E9C5E8547 /* Pods-SwiftyJSONAccelerator.debug.xcconfig */; 714 | buildSettings = { 715 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 716 | CODE_SIGN_ENTITLEMENTS = SwiftyJSONAccelerator/Support/SwiftyJSONAccelerator.entitlements; 717 | CODE_SIGN_IDENTITY = "-"; 718 | CODE_SIGN_STYLE = Automatic; 719 | COMBINE_HIDPI_IMAGES = YES; 720 | CURRENT_PROJECT_VERSION = 14; 721 | DEVELOPMENT_TEAM = UYGU8PDBPS; 722 | INFOPLIST_FILE = SwiftyJSONAccelerator/Support/Info.plist; 723 | LD_RUNPATH_SEARCH_PATHS = ( 724 | "$(inherited)", 725 | "@executable_path/../Frameworks", 726 | ); 727 | MACOSX_DEPLOYMENT_TARGET = 10.11; 728 | MARKETING_VERSION = 2.2; 729 | PRODUCT_BUNDLE_IDENTIFIER = com.karthik.SwiftyJSONAccelerator; 730 | PRODUCT_NAME = "$(TARGET_NAME)"; 731 | SWIFT_VERSION = 5.0; 732 | }; 733 | name = Debug; 734 | }; 735 | 93E0F12122E859D6008D3B16 /* Release */ = { 736 | isa = XCBuildConfiguration; 737 | baseConfigurationReference = DA85A8FD9057653442961237 /* Pods-SwiftyJSONAccelerator.release.xcconfig */; 738 | buildSettings = { 739 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 740 | CODE_SIGN_ENTITLEMENTS = SwiftyJSONAccelerator/Support/SwiftyJSONAccelerator.entitlements; 741 | CODE_SIGN_IDENTITY = "-"; 742 | CODE_SIGN_STYLE = Automatic; 743 | COMBINE_HIDPI_IMAGES = YES; 744 | CURRENT_PROJECT_VERSION = 14; 745 | DEVELOPMENT_TEAM = UYGU8PDBPS; 746 | INFOPLIST_FILE = SwiftyJSONAccelerator/Support/Info.plist; 747 | LD_RUNPATH_SEARCH_PATHS = ( 748 | "$(inherited)", 749 | "@executable_path/../Frameworks", 750 | ); 751 | MACOSX_DEPLOYMENT_TARGET = 10.11; 752 | MARKETING_VERSION = 2.2; 753 | PRODUCT_BUNDLE_IDENTIFIER = com.karthik.SwiftyJSONAccelerator; 754 | PRODUCT_NAME = "$(TARGET_NAME)"; 755 | SWIFT_VERSION = 5.0; 756 | }; 757 | name = Release; 758 | }; 759 | 93E0F12322E859D6008D3B16 /* Debug */ = { 760 | isa = XCBuildConfiguration; 761 | baseConfigurationReference = 9C98DCCC52F206DFAE6F188A /* Pods-SwiftyJSONAcceleratorTests.debug.xcconfig */; 762 | buildSettings = { 763 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 764 | BUNDLE_LOADER = "$(TEST_HOST)"; 765 | CODE_SIGN_IDENTITY = "Mac Developer"; 766 | CODE_SIGN_STYLE = Automatic; 767 | COMBINE_HIDPI_IMAGES = YES; 768 | DEVELOPMENT_TEAM = UYGU8PDBPS; 769 | INFOPLIST_FILE = SwiftyJSONAcceleratorTests/Info.plist; 770 | LD_RUNPATH_SEARCH_PATHS = ( 771 | "$(inherited)", 772 | "@executable_path/../Frameworks", 773 | "@loader_path/../Frameworks", 774 | ); 775 | PRODUCT_BUNDLE_IDENTIFIER = com.karthik.SwiftyJSONAccelerator.SwiftyJSONAcceleratorTests; 776 | PRODUCT_NAME = "$(TARGET_NAME)"; 777 | PROVISIONING_PROFILE_SPECIFIER = ""; 778 | SWIFT_VERSION = 5.0; 779 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SwiftyJSONAccelerator.app/Contents/MacOS/SwiftyJSONAccelerator"; 780 | }; 781 | name = Debug; 782 | }; 783 | 93E0F12422E859D6008D3B16 /* Release */ = { 784 | isa = XCBuildConfiguration; 785 | baseConfigurationReference = 139667DBBC1C031005A38ECC /* Pods-SwiftyJSONAcceleratorTests.release.xcconfig */; 786 | buildSettings = { 787 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 788 | BUNDLE_LOADER = "$(TEST_HOST)"; 789 | CODE_SIGN_IDENTITY = "Mac Developer"; 790 | CODE_SIGN_STYLE = Automatic; 791 | COMBINE_HIDPI_IMAGES = YES; 792 | DEVELOPMENT_TEAM = UYGU8PDBPS; 793 | INFOPLIST_FILE = SwiftyJSONAcceleratorTests/Info.plist; 794 | LD_RUNPATH_SEARCH_PATHS = ( 795 | "$(inherited)", 796 | "@executable_path/../Frameworks", 797 | "@loader_path/../Frameworks", 798 | ); 799 | PRODUCT_BUNDLE_IDENTIFIER = com.karthik.SwiftyJSONAccelerator.SwiftyJSONAcceleratorTests; 800 | PRODUCT_NAME = "$(TARGET_NAME)"; 801 | PROVISIONING_PROFILE_SPECIFIER = ""; 802 | SWIFT_VERSION = 5.0; 803 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SwiftyJSONAccelerator.app/Contents/MacOS/SwiftyJSONAccelerator"; 804 | }; 805 | name = Release; 806 | }; 807 | /* End XCBuildConfiguration section */ 808 | 809 | /* Begin XCConfigurationList section */ 810 | 93E0F0FF22E859D5008D3B16 /* Build configuration list for PBXProject "SwiftyJSONAccelerator" */ = { 811 | isa = XCConfigurationList; 812 | buildConfigurations = ( 813 | 93E0F11D22E859D6008D3B16 /* Debug */, 814 | 93E0F11E22E859D6008D3B16 /* Release */, 815 | ); 816 | defaultConfigurationIsVisible = 0; 817 | defaultConfigurationName = Release; 818 | }; 819 | 93E0F11F22E859D6008D3B16 /* Build configuration list for PBXNativeTarget "SwiftyJSONAccelerator" */ = { 820 | isa = XCConfigurationList; 821 | buildConfigurations = ( 822 | 93E0F12022E859D6008D3B16 /* Debug */, 823 | 93E0F12122E859D6008D3B16 /* Release */, 824 | ); 825 | defaultConfigurationIsVisible = 0; 826 | defaultConfigurationName = Release; 827 | }; 828 | 93E0F12222E859D6008D3B16 /* Build configuration list for PBXNativeTarget "SwiftyJSONAcceleratorTests" */ = { 829 | isa = XCConfigurationList; 830 | buildConfigurations = ( 831 | 93E0F12322E859D6008D3B16 /* Debug */, 832 | 93E0F12422E859D6008D3B16 /* Release */, 833 | ); 834 | defaultConfigurationIsVisible = 0; 835 | defaultConfigurationName = Release; 836 | }; 837 | /* End XCConfigurationList section */ 838 | }; 839 | rootObject = 93E0F0FC22E859D5008D3B16 /* Project object */; 840 | } 841 | -------------------------------------------------------------------------------- /SwiftyJSONAccelerator.xcodeproj/xcshareddata/xcschemes/SwiftyJSONAccelerator.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 34 | 35 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 57 | 63 | 64 | 65 | 66 | 67 | 77 | 79 | 85 | 86 | 87 | 88 | 94 | 96 | 102 | 103 | 104 | 105 | 107 | 108 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /SwiftyJSONAccelerator/Support/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SwiftyJSONAccelerator 4 | // 5 | // Created by Karthikeya Udupa on 24/07/2019. 6 | // Copyright © 2019 Karthikeya Udupa. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDelegate { 13 | func applicationDidFinishLaunching(_: Notification) { 14 | NSUserNotificationCenter.default.delegate = self 15 | } 16 | 17 | func applicationWillTerminate(_: Notification) {} 18 | 19 | func userNotificationCenter(_: NSUserNotificationCenter, shouldPresent _: NSUserNotification) -> Bool { 20 | // Since our notification is to be shown when app is in focus, this function always returns true. 21 | return true 22 | } 23 | 24 | func userNotificationCenter(_: NSUserNotificationCenter, didActivate notification: NSUserNotification) { 25 | guard let pathString = notification.userInfo![Constants.filePathKey] as? String else { 26 | return 27 | } 28 | // Open the path mentioned in the notification. 29 | let urlPath = URL(fileURLWithPath: pathString, isDirectory: true) 30 | if notification.activationType == .actionButtonClicked { 31 | NSWorkspace.shared.activateFileViewerSelecting([urlPath]) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /SwiftyJSONAccelerator/Support/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "Icon_16x16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "Icon_16x16@2x.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "Icon_32x32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "Icon_32x32@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "Icon_128x128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "Icon_128x128@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "Icon_256x256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "Icon_256x256@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "Icon_512x512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "Icon_512x512@2x.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /SwiftyJSONAccelerator/Support/Assets.xcassets/AppIcon.appiconset/Icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insanoid/SwiftyJSONAccelerator/af702ae3fdf691bda8813c3a890bf1ea7bd14a6f/SwiftyJSONAccelerator/Support/Assets.xcassets/AppIcon.appiconset/Icon_128x128.png -------------------------------------------------------------------------------- /SwiftyJSONAccelerator/Support/Assets.xcassets/AppIcon.appiconset/Icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insanoid/SwiftyJSONAccelerator/af702ae3fdf691bda8813c3a890bf1ea7bd14a6f/SwiftyJSONAccelerator/Support/Assets.xcassets/AppIcon.appiconset/Icon_128x128@2x.png -------------------------------------------------------------------------------- /SwiftyJSONAccelerator/Support/Assets.xcassets/AppIcon.appiconset/Icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insanoid/SwiftyJSONAccelerator/af702ae3fdf691bda8813c3a890bf1ea7bd14a6f/SwiftyJSONAccelerator/Support/Assets.xcassets/AppIcon.appiconset/Icon_16x16.png -------------------------------------------------------------------------------- /SwiftyJSONAccelerator/Support/Assets.xcassets/AppIcon.appiconset/Icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insanoid/SwiftyJSONAccelerator/af702ae3fdf691bda8813c3a890bf1ea7bd14a6f/SwiftyJSONAccelerator/Support/Assets.xcassets/AppIcon.appiconset/Icon_16x16@2x.png -------------------------------------------------------------------------------- /SwiftyJSONAccelerator/Support/Assets.xcassets/AppIcon.appiconset/Icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insanoid/SwiftyJSONAccelerator/af702ae3fdf691bda8813c3a890bf1ea7bd14a6f/SwiftyJSONAccelerator/Support/Assets.xcassets/AppIcon.appiconset/Icon_256x256.png -------------------------------------------------------------------------------- /SwiftyJSONAccelerator/Support/Assets.xcassets/AppIcon.appiconset/Icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insanoid/SwiftyJSONAccelerator/af702ae3fdf691bda8813c3a890bf1ea7bd14a6f/SwiftyJSONAccelerator/Support/Assets.xcassets/AppIcon.appiconset/Icon_256x256@2x.png -------------------------------------------------------------------------------- /SwiftyJSONAccelerator/Support/Assets.xcassets/AppIcon.appiconset/Icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insanoid/SwiftyJSONAccelerator/af702ae3fdf691bda8813c3a890bf1ea7bd14a6f/SwiftyJSONAccelerator/Support/Assets.xcassets/AppIcon.appiconset/Icon_32x32.png -------------------------------------------------------------------------------- /SwiftyJSONAccelerator/Support/Assets.xcassets/AppIcon.appiconset/Icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insanoid/SwiftyJSONAccelerator/af702ae3fdf691bda8813c3a890bf1ea7bd14a6f/SwiftyJSONAccelerator/Support/Assets.xcassets/AppIcon.appiconset/Icon_32x32@2x.png -------------------------------------------------------------------------------- /SwiftyJSONAccelerator/Support/Assets.xcassets/AppIcon.appiconset/Icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insanoid/SwiftyJSONAccelerator/af702ae3fdf691bda8813c3a890bf1ea7bd14a6f/SwiftyJSONAccelerator/Support/Assets.xcassets/AppIcon.appiconset/Icon_512x512.png -------------------------------------------------------------------------------- /SwiftyJSONAccelerator/Support/Assets.xcassets/AppIcon.appiconset/Icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insanoid/SwiftyJSONAccelerator/af702ae3fdf691bda8813c3a890bf1ea7bd14a6f/SwiftyJSONAccelerator/Support/Assets.xcassets/AppIcon.appiconset/Icon_512x512@2x.png -------------------------------------------------------------------------------- /SwiftyJSONAccelerator/Support/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /SwiftyJSONAccelerator/Support/Assets.xcassets/failure.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "failure.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "failure@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "failure@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /SwiftyJSONAccelerator/Support/Assets.xcassets/failure.imageset/failure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insanoid/SwiftyJSONAccelerator/af702ae3fdf691bda8813c3a890bf1ea7bd14a6f/SwiftyJSONAccelerator/Support/Assets.xcassets/failure.imageset/failure.png -------------------------------------------------------------------------------- /SwiftyJSONAccelerator/Support/Assets.xcassets/failure.imageset/failure@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insanoid/SwiftyJSONAccelerator/af702ae3fdf691bda8813c3a890bf1ea7bd14a6f/SwiftyJSONAccelerator/Support/Assets.xcassets/failure.imageset/failure@2x.png -------------------------------------------------------------------------------- /SwiftyJSONAccelerator/Support/Assets.xcassets/failure.imageset/failure@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insanoid/SwiftyJSONAccelerator/af702ae3fdf691bda8813c3a890bf1ea7bd14a6f/SwiftyJSONAccelerator/Support/Assets.xcassets/failure.imageset/failure@3x.png -------------------------------------------------------------------------------- /SwiftyJSONAccelerator/Support/Assets.xcassets/success.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "success.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "success@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "success@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /SwiftyJSONAccelerator/Support/Assets.xcassets/success.imageset/success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insanoid/SwiftyJSONAccelerator/af702ae3fdf691bda8813c3a890bf1ea7bd14a6f/SwiftyJSONAccelerator/Support/Assets.xcassets/success.imageset/success.png -------------------------------------------------------------------------------- /SwiftyJSONAccelerator/Support/Assets.xcassets/success.imageset/success@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insanoid/SwiftyJSONAccelerator/af702ae3fdf691bda8813c3a890bf1ea7bd14a6f/SwiftyJSONAccelerator/Support/Assets.xcassets/success.imageset/success@2x.png -------------------------------------------------------------------------------- /SwiftyJSONAccelerator/Support/Assets.xcassets/success.imageset/success@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insanoid/SwiftyJSONAccelerator/af702ae3fdf691bda8813c3a890bf1ea7bd14a6f/SwiftyJSONAccelerator/Support/Assets.xcassets/success.imageset/success@3x.png -------------------------------------------------------------------------------- /SwiftyJSONAccelerator/Support/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | LSApplicationCategoryType 24 | public.app-category.developer-tools 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSHumanReadableCopyright 28 | Copyright © 2019 Karthikeya Udupa. All rights reserved. 29 | NSMainStoryboardFile 30 | Main 31 | NSPrincipalClass 32 | NSApplication 33 | NSUserNotificationAlertStyle 34 | alert 35 | 36 | 37 | -------------------------------------------------------------------------------- /SwiftyJSONAccelerator/Support/SwiftyJSONAccelerator.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /SwiftyJSONAccelerator/UI/External-Libraries/LineNumberRulerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LineNumberRulerView.swift 3 | // LineNumber 4 | // 5 | // Copyright (c) 2015 Yichi Zhang. All rights reserved. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a 8 | // copy of this software and associated documentation files (the "Software"), 9 | // to deal in the Software without restriction, including without limitation 10 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | // and/or sell copies of the Software, and to permit persons to whom the 12 | // Software is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 20 | // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | // DEALINGS IN THE SOFTWARE. 24 | // 25 | 26 | import AppKit 27 | import Foundation 28 | import ObjectiveC 29 | 30 | var LineNumberViewAssocObjKey: UInt8 = 0 31 | 32 | extension NSTextView { 33 | var lineNumberView: LineNumberRulerView { 34 | get { 35 | return objc_getAssociatedObject(self, &LineNumberViewAssocObjKey) as! LineNumberRulerView 36 | } 37 | set { 38 | objc_setAssociatedObject(self, &LineNumberViewAssocObjKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 39 | } 40 | } 41 | 42 | func lnv_setUpLineNumberView() { 43 | if font == nil { 44 | font = NSFont.systemFont(ofSize: 12) 45 | } 46 | 47 | if let scrollView = enclosingScrollView { 48 | lineNumberView = LineNumberRulerView(textView: self) 49 | 50 | scrollView.verticalRulerView = lineNumberView 51 | scrollView.hasVerticalRuler = true 52 | scrollView.rulersVisible = true 53 | } 54 | 55 | postsFrameChangedNotifications = true 56 | 57 | NotificationCenter.default.addObserver(self, selector: #selector(NSTextView.lnv_framDidChange(_:)), name: NSView.frameDidChangeNotification, object: self) 58 | NotificationCenter.default.addObserver(self, selector: #selector(NSTextView.lnv_textDidChange(_:)), name: NSText.didChangeNotification, object: self) 59 | } 60 | 61 | @objc func lnv_framDidChange(_: Notification) { 62 | lineNumberView.needsDisplay = true 63 | } 64 | 65 | @objc func lnv_textDidChange(_: Notification) { 66 | lineNumberView.needsDisplay = true 67 | } 68 | } 69 | 70 | class LineNumberRulerView: NSRulerView { 71 | var font: NSFont! { 72 | didSet { 73 | needsDisplay = true 74 | } 75 | } 76 | 77 | init(textView: NSTextView) { 78 | super.init(scrollView: textView.enclosingScrollView!, orientation: NSRulerView.Orientation.verticalRuler) 79 | font = textView.font ?? NSFont.systemFont(ofSize: NSFont.smallSystemFontSize) 80 | clientView = textView 81 | 82 | ruleThickness = 40 83 | } 84 | 85 | @available(*, unavailable) 86 | required init(coder _: NSCoder) { 87 | fatalError("init(coder:) has not been implemented") 88 | } 89 | 90 | override func drawHashMarksAndLabels(in _: NSRect) { 91 | if let textView = clientView as? NSTextView { 92 | if let layoutManager = textView.layoutManager { 93 | let relativePoint = convert(NSZeroPoint, from: textView) 94 | let lineNumberAttributes = [NSAttributedString.Key.font.rawValue: textView.font!, NSAttributedString.Key.foregroundColor: NSColor.gray] as! [NSAttributedString.Key: Any] 95 | let drawLineNumber = { (lineNumberString: String, y: CGFloat) in 96 | let attString = NSAttributedString(string: lineNumberString, attributes: lineNumberAttributes) 97 | let x = 35 - attString.size().width 98 | attString.draw(at: NSPoint(x: x, y: relativePoint.y + y)) 99 | } 100 | 101 | let visibleGlyphRange = layoutManager.glyphRange(forBoundingRect: textView.visibleRect, in: textView.textContainer!) 102 | let firstVisibleGlyphCharacterIndex = layoutManager.characterIndexForGlyph(at: visibleGlyphRange.location) 103 | 104 | let newLineRegex = try! NSRegularExpression(pattern: "\n", options: []) 105 | // The line number for the first visible line 106 | var lineNumber = newLineRegex.numberOfMatches(in: textView.string, options: [], range: NSMakeRange(0, firstVisibleGlyphCharacterIndex)) + 1 107 | 108 | var glyphIndexForStringLine = visibleGlyphRange.location 109 | 110 | // Go through each line in the string. 111 | while glyphIndexForStringLine < NSMaxRange(visibleGlyphRange) { 112 | // Range of current line in the string. 113 | let characterRangeForStringLine = (textView.string as NSString).lineRange( 114 | for: NSMakeRange(layoutManager.characterIndexForGlyph(at: glyphIndexForStringLine), 0) 115 | ) 116 | let glyphRangeForStringLine = layoutManager.glyphRange(forCharacterRange: characterRangeForStringLine, actualCharacterRange: nil) 117 | 118 | var glyphIndexForGlyphLine = glyphIndexForStringLine 119 | var glyphLineCount = 0 120 | 121 | while glyphIndexForGlyphLine < NSMaxRange(glyphRangeForStringLine) { 122 | // See if the current line in the string spread across 123 | // several lines of glyphs 124 | var effectiveRange = NSRange(location: 0, length: 0) 125 | 126 | // Range of current "line of glyphs". If a line is wrapped, 127 | // then it will have more than one "line of glyphs" 128 | let lineRect = layoutManager.lineFragmentRect(forGlyphAt: glyphIndexForGlyphLine, effectiveRange: &effectiveRange, withoutAdditionalLayout: true) 129 | 130 | if glyphLineCount > 0 { 131 | drawLineNumber("-", lineRect.minY) 132 | } else { 133 | drawLineNumber("\(lineNumber)", lineRect.minY) 134 | } 135 | 136 | // Move to next glyph line 137 | glyphLineCount += 1 138 | glyphIndexForGlyphLine = NSMaxRange(effectiveRange) 139 | } 140 | 141 | glyphIndexForStringLine = NSMaxRange(glyphRangeForStringLine) 142 | lineNumber += 1 143 | } 144 | 145 | // Draw line number for the extra line at the end of the text 146 | if layoutManager.extraLineFragmentTextContainer != nil { 147 | drawLineNumber("\(lineNumber)", layoutManager.extraLineFragmentRect.minY) 148 | } 149 | } 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /SwiftyJSONAccelerator/UI/SJEditorViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SwiftyJSONAccelerator 4 | // 5 | // Created by Karthikeya Udupa on 24/07/2019. 6 | // Copyright © 2019 Karthikeya Udupa. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import SwiftyJSON 11 | 12 | class SJEditorViewController: NSViewController, NSTextViewDelegate { 13 | // MARK: Outlet files. 14 | 15 | @IBOutlet var textView: SJTextView! 16 | @IBOutlet var errorImageView: NSImageView! 17 | @IBOutlet var baseClassTextField: NSTextField! 18 | @IBOutlet var prefixClassTextField: NSTextField! 19 | @IBOutlet var companyNameTextField: NSTextField! 20 | @IBOutlet var authorNameTextField: NSTextField! 21 | @IBOutlet var variablesOptionalCheckbox: NSButton! 22 | @IBOutlet var separateCodingKeysCheckbox: NSButton! 23 | @IBOutlet var generateInitialiserFunctionCheckbox: NSButton! 24 | @IBOutlet var librarySelector: NSPopUpButton! 25 | @IBOutlet var accessControlSelector: NSPopUpButton! 26 | @IBOutlet var modelTypeSelectorSegment: NSSegmentedControl! 27 | 28 | override func viewDidLoad() { 29 | super.viewDidLoad() 30 | textView.delegate = self 31 | textView.updateFormat() 32 | resetView() 33 | textView!.lnv_setUpLineNumberView() 34 | accessControlSelector.addItems(withTitles: AccessControl.allCases.map(\.rawValue)) 35 | // Do any additional setup after loading the view. 36 | } 37 | 38 | func resetView() { 39 | textView.string = "" 40 | baseClassTextField.stringValue = "BaseClass" 41 | resetErrorImage() 42 | authorNameTextField?.stringValue = NSFullUserName() 43 | companyNameTextField.stringValue = "" 44 | prefixClassTextField.stringValue = "" 45 | librarySelector.selectItem(at: 0) 46 | accessControlSelector.selectItem(at: 0) 47 | modelTypeSelectorSegment.selectSegment(withTag: 0) 48 | variablesOptionalCheckbox.state = .on 49 | separateCodingKeysCheckbox.state = .on 50 | generateInitialiserFunctionCheckbox.state = .on 51 | generateInitialiserFunctionCheckbox.isEnabled = false 52 | } 53 | 54 | /// Validate and updates the textview 55 | /// 56 | /// - Parameter pretty: If the JSON is to be pretty printed. 57 | /// - Returns: If the format was valid. 58 | func validateAndFormat(_ pretty: Bool) -> Bool { 59 | if textView?.string.isEmpty == true { 60 | return false 61 | } 62 | textView!.updateFormat() 63 | let parserResponse = JSONHelper.isStringValidJSON(textView?.string) 64 | if parserResponse.isValid { 65 | correctJSONMessage() 66 | if pretty { 67 | textView?.string = JSONHelper.prettyJSON(textView?.string)! 68 | textView!.lnv_textDidChange(Notification(name: NSText.didChangeNotification, object: nil)) 69 | return true 70 | } 71 | } else if parserResponse.error != nil { 72 | handleError(parserResponse.error) 73 | textView!.lnv_textDidChange(Notification(name: NSText.didChangeNotification, object: nil)) 74 | return false 75 | } else { 76 | genericJSONError() 77 | } 78 | return false 79 | } 80 | 81 | override var representedObject: Any? { 82 | didSet { 83 | // Update the view, if already loaded. 84 | } 85 | } 86 | } 87 | 88 | /// TextView and UI actions 89 | extension SJEditorViewController { 90 | /// TextView delegate when change occurs. 91 | func textDidChange(_: Notification) { 92 | let isValid = validateAndFormat(false) 93 | if isValid { 94 | resetErrorImage() 95 | } 96 | } 97 | 98 | /// Handle loading multiple files at once 99 | @IBAction func handleMultipleFiles(_: AnyObject?) { 100 | let folderPath = openFile() 101 | // No file path was selected, go back! 102 | guard let path = folderPath else { return } 103 | 104 | do { 105 | let generatedModelInfo = try MultipleModelGenerator.generate(forPath: path) 106 | for file in generatedModelInfo.modelFiles { 107 | let content = FileGenerator.generateFileContentWith(file, configuration: generatedModelInfo.configuration) 108 | let name = file.fileName 109 | try FileGenerator.writeToFileWith(name, content: content, path: generatedModelInfo.configuration.filePath) 110 | } 111 | notify(fileCount: generatedModelInfo.modelFiles.count, path: generatedModelInfo.configuration.filePath) 112 | 113 | } catch let error as MultipleModelGeneratorError { 114 | let alert = NSAlert() 115 | alert.messageText = "Unable to generate the files." 116 | alert.informativeText = error.errorMessage() 117 | alert.runModal() 118 | } catch let error as NSError { 119 | let alert = NSAlert() 120 | alert.messageText = "Unable to generate the files." 121 | alert.informativeText = error.localizedDescription 122 | alert.runModal() 123 | } 124 | } 125 | 126 | /// Default function when "Open > New" is clicked. 127 | @IBAction func newDocument(_: Any?) { 128 | resetView() 129 | } 130 | 131 | /// When switching between versions of code being generated 132 | @IBAction func modelTypeSwitched(sender _: Any) { 133 | if modelTypeSelectorSegment.selectedSegment == 0 { 134 | generateInitialiserFunctionCheckbox.isEnabled = false 135 | } else { 136 | generateInitialiserFunctionCheckbox.isEnabled = true 137 | } 138 | } 139 | 140 | /// Mapping method to be used based on the selector control 141 | func mappingMethodForIndex(_ index: Int) -> JSONMappingMethod { 142 | if index == 2 { 143 | return JSONMappingMethod.swiftCodeExtended 144 | } 145 | return JSONMappingMethod.swiftNormal 146 | } 147 | 148 | func openFile() -> String? { 149 | let fileDialog = NSOpenPanel() 150 | fileDialog.canChooseFiles = false 151 | fileDialog.canChooseDirectories = true 152 | fileDialog.canCreateDirectories = true 153 | if fileDialog.runModal() == NSApplication.ModalResponse.OK { 154 | return fileDialog.url?.path 155 | } 156 | return nil 157 | } 158 | 159 | func notify(fileCount: Int, path: String) { 160 | NSUserNotificationCenter.default.removeAllDeliveredNotifications() 161 | let notification = NSUserNotification() 162 | notification.identifier = "SwiftyJSONAccelerator-" + UUID().uuidString 163 | notification.title = "SwiftyJSONAccelerator" 164 | if fileCount > 0 { 165 | notification.subtitle = "Completed - \(fileCount) Files Generated" 166 | } else { 167 | notification.subtitle = "No files were generated." 168 | } 169 | notification.userInfo = [Constants.filePathKey: path] 170 | notification.soundName = NSUserNotificationDefaultSoundName 171 | notification.hasActionButton = true 172 | notification.actionButtonTitle = "View" 173 | NSUserNotificationCenter.default.deliver(notification) 174 | } 175 | 176 | /// Function to format the code. 177 | @IBAction func format(_: AnyObject?) { 178 | if validateAndFormat(true) { 179 | generateModel() 180 | } 181 | } 182 | 183 | /// Main function to generate the model based on the options selected by the customer. 184 | func generateModel() { 185 | // The base class field is blank, cannot proceed without it. 186 | // Possibly can have a default value in the future. 187 | guard !baseClassTextField!.stringValue.isEmpty else { 188 | let alert = NSAlert() 189 | alert.messageText = "Enter a base class name to continue." 190 | alert.runModal() 191 | return 192 | } 193 | 194 | // Filepath to get the location to save the files. 195 | let filePath = openFile() 196 | 197 | // No file path was selected, cannot proceed ahead. 198 | if filePath == nil { 199 | return 200 | } 201 | 202 | let parserResponse = JSONHelper.convertToObject(textView?.string) 203 | let generateInitialiserFunction = modelTypeSelectorSegment.selectedSegment == 1 ? generateInitialiserFunctionCheckbox.state == .on : false 204 | 205 | // Checks for validity of the content, else can cause crashes. 206 | if parserResponse.parsedObject != nil { 207 | let destinationPath = filePath!.appending("/") 208 | let variablesOptional = variablesOptionalCheckbox.state == .on 209 | let separateCodingKeys = separateCodingKeysCheckbox.state == .on 210 | let constructType = modelTypeSelectorSegment.selectedSegment == 0 ? ConstructType.structType : ConstructType.classType 211 | let accessControl = AccessControl.allCases[accessControlSelector.indexOfSelectedItem] 212 | let libraryType = mappingMethodForIndex(librarySelector.indexOfSelectedItem) 213 | let configuration = ModelGenerationConfiguration( 214 | filePath: destinationPath, 215 | baseClassName: baseClassTextField.stringValue, 216 | authorName: authorNameTextField.stringValue, 217 | companyName: companyNameTextField.stringValue, 218 | prefix: prefixClassTextField.stringValue, 219 | constructType: constructType, 220 | accessControl: accessControl, 221 | modelMappingLibrary: libraryType, 222 | separateCodingKeys: separateCodingKeys, 223 | variablesOptional: variablesOptional, 224 | shouldGenerateInitMethod: generateInitialiserFunction 225 | ) 226 | let modelGenerator = ModelGenerator(JSON(parserResponse.parsedObject!), configuration) 227 | let filesGenerated = modelGenerator.generate() 228 | for file in filesGenerated { 229 | let content = FileGenerator.generateFileContentWith(file, configuration: configuration) 230 | let name = file.fileName 231 | let path = configuration.filePath 232 | do { 233 | try FileGenerator.writeToFileWith(name, content: content, path: path) 234 | } catch let error as NSError { 235 | let alert = NSAlert() 236 | alert.messageText = "Unable to generate the files, please check the contents of the folder." 237 | alert.informativeText = error.localizedDescription 238 | alert.runModal() 239 | } 240 | } 241 | notify(fileCount: filesGenerated.count, path: destinationPath) 242 | } else { 243 | let alert = NSAlert() 244 | alert.messageText = "Unable to save the file check the content." 245 | alert.runModal() 246 | } 247 | } 248 | } 249 | 250 | // MARK: - Resetting and showing error messages 251 | 252 | extension SJEditorViewController { 253 | /// Reset the whole error view with no image and message. 254 | func genericJSONError() { 255 | invalidJSONError("The JSON seems to be invalid!") 256 | } 257 | 258 | /// Reset the whole error view with no image and message. 259 | func resetErrorImage() { 260 | errorImageView?.image = nil 261 | } 262 | 263 | /// Show that the JSON is fine with proper icon. 264 | func correctJSONMessage() { 265 | errorImageView?.image = NSImage(named: "success") 266 | } 267 | 268 | /// Show the invalid JSON error with proper error and message. 269 | /// 270 | /// - Parameter message: Error message that is to be shown. 271 | func invalidJSONError(_: String) { 272 | errorImageView?.image = NSImage(named: "failure") 273 | } 274 | 275 | /// Handle Error message that is provided by the JSON helper and extract the message and showing them accordingly. 276 | /// 277 | /// - Parameter error: NSError that was provided. 278 | func handleError(_ error: NSError?) { 279 | if let message = error!.userInfo["debugDescription"] as? String { 280 | let numbers = message.components(separatedBy: CharacterSet.decimalDigits.inverted) 281 | 282 | var validNumbers: [Int] = [] 283 | for number in numbers where Int(number) != nil { 284 | validNumbers.append(Int(number)!) 285 | } 286 | 287 | if validNumbers.count == 1 { 288 | let index = validNumbers[0] 289 | let errorPosition: CharacterPosition = (textView?.string)!.characterRowAndLineAt(position: index) 290 | let customErrorMessage = "Error at line number: \(errorPosition.line) column: \(errorPosition.column) at Character: \(errorPosition.character)." 291 | invalidJSONError(customErrorMessage) 292 | } else { 293 | invalidJSONError(message) 294 | } 295 | } else { 296 | genericJSONError() 297 | } 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /SwiftyJSONAccelerator/UI/SJTextView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SJTextView.swift 3 | // SwiftyJSONAccelerator 4 | // 5 | // Created by Karthik on 16/10/2015. 6 | // Copyright © 2015 Karthikeya Udupa K M. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | public extension NSColor { 12 | @inline(__always) convenience init(RGB hex: UInt32, alpha: CGFloat = 1) { 13 | let red = CGFloat((hex & 0xFF0000) >> 16) / 255.0 14 | let green = CGFloat((hex & 0xFF00) >> 8) / 255.0 15 | let blue = CGFloat(hex & 0xFF) / 255.0 16 | 17 | self.init(deviceRed: red, green: green, blue: blue, alpha: alpha) 18 | } 19 | } 20 | 21 | public extension NSView { 22 | @inline(__always) var isDarkMode: Bool { 23 | if #available(OSX 10.14, *) { 24 | return effectiveAppearance.name == .darkAqua 25 | } 26 | 27 | return false 28 | } 29 | } 30 | 31 | /// A textview customization to handle formatting and handling removal of quotes. 32 | class SJTextView: NSTextView { 33 | let lightTextColor = NSColor(RGB: 0x24292D) 34 | let lightBackgroundColor = NSColor(RGB: 0xF6F8FA) 35 | 36 | let darkTextColor = NSColor(RGB: 0xD1D5DA) 37 | let darkBackgroundColor = NSColor(RGB: 0x24292D) 38 | 39 | override init(frame frameRect: NSRect, textContainer container: NSTextContainer?) { 40 | super.init(frame: frameRect, textContainer: container) 41 | disableAutoReplacement() 42 | } 43 | 44 | required init?(coder: NSCoder) { 45 | super.init(coder: coder) 46 | disableAutoReplacement() 47 | } 48 | 49 | override var readablePasteboardTypes: [NSPasteboard.PasteboardType] { 50 | return [NSPasteboard.PasteboardType.string] 51 | } 52 | 53 | internal func updateFormat() { 54 | textStorage?.font = NSFont(name: "Monaco", size: 12) 55 | 56 | let color = NSColor(RGB: 0x07C160) 57 | insertionPointColor = color 58 | selectedTextAttributes = [.backgroundColor: color.withAlphaComponent(0.2)] 59 | 60 | if isDarkMode { 61 | textColor = darkTextColor 62 | backgroundColor = darkBackgroundColor 63 | } else { 64 | textColor = lightTextColor 65 | backgroundColor = lightBackgroundColor 66 | } 67 | } 68 | 69 | override func paste(_ sender: Any?) { 70 | super.paste(sender) 71 | updateFormat() 72 | } 73 | 74 | override func lnv_textDidChange(_: Notification) { 75 | updateFormat() 76 | } 77 | 78 | private func disableAutoReplacement() { 79 | isAutomaticQuoteSubstitutionEnabled = false 80 | isAutomaticDashSubstitutionEnabled = false 81 | isAutomaticTextReplacementEnabled = false 82 | } 83 | 84 | override func layout() { 85 | super.layout() 86 | 87 | updateFormat() 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /SwiftyJSONAcceleratorTests/FileGeneratorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileGeneratorTests.swift 3 | // SwiftyJSONAcceleratorTests 4 | // 5 | // Created by Karthikeya Udupa on 06/08/2019. 6 | // Copyright © 2019 Karthikeya Udupa. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class FileGeneratorTests: XCTestCase { 12 | override func setUp() { 13 | // Put setup code here. This method is called before the invocation of each test method in the class. 14 | } 15 | 16 | override func tearDown() { 17 | // Put teardown code here. This method is called after the invocation of each test method in the class. 18 | } 19 | 20 | func testInvalidPath() { 21 | let value = try! FileGenerator.loadFileWith("doesNotExist") 22 | XCTAssertEqual(value, "") 23 | } 24 | 25 | func loadValidPath() { 26 | let fileContent = try! FileGenerator.loadFileWith("BaseTemplate") 27 | XCTAssert(!fileContent.isEmpty) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /SwiftyJSONAcceleratorTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /SwiftyJSONAcceleratorTests/JSONHelperTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSONHelperTests.swift 3 | // SwiftyJSONAccelerator 4 | // 5 | // Created by Karthik on 16/10/2015. 6 | // Copyright © 2015 Karthikeya Udupa K M. All rights reserved. 7 | // 8 | 9 | import SwiftyJSON 10 | import XCTest 11 | 12 | /// Test basic corner cases for the JSONHelper. 13 | class JSONHelperTests: XCTestCase { 14 | override func setUp() { 15 | super.setUp() 16 | } 17 | 18 | override func tearDown() { 19 | super.tearDown() 20 | } 21 | 22 | func testIsValidJSONString() { 23 | var response = JSONHelper.isStringValidJSON("temp") 24 | XCTAssert(response.isValid == false) 25 | XCTAssert(response.parsedObject == nil) 26 | 27 | response = JSONHelper.isStringValidJSON("{\"key\":\"value\"}") 28 | XCTAssert(response.isValid == true) 29 | XCTAssert(response.error == nil) 30 | XCTAssert(response.parsedObject!.isEqual(["key": "value"])) 31 | } 32 | 33 | func testConvertToJSON() { 34 | var response = JSONHelper.convertToObject("temp") 35 | XCTAssert(response.isValid == false) 36 | XCTAssert(response.error != nil) 37 | 38 | response = JSONHelper.convertToObject("{\"key\":\"value\"}") 39 | XCTAssert(response.isValid == true) 40 | XCTAssert(response.error == nil) 41 | XCTAssert(response.parsedObject!.isEqual(["key": "value"])) 42 | 43 | response = JSONHelper.convertToObject(nil) 44 | XCTAssert(response.isValid == false) 45 | XCTAssert(response.parsedObject == nil) 46 | } 47 | 48 | func testPrettyJSON() { 49 | var response = JSONHelper.prettyJSON("temp") 50 | XCTAssert(response == nil) 51 | response = JSONHelper.prettyJSON("{\"key\":\"value\"}") 52 | XCTAssert(response == "{\n \"key\" : \"value\"\n}") 53 | 54 | response = JSONHelper.prettyJSON(object: nil) 55 | XCTAssert(response == nil) 56 | response = JSONHelper.prettyJSON(object: JSONHelper.convertToObject("{\"key\": \"value\"}").parsedObject) 57 | XCTAssert(response == "{\n \"key\" : \"value\"\n}") 58 | } 59 | 60 | func testReduce() { 61 | let objects = ["mainKey": [["key1": ["A": 1, "B": true], "key2": false], ["key1": ["C": 1.2, "D": ["X": 1]], "key3": 3.4]]] 62 | let result = JSON(["mainKey": [["key2": false, "key3": 3.4, "key1": ["C": 1.2, "A": 1, "B": true, "D": ["X": 1]]]]]) 63 | let reducedObject = JSONHelper.reduce([JSON(objects)]) 64 | XCTAssert(reducedObject["mainKey"].array![0]["key1"] == result["mainKey"].array![0]["key1"]) 65 | XCTAssert(reducedObject["mainKey"].array![0]["key2"] == result["mainKey"].array![0]["key2"]) 66 | XCTAssert(reducedObject["mainKey"].array![0]["key3"] == result["mainKey"].array![0]["key3"]) 67 | XCTAssert(JSONHelper.reduce([JSON(objects)]) == JSON(result)) 68 | } 69 | 70 | func testJSONExtension() { 71 | let object = JSON(["key1": "string", 72 | "key2": true, 73 | "key3": [1, 2, 3], 74 | "key4": NSNumber(integerLiteral: 20), 75 | "key5": 10, 76 | "key6": 10.5, 77 | "key7": NSNull(), 78 | "key8": ["A": "value"]]) 79 | 80 | print(object["key1"].detailedValueType()) 81 | XCTAssert(object["key1"].detailedValueType() == .string) 82 | XCTAssert(object["key2"].detailedValueType() == .bool) 83 | XCTAssert(object["key3"].detailedValueType() == .array) 84 | XCTAssert(object["key4"].detailedValueType() == .int) 85 | XCTAssert(object["key5"].detailedValueType() == .int) 86 | XCTAssert(object["key6"].detailedValueType() == .float) 87 | XCTAssert(object["key7"].detailedValueType() == .null) 88 | XCTAssert(object["key8"].detailedValueType() == .object) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /SwiftyJSONAcceleratorTests/ModelGeneratorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ModelGeneratorTests.swift 3 | // SwiftyJSONAcceleratorTests 4 | // 5 | // Created by Karthikeya Udupa on 03/08/2019. 6 | // Copyright © 2019 Karthikeya Udupa. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | import XCTest 12 | 13 | class ModelGeneratorTests: XCTestCase { 14 | override func setUp() { 15 | super.setUp() 16 | NSUserNotificationCenter.default.removeAllDeliveredNotifications() 17 | } 18 | 19 | override func tearDown() { 20 | NSUserNotificationCenter.default.removeAllDeliveredNotifications() 21 | super.tearDown() 22 | } 23 | 24 | /// Generate a sample JSON class with all possible cases. 25 | /// 26 | /// - Returns: A valid JSON. 27 | func testJSON() -> JSON { 28 | return JSON( 29 | ["value_one": "string value", 30 | "value_two": 3, 31 | "value_three": true, 32 | "value_four": 3.4, 33 | "value_dont_show": JSON.null, 34 | "value_five": ["string", "random_stuff"], 35 | "value_six": ["sub_value": "value", "sub_value_second": false], 36 | "value_seven": [ 37 | [ 38 | "sub_value_third": 4.5, 39 | "double_value": Double(5.3), 40 | "sub_value_four": "value", 41 | "internal": "renamed_value", 42 | "sub_value_five": ["two_level_down": "value"], 43 | ], 44 | ], 45 | "value_eight": []]) 46 | } 47 | 48 | /// Default set of configuration that can be used for different cases 49 | /// 50 | /// - Parameter library: JSON Mapping method to be used. 51 | /// - Returns: Configuration with the default setup. 52 | func defaultConfiguration(library: JSONMappingMethod, optional: Bool) -> ModelGenerationConfiguration { 53 | return ModelGenerationConfiguration( 54 | filePath: "/tmp/", 55 | baseClassName: "BaseClass", 56 | authorName: "Jane Smith", 57 | companyName: "Acme Co.", 58 | prefix: "AC", 59 | constructType: .structType, 60 | accessControl: .internal, 61 | modelMappingLibrary: library, 62 | separateCodingKeys: true, 63 | variablesOptional: optional, 64 | shouldGenerateInitMethod: true 65 | ) 66 | } 67 | 68 | /// Test notification center local notifications - both successful and failure cases 69 | func testNotifications() { 70 | let config = defaultConfiguration(library: .swiftNormal, optional: false) 71 | let model = ModelGenerator(JSON([testJSON()]), config) 72 | let files = model.generate() 73 | let errorNotification = model.generateNotificationFor([]) 74 | XCTAssertEqual(errorNotification.title!, "SwiftyJSONAccelerator") 75 | XCTAssertEqual(errorNotification.subtitle!, "No files were generated, cannot model arrays inside arrays.") 76 | 77 | let correctNotification = model.generateNotificationFor(files) 78 | XCTAssertEqual(correctNotification.title!, "SwiftyJSONAccelerator") 79 | XCTAssertEqual(correctNotification.subtitle!, "Completed - ACBaseClass.swift") 80 | } 81 | 82 | /// Test invalid JSON. 83 | func testForInvalidJSON() { 84 | let config = defaultConfiguration(library: .swiftNormal, optional: false) 85 | let model = ModelGenerator(JSON(["hello!"]), config) 86 | let files = model.generate() 87 | XCTAssert(files.isEmpty) 88 | } 89 | 90 | /// Generate files for JSON which is an array. 91 | func testModelWithRootAsArray() { 92 | let config = defaultConfiguration(library: .swiftNormal, optional: false) 93 | let model = ModelGenerator(JSON([testJSON()]), config) 94 | let files = model.generate() 95 | XCTAssert(files.count == 4) 96 | XCTAssertEqual(files.count, 4) 97 | 98 | var objectKey = "ValueSix" 99 | objectKey.appendPrefix(config.prefix) 100 | 101 | var subValueKey = "ValueSeven" 102 | subValueKey.appendPrefix(config.prefix) 103 | 104 | var objectArrayKey = "SubValueFive" 105 | objectArrayKey.appendPrefix(config.prefix) 106 | 107 | let possibleMathces = [objectKey, subValueKey, objectArrayKey] 108 | let fileNames = [files[3].fileName, files[2].fileName, files[1].fileName] 109 | for match in possibleMathces { 110 | XCTAssert(fileNames.contains(match)) 111 | } 112 | 113 | let baseModelFile = files.first 114 | var baseClass = config.baseClassName 115 | baseClass.appendPrefix(config.prefix) 116 | XCTAssertEqual(baseModelFile!.fileName, baseClass) 117 | } 118 | 119 | func testModelGenerationConfiguration() { 120 | var modelConfig = ModelGenerationConfiguration(filePath: "A1", baseClassName: "A2", 121 | authorName: "A3", companyName: "A4", 122 | prefix: "A5", constructType: .classType, 123 | accessControl: .private, 124 | modelMappingLibrary: .swiftCodeExtended, 125 | separateCodingKeys: true, variablesOptional: true, 126 | shouldGenerateInitMethod: true) 127 | 128 | XCTAssertEqual(modelConfig.filePath, "A1") 129 | XCTAssertEqual(modelConfig.baseClassName, "A2") 130 | XCTAssertEqual(modelConfig.authorName, "A3") 131 | XCTAssertEqual(modelConfig.companyName, "A4") 132 | XCTAssertEqual(modelConfig.prefix, "A5") 133 | XCTAssertEqual(modelConfig.constructType, .classType) 134 | XCTAssertEqual(modelConfig.accessControl, .private) 135 | XCTAssertEqual(modelConfig.modelMappingLibrary, .swiftCodeExtended) 136 | XCTAssertEqual(modelConfig.separateCodingKeys, true) 137 | XCTAssertEqual(modelConfig.variablesOptional, true) 138 | 139 | modelConfig.defaultConfig() 140 | 141 | XCTAssertEqual(modelConfig.filePath, "") 142 | XCTAssertEqual(modelConfig.baseClassName, "") 143 | XCTAssertEqual(modelConfig.prefix, "") 144 | XCTAssertEqual(modelConfig.constructType, .classType) 145 | XCTAssertEqual(modelConfig.accessControl, .internal) 146 | XCTAssertEqual(modelConfig.modelMappingLibrary, .swiftNormal) 147 | XCTAssertEqual(modelConfig.separateCodingKeys, true) 148 | XCTAssertEqual(modelConfig.variablesOptional, true) 149 | } 150 | 151 | func testSwift5JSONModelsWithOptional() { 152 | let files = generateModelFiles(optional: true) 153 | testFileContent(files: files.0, optional: true, config: files.1) 154 | } 155 | 156 | func testSwift5JSONModelsWithNoOptionals() { 157 | let files = generateModelFiles(optional: false) 158 | testFileContent(files: files.0, optional: false, config: files.1) 159 | } 160 | 161 | func testFileContent(files: [ModelFile], optional: Bool, config: ModelGenerationConfiguration) { 162 | for file in files { 163 | validateDeclarations(filename: file.fileName, declarations: file.component.declarations, optional: optional) 164 | validateKeys(filename: file.fileName, stringKeys: file.component.stringConstants) 165 | validateInitialiser(filename: file.fileName, initialisers: file.component.initialisers, optional: optional) 166 | validateInitialiserFunctionComponents(filename: file.fileName, initialiserFunctionComponents: file.component.initialiserFunctionComponent, optional: optional) 167 | let content = FileGenerator.generateFileContentWith(file, configuration: config) 168 | let name = file.fileName 169 | let path = "/tmp/sj/" 170 | do { 171 | try FileGenerator.writeToFileWith(name, content: content, path: path) 172 | } catch { 173 | assertionFailure("File generation Failed") 174 | } 175 | } 176 | } 177 | 178 | func generateModelFiles(optional: Bool) -> ([ModelFile], ModelGenerationConfiguration) { 179 | let config = defaultConfiguration(library: .swiftNormal, optional: optional) 180 | let models = ModelGenerator(testJSON(), config) 181 | return (models.generate(), config) 182 | } 183 | 184 | func validateDeclarations(filename: String, declarations: [String], optional: Bool) { 185 | var possibleValues = ["ACBaseClass": ["var valueEight: Any?", "var valueTwo: Int?", "var valueThree: Bool?", 186 | "var valueSix: ACValueSix?", "var valueFour: Float?", "var valueOne: String?", 187 | "var valueFive: [String]?", "var valueSeven: [ACValueSeven]?"], 188 | "ACValueSix": ["var subValue: String?", "var subValueSecond: Bool?"], 189 | "ACValueSeven": ["var subValueFive: ACSubValueFive?", "var subValueFour: String?", 190 | "var internalProperty: String?", "var doubleValue: Float?", 191 | "var subValueThird: Float?"], 192 | "ACSubValueFive": ["var twoLevelDown: String?"]] 193 | 194 | if optional == false { 195 | possibleValues = ["ACBaseClass": ["var valueEight: Any", "var valueTwo: Int", "var valueThree: Bool", 196 | "var valueSix: ACValueSix", "var valueFour: Float", "var valueOne: String", 197 | "var valueFive: [String]", "var valueSeven: [ACValueSeven]"], 198 | "ACValueSix": ["var subValue: String", "var subValueSecond: Bool"], 199 | "ACValueSeven": ["var subValueFive: ACSubValueFive", "var subValueFour: String", 200 | "var internalProperty: String", "var doubleValue: Float", 201 | "var subValueThird: Float"], 202 | "ACSubValueFive": ["var twoLevelDown: String"]] 203 | } 204 | 205 | XCTAssertEqual(possibleValues[filename]?.count, declarations.count) 206 | for declaration in possibleValues[filename]! { 207 | XCTAssert(declarations.contains(declaration)) 208 | } 209 | } 210 | 211 | func validateKeys(filename: String, stringKeys: [String]) { 212 | let possibleValues = ["ACBaseClass": ["case valueThree = \"value_three\"", "case valueSix = \"value_six\"", "case valueEight = \"value_eight\"", "case valueFive = \"value_five\"", "case valueFour = \"value_four\"", "case valueSeven = \"value_seven\"", "case valueOne = \"value_one\"", "case valueTwo = \"value_two\""], 213 | "ACValueSix": ["case subValueSecond = \"sub_value_second\"", "case subValue = \"sub_value\""], 214 | "ACValueSeven": ["case subValueFour = \"sub_value_four\"", "case subValueFive = \"sub_value_five\"", "case doubleValue = \"double_value\"", "case subValueThird = \"sub_value_third\"", "case internalProperty = \"internal\""], 215 | "ACSubValueFive": ["case twoLevelDown = \"two_level_down\""]] 216 | XCTAssertEqual(possibleValues[filename]?.count, stringKeys.count) 217 | for stringKey in possibleValues[filename]! { 218 | XCTAssert(stringKeys.contains(stringKey)) 219 | } 220 | } 221 | 222 | func validateInitialiser(filename: String, initialisers: [String], optional: Bool) { 223 | var possibleValues = ["ACBaseClass": ["valueOne = try container.decodeIfPresent(String.self, forKey: .valueOne)", "valueThree = try container.decodeIfPresent(Bool.self, forKey: .valueThree)", "valueSeven = try container.decodeIfPresent([ACValueSeven].self, forKey: .valueSeven)", "valueEight = try container.decodeIfPresent([].self, forKey: .valueEight)", "valueFour = try container.decodeIfPresent(Float.self, forKey: .valueFour)", "valueFive = try container.decodeIfPresent([String].self, forKey: .valueFive)", "valueSix = try container.decodeIfPresent(ACValueSix.self, forKey: .valueSix)", "valueTwo = try container.decodeIfPresent(Int.self, forKey: .valueTwo)"], 224 | "ACValueSeven": ["subValueFive = try container.decodeIfPresent(ACSubValueFive.self, forKey: .subValueFive)", "doubleValue = try container.decodeIfPresent(Float.self, forKey: .doubleValue)", "subValueThird = try container.decodeIfPresent(Float.self, forKey: .subValueThird)", "internalProperty = try container.decodeIfPresent(String.self, forKey: .internalProperty)", "subValueFour = try container.decodeIfPresent(String.self, forKey: .subValueFour)"], 225 | "ACSubValueFive": ["twoLevelDown = try container.decodeIfPresent(String.self, forKey: .twoLevelDown)"], 226 | "ACValueSix": ["subValue = try container.decodeIfPresent(String.self, forKey: .subValue)", "subValueSecond = try container.decodeIfPresent(Bool.self, forKey: .subValueSecond)"]] 227 | 228 | if optional == false { 229 | possibleValues = ["ACBaseClass": ["valueSeven = try container.decode([ACValueSeven].self, forKey: .valueSeven)", "valueThree = try container.decode(Bool.self, forKey: .valueThree)", "valueFive = try container.decode([String].self, forKey: .valueFive)", "valueSix = try container.decode(ACValueSix.self, forKey: .valueSix)", "valueEight = try container.decode([].self, forKey: .valueEight)", "valueTwo = try container.decode(Int.self, forKey: .valueTwo)", "valueFour = try container.decode(Float.self, forKey: .valueFour)", "valueOne = try container.decode(String.self, forKey: .valueOne)"], 230 | "ACValueSeven": ["subValueFive = try container.decode(ACSubValueFive.self, forKey: .subValueFive)", "subValueFour = try container.decode(String.self, forKey: .subValueFour)", "internalProperty = try container.decode(String.self, forKey: .internalProperty)", "subValueThird = try container.decode(Float.self, forKey: .subValueThird)", "doubleValue = try container.decode(Float.self, forKey: .doubleValue)"], 231 | "ACSubValueFive": ["twoLevelDown = try container.decode(String.self, forKey: .twoLevelDown)"], 232 | "ACValueSix": ["subValue = try container.decode(String.self, forKey: .subValue)", "subValueSecond = try container.decode(Bool.self, forKey: .subValueSecond)"]] 233 | } 234 | XCTAssertEqual(possibleValues[filename]?.count, initialisers.count) 235 | for initialiser in possibleValues[filename]! { 236 | XCTAssert(initialisers.contains(initialiser)) 237 | } 238 | } 239 | 240 | func validateInitialiserFunctionComponents(filename: String, initialiserFunctionComponents: [InitialiserFunctionComponent], optional: Bool) { 241 | 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"], 242 | "ACValueSeven": ["self.subValueFive = subValueFive", "self.doubleValue = doubleValue", "self.subValueThird = subValueThird", "self.internalProperty = internalProperty", "self.subValueFour = subValueFour"], 243 | "ACSubValueFive": ["self.twoLevelDown = twoLevelDown"], 244 | "ACValueSix": ["self.subValue = subValue", "self.subValueSecond = subValueSecond"]] 245 | 246 | var possibleFunctionParamValues = ["ACBaseClass": ["valueSeven: [ACValueSeven]", "valueThree: Bool", "valueFive: [String]", "valueSix: ACValueSix", "valueEight: Any", "valueTwo: Int", "valueFour: Float", "valueOne: String"], 247 | "ACValueSeven": ["subValueFive: ACSubValueFive", "subValueFour: String", "internalProperty: String", "subValueThird: Float", "doubleValue: Float"], 248 | "ACSubValueFive": ["twoLevelDown: String"], 249 | "ACValueSix": ["subValue: String", "subValueSecond: Bool"]] 250 | 251 | if optional == true { 252 | possibleFunctionParamValues = ["ACBaseClass": ["valueSeven: [ACValueSeven]?", "valueThree: Bool?", "valueFive: [String]?", "valueSix: ACValueSix?", "valueEight: Any?", "valueTwo: Int?", "valueFour: Float?", "valueOne: String?"], 253 | "ACValueSeven": ["subValueFive: ACSubValueFive?", "subValueFour: String?", "internalProperty: String?", "subValueThird: Float?", "doubleValue: Float?"], 254 | "ACSubValueFive": ["twoLevelDown: String?"], 255 | "ACValueSix": ["subValue: String?", "subValueSecond: Bool?"]] 256 | } 257 | 258 | XCTAssertEqual(possibleAssignmentValues[filename]?.count, initialiserFunctionComponents.count) 259 | XCTAssertEqual(possibleFunctionParamValues[filename]?.count, initialiserFunctionComponents.count) 260 | for initialiserFunctionComponent in initialiserFunctionComponents { 261 | XCTAssert(possibleAssignmentValues[filename]!.contains(initialiserFunctionComponent.assignmentString)) 262 | XCTAssert(possibleFunctionParamValues[filename]!.contains(initialiserFunctionComponent.functionParameter)) 263 | } 264 | } 265 | 266 | func testClassModelGenerator() { 267 | var config = defaultConfiguration(library: .swiftNormal, optional: true) 268 | config.constructType = .classType 269 | let models = ModelGenerator(testJSON(), config) 270 | let files = models.generate() 271 | 272 | for file in files { 273 | let content = FileGenerator.generateFileContentWith(file, configuration: config) 274 | XCTAssert(content.contains(" required init(from decoder: Decoder) throws {")) 275 | } 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /SwiftyJSONAcceleratorTests/MultipleModelGeneratorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MultipleModelGeneratorTests.swift 3 | // SwiftyJSONAccelerator 4 | // 5 | // Created by Karthik on 24/12/2016. 6 | // Copyright © 2016 Karthikeya Udupa K M. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XCTest 11 | 12 | /// Test for multiple model generator. 13 | class MultipleModelGeneratorTests: XCTestCase { 14 | override func setUp() { 15 | let manager = FileManager.default 16 | let path = "/tmp/random/folder" 17 | do { 18 | try manager.removeItem(atPath: path) 19 | } catch {} 20 | super.setUp() 21 | } 22 | 23 | override func tearDown() { 24 | super.tearDown() 25 | } 26 | 27 | func testFileCheck() { 28 | do { 29 | let testBundle = Bundle(for: type(of: self)) 30 | if let path = testBundle.path(forResource: "album", ofType: ".json") { 31 | let folderPath = path.replacingOccurrences(of: "album.json", with: "") 32 | let generatedModelInfo = try MultipleModelGenerator.generate(forPath: folderPath) 33 | XCTAssertEqual(generatedModelInfo.modelFiles.count, 6) 34 | for file in generatedModelInfo.modelFiles { 35 | let content = FileGenerator.generateFileContentWith(file, configuration: generatedModelInfo.configuration) 36 | let name = file.fileName 37 | let path = generatedModelInfo.configuration.filePath 38 | do { 39 | try FileGenerator.writeToFileWith(name, content: content, path: path) 40 | } catch { 41 | assertionFailure("File generation failed.") 42 | } 43 | } 44 | } else { 45 | assertionFailure("File generation failed - Nothing found at the path.") 46 | } 47 | } catch { 48 | assertionFailure("Something went wrong with model generation.") 49 | } 50 | } 51 | 52 | func testModelGenerationError() { 53 | let noFiles: MultipleModelGeneratorError = .noJSONFiles 54 | let noConfigFile: MultipleModelGeneratorError = .noConfigFile 55 | let invalidConfigJSON: MultipleModelGeneratorError = .invalidConfigJSON 56 | let invalidPath: MultipleModelGeneratorError = .invalidPath 57 | let configInvalid: MultipleModelGeneratorError = .configInvalid(rule: "Config Error") 58 | let invalidJSONFile: MultipleModelGeneratorError = .invalidJSONFile(filename: "InvalidJSONFile") 59 | 60 | XCTAssertEqual(noFiles.errorMessage(), "No JSON file at the given path.") 61 | XCTAssertEqual(noConfigFile.errorMessage(), "No .config.json was found at the path.") 62 | XCTAssertEqual(invalidConfigJSON.errorMessage(), "The .config.json has an invalid JSON.") 63 | XCTAssertEqual(invalidPath.errorMessage(), "The path is invalid.") 64 | XCTAssertEqual(configInvalid.errorMessage(), "Config Error") 65 | XCTAssertEqual(invalidJSONFile.errorMessage(), "The file InvalidJSONFile is invalid.") 66 | } 67 | 68 | func testMultipleFileGeneratorErrorCases() { 69 | let invalidPath: MultipleModelGeneratorError = .invalidPath 70 | XCTAssertThrowsError(try MultipleModelGenerator.generate(forPath: "/random/path/doesnot/exists"), "invalid folder") { error in 71 | XCTAssertEqual((error as? MultipleModelGeneratorError)!, invalidPath) 72 | } 73 | 74 | let manager = FileManager.default 75 | let path = "/tmp/random/folder/" 76 | try! manager.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil) 77 | let noJSONFiles: MultipleModelGeneratorError = .noJSONFiles 78 | XCTAssertThrowsError(try MultipleModelGenerator.generate(forPath: path), "invalid folder") { error in 79 | XCTAssertEqual((error as? MultipleModelGeneratorError)!, noJSONFiles) 80 | } 81 | 82 | manager.createFile(atPath: "/tmp/random/folder/tempfile.json", contents: "Data".data(using: .utf8), attributes: nil) 83 | sleep(2) 84 | let noConfigFile: MultipleModelGeneratorError = .noConfigFile 85 | XCTAssertThrowsError(try MultipleModelGenerator.generate(forPath: path), "invalid folder") { error in 86 | XCTAssertEqual((error as? MultipleModelGeneratorError)!, noConfigFile) 87 | } 88 | 89 | manager.createFile(atPath: "/tmp/random/folder/.config.json", contents: "NO_REAL_JSON".data(using: .utf8), attributes: nil) 90 | sleep(2) 91 | let invalidConfigJSON: MultipleModelGeneratorError = .invalidConfigJSON 92 | XCTAssertThrowsError(try MultipleModelGenerator.generate(forPath: path), "invalid folder") { error in 93 | XCTAssertEqual((error as? MultipleModelGeneratorError)!, invalidConfigJSON) 94 | } 95 | 96 | manager.createFile(atPath: "/tmp/random/folder/.config.json", contents: "[]".data(using: .utf8), attributes: nil) 97 | sleep(2) 98 | let invalidJSONFile: MultipleModelGeneratorError = .invalidJSONFile(filename: "tempfile.json") 99 | XCTAssertThrowsError(try MultipleModelGenerator.generate(forPath: path), "invalid folder") { error in 100 | XCTAssertEqual((error as? MultipleModelGeneratorError)!, invalidJSONFile) 101 | } 102 | } 103 | 104 | func testFilePathAndLoading() { 105 | // Test how invalid files are loaded. 106 | XCTAssertEqual(MultipleModelGenerator.loadJSON(fromFile: "/tmp/random/random"), nil) 107 | 108 | // Test how destination file paths are generated with different path types (absolute, relative, missing / etc) 109 | XCTAssertEqual(MultipleModelGenerator.generatePathToSave(fromBasePath: "/tmp/random/", destinationPath: "generatedFiles"), "/tmp/random/generatedFiles/") 110 | XCTAssertEqual(MultipleModelGenerator.generatePathToSave(fromBasePath: "/tmp/random", destinationPath: "generatedFiles"), "/tmp/random/generatedFiles/") 111 | XCTAssertEqual(MultipleModelGenerator.generatePathToSave(fromBasePath: "/tmp/random", destinationPath: "/generatedFiles"), "/generatedFiles/") 112 | 113 | let tempModelFile = SwiftJSONModelFile() 114 | XCTAssertEqual(MultipleModelGenerator.merge(models: [tempModelFile]).count, 1) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /SwiftyJSONAcceleratorTests/StringTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringTests.swift 3 | // SwiftyJSONAccelerator 4 | // 5 | // Created by Karthik on 06/07/2016. 6 | // Copyright © 2016 Karthikeya Udupa K M. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XCTest 11 | 12 | /// Additional tests foe String extensions. 13 | class StringTests: XCTestCase { 14 | override func setUp() { 15 | super.setUp() 16 | } 17 | 18 | override func tearDown() { 19 | super.tearDown() 20 | } 21 | 22 | func testFirst() { 23 | let str1 = "demo" 24 | XCTAssert(str1.first == "d") 25 | 26 | let str2 = "" 27 | XCTAssertNil(str2.first) 28 | } 29 | 30 | func testCases() { 31 | var str1 = "demo" 32 | str1.uppercaseFirst() 33 | XCTAssert(str1 == "Demo") 34 | str1.uppercaseFirst() 35 | XCTAssert(str1 == "Demo") 36 | str1.lowercaseFirst() 37 | XCTAssert(str1 == "demo") 38 | str1.lowercaseFirst() 39 | XCTAssert(str1 == "demo") 40 | } 41 | 42 | func testReplacement() { 43 | var str1 = "demo_of-the%app_works-well" 44 | str1.replaceOccurrencesOfStringsWithString(["_", "-", "%"], " ") 45 | XCTAssert(str1 == "demo of the app works well") 46 | } 47 | 48 | func testPrefix() { 49 | var str1 = "Demo" 50 | str1.appendPrefix("AC") 51 | XCTAssert(str1 == "ACDemo") 52 | str1.appendPrefix("") 53 | XCTAssert(str1 == "ACDemo") 54 | str1.appendPrefix(nil) 55 | XCTAssert(str1 == "ACDemo") 56 | } 57 | 58 | func testTrimChars() { 59 | var str1 = " De mo " 60 | str1.trim() 61 | XCTAssert(str1 == "De mo") 62 | } 63 | 64 | func testCharAtIndex() { 65 | let str1 = "0123456789\n1234567890" 66 | XCTAssert(str1.characterRowAndLineAt(position: 1).character == "0") 67 | XCTAssert(str1.characterRowAndLineAt(position: 12).character == "1") 68 | XCTAssert(str1.characterRowAndLineAt(position: 12).line == 2) 69 | XCTAssert(str1.characterRowAndLineAt(position: 12).column == 1) 70 | XCTAssert("".characterRowAndLineAt(position: 12).character.isEmpty) 71 | XCTAssert(str1.characterRowAndLineAt(position: 11).character == "\n") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /SwiftyJSONAcceleratorTests/Support Files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /SwiftyJSONAcceleratorTests/Support Files/MultipleModelTests/album.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "123AA", 3 | "title": "Barcelona", 4 | "user": { 5 | "user_id": "12345", 6 | "username": "jameso1" 7 | }, 8 | "photo": [{ 9 | "id": "P123", 10 | "url": "http://flickr.com/p123" 11 | }, { 12 | "id": "P124", 13 | "url": "http://flickr.com/p124" 14 | }] 15 | } 16 | -------------------------------------------------------------------------------- /SwiftyJSONAcceleratorTests/Support Files/MultipleModelTests/community.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Talk On Travel Pool", 3 | "link": "http://www.flickr.com/groups/talkontravel/pool/", 4 | "description": "Travel and vacation photos from around the world.", 5 | "modified": "2009-02-02T11:10:27Z", 6 | "generator": "http://www.flickr.com/", 7 | "user": [{ 8 | "user_id": "12345", 9 | "username": "jameso1" 10 | }, { 11 | "user_id": "12346", 12 | "username": "jameso2" 13 | }, { 14 | "user_id": "12347", 15 | "username": "jameso3", 16 | "is_disabled": false 17 | }] 18 | } 19 | -------------------------------------------------------------------------------- /SwiftyJSONAcceleratorTests/Support Files/MultipleModelTests/photo.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "P123", 3 | "url": "http://flickr.com/p123", 4 | "album": [{ 5 | "id": "123AA", 6 | "url": "http://flickr.com/alb/123AA" 7 | }], 8 | "user": { 9 | "user_id": "12345", 10 | "username": "jameso1", 11 | "is_user_visible": false 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /SwiftyJSONAcceleratorTests/Support Files/MultipleModelTests/test_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "destination_path": "/tmp/multiple_models/", 3 | "author_name": "John Smith", 4 | "company_name": "Acme Inc", 5 | "construct_type": "struct", 6 | "access_control": "internal", 7 | "prefix": "NS", 8 | "json_mapping_method": "swiftCodingVanilla", 9 | "variable_option": false, 10 | "separate_coding_keys": true 11 | } 12 | -------------------------------------------------------------------------------- /SwiftyJSONAcceleratorTests/Support Files/MultipleModelTests/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "firstName": "John", 3 | "lastName": "Smith", 4 | "user_id": "12347", 5 | "username": "jameso3", 6 | "age": 25, 7 | "address": { 8 | "streetAddress": "21 2nd Street", 9 | "city": "New York", 10 | "state": "NY", 11 | "postalCode": "10021" 12 | }, 13 | "phoneNumber": [{ 14 | "type": "home", 15 | "number": "212 555-1234" 16 | }, { 17 | "type": "fax", 18 | "number": "646 555-4567" 19 | }], 20 | "album": [{ 21 | "id": "123AA", 22 | "url": "http://flickr.com/alb/123AA" 23 | }] 24 | } 25 | -------------------------------------------------------------------------------- /SwiftyJSONAcceleratorTests/Support Files/TestJSONFile.sample: -------------------------------------------------------------------------------- 1 | { 2 | "glossary": { 3 | "title": "example glossary", 4 | "GlossDiv": { 5 | "isHighlighted": true, 6 | "currentScore": 20, 7 | "title": "S", 8 | "GlossList": { 9 | "GlossEntry": { 10 | "SortAs": "SGML", 11 | "Abbrev": "ISO 8879:1986", 12 | "GlossTerm": "Standard Generalized Markup Language", 13 | "GlossDef": { 14 | "GlossSeeAlso": [ 15 | "GML", 16 | "XML" 17 | ], 18 | "para": "A meta-markup language, used to create markup languages such as DocBook." 19 | }, 20 | "GlossSee": "markup", 21 | "ID": "SGML", 22 | "Acronym": "SGML" 23 | } 24 | }, 25 | "starCount": 5 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "Pods" 3 | - "SwiftyJSONAccelerator/UI" 4 | - "SwiftyJSONAccelerator/Support" 5 | -------------------------------------------------------------------------------- /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insanoid/SwiftyJSONAccelerator/af702ae3fdf691bda8813c3a890bf1ea7bd14a6f/preview.png --------------------------------------------------------------------------------