Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add shell completions #780

Merged
merged 11 commits into from
Apr 11, 2024
2 changes: 1 addition & 1 deletion Sources/tart/Commands/Clone.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ struct Clone: AsyncParsableCommand {
"""
)

@Argument(help: "source VM name")
@Argument(help: "source VM name", completion: .custom(completeMachines))
var sourceName: String

@Argument(help: "new VM name")
Expand Down
2 changes: 1 addition & 1 deletion Sources/tart/Commands/Delete.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import SwiftUI
struct Delete: AsyncParsableCommand {
static var configuration = CommandConfiguration(abstract: "Delete a VM")

@Argument(help: "VM name")
@Argument(help: "VM name", completion: .custom(completeMachines))
var name: [String]

func run() async throws {
Expand Down
2 changes: 1 addition & 1 deletion Sources/tart/Commands/Export.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Foundation
struct Export: AsyncParsableCommand {
static var configuration = CommandConfiguration(abstract: "Export VM to a compressed .tvm file")

@Argument(help: "Source VM name.")
@Argument(help: "Source VM name.", completion: .custom(completeMachines))
var name: String

@Argument(help: "Path to the destination file.")
Expand Down
2 changes: 1 addition & 1 deletion Sources/tart/Commands/FQN.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import SystemConfiguration
struct FQN: AsyncParsableCommand {
static var configuration = CommandConfiguration(abstract: "Get a fully-qualified VM name", shouldDisplay: false)

@Argument(help: "VM name")
@Argument(help: "VM name", completion: .custom(completeMachines))
var name: String

func run() async throws {
Expand Down
2 changes: 1 addition & 1 deletion Sources/tart/Commands/Get.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ fileprivate struct VMInfo: Encodable {
struct Get: AsyncParsableCommand {
static var configuration = CommandConfiguration(commandName: "get", abstract: "Get a VM's configuration")

@Argument(help: "VM name.")
@Argument(help: "VM name.", completion: .custom(completeLocalMachines))
var name: String

@Option(help: "Output format: text or json")
Expand Down
4 changes: 2 additions & 2 deletions Sources/tart/Commands/IP.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ enum IPResolutionStrategy: String, ExpressibleByArgument, CaseIterable {
struct IP: AsyncParsableCommand {
static var configuration = CommandConfiguration(abstract: "Get VM's IP address")

@Argument(help: "VM name")
@Argument(help: "VM name", completion: .custom(completeLocalMachines))
var name: String

@Option(help: "Number of seconds to wait for a potential VM booting")
Expand Down Expand Up @@ -61,7 +61,7 @@ struct IP: AsyncParsableCommand {
return ip
}
case .dhcp:
if let leases = try Leases(), let ip = try leases.ResolveMACAddress(macAddress: vmMACAddress) {
if let leases = try Leases(), let ip = leases.ResolveMACAddress(macAddress: vmMACAddress) {
return ip
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/tart/Commands/Push.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Compression
struct Push: AsyncParsableCommand {
static var configuration = CommandConfiguration(abstract: "Push a VM to a registry")

@Argument(help: "local or remote VM name")
@Argument(help: "local or remote VM name", completion: .custom(completeMachines))
var localName: String

@Argument(help: "remote VM name(s)")
Expand Down
2 changes: 1 addition & 1 deletion Sources/tart/Commands/Rename.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Foundation
struct Rename: AsyncParsableCommand {
static var configuration = CommandConfiguration(abstract: "Rename a VM")

@Argument(help: "VM name")
@Argument(help: "VM name", completion: .custom(completeLocalMachines))
var name: String

@Argument(help: "new VM name")
Expand Down
4 changes: 2 additions & 2 deletions Sources/tart/Commands/Run.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ struct IPNotFound: Error {
struct Run: AsyncParsableCommand {
static var configuration = CommandConfiguration(abstract: "Run a VM")

@Argument(help: "VM name")
@Argument(help: "VM name", completion: .custom(completeLocalMachines))
var name: String

@Flag(help: ArgumentHelp(
Expand Down Expand Up @@ -54,7 +54,7 @@ struct Run: AsyncParsableCommand {

#if arch(arm64)
@Flag(help: ArgumentHelp(
"Use Virtualization.Framework's VNC server instead of the build-in UI.",
"Use Virtualization.Framework's VNC server instead of the built-in UI.",
discussion: "Useful since this type of VNC is available in recovery mode and in macOS installation.\n"
+ "Note that this feature is experimental and there may be bugs present when using VNC."))
#endif
Expand Down
2 changes: 1 addition & 1 deletion Sources/tart/Commands/Set.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Foundation
struct Set: AsyncParsableCommand {
static var configuration = CommandConfiguration(commandName: "set", abstract: "Modify VM's configuration")

@Argument(help: "VM name")
@Argument(help: "VM name", completion: .custom(completeLocalMachines))
var name: String

@Option(help: "Number of VM CPUs")
Expand Down
2 changes: 1 addition & 1 deletion Sources/tart/Commands/Stop.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import SwiftDate
struct Stop: AsyncParsableCommand {
static var configuration = CommandConfiguration(commandName: "stop", abstract: "Stop a VM")

@Argument(help: "VM name")
@Argument(help: "VM name", completion: .custom(completeRunningMachines))
var name: String

@Option(name: [.short, .long], help: "Seconds to wait for graceful termination before forcefully terminating the VM")
Expand Down
2 changes: 1 addition & 1 deletion Sources/tart/Commands/Suspend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import SwiftDate
struct Suspend: AsyncParsableCommand {
static var configuration = CommandConfiguration(commandName: "suspend", abstract: "Suspend a VM")

@Argument(help: "VM name")
@Argument(help: "VM name", completion: .custom(completeRunningMachines))
var name: String

func run() async throws {
Expand Down
28 changes: 28 additions & 0 deletions Sources/tart/ShellCompletions/ShellCompletions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Foundation

fileprivate func normalizeName(_ name: String) -> String {
// Colons are misinterpreted by Zsh completion
return name.replacingOccurrences(of: ":", with: "\\:")
}

func completeMachines(_ arguments: [String]) -> [String] {
let localVMs = (try? VMStorageLocal().list().map { name, _ in
normalizeName(name)
}) ?? []
let ociVMs = (try? VMStorageOCI().list().map { name, _, _ in
normalizeName(name)
}) ?? []
return (localVMs + ociVMs)
}

func completeLocalMachines(_ arguments: [String]) -> [String] {
let localVMs = (try? VMStorageLocal().list()) ?? []
return localVMs.map { name, _ in normalizeName(name) }
}

func completeRunningMachines(_ arguments: [String]) -> [String] {
let localVMs = (try? VMStorageLocal().list()) ?? []
return localVMs
.filter { _, vmDir in (try? vmDir.state() == .Running) ?? false}
.map { name, _ in normalizeName(name) }
}