├── .github └── workflows │ └── swift.yml ├── .gitignore ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── .version ├── LICENSE ├── Makefile ├── Package.resolved ├── Package.swift ├── README.md ├── Sources ├── PackageBuilder │ ├── PackageBuilder.swift │ ├── PackageManifestBuilder.swift │ ├── Shell.swift │ ├── TemplateFilesManager.swift │ ├── Templates │ │ ├── dslOnlyPackageTemplate.swift │ │ ├── mainTemplate.swift │ │ └── packageTemplate.swift │ └── TemporalPathBuilder.swift ├── PodfileBuilder │ ├── PodfileBuilder.swift │ └── podfileTemplate.swift ├── Storage │ └── FileSystem.swift ├── SwiftyPods │ ├── Commands │ │ ├── CreateCommand.swift │ │ ├── EditCommand.swift │ │ └── GenerateCommand.swift │ ├── Services │ │ ├── CreateService.swift │ │ ├── EditService.swift │ │ └── GenerateService.swift │ ├── TemplateArgumentParser.swift │ ├── TemplateLocator.swift │ ├── emtyPodfileTemplate.swift │ └── main.swift └── TemplateRenderer │ └── TemplateRenderer.swift ├── Tests ├── LinuxMain.swift ├── PackageBuilderTests │ ├── PackageBuilderTests.swift │ ├── PackageManifestBuilderTests.swift │ ├── TemplateFilesManagerTests.swift │ └── XCTestManifests.swift ├── PodfileBuilderTests │ ├── PodfileBuilderTests.swift │ └── XCTestManifests.swift ├── SwiftyPodsTests │ ├── SwiftyPodsTests.swift │ └── XCTestManifests.swift └── TemplateRendererTests │ ├── TemplateRendererTests.swift │ └── XCTestManifests.swift └── testfolder └── podfile.swift /.github/workflows/swift.yml: -------------------------------------------------------------------------------- 1 | name: Swift 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | SPM: 11 | name: "Tests via SPM" 12 | runs-on: macOS-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Run tests 17 | run: set -o pipefail && swift test 18 | 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | .DS_Store 92 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.version: -------------------------------------------------------------------------------- 1 | 0.2.0 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 David 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PREFIX?=/usr/local 2 | 3 | build: 4 | swift build -c release 5 | 6 | clean_build: 7 | rm -rf .build 8 | make build 9 | 10 | install: build 11 | mkdir -p "$(PREFIX)/bin" 12 | cp -f ".build/release/swiftypods" "$(PREFIX)/bin/swiftypods" 13 | 14 | get_version: 15 | @cat .version 16 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "swift-argument-parser", 6 | "repositoryURL": "https://github.com/apple/swift-argument-parser", 7 | "state": { 8 | "branch": null, 9 | "revision": "8d31a0905c346a45c87773ad50862b5b3df8dff6", 10 | "version": "0.0.4" 11 | } 12 | }, 13 | { 14 | "package": "SwiftShell", 15 | "repositoryURL": "https://github.com/kareman/SwiftShell", 16 | "state": { 17 | "branch": null, 18 | "revision": "fb7fc2c9ad8811caf324431a508fb79e3fb74f99", 19 | "version": "5.0.1" 20 | } 21 | }, 22 | { 23 | "package": "SwiftyPodsDSL", 24 | "repositoryURL": "https://github.com/bitomule/SwiftyPodsDSL", 25 | "state": { 26 | "branch": null, 27 | "revision": "dcedec1ca16d041fc072b46925c7b25f7db5e323", 28 | "version": "0.2.0" 29 | } 30 | } 31 | ] 32 | }, 33 | "version": 1 34 | } 35 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "SwiftyPods", 7 | products: [ 8 | .executable(name: "swiftypods", targets: ["SwiftyPods"]), 9 | .library(name: "PodfileBuilder", targets: ["PodfileBuilder"]), 10 | ], 11 | dependencies: [ 12 | .package(url: "https://github.com/apple/swift-argument-parser", from: "0.0.1"), 13 | .package(url: "https://github.com/kareman/SwiftShell", from: "5.0.1"), 14 | .package(url: "https://github.com/bitomule/SwiftyPodsDSL", from: "0.2.0") 15 | ], 16 | targets: [ 17 | .target( 18 | name: "SwiftyPods", 19 | dependencies: [ 20 | .product(name: "ArgumentParser", package: "swift-argument-parser"), 21 | "SwiftyPodsDSL", 22 | "PackageBuilder", 23 | "TemplateRenderer", 24 | "Storage", 25 | "SwiftShell" 26 | ]), 27 | .target( 28 | name: "PackageBuilder", 29 | dependencies: [ 30 | "TemplateRenderer", 31 | "Storage", 32 | "SwiftShell" 33 | ]), 34 | .target( 35 | name: "TemplateRenderer", 36 | dependencies: [ 37 | "Storage" 38 | ]), 39 | .target( 40 | name: "Storage", 41 | dependencies: [] 42 | ), 43 | .target( 44 | name: "PodfileBuilder", 45 | dependencies: [ 46 | "Storage", 47 | "SwiftShell", 48 | "TemplateRenderer", 49 | "SwiftyPodsDSL" 50 | ] 51 | ), 52 | .testTarget( 53 | name: "TemplateRendererTests", 54 | dependencies: [ 55 | "TemplateRenderer", 56 | "Storage" 57 | ]), 58 | .testTarget( 59 | name: "PackageBuilderTests", 60 | dependencies: [ 61 | "TemplateRenderer", 62 | "Storage", 63 | "PackageBuilder" 64 | ]), 65 | .testTarget( 66 | name: "PodfileBuilderTests", 67 | dependencies: [ 68 | "TemplateRenderer", 69 | "Storage", 70 | "PodfileBuilder" 71 | ]), 72 | .testTarget( 73 | name: "SwiftyPodsTests", 74 | dependencies: [ 75 | "SwiftyPods" 76 | ]), 77 | ] 78 | ) 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Swift](https://github.com/bitomule/SwiftyPods/workflows/Swift/badge.svg) 2 |

3 | 4 | 5 | Swift Package Manager 6 | 7 | Mac + Linux 8 | 9 | Twitter: @bitomule 10 | 11 |

12 | 13 | # SwiftyPods 14 | 15 | SwiftyPods is a command-line tool that allows you to generate your CocoaPods podfile using Swift. It uses [SwiftyPodsDSL](https://github.com/bitomule/SwiftyPodsDSL) as syntax. 16 | 17 | ## Motivation 18 | 19 | Reading and editing the podfile becomes impossible when your project based on CocoaPods grows. You start grouping using variables and comments but it's still a long file where the safety is the error you get when you run pod install. 20 | 21 | SwiftyPods enables two big features: 22 | * **Safety**: Swift will type check your file before you finish editing it. 23 | * **Modularization of your podfile**: You can split your podfile in multiple podfile.swift files. You choose: feature pods, module pods, team pods... Once you generate your podfile they will all get merged into a single, classic podfile. 24 | 25 | ### This is what your podfile.swift will look like 26 | 27 | ```swift 28 | let podfile = Podfile( 29 | targets: [ 30 | .target( 31 | name: "Target", 32 | project: "Project", 33 | dependencies: [ 34 | .dependency(name: "Dependency1"), 35 | .dependency(name: "Dependency2", 36 | version: "1.2.3"), 37 | .dependency(name: "Dependency3", 38 | .git(url: "repo"), 39 | .branch(name: "master")) 40 | ], 41 | childTargets: [ 42 | .target(name: "ChildTarget", project: "Project2") 43 | ] 44 | ) 45 | ] 46 | ) 47 | ``` 48 | 49 | ## Installing 50 | 51 | ### Using [Homebrew](http://brew.sh/) 52 | 53 | ```sh 54 | $ brew install bitomule/homebrew-tap/swiftypods 55 | ``` 56 | 57 | ### Using [Mint](https://github.com/yonaskolb/mint) 58 | 59 | ```sh 60 | $ mint install bitomule/swiftypods 61 | ``` 62 | ### Compiling from source 63 | 64 | ```sh 65 | $ git clone https://github.com/bitomule/SwiftyPods.git 66 | $ cd SwiftyPods 67 | $ make install 68 | ``` 69 | 70 | ## Usage 71 | ### Create your first empty podfile 72 | 73 | You can create an empty podfile.swift using the create command: 74 | 75 | ```sh 76 | $ swiftypods create 77 | ``` 78 | 79 | It takes an optional path parameter that you can use to create your podfile at an specific location: 80 | 81 | ```sh 82 | $ swiftypods create --path "path/to/folder" 83 | ``` 84 | 85 | Once the file is created you can jump directly to editing. 86 | 87 | ### Editing 88 | 89 | You can open your podfile.swift anytime and edit it, but if you want to use the power of the Swift compiler and autocomplete use: 90 | 91 | ```sh 92 | $ swiftypods edit 93 | ``` 94 | 95 | This command will: 96 | 1) Find all your podfile.swift files inside the folder 97 | 2) Create a temporal Xcode project inside tmp folder 98 | 3) Open that project for you 99 | 100 | Once the project is opened you can edit your files content. 101 | 102 | When you have finished editing just close the Xcode project, go back to terminal and press any key. This will copy your templates back to original locations and delete temporal folder. Keep in mind that SwiftyPods has to copy edited files back to original locations. If you don't complete the terminal process, your podfiles won't be updated. 103 | 104 | ### Generating podfile 105 | 106 | In order to generate your podfile just run: 107 | 108 | ```sh 109 | $ swiftypods generate 110 | ``` 111 | 112 | By default it will use an almost empty podfile template but you can use your own template using: 113 | 114 | ```sh 115 | $ swiftypods generate --template "templateLocation" 116 | ``` 117 | 118 | The only thing your template needs to follow is adding {{pods}} where you want your dependencies to get generated. Base podfile template is: 119 | 120 | ``` 121 | platform :ios, '13.0' 122 | inhibit_all_warnings! 123 | 124 | {{pods}} 125 | ``` 126 | 127 | Use your own template to add features not supported by SwiftyPods like hooks or plugins. 128 | 129 | ## Contributions and support 130 | 131 | Contributions are more than welcome. 132 | 133 | Before you start using SwiftyPods, please take a few minutes to check the implementation so you can identify issues or missing features. 134 | 135 | Keep in mind this is a very experimental project, expect breaking changes. 136 | 137 | This project does not come with Github Issues enabled. If you find an issue, missing feature or missing documentation please [open a Pull Request](https://github.com/bitomule/SwiftyPods/pull/new). Your PR could just contain a draft of the changes you plan to do or a test that reproduces the issue so we can start the discussion there. 138 | -------------------------------------------------------------------------------- /Sources/PackageBuilder/PackageBuilder.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Storage 3 | import SwiftShell 4 | import TemplateRenderer 5 | 6 | public protocol PackageBuilding { 7 | @discardableResult 8 | func build(from path: URL, files: [URL]) throws -> URL 9 | func finish(originalFiles: [URL], path: URL) throws 10 | func buildProject(from path: URL, files: [URL]) throws -> URL 11 | func clean() 12 | } 13 | 14 | public final class PackageBuilder: PackageBuilding { 15 | private let temporalPathBuilder: TemporalPathBuilding 16 | private let manifestBuilder: PackageManifestBuilding 17 | private let templateFilesManager: TemplateFilesCoping 18 | private let templateRenderer: TemplateRendering 19 | private let storage: FileSysteming 20 | private let bash: BashRunning 21 | private let packageName: String 22 | private let templateWithCommands: String 23 | private let templateWithoutCommands: String 24 | private let mainFileWithCommandsTemplate: String 25 | 26 | public convenience init(packageName: String) { 27 | self.init( 28 | temporalPathBuilder: TemporalPathBuilder(), 29 | manifestBuilder: PackageManifestBuilder(), 30 | templateFilesManager: TemplateFilesManager(), 31 | templateRenderer: TemplateRenderer(), 32 | storage: FileSystem(), 33 | bash: Bash(), 34 | packageName: packageName, 35 | templateWithCommands: packageTemplate, 36 | templateWithoutCommands: dslOnlyPackage, 37 | mainFileWithCommandsTemplate: mainTemplate) 38 | } 39 | 40 | init( 41 | temporalPathBuilder: TemporalPathBuilding, 42 | manifestBuilder: PackageManifestBuilding, 43 | templateFilesManager: TemplateFilesCoping, 44 | templateRenderer: TemplateRendering, 45 | storage: FileSysteming, 46 | bash: BashRunning, 47 | packageName: String, 48 | templateWithCommands: String, 49 | templateWithoutCommands: String, 50 | mainFileWithCommandsTemplate: String 51 | ) { 52 | self.temporalPathBuilder = temporalPathBuilder 53 | self.manifestBuilder = manifestBuilder 54 | self.templateFilesManager = templateFilesManager 55 | self.templateRenderer = templateRenderer 56 | self.storage = storage 57 | self.bash = bash 58 | self.packageName = packageName 59 | self.templateWithCommands = templateWithCommands 60 | self.templateWithoutCommands = templateWithoutCommands 61 | self.mainFileWithCommandsTemplate = mainFileWithCommandsTemplate 62 | } 63 | 64 | @discardableResult 65 | public func build(from path: URL, files: [URL]) throws -> URL { 66 | let temporalPath = try createBasicPackage(path: path, files: files, manifestTemplate: templateWithCommands) 67 | try createMainSwift(sourcesPath: temporalPath, files: files) 68 | return temporalPath 69 | } 70 | 71 | public func buildProject(from path: URL, files: [URL]) throws -> URL { 72 | let temporalPath = try createBasicPackage(path: path, files: files, manifestTemplate: templateWithoutCommands) 73 | try createEmptyMainSwift(sourcesPath: temporalPath, files: files) 74 | print("Generating temporal project. This may take a few seconds...\n") 75 | bash.run(bash: "(cd \(temporalPath.path) && swift package generate-xcodeproj)") 76 | return temporalPath 77 | } 78 | 79 | public func finish(originalFiles: [URL], path: URL) throws { 80 | try copyFilesBack(originalFiles: originalFiles, path: path) 81 | try storage.delete(at: temporalPathBuilder.getRootTemporalPath()) 82 | } 83 | 84 | public func clean() { 85 | try? storage.delete(at: temporalPathBuilder.getRootTemporalPath()) 86 | } 87 | 88 | private func createBasicPackage(path: URL, files: [URL], manifestTemplate: String) throws -> URL { 89 | let temporalPath = try temporalPathBuilder.build(at: path) 90 | try files.forEach { file in 91 | let newFilePath = temporalPath.appendingPathComponent(file.lastPathComponent) 92 | try templateFilesManager.copyTemplate(from: file, to: newFilePath, override: false) 93 | } 94 | try manifestBuilder.build(at: temporalPath, packageName: packageName, template: manifestTemplate) 95 | return temporalPath 96 | } 97 | 98 | private func copyFilesBack(originalFiles: [URL], path: URL) throws { 99 | try originalFiles.forEach { file in 100 | let newFilePath = path.appendingPathComponent(file.lastPathComponent) 101 | try templateFilesManager.restoreTemplate(from: newFilePath, to: file) 102 | } 103 | } 104 | 105 | private func createMainSwift(sourcesPath: URL, files: [URL]) throws { 106 | let fileNames = try files.map { try templateFilesManager.getTemplateNameFrom(url: $0) } 107 | let mainContent = try templateRenderer.render(template: mainFileWithCommandsTemplate, context: ["podfiles": fileNames.joined(separator: ", ")]) 108 | try storage.saveFile(name: "main.swift", path: sourcesPath, content: mainContent, overwrite: true) 109 | } 110 | 111 | private func createEmptyMainSwift(sourcesPath: URL, files: [URL]) throws { 112 | try storage.saveFile(name: "main.swift", path: sourcesPath, content: "", overwrite: true) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Sources/PackageBuilder/PackageManifestBuilder.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import TemplateRenderer 3 | import Storage 4 | 5 | protocol PackageManifestBuilding { 6 | func build(at path: URL, packageName: String, template: String) throws 7 | } 8 | 9 | public final class PackageManifestBuilder: PackageManifestBuilding { 10 | enum Constant { 11 | static let packageFileName = "Package.swift" 12 | } 13 | private let templateRenderer: TemplateRendering 14 | private let storage: FileSysteming 15 | 16 | init(templateRenderer: TemplateRendering = TemplateRenderer(), 17 | storage: FileSysteming = FileSystem()) { 18 | self.templateRenderer = templateRenderer 19 | self.storage = storage 20 | } 21 | 22 | func build(at path: URL, packageName: String, template: String) throws { 23 | let content = try templateRenderer.render(template: template, context: ["packageName": packageName]) 24 | try storage.saveFile(name: Constant.packageFileName, path: path, content: content, overwrite: true) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/PackageBuilder/Shell.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SwiftShell 3 | 4 | protocol BashRunning { 5 | func run(bash: String) 6 | } 7 | 8 | final class Bash: BashRunning { 9 | func run(bash: String) { 10 | main.run(bash: bash) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/PackageBuilder/TemplateFilesManager.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Storage 3 | 4 | protocol TemplateFilesCoping { 5 | func copyTemplate(from: URL, to: URL, override: Bool) throws 6 | func restoreTemplate(from: URL, to: URL) throws 7 | func getTemplateNameFrom(url: URL) throws -> String 8 | } 9 | 10 | final class TemplateFilesManager: TemplateFilesCoping { 11 | enum Constant { 12 | static let defaultFileName = "podfile" 13 | static let fileExtension = ".swift" 14 | } 15 | 16 | private let storage: FileSysteming 17 | 18 | init (storage: FileSysteming = FileSystem()) { 19 | self.storage = storage 20 | } 21 | 22 | func copyTemplate(from: URL, to: URL, override: Bool) throws { 23 | try storage.copyFile(from: from, to: buildTemplateURLFromPropertyName(url: to, originalFile: from), override: override) 24 | } 25 | 26 | func restoreTemplate(from: URL, to: URL) throws { 27 | try storage.copyFile(from: buildTemplateURLFromPropertyName(url: from, originalFile: to), to: to, override: true) 28 | } 29 | 30 | func getTemplateNameFrom(url: URL) throws -> String { 31 | try findNameForFile(at: url) 32 | } 33 | 34 | private func buildTemplateURLFromPropertyName(url: URL, originalFile: URL) throws -> URL { 35 | let nameForTarget = try findNameForFile(at: originalFile) + Constant.fileExtension 36 | let targetUrl = url.deletingLastPathComponent() 37 | return targetUrl.appendingPathComponent(nameForTarget) 38 | } 39 | 40 | private func findNameForFile(at url: URL) throws -> String { 41 | let content = try storage.getFile(at: url) 42 | return try getNameFromContent(content: content) ?? Constant.defaultFileName 43 | } 44 | 45 | private func getNameFromContent(content: String) throws -> String? { 46 | return try match(for: #"let ([^\s]+)\s?=\s?Podfile"#, in: content) 47 | } 48 | 49 | private func match(for regex: String, in text: String) throws -> String? { 50 | let regex = try NSRegularExpression(pattern: regex) 51 | guard let wholeMatch = regex.firstMatch(in: text, range: NSRange(text.startIndex..., in: text)), 52 | let matchRange = Range(wholeMatch.range(at: 1), in: text) else { 53 | return nil 54 | } 55 | return String(text[matchRange]) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Sources/PackageBuilder/Templates/dslOnlyPackageTemplate.swift: -------------------------------------------------------------------------------- 1 | let dslOnlyPackage = """ 2 | // swift-tools-version:5.1 3 | // The swift-tools-version declares the minimum version of Swift required to build this package. 4 | 5 | import PackageDescription 6 | 7 | let package = Package( 8 | name: "{{packageName}}", 9 | products: [ 10 | .executable(name: "{{packageName}}", targets: ["{{packageName}}"]),], 11 | dependencies: [ 12 | .package(url: "https://github.com/bitomule/SwiftyPodsDSL", .branch("master")) 13 | ], 14 | targets: [ 15 | .target( 16 | name: "{{packageName}}", 17 | dependencies: [ 18 | "SwiftyPodsDSL" 19 | ], 20 | path: "" 21 | ) 22 | ] 23 | ) 24 | """ 25 | -------------------------------------------------------------------------------- /Sources/PackageBuilder/Templates/mainTemplate.swift: -------------------------------------------------------------------------------- 1 | let mainTemplate = """ 2 | import Foundation 3 | import ArgumentParser 4 | import SwiftyPodsDSL 5 | import PodfileBuilder 6 | 7 | let podfiles = [{{podfiles}}] 8 | 9 | struct Generate: ParsableCommand { 10 | public static let configuration = CommandConfiguration(abstract: "Generate podfile") 11 | 12 | @Argument(help: "Path where podfile will be generated") 13 | private var path: String 14 | 15 | @Option(name: .shortAndLong, default: nil, help: "Optional path to template file") 16 | private var templatePath: String? 17 | 18 | func run() throws { 19 | try PodfileBuilder().buildPodfile(podfiles: podfiles, path: path, templatePath: templatePath) 20 | } 21 | } 22 | 23 | Generate.main() 24 | """ 25 | 26 | -------------------------------------------------------------------------------- /Sources/PackageBuilder/Templates/packageTemplate.swift: -------------------------------------------------------------------------------- 1 | let packageTemplate = """ 2 | // swift-tools-version:5.1 3 | // The swift-tools-version declares the minimum version of Swift required to build this package. 4 | 5 | import PackageDescription 6 | 7 | let package = Package( 8 | name: "{{packageName}}", 9 | products: [ 10 | .executable(name: "{{packageName}}", targets: ["{{packageName}}"]),], 11 | dependencies: [ 12 | .package(url: "git@github.com:bitomule/SwiftyPods", .branch("master")), 13 | .package(url: "https://github.com/apple/swift-argument-parser", from: "0.0.1"), 14 | .package(url: "https://github.com/bitomule/SwiftyPodsDSL", .branch("master")) 15 | ], 16 | targets: [ 17 | .target( 18 | name: "{{packageName}}", 19 | dependencies: [ 20 | .product(name: "ArgumentParser", package: "swift-argument-parser"), 21 | "SwiftyPodsDSL", 22 | "PodfileBuilder" 23 | ], 24 | path: "" 25 | ) 26 | ] 27 | ) 28 | 29 | """ 30 | -------------------------------------------------------------------------------- /Sources/PackageBuilder/TemporalPathBuilder.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Storage 3 | 4 | protocol TemporalPathBuilding { 5 | func build(at path: URL) throws -> URL 6 | func getRootTemporalPath() -> String 7 | } 8 | 9 | final class TemporalPathBuilder: TemporalPathBuilding { 10 | enum Constant { 11 | static let temporalFolderPath = "tmp/" 12 | } 13 | private let storage: FileSysteming 14 | 15 | init(storage: FileSysteming = FileSystem()) { 16 | self.storage = storage 17 | } 18 | 19 | func build(at path: URL) throws -> URL { 20 | let uuid = UUID().uuidString 21 | let tmpPath = Constant.temporalFolderPath + uuid + "/" 22 | let newPath = path.appendingPathComponent(tmpPath, isDirectory: true) 23 | try storage.createFolder(at: newPath) 24 | return newPath 25 | } 26 | 27 | func getRootTemporalPath() -> String { 28 | Constant.temporalFolderPath 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/PodfileBuilder/PodfileBuilder.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Storage 3 | import SwiftyPodsDSL 4 | import TemplateRenderer 5 | 6 | public final class PodfileBuilder { 7 | private enum Constant { 8 | static let propertyName = "pods" 9 | } 10 | 11 | private let templateRenderer: TemplateRendering 12 | private let storage: FileSysteming 13 | private let defaultTemplate: String 14 | 15 | public convenience init() { 16 | self.init(templateRenderer: TemplateRenderer(), storage: FileSystem(), defaultTemplate: podfileTemplate) 17 | } 18 | 19 | init(templateRenderer: TemplateRendering, 20 | storage: FileSysteming, 21 | defaultTemplate: String) { 22 | self.templateRenderer = templateRenderer 23 | self.storage = storage 24 | self.defaultTemplate = defaultTemplate 25 | } 26 | 27 | public func buildPodfile(podfiles: [Podfile], path: String, templatePath: String? = nil) throws { 28 | let url = URL(fileURLWithPath: path, isDirectory: true) 29 | let lines = dependenciesLines(from: podfiles) 30 | let template = try templateRenderer.render( 31 | template: try loadTemplateFromPath(templatePath) ?? defaultTemplate, 32 | context: lines 33 | ) 34 | try storage.saveFile(name: "podfile", path: url, content: template, overwrite: true) 35 | } 36 | 37 | private func loadTemplateFromPath(_ path: String?) throws -> String? { 38 | guard let path = path else { return nil } 39 | return try storage.getFile(at: URL(fileURLWithPath: path)) 40 | } 41 | 42 | private func dependenciesLines(from podfiles: [Podfile]) -> [String: String] { 43 | [Constant.propertyName: podfiles.map { $0.render() }.joined(separator: "\n")] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/PodfileBuilder/podfileTemplate.swift: -------------------------------------------------------------------------------- 1 | let podfileTemplate = """ 2 | platform :ios, '13.0' 3 | inhibit_all_warnings! 4 | 5 | {{pods}} 6 | """ 7 | -------------------------------------------------------------------------------- /Sources/Storage/FileSystem.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum FileSystemError: Error { 4 | case fileDoesNotExists(String) 5 | } 6 | 7 | public protocol FileSysteming { 8 | func saveFile(name: String, path: URL, content: String, overwrite: Bool) throws 9 | func getFile(at: URL) throws -> String 10 | func copyFile(from: URL, to: URL, override: Bool) throws 11 | func delete(at path: String) throws 12 | func createFolder(at url:URL) throws 13 | func findFilesInFolder(at url: URL, matching: (URL) -> Bool) throws -> [URL] 14 | func fileExists(at url: URL) -> Bool 15 | } 16 | 17 | public final class FileSystem: FileSysteming { 18 | private let manager: FileManager 19 | private let encoding: String.Encoding 20 | 21 | public init(manager: FileManager = FileManager.default, encoding: String.Encoding = .utf8) { 22 | self.manager = manager 23 | self.encoding = encoding 24 | } 25 | 26 | public func saveFile(name: String, path: URL, content: String, overwrite: Bool) throws { 27 | let newFile = path.appendingPathComponent(name).path 28 | if(overwrite || !manager.fileExists(atPath:newFile)){ 29 | manager.createFile(atPath: newFile, contents: content.data(using: encoding), attributes: nil) 30 | } else{ 31 | print("File is already created") 32 | } 33 | } 34 | 35 | public func copyFile(from: URL, to: URL, override: Bool) throws { 36 | guard manager.fileExists(atPath: from.path) else { 37 | throw FileSystemError.fileDoesNotExists(from.path) 38 | } 39 | if override && manager.fileExists(atPath: to.path) { 40 | try manager.removeItem(at: to) 41 | } 42 | try manager.copyItem(at: from, to: to) 43 | } 44 | 45 | public func getFile(at url: URL) throws -> String { 46 | try String(contentsOf: url) 47 | } 48 | 49 | public func delete(at path: String) throws { 50 | try manager.removeItem(atPath: path) 51 | } 52 | 53 | public func createFolder(at url: URL) throws { 54 | try manager.createDirectory(atPath: url.path, withIntermediateDirectories: true, attributes: nil) 55 | } 56 | 57 | public func findFilesInFolder(at url: URL, matching: (URL) -> Bool) throws -> [URL] { 58 | try manager 59 | .enumerator(at: url, includingPropertiesForKeys: [.isDirectoryKey], options: [.skipsHiddenFiles], errorHandler: nil)? 60 | .allObjects 61 | .map { $0 as! URL } 62 | .filter { url in 63 | let resourceValues = try url.resourceValues(forKeys: [.isDirectoryKey]) 64 | return !(resourceValues.isDirectory ?? true) 65 | } 66 | .filter(matching) ?? [] 67 | } 68 | 69 | public func fileExists(at url: URL) -> Bool { 70 | manager.fileExists(atPath: url.path) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Sources/SwiftyPods/Commands/CreateCommand.swift: -------------------------------------------------------------------------------- 1 | import ArgumentParser 2 | import Foundation 3 | 4 | struct Create: ParsableCommand { 5 | 6 | public static let configuration = CommandConfiguration(abstract: "Create an empty podfile.swift") 7 | 8 | @Option(name: .shortAndLong, default: nil, help: "Optional path where podfile.swift should be created") 9 | private var path: String? 10 | 11 | func run() throws { 12 | try CreateService().run(path: path) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/SwiftyPods/Commands/EditCommand.swift: -------------------------------------------------------------------------------- 1 | import ArgumentParser 2 | import Foundation 3 | 4 | struct Edit: ParsableCommand { 5 | public static let configuration = CommandConfiguration(abstract: "Open templates in folder to edit") 6 | 7 | func run() throws { 8 | try EditService().run() 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sources/SwiftyPods/Commands/GenerateCommand.swift: -------------------------------------------------------------------------------- 1 | import ArgumentParser 2 | import Foundation 3 | 4 | struct Generate: ParsableCommand { 5 | 6 | public static let configuration = CommandConfiguration(abstract: "Generate a pod file from template") 7 | 8 | @Option(name: .shortAndLong, default: nil, help: "Optional path to template file") 9 | private var template: String? 10 | 11 | func run() throws { 12 | try GenerateService().run(template: template) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/SwiftyPods/Services/CreateService.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Storage 3 | import PackageBuilder 4 | import SwiftShell 5 | 6 | final class CreateService { 7 | private enum Constant { 8 | static let fileName = "podfile.swift" 9 | } 10 | private let storage: FileSysteming 11 | private let template: String 12 | 13 | init(storage: FileSysteming = FileSystem(), 14 | template: String = emptyPodfileTemplate) { 15 | self.storage = storage 16 | self.template = template 17 | } 18 | 19 | func run(path: String?) throws { 20 | let url: URL 21 | if let path = path { 22 | url = URL(fileURLWithPath: path, isDirectory: true) 23 | } else { 24 | url = URL(fileURLWithPath: "", isDirectory: true) 25 | } 26 | try storage.saveFile(name: Constant.fileName, path: url, content: template, overwrite: false) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/SwiftyPods/Services/EditService.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import PackageBuilder 3 | import SwiftShell 4 | 5 | final class EditService { 6 | enum Constant { 7 | static let openCommand = "open" 8 | static let packageName = "SwiftyPodsTemporalProject" 9 | static let packageFileName = "Package.swift" 10 | static let projectFileExtension = ".xcodeproj" 11 | } 12 | 13 | private let packageBuilder: PackageBuilding 14 | private let templatesLocator: TemplateLocating 15 | 16 | init(packageBuilder: PackageBuilding = PackageBuilder(packageName: Constant.packageName), 17 | templatesLocator: TemplateLocating = TemplateLocator()) { 18 | self.packageBuilder = packageBuilder 19 | self.templatesLocator = templatesLocator 20 | } 21 | 22 | func run() throws { 23 | packageBuilder.clean() 24 | let baseUrl = URL(fileURLWithPath: "", isDirectory: true) 25 | let files = try getTemplateFiles() 26 | let url = try packageBuilder.buildProject(from: baseUrl, files: files) 27 | open(url: url) 28 | try waitForUserInput(projectUrl: url, files: files) 29 | } 30 | 31 | private func getTemplateFiles() throws -> [URL] { 32 | let baseUrl = URL(fileURLWithPath: "", isDirectory: true) 33 | return try templatesLocator.findTemplates(at: baseUrl) 34 | } 35 | 36 | private func waitForUserInput(projectUrl: URL, files: [URL]) throws { 37 | print("Opening Xcode project. Press any key to when you've finished editing") 38 | var ended = false 39 | while !ended, let _ = main.stdin.readSome() { 40 | ended = true 41 | try packageBuilder.finish(originalFiles: files, path: projectUrl) 42 | print("Your edited podfiles are ready") 43 | } 44 | } 45 | 46 | private func open(url: URL) { 47 | let packageURL = url.appendingPathComponent(Constant.packageName + Constant.projectFileExtension) 48 | main.run(Constant.openCommand, packageURL.relativeString) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/SwiftyPods/Services/GenerateService.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Storage 3 | import PackageBuilder 4 | import SwiftShell 5 | 6 | final class GenerateService { 7 | private enum Constant { 8 | static let templateFileName = "podfile" 9 | static let packageName = "SwiftyPodsTemporalProject" 10 | } 11 | private let templateArgumentParser: TemplateArgumentParser 12 | private let packageBuilder: PackageBuilding 13 | private let templatesLocator: TemplateLocating 14 | 15 | init(templateArgumentParser: TemplateArgumentParser = TemplateArgumentParser(), 16 | packageBuilder: PackageBuilding = PackageBuilder(packageName: Constant.packageName), 17 | templatesLocator: TemplateLocating = TemplateLocator()) { 18 | self.templateArgumentParser = templateArgumentParser 19 | self.packageBuilder = packageBuilder 20 | self.templatesLocator = templatesLocator 21 | } 22 | 23 | func run(template: String?) throws { 24 | packageBuilder.clean() 25 | let baseUrl = URL(fileURLWithPath: "", isDirectory: true) 26 | let files = try templatesLocator.findTemplates(at: baseUrl) 27 | let url = try packageBuilder.build(from: baseUrl, files: files) 28 | try generate(url: url, template: template) 29 | print("Cleaning") 30 | try packageBuilder.finish(originalFiles: files, path: url) 31 | } 32 | 33 | private func generate(url: URL, template: String?) throws { 34 | print("Generating podfile\n") 35 | if let template = template { 36 | let templatePath = URL(fileURLWithPath: template) 37 | try main.runAndPrint(bash: "(cd \(url.path) && swift run \(Constant.packageName) \(URL(fileURLWithPath: "", isDirectory: true).path) --template-path \"\(templatePath.path)\")") 38 | } else { 39 | try main.runAndPrint(bash: "(cd \(url.path) && swift run \(Constant.packageName) \(URL(fileURLWithPath: "", isDirectory: true).path))") 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/SwiftyPods/TemplateArgumentParser.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Storage 3 | 4 | class TemplateArgumentParser { 5 | private let storage: FileSysteming 6 | 7 | init(storage: FileSysteming = FileSystem()) { 8 | self.storage = storage 9 | } 10 | 11 | func getTemplateName(template: String) -> URL? { 12 | let templateURL = URL(fileURLWithPath: template) 13 | guard storage.fileExists(at: templateURL) else { 14 | return nil 15 | } 16 | return templateURL 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/SwiftyPods/TemplateLocator.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Storage 3 | 4 | protocol TemplateLocating { 5 | func findTemplates(at path: URL) throws -> [URL] 6 | } 7 | 8 | final class TemplateLocator: TemplateLocating { 9 | enum Constant { 10 | static let templateName = "podfile.swift" 11 | } 12 | 13 | private let storage: FileSysteming 14 | 15 | init(storage: FileSysteming = FileSystem()) { 16 | self.storage = storage 17 | } 18 | 19 | func findTemplates(at path: URL) throws -> [URL] { 20 | try storage.findFilesInFolder(at: path, matching: { $0.lastPathComponent == Constant.templateName }) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/SwiftyPods/emtyPodfileTemplate.swift: -------------------------------------------------------------------------------- 1 | let emptyPodfileTemplate = """ 2 | import Foundation 3 | import SwiftyPodsDSL 4 | 5 | let podfile = Podfile(targets: []) 6 | """ 7 | -------------------------------------------------------------------------------- /Sources/SwiftyPods/main.swift: -------------------------------------------------------------------------------- 1 | import ArgumentParser 2 | 3 | struct SwiftyPods: ParsableCommand { 4 | static let configuration = CommandConfiguration( 5 | abstract: "A Swift command-line tool to manage podfile", 6 | subcommands: [ 7 | Generate.self, 8 | Edit.self, 9 | Create.self 10 | ]) 11 | 12 | init() { } 13 | } 14 | 15 | SwiftyPods.main() 16 | -------------------------------------------------------------------------------- /Sources/TemplateRenderer/TemplateRenderer.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Storage 3 | 4 | public protocol TemplateRendering { 5 | func render( 6 | template: String, 7 | context: [String: String] 8 | ) throws -> String 9 | 10 | func render( 11 | templateFile: URL, 12 | context: [String: String] 13 | ) throws -> String 14 | } 15 | 16 | public final class TemplateRenderer: TemplateRendering { 17 | private let storage: FileSysteming 18 | 19 | public init(storage: FileSysteming = FileSystem()){ 20 | self.storage = storage 21 | } 22 | 23 | public func render( 24 | template: String, 25 | context: [String: String] 26 | ) throws -> String { 27 | context.reduce(template) { result, dict in 28 | return generateFile(template: result, value: dict.value, keyToReplace: dict.key) 29 | } 30 | } 31 | 32 | public func render( 33 | templateFile: URL, 34 | context: [String: String] 35 | ) throws -> String { 36 | let template = try storage.getFile(at: templateFile) 37 | return context.reduce(template) { result, dict in 38 | return generateFile(template: result, value: dict.value, keyToReplace: dict.key) 39 | } 40 | } 41 | 42 | private func generateFile(template: String, value: String, keyToReplace: String) -> String { 43 | template.replacingOccurrences(of: "{{\(keyToReplace)}}", with: value) 44 | } 45 | 46 | private func filePath(from url: URL) -> String { 47 | url.deletingLastPathComponent().relativeString 48 | } 49 | 50 | private func fileName(from url: URL) -> String { 51 | url.lastPathComponent 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import SwiftyPodsTests 4 | import TemplateRendererTests 5 | 6 | var tests = [XCTestCaseEntry]() 7 | tests += SwiftyPodsTests.allTests() 8 | tests += TemplateRendererTests.allTests() 9 | XCTMain(tests) 10 | -------------------------------------------------------------------------------- /Tests/PackageBuilderTests/PackageBuilderTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import class Foundation.Bundle 3 | @testable import PackageBuilder 4 | import TemplateRenderer 5 | import Storage 6 | 7 | final class PackageBuilderTests: XCTestCase { 8 | private var packageBuilder: PackageBuilder! 9 | 10 | // MARK: - Build 11 | 12 | func testBuildCreatesTemporalPath() throws { 13 | let temporalPathBuilder = TemporalPathBuidingMock() 14 | packageBuilder = PackageBuilder( 15 | temporalPathBuilder: temporalPathBuilder, 16 | manifestBuilder: PackageManifestBuildingMock(), 17 | templateFilesManager: TemplateFilesCopingMock(), 18 | templateRenderer: TemplateRenderingMock(), 19 | storage: FileSystemMock(), 20 | bash: BashMock(), 21 | packageName: "packageName", 22 | templateWithCommands: "", 23 | templateWithoutCommands: "", 24 | mainFileWithCommandsTemplate: "") 25 | 26 | _ = try packageBuilder.build(from: URL(fileURLWithPath: ""), files: []) 27 | 28 | XCTAssertNotNil(temporalPathBuilder.buildArguments) 29 | XCTAssertEqual(temporalPathBuilder.buildArguments!.path.relativePath, ".") 30 | } 31 | 32 | func testBuildCopiesFiles() throws { 33 | let templateFilesManager = TemplateFilesCopingMock() 34 | templateFilesManager.templateName = "TemplateName" 35 | packageBuilder = PackageBuilder( 36 | temporalPathBuilder: TemporalPathBuidingMock(), 37 | manifestBuilder: PackageManifestBuildingMock(), 38 | templateFilesManager: templateFilesManager, 39 | templateRenderer: TemplateRenderingMock(), 40 | storage: FileSystemMock(), 41 | bash: BashMock(), 42 | packageName: "packageName", 43 | templateWithCommands: "", 44 | templateWithoutCommands: "", 45 | mainFileWithCommandsTemplate: "") 46 | 47 | _ = try packageBuilder.build(from: URL(fileURLWithPath: ""), files: [URL(fileURLWithPath: "aFolder/podfile.swift")]) 48 | 49 | XCTAssertNotNil(templateFilesManager.copyTemplateArguments) 50 | XCTAssertEqual(templateFilesManager.copyTemplateArguments!.from.relativePath, "aFolder/podfile.swift") 51 | XCTAssertEqual(templateFilesManager.copyTemplateArguments!.to.relativePath, "tmp/podfile.swift") 52 | } 53 | 54 | func testBuildCreatesManifest() throws { 55 | let manifestBuilderMock = PackageManifestBuildingMock() 56 | packageBuilder = PackageBuilder( 57 | temporalPathBuilder: TemporalPathBuidingMock(), 58 | manifestBuilder: manifestBuilderMock, 59 | templateFilesManager: TemplateFilesCopingMock(), 60 | templateRenderer: TemplateRenderingMock(), 61 | storage: FileSystemMock(), 62 | bash: BashMock(), 63 | packageName: "packageName", 64 | templateWithCommands: "templateWithCommands", 65 | templateWithoutCommands: "templateWithoutCommands", 66 | mainFileWithCommandsTemplate: "") 67 | 68 | _ = try packageBuilder.build(from: URL(fileURLWithPath: ""), files: [URL(fileURLWithPath: "aFolder/podfile.swift")]) 69 | 70 | XCTAssertNotNil(manifestBuilderMock.buildArguments) 71 | XCTAssertEqual(manifestBuilderMock.buildArguments!.path.relativePath, "tmp") 72 | XCTAssertEqual(manifestBuilderMock.buildArguments!.packageName, "packageName") 73 | XCTAssertEqual(manifestBuilderMock.buildArguments!.template, "templateWithCommands") 74 | } 75 | 76 | func testBuildCreatesCommandsMain() throws { 77 | let fileSystemMock = FileSystemMock() 78 | let templateRendererMock = TemplateRenderingMock() 79 | packageBuilder = PackageBuilder( 80 | temporalPathBuilder: TemporalPathBuidingMock(), 81 | manifestBuilder: PackageManifestBuildingMock(), 82 | templateFilesManager: TemplateFilesCopingMock(), 83 | templateRenderer: templateRendererMock, 84 | storage: fileSystemMock, 85 | bash: BashMock(), 86 | packageName: "packageName", 87 | templateWithCommands: "templateWithCommands", 88 | templateWithoutCommands: "templateWithoutCommands", 89 | mainFileWithCommandsTemplate: "mainTemplateHere") 90 | 91 | _ = try packageBuilder.build(from: URL(fileURLWithPath: ""), files: [URL(fileURLWithPath: "aFolder/podfile.swift")]) 92 | 93 | XCTAssertNotNil(templateRendererMock.renderArguments) 94 | XCTAssertEqual(templateRendererMock.renderArguments!.template, "mainTemplateHere") 95 | XCTAssertNotNil(templateRendererMock.renderArguments!.context["podfiles"]) 96 | 97 | XCTAssertNotNil(fileSystemMock.saveFileArguments) 98 | XCTAssertEqual(fileSystemMock.saveFileArguments!.name, "main.swift") 99 | XCTAssertEqual(fileSystemMock.saveFileArguments!.path.relativePath, "tmp") 100 | XCTAssertEqual(fileSystemMock.saveFileArguments!.content, "") 101 | XCTAssertTrue(fileSystemMock.saveFileArguments!.overwrite) 102 | } 103 | 104 | // MARK: - BuildProject 105 | 106 | func testBuildProjectCreatesEmptyMain() throws { 107 | let fileSystemMock = FileSystemMock() 108 | packageBuilder = PackageBuilder( 109 | temporalPathBuilder: TemporalPathBuidingMock(), 110 | manifestBuilder: PackageManifestBuildingMock(), 111 | templateFilesManager: TemplateFilesCopingMock(), 112 | templateRenderer: TemplateRenderingMock(), 113 | storage: fileSystemMock, 114 | bash: BashMock(), 115 | packageName: "packageName", 116 | templateWithCommands: "templateWithCommands", 117 | templateWithoutCommands: "templateWithoutCommands", 118 | mainFileWithCommandsTemplate: "") 119 | 120 | _ = try packageBuilder.buildProject(from: URL(fileURLWithPath: ""), files: [URL(fileURLWithPath: "aFolder/podfile.swift")]) 121 | 122 | XCTAssertNotNil(fileSystemMock.saveFileArguments) 123 | XCTAssertEqual(fileSystemMock.saveFileArguments!.name, "main.swift") 124 | XCTAssertEqual(fileSystemMock.saveFileArguments!.path.relativePath, "tmp") 125 | XCTAssertEqual(fileSystemMock.saveFileArguments!.content, "") 126 | XCTAssertTrue(fileSystemMock.saveFileArguments!.overwrite) 127 | } 128 | 129 | func testBuildProjectCreatesManifest() throws { 130 | let manifestBuilderMock = PackageManifestBuildingMock() 131 | packageBuilder = PackageBuilder( 132 | temporalPathBuilder: TemporalPathBuidingMock(), 133 | manifestBuilder: manifestBuilderMock, 134 | templateFilesManager: TemplateFilesCopingMock(), 135 | templateRenderer: TemplateRenderingMock(), 136 | storage: FileSystemMock(), 137 | bash: BashMock(), 138 | packageName: "packageName", 139 | templateWithCommands: "templateWithCommands", 140 | templateWithoutCommands: "templateWithoutCommands", 141 | mainFileWithCommandsTemplate: "") 142 | 143 | _ = try packageBuilder.buildProject(from: URL(fileURLWithPath: ""), files: [URL(fileURLWithPath: "aFolder/podfile.swift")]) 144 | 145 | XCTAssertNotNil(manifestBuilderMock.buildArguments) 146 | XCTAssertEqual(manifestBuilderMock.buildArguments!.path.relativePath, "tmp") 147 | XCTAssertEqual(manifestBuilderMock.buildArguments!.packageName, "packageName") 148 | XCTAssertEqual(manifestBuilderMock.buildArguments!.template, "templateWithoutCommands") 149 | } 150 | 151 | func testBuildProjectRunsGenerateXcodeProj() throws { 152 | let bashMock = BashMock() 153 | packageBuilder = PackageBuilder( 154 | temporalPathBuilder: TemporalPathBuidingMock(), 155 | manifestBuilder: PackageManifestBuildingMock(), 156 | templateFilesManager: TemplateFilesCopingMock(), 157 | templateRenderer: TemplateRenderingMock(), 158 | storage: FileSystemMock(), 159 | bash: bashMock, 160 | packageName: "packageName", 161 | templateWithCommands: "templateWithCommands", 162 | templateWithoutCommands: "templateWithoutCommands", 163 | mainFileWithCommandsTemplate: "") 164 | 165 | let path = try packageBuilder.buildProject(from: URL(fileURLWithPath: ""), files: [URL(fileURLWithPath: "aFolder/podfile.swift")]) 166 | 167 | XCTAssertNotNil(bashMock.bashArgument) 168 | XCTAssertEqual(bashMock.bashArgument!, "(cd \(path.path) && swift package generate-xcodeproj)") 169 | } 170 | 171 | static var allTests = [ 172 | ("testBuildCreatesTemporalPath", testBuildCreatesTemporalPath), 173 | ("testBuildCopiesFiles", testBuildCopiesFiles), 174 | ("testBuildCreatesManifest", testBuildCreatesManifest), 175 | ("testBuildProjectCreatesEmptyMain", testBuildProjectCreatesEmptyMain), 176 | ("testBuildProjectCreatesManifest", testBuildProjectCreatesManifest), 177 | ("testBuildCreatesCommandsMain", testBuildCreatesCommandsMain), 178 | ("testBuildProjectRunsGenerateXcodeProj", testBuildProjectRunsGenerateXcodeProj) 179 | ] 180 | } 181 | 182 | // MARK: - Mocks 183 | 184 | private final class TemporalPathBuidingMock: TemporalPathBuilding { 185 | struct BuildArguments { 186 | let path: URL 187 | } 188 | var buildArguments: BuildArguments? 189 | 190 | func build(at path: URL) throws -> URL { 191 | buildArguments = BuildArguments(path: path) 192 | return URL(fileURLWithPath: "tmp") 193 | } 194 | 195 | func getRootTemporalPath() -> String { 196 | fatalError() 197 | } 198 | } 199 | 200 | private final class PackageManifestBuildingMock: PackageManifestBuilding { 201 | struct BuildArguments { 202 | let path: URL 203 | let packageName: String 204 | let template: String 205 | } 206 | var buildArguments: BuildArguments? 207 | 208 | func build(at path: URL, packageName: String, template: String) throws { 209 | buildArguments = BuildArguments(path: path, packageName: packageName, template: template) 210 | } 211 | } 212 | 213 | private final class TemplateFilesCopingMock: TemplateFilesCoping { 214 | struct CopyTemplateArguments { 215 | let from: URL 216 | let to: URL 217 | let override: Bool 218 | } 219 | var copyTemplateArguments: CopyTemplateArguments? 220 | var templateName: String? 221 | 222 | func copyTemplate(from: URL, to: URL, override: Bool) throws { 223 | copyTemplateArguments = CopyTemplateArguments(from: from, to: to, override: override) 224 | } 225 | 226 | func restoreTemplate(from: URL, to: URL) throws { 227 | fatalError() 228 | } 229 | 230 | func getTemplateNameFrom(url: URL) throws -> String { 231 | templateName ?? "" 232 | } 233 | } 234 | 235 | private final class TemplateRenderingMock: TemplateRendering { 236 | struct RenderArguments { 237 | let template: String 238 | let context: [String: String] 239 | } 240 | var renderArguments: RenderArguments? 241 | 242 | func render( 243 | template: String, 244 | context: [String: String] 245 | ) throws -> String { 246 | renderArguments = RenderArguments(template: template, context: context) 247 | return "" 248 | } 249 | 250 | func render( 251 | templateFile: URL, 252 | context: [String: String] 253 | ) throws -> String { 254 | "" 255 | } 256 | } 257 | 258 | private final class FileSystemMock: FileSysteming { 259 | struct SaveFileArguments { 260 | let name: String 261 | let path: URL 262 | let content: String 263 | let overwrite: Bool 264 | } 265 | var saveFileArguments: SaveFileArguments? 266 | 267 | func saveFile(name: String, path: URL, content: String, overwrite: Bool) throws { 268 | saveFileArguments = SaveFileArguments(name: name, path: path, content: content, overwrite: overwrite) 269 | } 270 | 271 | func getFile(at: URL) throws -> String { 272 | podfileStub 273 | } 274 | 275 | func copyFile(from: URL, to: URL, override: Bool) throws {} 276 | 277 | func delete(at path: String) throws { 278 | fatalError() 279 | } 280 | 281 | func createFolder(at url:URL) throws { 282 | fatalError() 283 | } 284 | 285 | func findFilesInFolder(at url: URL, matching: (URL) -> Bool) throws -> [URL] { 286 | [] 287 | } 288 | 289 | func fileExists(at url: URL) -> Bool { 290 | false 291 | } 292 | } 293 | 294 | private final class BashMock: BashRunning { 295 | var bashArgument: String? 296 | 297 | func run(bash: String) { 298 | bashArgument = bash 299 | } 300 | } 301 | 302 | private let podfileStub = """ 303 | import Foundation 304 | import SwiftyPodsDSL 305 | 306 | let example = Podfile( 307 | targets: [ 308 | .target( 309 | name: "Target", 310 | project: "Project", 311 | dependencies: [ 312 | .dependency(name: "Dependency1"), 313 | .dependency(name: "Dependency2", 314 | version: "1.2.3"), 315 | .dependency(name: "Dependency3", 316 | .git(url: "repo"), 317 | .branch(name: "master")) 318 | ], 319 | childTargets: [ 320 | .target(name: "ChildTarget", project: "Project2") 321 | ] 322 | ) 323 | ] 324 | ) 325 | """ 326 | -------------------------------------------------------------------------------- /Tests/PackageBuilderTests/PackageManifestBuilderTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import class Foundation.Bundle 3 | @testable import PackageBuilder 4 | import TemplateRenderer 5 | import Storage 6 | 7 | final class PackageManifestBuilderTests: XCTestCase { 8 | private var manifestBuilder: PackageManifestBuilder! 9 | 10 | func testSavesManifest() throws { 11 | let templateRendererMock = TemplateRendererMock() 12 | templateRendererMock.rendered = "Rendered" 13 | let fileSystemMock = FileSystemMock() 14 | manifestBuilder = PackageManifestBuilder(templateRenderer: templateRendererMock, 15 | storage:fileSystemMock) 16 | try manifestBuilder.build(at: URL(fileURLWithPath: "packagePath"), packageName: "TestPackage", template: "") 17 | XCTAssertNotNil(fileSystemMock.saveFileArguments) 18 | XCTAssertEqual(fileSystemMock.saveFileArguments!.name, "Package.swift") 19 | XCTAssertEqual(fileSystemMock.saveFileArguments!.path.relativePath, "packagePath") 20 | XCTAssertEqual(fileSystemMock.saveFileArguments!.content, "Rendered") 21 | XCTAssertTrue(fileSystemMock.saveFileArguments!.overwrite) 22 | } 23 | 24 | 25 | static var allTests = [ 26 | ("testSavesManifest", testSavesManifest) 27 | ] 28 | } 29 | 30 | // MARK: - Mocks 31 | 32 | private final class TemplateRendererMock: TemplateRendering { 33 | var rendered: String = "" 34 | 35 | func render( 36 | template: String, 37 | context: [String: String] 38 | ) throws -> String { 39 | rendered 40 | } 41 | 42 | func render( 43 | templateFile: URL, 44 | context: [String: String] 45 | ) throws -> String { 46 | rendered 47 | } 48 | } 49 | 50 | private final class FileSystemMock: FileSysteming { 51 | struct SaveFileArguments { 52 | let name: String 53 | let path: URL 54 | let content: String 55 | let overwrite: Bool 56 | } 57 | var saveFileArguments: SaveFileArguments? 58 | 59 | func saveFile(name: String, path: URL, content: String, overwrite: Bool) throws { 60 | saveFileArguments = SaveFileArguments(name: name, path: path, content: content, overwrite: overwrite) 61 | } 62 | 63 | func getFile(at: URL) throws -> String { 64 | fatalError() 65 | } 66 | 67 | func copyFile(from: URL, to: URL, override: Bool) throws { 68 | fatalError() 69 | } 70 | 71 | func delete(at path: String) throws { 72 | fatalError() 73 | } 74 | 75 | func createFolder(at url:URL) throws { 76 | fatalError() 77 | } 78 | 79 | func findFilesInFolder(at url: URL, matching: (URL) -> Bool) throws -> [URL] { 80 | [] 81 | } 82 | 83 | func fileExists(at url: URL) -> Bool { 84 | false 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Tests/PackageBuilderTests/TemplateFilesManagerTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import class Foundation.Bundle 3 | @testable import PackageBuilder 4 | import TemplateRenderer 5 | import Storage 6 | 7 | final class TemplateFilesManagerTests: XCTestCase { 8 | private var templateFilesManager: TemplateFilesManager! 9 | 10 | func testCopiesTemplate() throws { 11 | let fileSystemMock = FileSystemMock() 12 | templateFilesManager = TemplateFilesManager(storage: fileSystemMock) 13 | 14 | try templateFilesManager.copyTemplate(from: URL(fileURLWithPath: "one.swift"), to: URL(fileURLWithPath: "two.swift"), override: true) 15 | 16 | XCTAssertNotNil(fileSystemMock.copyFileArguments) 17 | XCTAssertEqual(fileSystemMock.copyFileArguments!.from.relativePath, "one.swift") 18 | XCTAssertEqual(fileSystemMock.copyFileArguments!.to.relativePath, "./example.swift") 19 | XCTAssertTrue(fileSystemMock.copyFileArguments!.override) 20 | } 21 | 22 | func testRestoresTemplate() throws { 23 | let fileSystemMock = FileSystemMock() 24 | templateFilesManager = TemplateFilesManager(storage: fileSystemMock) 25 | 26 | try templateFilesManager.restoreTemplate(from: URL(fileURLWithPath: "./example.swift"), to: URL(fileURLWithPath: "one.swift")) 27 | 28 | XCTAssertNotNil(fileSystemMock.copyFileArguments) 29 | XCTAssertEqual(fileSystemMock.copyFileArguments!.from.relativePath, "./example.swift") 30 | XCTAssertEqual(fileSystemMock.copyFileArguments!.to.relativePath, "one.swift") 31 | XCTAssertTrue(fileSystemMock.copyFileArguments!.override) 32 | } 33 | 34 | func testGetsTemplateNameFromFile() throws { 35 | let fileSystemMock = FileSystemMock() 36 | templateFilesManager = TemplateFilesManager(storage: fileSystemMock) 37 | 38 | let name = try templateFilesManager.getTemplateNameFrom(url: URL(fileURLWithPath: "one.swift")) 39 | 40 | XCTAssertEqual(name, "example") 41 | } 42 | 43 | 44 | static var allTests = [ 45 | ("testCopiesTemplate", testCopiesTemplate), 46 | ("testRestoresTemplate", testRestoresTemplate), 47 | ("testGetsTemplateNameFromFile", testGetsTemplateNameFromFile) 48 | ] 49 | } 50 | 51 | // MARK: - Mocks 52 | 53 | private final class FileSystemMock: FileSysteming { 54 | struct CopyFileArguments { 55 | let from: URL 56 | let to: URL 57 | let override: Bool 58 | } 59 | var copyFileArguments: CopyFileArguments? 60 | 61 | func saveFile(name: String, path: URL, content: String, overwrite: Bool) throws { 62 | fatalError() 63 | } 64 | 65 | func getFile(at: URL) throws -> String { 66 | podfileStub 67 | } 68 | 69 | func copyFile(from: URL, to: URL, override: Bool) throws { 70 | copyFileArguments = CopyFileArguments(from: from, to: to, override: override) 71 | } 72 | 73 | func delete(at path: String) throws { 74 | fatalError() 75 | } 76 | 77 | func createFolder(at url:URL) throws { 78 | fatalError() 79 | } 80 | 81 | func findFilesInFolder(at url: URL, matching: (URL) -> Bool) throws -> [URL] { 82 | [] 83 | } 84 | 85 | func fileExists(at url: URL) -> Bool { 86 | false 87 | } 88 | } 89 | 90 | private let podfileStub = """ 91 | import Foundation 92 | import SwiftyPodsDSL 93 | 94 | let example = Podfile( 95 | targets: [ 96 | .target( 97 | name: "Target", 98 | project: "Project", 99 | dependencies: [ 100 | .dependency(name: "Dependency1"), 101 | .dependency(name: "Dependency2", 102 | version: "1.2.3"), 103 | .dependency(name: "Dependency3", 104 | .git(url: "repo"), 105 | .branch(name: "master")) 106 | ], 107 | childTargets: [ 108 | .target(name: "ChildTarget", project: "Project2") 109 | ] 110 | ) 111 | ] 112 | ) 113 | """ 114 | -------------------------------------------------------------------------------- /Tests/PackageBuilderTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(PackageBuilderTests.allTests), 7 | testCase(PackageManifestBuilderTests.allTests), 8 | testCase(TemplateFilesManagerTests.allTests) 9 | ] 10 | } 11 | #endif 12 | -------------------------------------------------------------------------------- /Tests/PodfileBuilderTests/PodfileBuilderTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import class Foundation.Bundle 3 | @testable import PodfileBuilder 4 | import Storage 5 | import TemplateRenderer 6 | 7 | final class PodfileBuilderTests: XCTestCase { 8 | private var podfileBuilder: PodfileBuilder! 9 | 10 | func testSavesGeneratedEmptyPodfile() throws { 11 | let fileSystemMock = FileSystemMock() 12 | let templateRenderMock = TemplateRenderingMock() 13 | let defaultTemplate = "content{{pods}}" 14 | podfileBuilder = PodfileBuilder(templateRenderer: templateRenderMock, storage: fileSystemMock, defaultTemplate: defaultTemplate) 15 | 16 | try podfileBuilder.buildPodfile(podfiles: [], path: "path") 17 | 18 | XCTAssertNotNil(fileSystemMock.saveFileArguments) 19 | XCTAssertNotNil(templateRenderMock.renderArguments) 20 | XCTAssertEqual(fileSystemMock.saveFileArguments!.name, "podfile") 21 | XCTAssertEqual(templateRenderMock.renderArguments!.context["pods"], "") 22 | XCTAssertEqual(templateRenderMock.renderArguments!.template, defaultTemplate) 23 | } 24 | 25 | func testSavesGeneratedPodfileFromFile() throws { 26 | let fileSystemMock = FileSystemMock() 27 | let fileTemplate = "fileTemplate{{pods}}" 28 | fileSystemMock.getFile = fileTemplate 29 | let templateRenderMock = TemplateRenderingMock() 30 | let defaultTemplate = "content{{pods}}" 31 | podfileBuilder = PodfileBuilder(templateRenderer: templateRenderMock, storage: fileSystemMock, defaultTemplate: defaultTemplate) 32 | 33 | try podfileBuilder.buildPodfile(podfiles: [], path: "path", templatePath: "path") 34 | 35 | XCTAssertNotNil(fileSystemMock.saveFileArguments) 36 | XCTAssertNotNil(templateRenderMock.renderArguments) 37 | XCTAssertEqual(fileSystemMock.saveFileArguments!.name, "podfile") 38 | XCTAssertEqual(templateRenderMock.renderArguments!.context["pods"], "") 39 | XCTAssertEqual(templateRenderMock.renderArguments!.template, fileTemplate) 40 | } 41 | 42 | static var allTests = [ 43 | ("testSavesGeneratedEmptyPodfile", testSavesGeneratedEmptyPodfile) 44 | ] 45 | } 46 | 47 | // MARK: - Mocks 48 | 49 | private final class FileSystemMock: FileSysteming { 50 | struct SaveFileArguments { 51 | let name: String 52 | let path: URL 53 | let content: String 54 | let overwrite: Bool 55 | } 56 | var saveFileArguments: SaveFileArguments? 57 | 58 | var getFile: String? 59 | 60 | func saveFile(name: String, path: URL, content: String, overwrite: Bool) throws { 61 | saveFileArguments = SaveFileArguments(name: name, path: path, content: content, overwrite: overwrite) 62 | } 63 | 64 | func getFile(at: URL) throws -> String { 65 | getFile ?? "" 66 | } 67 | 68 | func copyFile(from: URL, to: URL, override: Bool) throws { 69 | fatalError() 70 | } 71 | 72 | func delete(at path: String) throws { 73 | fatalError() 74 | } 75 | 76 | func createFolder(at url:URL) throws { 77 | fatalError() 78 | } 79 | 80 | func findFilesInFolder(at url: URL, matching: (URL) -> Bool) throws -> [URL] { 81 | [] 82 | } 83 | 84 | func fileExists(at url: URL) -> Bool { 85 | false 86 | } 87 | } 88 | 89 | private final class TemplateRenderingMock: TemplateRendering { 90 | struct RenderArguments { 91 | let template: String 92 | let context: [String: String] 93 | } 94 | var renderArguments: RenderArguments? 95 | 96 | func render( 97 | template: String, 98 | context: [String: String] 99 | ) throws -> String { 100 | renderArguments = RenderArguments(template: template, context: context) 101 | return "" 102 | } 103 | 104 | func render( 105 | templateFile: URL, 106 | context: [String: String] 107 | ) throws -> String { 108 | "" 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Tests/PodfileBuilderTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(TemplateRendererTests.allTests) 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /Tests/SwiftyPodsTests/SwiftyPodsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import class Foundation.Bundle 3 | 4 | final class SwiftyPodsTests: XCTestCase { 5 | // func testExample() throws { 6 | // // This is an example of a functional test case. 7 | // // Use XCTAssert and related functions to verify your tests produce the correct 8 | // // results. 9 | // 10 | // // Some of the APIs that we use below are available in macOS 10.13 and above. 11 | // guard #available(macOS 10.13, *) else { 12 | // return 13 | // } 14 | // 15 | // let fooBinary = productsDirectory.appendingPathComponent("SwiftyPods") 16 | // 17 | // let process = Process() 18 | // process.executableURL = fooBinary 19 | // 20 | // let pipe = Pipe() 21 | // process.standardOutput = pipe 22 | // 23 | // try process.run() 24 | // process.waitUntilExit() 25 | // 26 | // let data = pipe.fileHandleForReading.readDataToEndOfFile() 27 | // let output = String(data: data, encoding: .utf8) 28 | // 29 | // XCTAssertEqual(output, "Hello, world!\n") 30 | // } 31 | // 32 | // /// Returns path to the built products directory. 33 | // var productsDirectory: URL { 34 | // #if os(macOS) 35 | // for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") { 36 | // return bundle.bundleURL.deletingLastPathComponent() 37 | // } 38 | // fatalError("couldn't find the products directory") 39 | // #else 40 | // return Bundle.main.bundleURL 41 | // #endif 42 | // } 43 | // 44 | // static var allTests = [ 45 | // ("testExample", testExample), 46 | // ] 47 | } 48 | -------------------------------------------------------------------------------- /Tests/SwiftyPodsTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(SwiftyPodsTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /Tests/TemplateRendererTests/TemplateRendererTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import class Foundation.Bundle 3 | import TemplateRenderer 4 | import Storage 5 | 6 | final class TemplateRendererTests: XCTestCase { 7 | private var templateRenderer: TemplateRenderer! 8 | 9 | func testGeneratesfromString() throws { 10 | templateRenderer = TemplateRenderer(storage: FileSystemMock()) 11 | let expectedTemplate = "Hello David" 12 | let generatedTemplate = try templateRenderer.render(template: "Hello {{name}}", context: ["name": "David"]) 13 | 14 | XCTAssertEqual(generatedTemplate, expectedTemplate) 15 | } 16 | 17 | func testGeneratesfromFile() throws { 18 | let storageMock = FileSystemMock() 19 | storageMock.fileContent = "Hello {{name}}" 20 | templateRenderer = TemplateRenderer(storage: storageMock) 21 | let expectedTemplate = "Hello David" 22 | let generatedTemplate = try templateRenderer.render(templateFile: URL(fileURLWithPath: ""), context: ["name": "David"]) 23 | 24 | XCTAssertEqual(generatedTemplate, expectedTemplate) 25 | } 26 | 27 | static var allTests = [ 28 | ("testGeneratesfromString", testGeneratesfromString), 29 | ("testGeneratesfromFile", testGeneratesfromFile) 30 | ] 31 | } 32 | 33 | // MARK: - Mocks 34 | 35 | private final class FileSystemMock: FileSysteming { 36 | var fileContent: String? 37 | 38 | func saveFile(name: String, path: URL, content: String, overwrite: Bool) throws { 39 | fatalError() 40 | } 41 | 42 | func getFile(at: URL) throws -> String { 43 | if let fileContent = fileContent { 44 | return fileContent 45 | } 46 | fatalError() 47 | } 48 | 49 | func copyFile(from: URL, to: URL, override: Bool) throws { 50 | fatalError() 51 | } 52 | 53 | func delete(at path: String) throws { 54 | fatalError() 55 | } 56 | 57 | func createFolder(at url:URL) throws { 58 | fatalError() 59 | } 60 | 61 | func findFilesInFolder(at url: URL, matching: (URL) -> Bool) throws -> [URL] { 62 | [] 63 | } 64 | 65 | func fileExists(at url: URL) -> Bool { 66 | false 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Tests/TemplateRendererTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(TemplateRendererTests.allTests) 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /testfolder/podfile.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SwiftyPodsDSL 3 | 4 | let example = Podfile( 5 | targets: [ 6 | .target( 7 | name: "Target", 8 | project: "Project", 9 | dependencies: [ 10 | .dependency(name: "Dependency1"), 11 | .dependency(name: "Dependency2", 12 | version: "1.2.3"), 13 | .dependency(name: "Dependency3", 14 | .git(url: "repo"), 15 | .branch(name: "master")) 16 | ], 17 | childTargets: [ 18 | .target(name: "ChildTarget", project: "Project2") 19 | ] 20 | ) 21 | ] 22 | ) 23 | --------------------------------------------------------------------------------