Add shell completions (#780)

* add VM completion for run command

* add VM completion for stop command

* create ShellCompletions utilities

* add shell completions to some commands

* add shell completion for fqn command

* run command: fix tiny typo

* add shell completion for get command

* more shell completions

* remove unnecessary `try`

* refactor ShellCompletions file
This commit is contained in:
Bartek Pacia 2024-04-11 11:22:50 +01:00 committed by GitHub
parent 97b7ffef52
commit da8afa1348
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 42 additions and 14 deletions

View File

@ -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")

View File

@ -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 {

View File

@ -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.")

View File

@ -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 {

View File

@ -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")

View File

@ -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")
@ -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
}
}

View File

@ -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)")

View File

@ -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")

View File

@ -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(
@ -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

View File

@ -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")

View File

@ -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")

View File

@ -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 {

View File

@ -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) }
}