├── LICENSE
├── README.md
├── TheComposableArchitecture.xctemplate
├── Default
│ ├── ___FILEBASENAME___Action.swift
│ ├── ___FILEBASENAME___Environment.swift
│ ├── ___FILEBASENAME___Feature.swift
│ ├── ___FILEBASENAME___State+view.swift
│ ├── ___FILEBASENAME___State.swift
│ ├── ___FILEBASENAME___View.Action+feature.swift
│ └── ___FILEBASENAME___View.swift
├── FileHeaderComments
│ ├── ___FILEBASENAME___Action.swift
│ ├── ___FILEBASENAME___Environment.swift
│ ├── ___FILEBASENAME___Feature.swift
│ ├── ___FILEBASENAME___State+view.swift
│ ├── ___FILEBASENAME___State.swift
│ ├── ___FILEBASENAME___View.Action+feature.swift
│ └── ___FILEBASENAME___View.swift
├── ReducerProtocol
│ ├── ___FILEBASENAME___Feature.swift
│ ├── ___FILEBASENAME___State+view.swift
│ ├── ___FILEBASENAME___View.Action+feature.swift
│ └── ___FILEBASENAME___View.swift
├── ReducerProtocolFileHeaderComments
│ ├── ___FILEBASENAME___Feature.swift
│ ├── ___FILEBASENAME___State+view.swift
│ ├── ___FILEBASENAME___View.Action+feature.swift
│ └── ___FILEBASENAME___View.swift
├── TemplateIcon.icns
└── TemplateInfo.plist
├── install.swift
└── xcode-dialog.png
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Christian Elies
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TheComposableArchitecture SwiftUI Template for Xcode
2 |
3 |
4 |
5 |
6 |
7 | This repository contains a SwiftUI Xcode template for [The Composable Architecture (short: TCA)](https://github.com/pointfreeco/swift-composable-architecture).
8 |
9 | Quickly create all components of a TCA stack using the template in Xcode and focus on implementing your feature 🚀
10 |
11 | 
12 |
13 | ## How to use 🛠
14 |
15 | Use the install script.
16 |
17 | ```swift install.swift```
18 |
19 | ℹ️ **If the script fails for whatever reason just copy the `TheComposableArchitecture.xctemplate` directory into the directory `/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Templates/Project Templates/iOS/Application`.**
20 |
21 | ⚠️ This template is a **project template** (*not a file template*). That's why Xcode does not show the template in the *new file wizard* when you are working on a *Swift package* (*when you opened a Package.swift with Xcode*).
22 |
23 | ## 🤖 Author
24 |
25 | Christian Elies, https://www.christianelies.de
26 |
27 | ## 📄 License
28 |
29 | This Template is available under the MIT license. See the LICENSE file for more info.
--------------------------------------------------------------------------------
/TheComposableArchitecture.xctemplate/Default/___FILEBASENAME___Action.swift:
--------------------------------------------------------------------------------
1 | enum ___VARIABLE_MODULENAME___Action: Equatable {
2 | case onAppear
3 | }
4 |
--------------------------------------------------------------------------------
/TheComposableArchitecture.xctemplate/Default/___FILEBASENAME___Environment.swift:
--------------------------------------------------------------------------------
1 | import ComposableArchitecture
2 |
3 | struct ___VARIABLE_MODULENAME___Environment {
4 |
5 | }
6 |
--------------------------------------------------------------------------------
/TheComposableArchitecture.xctemplate/Default/___FILEBASENAME___Feature.swift:
--------------------------------------------------------------------------------
1 | import ComposableArchitecture
2 |
3 | enum ___VARIABLE_MODULENAME___Feature {}
4 |
5 | extension ___VARIABLE_MODULENAME___Feature {
6 | static var reducer: Reducer<___VARIABLE_MODULENAME___State, ___VARIABLE_MODULENAME___Action, ___VARIABLE_MODULENAME___Environment> {
7 | .init { state, action, environment in
8 | return .none
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/TheComposableArchitecture.xctemplate/Default/___FILEBASENAME___State+view.swift:
--------------------------------------------------------------------------------
1 | extension ___VARIABLE_MODULENAME___State {
2 | var view: ___VARIABLE_MODULENAME___View.State {
3 | .init()
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/TheComposableArchitecture.xctemplate/Default/___FILEBASENAME___State.swift:
--------------------------------------------------------------------------------
1 | struct ___VARIABLE_MODULENAME___State: Equatable {
2 |
3 | }
4 |
--------------------------------------------------------------------------------
/TheComposableArchitecture.xctemplate/Default/___FILEBASENAME___View.Action+feature.swift:
--------------------------------------------------------------------------------
1 | extension ___VARIABLE_MODULENAME___View.Action {
2 | var feature: ___VARIABLE_MODULENAME___Action {
3 | switch self {
4 | case .onAppear:
5 | return .onAppear
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/TheComposableArchitecture.xctemplate/Default/___FILEBASENAME___View.swift:
--------------------------------------------------------------------------------
1 | import ComposableArchitecture
2 | import SwiftUI
3 |
4 | struct ___VARIABLE_MODULENAME___View: View {
5 | struct State: Equatable {
6 |
7 | }
8 |
9 | enum Action: Equatable {
10 | case onAppear
11 | }
12 |
13 | let store: Store<___VARIABLE_MODULENAME___State, ___VARIABLE_MODULENAME___Action>
14 |
15 | var body: some View {
16 | WithViewStore(
17 | store.scope(
18 | state: { $0.view },
19 | action: { (viewAction: ___VARIABLE_MODULENAME___View.Action) in
20 | viewAction.feature
21 | }
22 | )
23 | ) { viewStore in
24 | Text("Hello world!")
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/TheComposableArchitecture.xctemplate/FileHeaderComments/___FILEBASENAME___Action.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ___FILENAME___
3 | // ___PROJECTNAME___
4 | //
5 | // Created ___FULLUSERNAME___ on ___DATE___.
6 | // Copyright © ___YEAR___ ___ORGANIZATIONNAME___. All rights reserved.
7 | //
8 | // Template generated by Christian Elies @crelies
9 | // https://www.christianelies.de
10 | //
11 |
12 | enum ___VARIABLE_MODULENAME___Action: Equatable {
13 | case onAppear
14 | }
15 |
--------------------------------------------------------------------------------
/TheComposableArchitecture.xctemplate/FileHeaderComments/___FILEBASENAME___Environment.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ___FILENAME___
3 | // ___PROJECTNAME___
4 | //
5 | // Created ___FULLUSERNAME___ on ___DATE___.
6 | // Copyright © ___YEAR___ ___ORGANIZATIONNAME___. All rights reserved.
7 | //
8 | // Template generated by Christian Elies @crelies
9 | // https://www.christianelies.de
10 | //
11 |
12 | import ComposableArchitecture
13 |
14 | struct ___VARIABLE_MODULENAME___Environment {
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/TheComposableArchitecture.xctemplate/FileHeaderComments/___FILEBASENAME___Feature.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ___FILENAME___
3 | // ___PROJECTNAME___
4 | //
5 | // Created ___FULLUSERNAME___ on ___DATE___.
6 | // Copyright © ___YEAR___ ___ORGANIZATIONNAME___. All rights reserved.
7 | //
8 | // Template generated by Christian Elies @crelies
9 | // https://www.christianelies.de
10 | //
11 |
12 | import ComposableArchitecture
13 |
14 | enum ___VARIABLE_MODULENAME___Feature {}
15 |
16 | extension ___VARIABLE_MODULENAME___Feature {
17 | static var reducer: Reducer<___VARIABLE_MODULENAME___State, ___VARIABLE_MODULENAME___Action, ___VARIABLE_MODULENAME___Environment> {
18 | .init { state, action, environment in
19 | return .none
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/TheComposableArchitecture.xctemplate/FileHeaderComments/___FILEBASENAME___State+view.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ___FILENAME___
3 | // ___PROJECTNAME___
4 | //
5 | // Created ___FULLUSERNAME___ on ___DATE___.
6 | // Copyright © ___YEAR___ ___ORGANIZATIONNAME___. All rights reserved.
7 | //
8 | // Template generated by Christian Elies @crelies
9 | // https://www.christianelies.de
10 | //
11 |
12 | extension ___VARIABLE_MODULENAME___State {
13 | var view: ___VARIABLE_MODULENAME___View.State {
14 | .init()
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/TheComposableArchitecture.xctemplate/FileHeaderComments/___FILEBASENAME___State.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ___FILENAME___
3 | // ___PROJECTNAME___
4 | //
5 | // Created ___FULLUSERNAME___ on ___DATE___.
6 | // Copyright © ___YEAR___ ___ORGANIZATIONNAME___. All rights reserved.
7 | //
8 | // Template generated by Christian Elies @crelies
9 | // https://www.christianelies.de
10 | //
11 |
12 | struct ___VARIABLE_MODULENAME___State: Equatable {
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/TheComposableArchitecture.xctemplate/FileHeaderComments/___FILEBASENAME___View.Action+feature.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ___FILENAME___
3 | // ___PROJECTNAME___
4 | //
5 | // Created ___FULLUSERNAME___ on ___DATE___.
6 | // Copyright © ___YEAR___ ___ORGANIZATIONNAME___. All rights reserved.
7 | //
8 | // Template generated by Christian Elies @crelies
9 | // https://www.christianelies.de
10 | //
11 |
12 | extension ___VARIABLE_MODULENAME___View.Action {
13 | var feature: ___VARIABLE_MODULENAME___Action {
14 | switch self {
15 | case .onAppear:
16 | return .onAppear
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/TheComposableArchitecture.xctemplate/FileHeaderComments/___FILEBASENAME___View.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ___FILENAME___
3 | // ___PROJECTNAME___
4 | //
5 | // Created ___FULLUSERNAME___ on ___DATE___.
6 | // Copyright © ___YEAR___ ___ORGANIZATIONNAME___. All rights reserved.
7 | //
8 | // Template generated by Christian Elies @crelies
9 | // https://www.christianelies.de
10 | //
11 |
12 | import ComposableArchitecture
13 | import SwiftUI
14 |
15 | struct ___VARIABLE_MODULENAME___View: View {
16 | struct State: Equatable {
17 |
18 | }
19 |
20 | enum Action: Equatable {
21 | case onAppear
22 | }
23 |
24 | let store: Store<___VARIABLE_MODULENAME___State, ___VARIABLE_MODULENAME___Action>
25 |
26 | var body: some View {
27 | WithViewStore(
28 | store.scope(
29 | state: { $0.view },
30 | action: { (viewAction: ___VARIABLE_MODULENAME___View.Action) in
31 | viewAction.feature
32 | }
33 | )
34 | ) { viewStore in
35 | Text("Hello world!")
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/TheComposableArchitecture.xctemplate/ReducerProtocol/___FILEBASENAME___Feature.swift:
--------------------------------------------------------------------------------
1 | import ComposableArchitecture
2 |
3 | struct ___VARIABLE_MODULENAME___Feature: ReducerProtocol {
4 | struct State: Equatable {}
5 |
6 | enum Action: Equatable {
7 | case onAppear
8 | }
9 |
10 | var body: some ReducerProtocol {
11 | Reduce { state, action in
12 | return .none
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/TheComposableArchitecture.xctemplate/ReducerProtocol/___FILEBASENAME___State+view.swift:
--------------------------------------------------------------------------------
1 | extension ___VARIABLE_MODULENAME___Feature.State {
2 | var view: ___VARIABLE_MODULENAME___View.State {
3 | .init(state: self)
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/TheComposableArchitecture.xctemplate/ReducerProtocol/___FILEBASENAME___View.Action+feature.swift:
--------------------------------------------------------------------------------
1 | extension ___VARIABLE_MODULENAME___View.Action {
2 | var feature: ___VARIABLE_MODULENAME___Feature.Action {
3 | switch self {
4 | case .onAppear:
5 | return .onAppear
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/TheComposableArchitecture.xctemplate/ReducerProtocol/___FILEBASENAME___View.swift:
--------------------------------------------------------------------------------
1 | import ComposableArchitecture
2 | import SwiftUI
3 |
4 | struct ___VARIABLE_MODULENAME___View: View {
5 | struct State: Equatable {
6 | init(state: ___VARIABLE_MODULENAME___Feature.State) {}
7 | }
8 |
9 | enum Action: Equatable {
10 | case onAppear
11 | }
12 |
13 | let store: StoreOf<___VARIABLE_MODULENAME___Feature>
14 |
15 | var body: some View {
16 | WithViewStore(
17 | self.store,
18 | observe: ___VARIABLE_MODULENAME___View.State.init,
19 | send: { (viewAction: Action) in viewAction.feature }
20 | ) { viewStore in
21 | Text("Hello world!")
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/TheComposableArchitecture.xctemplate/ReducerProtocolFileHeaderComments/___FILEBASENAME___Feature.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ___FILENAME___
3 | // ___PROJECTNAME___
4 | //
5 | // Created ___FULLUSERNAME___ on ___DATE___.
6 | // Copyright © ___YEAR___ ___ORGANIZATIONNAME___. All rights reserved.
7 | //
8 | // Template generated by Christian Elies @crelies
9 | // https://www.christianelies.de
10 | //
11 |
12 | import ComposableArchitecture
13 |
14 | struct ___VARIABLE_MODULENAME___Feature: ReducerProtocol {
15 | struct State: Equatable {}
16 |
17 | enum Action: Equatable {
18 | case onAppear
19 | }
20 |
21 | var body: some ReducerProtocol {
22 | Reduce { state, action in
23 | return .none
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/TheComposableArchitecture.xctemplate/ReducerProtocolFileHeaderComments/___FILEBASENAME___State+view.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ___FILENAME___
3 | // ___PROJECTNAME___
4 | //
5 | // Created ___FULLUSERNAME___ on ___DATE___.
6 | // Copyright © ___YEAR___ ___ORGANIZATIONNAME___. All rights reserved.
7 | //
8 | // Template generated by Christian Elies @crelies
9 | // https://www.christianelies.de
10 | //
11 |
12 | extension ___VARIABLE_MODULENAME___Feature.State {
13 | var view: ___VARIABLE_MODULENAME___View.State {
14 | .init(state: self)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/TheComposableArchitecture.xctemplate/ReducerProtocolFileHeaderComments/___FILEBASENAME___View.Action+feature.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ___FILENAME___
3 | // ___PROJECTNAME___
4 | //
5 | // Created ___FULLUSERNAME___ on ___DATE___.
6 | // Copyright © ___YEAR___ ___ORGANIZATIONNAME___. All rights reserved.
7 | //
8 | // Template generated by Christian Elies @crelies
9 | // https://www.christianelies.de
10 | //
11 |
12 | extension ___VARIABLE_MODULENAME___View.Action {
13 | var feature: ___VARIABLE_MODULENAME___Feature.Action {
14 | switch self {
15 | case .onAppear:
16 | return .onAppear
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/TheComposableArchitecture.xctemplate/ReducerProtocolFileHeaderComments/___FILEBASENAME___View.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ___FILENAME___
3 | // ___PROJECTNAME___
4 | //
5 | // Created ___FULLUSERNAME___ on ___DATE___.
6 | // Copyright © ___YEAR___ ___ORGANIZATIONNAME___. All rights reserved.
7 | //
8 | // Template generated by Christian Elies @crelies
9 | // https://www.christianelies.de
10 | //
11 |
12 | import ComposableArchitecture
13 | import SwiftUI
14 |
15 | struct ___VARIABLE_MODULENAME___View: View {
16 | struct State: Equatable {
17 | init(state: ___VARIABLE_MODULENAME___Feature.State) {}
18 | }
19 |
20 | enum Action: Equatable {
21 | case onAppear
22 | }
23 |
24 | let store: StoreOf<___VARIABLE_MODULENAME___Feature>
25 |
26 | var body: some View {
27 | WithViewStore(
28 | self.store,
29 | observe: ___VARIABLE_MODULENAME___View.State.init,
30 | send: { (viewAction: Action) in viewAction.feature }
31 | ) { viewStore in
32 | Text("Hello world!")
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/TheComposableArchitecture.xctemplate/TemplateIcon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chris-swift-dev/TheComposableArchitecture-Xcode-Template/7fe79d651a54e2dc420f73dea54055ea184c2672/TheComposableArchitecture.xctemplate/TemplateIcon.icns
--------------------------------------------------------------------------------
/TheComposableArchitecture.xctemplate/TemplateInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | AllowedTypes
6 |
7 | public.swift-source
8 |
9 | Platforms
10 |
11 | com.apple.platform.iphoneos
12 |
13 | DefaultCompletionName
14 | SwiftTCAComponents
15 | Description
16 | TheComposableArchitecture Components
17 | Kind
18 | Xcode.IDEKit.TextSubstitutionFileTemplateKind
19 | MainTemplateFile
20 | ___FILEBASENAME___.swift
21 | SortOrder
22 | 1
23 | Template Author
24 | Christian Elies @ 2023
25 | Options
26 |
27 |
28 | Description
29 | Name of feature you want to implement
30 | Default
31 |
32 | Identifier
33 | MODULENAME
34 | Name
35 | Feature name:
36 | Required
37 | YES
38 | Type
39 | text
40 |
41 |
42 | Default
43 | ___VARIABLE_MODULENAME___
44 | Identifier
45 | productName
46 | Type
47 | static
48 |
49 |
50 | Description
51 | Use ReducerProtocol
52 | Default
53 | false
54 | Identifier
55 | ReducerProtocol
56 | Name
57 | Use ReducerProtocol
58 | Required
59 | YES
60 | Type
61 | checkbox
62 |
63 |
64 | Description
65 | Add file header comments
66 | Default
67 | false
68 | Identifier
69 | FileHeaderComments
70 | Name
71 | Add file header comments
72 | Required
73 | YES
74 | Type
75 | checkbox
76 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/install.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | enum Constants {
4 | enum CommandLineValues {
5 | static let yes = "y"
6 | static let no = "n"
7 | }
8 |
9 | enum File {
10 | static let templateName = "TheComposableArchitecture.xctemplate"
11 | static let destinationRelativePath = "/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Templates/Project Templates/iOS/Application"
12 | }
13 |
14 | enum Messages {
15 | static let successMessage = "✅ Template was installed succesfully 🎉. Enjoy it 🙂"
16 | static let successfullReplaceMessage = "✅ The Template has been replaced for you with the new version 🎉. Enjoy it 🙂"
17 | static let errorMessage = "❌ Ooops! Something went wrong 😡"
18 | static let exitMessage = "Bye Bye 👋"
19 | static let promptReplace = "That Template already exists. Do you want to replace it? (y or n)"
20 | }
21 |
22 | enum Blocks {
23 | static let printSeparator = { print("====================================") }
24 | }
25 | }
26 |
27 | func printToConsole(_ message:Any) {
28 | Constants.Blocks.printSeparator()
29 | print("\(message)")
30 | Constants.Blocks.printSeparator()
31 | }
32 |
33 | func moveTemplate() {
34 | do {
35 | let fileManager = FileManager.default
36 | let destinationPath = bash(command: "xcode-select", arguments: ["--print-path"]).appending(Constants.File.destinationRelativePath)
37 |
38 | printToConsole("Template will be copied to: \(destinationPath)")
39 |
40 | if !fileManager.fileExists(atPath: "\(destinationPath)/\(Constants.File.templateName)") {
41 | try fileManager.copyItem(atPath: Constants.File.templateName, toPath: "\(destinationPath)/\(Constants.File.templateName)")
42 | printToConsole(Constants.Messages.successMessage)
43 | } else {
44 | print(Constants.Messages.promptReplace)
45 |
46 | var input = ""
47 | repeat {
48 | guard let textFormCommandLine = readLine(strippingNewline: true) else {
49 | continue
50 | }
51 | input = textFormCommandLine
52 | } while(input != Constants.CommandLineValues.yes && input != Constants.CommandLineValues.no)
53 |
54 | if input == Constants.CommandLineValues.yes {
55 | try replaceItemAt(URL(fileURLWithPath: "\(destinationPath)/\(Constants.File.templateName)"), withItemAt: URL(fileURLWithPath: Constants.File.templateName))
56 | printToConsole(Constants.Messages.successfullReplaceMessage)
57 | } else {
58 | print(Constants.Messages.exitMessage)
59 | }
60 | }
61 | } catch let error as NSError {
62 | printToConsole("\(Constants.Messages.errorMessage) : \(error.localizedFailureReason!)")
63 | }
64 | }
65 |
66 | func replaceItemAt(_ url: URL, withItemAt itemAtUrl: URL) throws {
67 | let fileManager = FileManager.default
68 | try fileManager.removeItem(at: url)
69 | try fileManager.copyItem(atPath: itemAtUrl.path, toPath: url.path)
70 | }
71 |
72 | func shell(launchPath: String, arguments: [String]) -> String {
73 | let task = Process()
74 | task.launchPath = launchPath
75 | task.arguments = arguments
76 |
77 | let pipe = Pipe()
78 | task.standardOutput = pipe
79 | task.launch()
80 |
81 | let data = pipe.fileHandleForReading.readDataToEndOfFile()
82 | let output = String(data: data, encoding: String.Encoding.utf8)!
83 | if output.count > 0 {
84 | // remove newline character.
85 | let lastIndex = output.index(before: output.endIndex)
86 | return String(output[output.startIndex ..< lastIndex])
87 | }
88 | return output
89 | }
90 |
91 | func bash(command: String, arguments: [String]) -> String {
92 | let whichPathForCommand = shell(launchPath: "/bin/bash", arguments: [ "-l", "-c", "which \(command)" ])
93 | return shell(launchPath: whichPathForCommand, arguments: arguments)
94 | }
95 |
96 | moveTemplate()
97 |
--------------------------------------------------------------------------------
/xcode-dialog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chris-swift-dev/TheComposableArchitecture-Xcode-Template/7fe79d651a54e2dc420f73dea54055ea184c2672/xcode-dialog.png
--------------------------------------------------------------------------------