Skip to content

Commit 4045282

Browse files
Merge pull request #25 from Ether-CLI/develop
Template List, Command Group, And Auto Committing
2 parents 8179912 + e8902b2 commit 4045282

17 files changed

+255
-44
lines changed

CHANGELOG.md

+10
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
## [2018.05.25]
2+
3+
### Added
4+
- `install-commit` configuration key.
5+
- `remove-commit` configuration key.
6+
- `signed-commits` configuration key.
7+
- Template command group.
8+
- `template list` command.
9+
- Default answer `n` for pre-release version install question.
10+
111
## [2018.05.22]
212

313
### Fixed

Package.resolved

+4-4
Original file line numberDiff line numberDiff line change
@@ -87,17 +87,17 @@
8787
"repositoryURL": "https://github.com/apple/swift-nio.git",
8888
"state": {
8989
"branch": null,
90-
"revision": "bad7c297427b5efedb96c4044f9e57b42881e9ea",
91-
"version": "1.7.0"
90+
"revision": "80363d9d8c1c572d228350ea87132bfa49c8ca03",
91+
"version": "1.7.1"
9292
}
9393
},
9494
{
9595
"package": "swift-nio-ssl",
9696
"repositoryURL": "https://github.com/apple/swift-nio-ssl.git",
9797
"state": {
9898
"branch": null,
99-
"revision": "38955a5f806a952daf2b16fbfe9aa529749cf1dd",
100-
"version": "1.1.0"
99+
"revision": "0adc938bc8de3d3829b842f9767d81c7480b8403",
100+
"version": "1.1.1"
101101
}
102102
},
103103
{

Package.swift

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import PackageDescription
44

55
let package = Package(
66
name: "Ether",
7+
products: [
8+
.executable(name: "Executable", targets: ["Executable"]),
9+
.library(name: "Ether", targets: ["Helpers", "Ether"])
10+
],
711
dependencies: [
812
.package(url: "https://github.com/vapor/vapor.git", from: "3.0.3"),
913
.package(url: "https://github.com/vapor/console.git", from: "3.0.2"),

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -84,15 +84,15 @@ dependencies: [
8484

8585
**Note:**
8686

87-
The install command has a rather interesting method of getting the proper package. Because you can have packages with the same name by different authors, Ether will run a search based on the argument you pass in and get the most stared result. If the name contains a slash (`/`), then the URL will created directly without a search like this:
87+
The install command has a rather interesting method of getting the proper package. Because you can have packages with the same name by different authors, Ether will run a search based on the argument you pass in and get the most stared result. If the name contains a slash (`/`), then the URL will be created directly without a search like this:
8888

8989
https://github.com/<NAME>.git
9090

9191
Note that this is case insensitive.
9292

9393
### Fix Install
9494

95-
Fixes the install process when an error occurs during `install`
95+
Fixes the install process when an error occurs during `install`, such as a git conflict.
9696

9797
ether fix-install
9898

Sources/Ether/Configuration.swift

+39-4
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,15 @@ import Bits
2929

3030
public class Configuration: Command {
3131
public var arguments: [CommandArgument] = [
32-
CommandArgument.argument(name: "key", help: ["The configuration JSON key to set"]),
33-
CommandArgument.argument(name: "value", help: ["The new value for the key passed in"])
32+
CommandArgument.argument(name: "key", help: [
33+
"The configuration JSON key to set",
34+
"Valid keys are:",
35+
"- access-token: The GitHub access token to use for interacting the the GraphQL API. You can create on at https://github.com/settings/token",
36+
"- install-commit: The commit message to use on package install. Use &0 as package name placeholder",
37+
"- remove-commit: The commit message to use on when a package is removed. Use &0 as package name placeholder",
38+
"- signed-commits: If set to a truthy value (true, yes, y, 1), auto-commits will pass in the '-S' flag"
39+
]),
40+
CommandArgument.argument(name: "value", help: ["The new value for the key passed in. If no value is passed in, the key will be removed from the config"])
3441
]
3542

3643
public var options: [CommandOption] = []
@@ -44,7 +51,7 @@ public class Configuration: Command {
4451
_ = setter.start(on: context.container)
4552

4653
let key = try context.argument("key")
47-
let value = try context.argument("value")
54+
let value = context.arguments["value"]
4855
let user = try Process.execute("whoami")
4956

5057
var configuration = try Configuration.get()
@@ -92,9 +99,15 @@ public class Configuration: Command {
9299

93100
public struct Config: Codable, Reflectable {
94101
public var accessToken: String?
102+
public var installCommit: String?
103+
public var removeCommit: String?
104+
public var signedCommits: String?
95105

96106
static let properties: [String: WritableKeyPath<Config, String?>] = [
97-
"access-token": \.accessToken
107+
"access-token": \.accessToken,
108+
"install-commit": \.installCommit,
109+
"remove-commit": \.removeCommit,
110+
"signed-commits": \.signedCommits
98111
]
99112

100113
func token()throws -> String {
@@ -112,4 +125,26 @@ public struct Config: Codable, Reflectable {
112125
}
113126
return token
114127
}
128+
129+
func signed() -> Bool {
130+
switch (self.signedCommits ?? "n").lowercased() {
131+
case "true", "yes", "y", "1": return true
132+
default: return false
133+
}
134+
}
135+
136+
func commit(with message: String?, on context: CommandContext, replacements: [String] = [])throws {
137+
if var commit = message {
138+
for (index, value) in replacements.enumerated() {
139+
commit = commit.replacingOccurrences(of: "&\(index)", with: value)
140+
}
141+
142+
var commitOptions = ["commit", "-m", commit.description]
143+
if self.signed() { commitOptions.insert("-S", at: 1) }
144+
145+
_ = try Process.execute("git", "add", "Package.swift", "Package.resolved")
146+
let commitMessage = try Process.execute("git", commitOptions)
147+
context.console.print(commitMessage)
148+
}
149+
}
115150
}

Sources/Ether/Install.swift

+5-2
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ public final class Install: Command {
8585
}
8686

8787
context.console.output("📦 \(newPinCount - oldPinCount) packages installed", style: .plain, newLine: true)
88+
89+
let config = try Configuration.get()
90+
try config.commit(with: config.installCommit, on: context, replacements: [name])
8891
}
8992
}
9093

@@ -179,9 +182,9 @@ public final class Install: Command {
179182

180183
while true {
181184
answer = context.console.ask(
182-
ConsoleText(stringLiteral:"The latest version found (\(first)) is a pre-release. Would you like to use an earlier stable release? (y/n)")
185+
ConsoleText(stringLiteral:"The latest version found (\(first)) is a pre-release. Would you like to use an earlier stable release? (y/N)")
183186
).lowercased()
184-
if answer == "y" || answer == "n" { break }
187+
if answer == "y" || answer == "n" || answer == "" { break }
185188
}
186189

187190
if answer == "y" {

Sources/Ether/Remove.swift

+4
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ public final class Remove: Command {
7272
}
7373

7474
context.console.print("📦 \(removed) packages removed")
75+
76+
let config = try Configuration.get()
77+
try config.commit(with: config.removeCommit, on: context, replacements: [name])
78+
7579
return context.container.eventLoop.newSucceededFuture(result: ())
7680
}
7781
}

Sources/Ether/Template/Template.swift

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// The MIT License (MIT)
2+
//
3+
// Copyright (c) 2017 Caleb Kleveter
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+
23+
import Command
24+
25+
public let template = Commands(
26+
commands: [
27+
"create": TemplateCreate(),
28+
"remove": TemplateRemove(),
29+
"list": TemplateList()
30+
],
31+
defaultCommand: "list"
32+
).group(help: [
33+
"For saving and deleting template projects"
34+
])

Sources/Ether/Template.swift renamed to Sources/Ether/Template/TemplateCreate.swift

+8-19
Original file line numberDiff line numberDiff line change
@@ -24,27 +24,21 @@ import Foundation
2424
import Helpers
2525
import Command
2626

27-
public final class Template: Command {
27+
public final class TemplateCreate: Command {
2828
public var arguments: [CommandArgument] = [
2929
CommandArgument.argument(name: "name", help: ["The name used to identify the template"])
3030
]
3131

32-
public var options: [CommandOption] = [
33-
CommandOption.flag(name: "remove", short: "r", help: ["Deletes the template"])
34-
// TODO: Add `github` flag to create remote repo and push.
35-
]
32+
public var options: [CommandOption] = []
3633

3734
public var help: [String] = ["Creates and stores a template for use as the starting point of a project."]
3835

3936
public init() {}
4037

4138
public func run(using context: CommandContext) throws -> EventLoopFuture<Void> {
4239
let name = try context.argument("name")
43-
let removeTemplate = context.options["remove"] == nil ? false : true
44-
let manager = FileManager.default
45-
let barTitle = removeTemplate ? "Deleting Template" : "Saving Template"
4640

47-
let temapletBar = context.console.loadingBar(title: barTitle)
41+
let temapletBar = context.console.loadingBar(title: "Saving Template")
4842
_ = temapletBar.start(on: context.container)
4943

5044
let user = try Process.execute("whoami")
@@ -55,16 +49,11 @@ public final class Template: Command {
5549
)
5650

5751
var isDir : ObjCBool = true
58-
let directoryExists = manager.fileExists(atPath: "/Users/\(user)/Library/Application Support/Ether/Templates/\(name)", isDirectory: &isDir)
59-
60-
if removeTemplate {
61-
if !directoryExists { throw EtherError(identifier: "templateNotFound", reason: "No template with the name '\(name)' was found") }
62-
_ = try Process.execute("rm", ["-rf", "/Users/\(user)/Library/Application Support/Ether/Templates/\(name)"])
63-
} else {
64-
if directoryExists { throw EtherError(identifier: "templateAlreadyExists", reason: "A template with the name '\(name)' was found") }
65-
let current = manager.currentDirectoryPath + "/."
66-
_ = try Process.execute("cp", ["-a", "\(current)", "/Users/\(user)/Library/Application Support/Ether/Templates/\(name)"])
67-
}
52+
let directoryExists = FileManager.default.fileExists(atPath: "/Users/\(user)/Library/Application Support/Ether/Templates/\(name)", isDirectory: &isDir)
53+
54+
if directoryExists { throw EtherError(identifier: "templateAlreadyExists", reason: "A template with the name '\(name)' was found") }
55+
let current = FileManager.default.currentDirectoryPath + "/."
56+
_ = try Process.execute("cp", ["-a", "\(current)", "/Users/\(user)/Library/Application Support/Ether/Templates/\(name)"])
6857

6958
temapletBar.succeed()
7059
return context.container.eventLoop.newSucceededFuture(result: ())
+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// The MIT License (MIT)
2+
//
3+
// Copyright (c) 2017 Caleb Kleveter
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+
23+
import Command
24+
25+
final class TemplateList: Command {
26+
var arguments: [CommandArgument] = []
27+
var options: [CommandOption] = []
28+
29+
var help: [String] = ["Lists all saved project templates"]
30+
31+
func run(using context: CommandContext) throws -> EventLoopFuture<Void> {
32+
33+
let user = try Process.execute("whoami")
34+
try FileManager.default.createDirectory(
35+
at: URL(string: "file:/Users/\(user)/Library/Application%20Support/Ether/Templates")!,
36+
withIntermediateDirectories: true,
37+
attributes: [:]
38+
)
39+
40+
let projects = try Process.execute("ls", "/Users/\(user)/Library/Application Support/Ether/Templates/")
41+
for project in projects.split(separator: "\n").map(String.init) {
42+
context.console.info("- ", newLine: false)
43+
context.console.print(project)
44+
}
45+
46+
return context.container.eventLoop.newSucceededFuture(result: ())
47+
}
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// The MIT License (MIT)
2+
//
3+
// Copyright (c) 2017 Caleb Kleveter
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+
23+
import Foundation
24+
import Helpers
25+
import Command
26+
27+
public final class TemplateRemove: Command {
28+
public var arguments: [CommandArgument] = [
29+
CommandArgument.argument(name: "name", help: ["The name used to identify the template"])
30+
]
31+
32+
public var options: [CommandOption] = []
33+
34+
public var help: [String] = ["Deletes a stored project template"]
35+
36+
public init() {}
37+
38+
public func run(using context: CommandContext) throws -> EventLoopFuture<Void> {
39+
let name = try context.argument("name")
40+
41+
let temapletBar = context.console.loadingBar(title: "Deleting Template")
42+
_ = temapletBar.start(on: context.container)
43+
44+
let user = try Process.execute("whoami")
45+
try FileManager.default.createDirectory(
46+
at: URL(string: "file:/Users/\(user)/Library/Application%20Support/Ether/Templates")!,
47+
withIntermediateDirectories: true,
48+
attributes: [:]
49+
)
50+
51+
var isDir : ObjCBool = true
52+
let directoryExists = FileManager.default.fileExists(atPath: "/Users/\(user)/Library/Application Support/Ether/Templates/\(name)", isDirectory: &isDir)
53+
54+
if !directoryExists { throw EtherError(identifier: "templateNotFound", reason: "No template with the name '\(name)' was found") }
55+
_ = try Process.execute("rm", ["-rf", "/Users/\(user)/Library/Application Support/Ether/Templates/\(name)"])
56+
57+
temapletBar.succeed()
58+
return context.container.eventLoop.newSucceededFuture(result: ())
59+
}
60+
}

0 commit comments

Comments
 (0)