From 2006358dac90f5898366adbf2e28e19e12f95e21 Mon Sep 17 00:00:00 2001 From: Bartek Pacia Date: Tue, 9 Apr 2024 00:55:08 +0100 Subject: [PATCH 01/10] add VM completion for run command --- Sources/tart/Commands/Run.swift | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Sources/tart/Commands/Run.swift b/Sources/tart/Commands/Run.swift index 84e3e28a..e59e6750 100644 --- a/Sources/tart/Commands/Run.swift +++ b/Sources/tart/Commands/Run.swift @@ -11,10 +11,18 @@ var vm: VM? struct IPNotFound: Error { } +func completeMachines(_ args: [String]) -> [String] { + if let vms = try? VMStorageLocal().list() { + return vms.map { (name, _) in return name } + } + return [] +} + + struct Run: AsyncParsableCommand { static var configuration = CommandConfiguration(abstract: "Run a VM") - @Argument(help: "VM name") + @Argument(help: "VM name", completion: .custom(completeMachines)) var name: String @Flag(help: ArgumentHelp( From f9816ab0300e6544563d634c4ab6af0f1e3b36ae Mon Sep 17 00:00:00 2001 From: Bartek Pacia Date: Tue, 9 Apr 2024 01:45:01 +0100 Subject: [PATCH 02/10] add VM completion for stop command --- Sources/tart/Commands/Stop.swift | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Sources/tart/Commands/Stop.swift b/Sources/tart/Commands/Stop.swift index 85f5d2cb..30fc1d27 100644 --- a/Sources/tart/Commands/Stop.swift +++ b/Sources/tart/Commands/Stop.swift @@ -3,10 +3,26 @@ import Foundation import System import SwiftDate +func completeRunningMachines(_ args: [String]) -> [String] { + if let vms = try? VMStorageLocal().list() { + return vms + .filter { (_, vm) in + if let state = try? vm.state() { + return state == "suspended" || state == "running" + } + return false + } + .map { (name, _) in + return name + } + } + return [] +} + struct Stop: AsyncParsableCommand { static var configuration = CommandConfiguration(commandName: "stop", abstract: "Stop a VM") - @Argument(help: "VM name") + @Argument(help: "VM name", completion: .custom(completeMachines)) var name: String @Option(name: [.short, .long], help: "Seconds to wait for graceful termination before forcefully terminating the VM") From 09645c8820ca13408addab801595a31eba1b8ee7 Mon Sep 17 00:00:00 2001 From: Bartek Pacia Date: Wed, 10 Apr 2024 02:41:35 +0100 Subject: [PATCH 03/10] create ShellCompletions utilities --- .../ShellCompletions/ShellCompletions.swift | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 Sources/tart/ShellCompletions/ShellCompletions.swift diff --git a/Sources/tart/ShellCompletions/ShellCompletions.swift b/Sources/tart/ShellCompletions/ShellCompletions.swift new file mode 100644 index 00000000..c478465c --- /dev/null +++ b/Sources/tart/ShellCompletions/ShellCompletions.swift @@ -0,0 +1,67 @@ +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] { + if let vms = try? VMStorageLocal().list() { + return vms.map { name, vmDir in + return name + } + } + return [] +} + +//func completeMachines(_ arguments: [String]) -> [String] { +// if let vms = try? VMStorageLocal().list() { +// return vms.enumerated().map { (index, data) in +// let (name, vmDir) = data +// return "name\(String(describing: name.first!))" +// } +// } +// return ["siemka"] +//} + +func completeRunningMachines(_ arguments: [String]) -> [String] { + if let vms = try? VMStorageLocal().list() { + return vms + .filter { (_, vm) in + if let state = try? vm.state() { + return state == "running" + } + return false + } + .map { (name, _) in + return name + } + } + return [] +} + +func completeActiveMachines(_ arguments: [String]) -> [String] { + if let vms = try? VMStorageLocal().list() { + return vms + .filter { (_, vm) in + if let state = try? vm.state() { + return state == "suspended" || state == "running" + } + return false + } + .map { (name, _) in + return name + } + } + return [] +} From 9c7b5e688b37bfb4fd5256b30b8c999ca32519ca Mon Sep 17 00:00:00 2001 From: Bartek Pacia Date: Wed, 10 Apr 2024 02:41:49 +0100 Subject: [PATCH 04/10] add shell completions to some commands --- Sources/tart/Commands/Clone.swift | 2 +- Sources/tart/Commands/Delete.swift | 2 +- Sources/tart/Commands/Run.swift | 10 +--------- Sources/tart/Commands/Stop.swift | 18 +----------------- Sources/tart/Commands/Suspend.swift | 2 +- 5 files changed, 5 insertions(+), 29 deletions(-) diff --git a/Sources/tart/Commands/Clone.swift b/Sources/tart/Commands/Clone.swift index 73a84934..15629290 100644 --- a/Sources/tart/Commands/Clone.swift +++ b/Sources/tart/Commands/Clone.swift @@ -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") diff --git a/Sources/tart/Commands/Delete.swift b/Sources/tart/Commands/Delete.swift index 4f8308f2..366985f2 100644 --- a/Sources/tart/Commands/Delete.swift +++ b/Sources/tart/Commands/Delete.swift @@ -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 { diff --git a/Sources/tart/Commands/Run.swift b/Sources/tart/Commands/Run.swift index e59e6750..8c705478 100644 --- a/Sources/tart/Commands/Run.swift +++ b/Sources/tart/Commands/Run.swift @@ -11,18 +11,10 @@ var vm: VM? struct IPNotFound: Error { } -func completeMachines(_ args: [String]) -> [String] { - if let vms = try? VMStorageLocal().list() { - return vms.map { (name, _) in return name } - } - return [] -} - - struct Run: AsyncParsableCommand { static var configuration = CommandConfiguration(abstract: "Run a VM") - @Argument(help: "VM name", completion: .custom(completeMachines)) + @Argument(help: "VM name", completion: .custom(completeLocalMachines)) var name: String @Flag(help: ArgumentHelp( diff --git a/Sources/tart/Commands/Stop.swift b/Sources/tart/Commands/Stop.swift index 30fc1d27..7f46f4ed 100644 --- a/Sources/tart/Commands/Stop.swift +++ b/Sources/tart/Commands/Stop.swift @@ -3,26 +3,10 @@ import Foundation import System import SwiftDate -func completeRunningMachines(_ args: [String]) -> [String] { - if let vms = try? VMStorageLocal().list() { - return vms - .filter { (_, vm) in - if let state = try? vm.state() { - return state == "suspended" || state == "running" - } - return false - } - .map { (name, _) in - return name - } - } - return [] -} - struct Stop: AsyncParsableCommand { static var configuration = CommandConfiguration(commandName: "stop", abstract: "Stop a VM") - @Argument(help: "VM name", completion: .custom(completeMachines)) + @Argument(help: "VM name", completion: .custom(completeLocalMachines)) var name: String @Option(name: [.short, .long], help: "Seconds to wait for graceful termination before forcefully terminating the VM") diff --git a/Sources/tart/Commands/Suspend.swift b/Sources/tart/Commands/Suspend.swift index b476240f..b38fc3b8 100644 --- a/Sources/tart/Commands/Suspend.swift +++ b/Sources/tart/Commands/Suspend.swift @@ -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 { From 07e2ff3e2fb45c49bfabefd770b761a771ae0319 Mon Sep 17 00:00:00 2001 From: Bartek Pacia Date: Wed, 10 Apr 2024 22:54:26 +0100 Subject: [PATCH 05/10] add shell completion for fqn command --- Sources/tart/Commands/FQN.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/tart/Commands/FQN.swift b/Sources/tart/Commands/FQN.swift index 0fd7f060..eb1e5816 100644 --- a/Sources/tart/Commands/FQN.swift +++ b/Sources/tart/Commands/FQN.swift @@ -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 { From cbb62d6851583789cc3810f48c449a085dbd5a57 Mon Sep 17 00:00:00 2001 From: Bartek Pacia Date: Wed, 10 Apr 2024 22:56:33 +0100 Subject: [PATCH 06/10] run command: fix tiny typo --- Sources/tart/Commands/Run.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/tart/Commands/Run.swift b/Sources/tart/Commands/Run.swift index 8c705478..8c9446bc 100644 --- a/Sources/tart/Commands/Run.swift +++ b/Sources/tart/Commands/Run.swift @@ -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 From 956331a9770f0bfdd2d8c716a83522511b5c3c40 Mon Sep 17 00:00:00 2001 From: Bartek Pacia Date: Wed, 10 Apr 2024 23:01:26 +0100 Subject: [PATCH 07/10] add shell completion for get command --- Sources/tart/Commands/Get.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/tart/Commands/Get.swift b/Sources/tart/Commands/Get.swift index 89f57f8c..aadb50c7 100644 --- a/Sources/tart/Commands/Get.swift +++ b/Sources/tart/Commands/Get.swift @@ -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") From 8c5a1b300c62ad561830393dc780c3b5430be093 Mon Sep 17 00:00:00 2001 From: Bartek Pacia Date: Wed, 10 Apr 2024 23:11:21 +0100 Subject: [PATCH 08/10] more shell completions --- Sources/tart/Commands/Export.swift | 2 +- Sources/tart/Commands/IP.swift | 2 +- Sources/tart/Commands/Push.swift | 2 +- Sources/tart/Commands/Rename.swift | 2 +- Sources/tart/Commands/Set.swift | 2 +- Sources/tart/Commands/Stop.swift | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/tart/Commands/Export.swift b/Sources/tart/Commands/Export.swift index f0b5dcae..5e70ae8a 100644 --- a/Sources/tart/Commands/Export.swift +++ b/Sources/tart/Commands/Export.swift @@ -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.") diff --git a/Sources/tart/Commands/IP.swift b/Sources/tart/Commands/IP.swift index 662d49e2..77d93d2d 100644 --- a/Sources/tart/Commands/IP.swift +++ b/Sources/tart/Commands/IP.swift @@ -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") diff --git a/Sources/tart/Commands/Push.swift b/Sources/tart/Commands/Push.swift index eb418366..a8b5aa12 100644 --- a/Sources/tart/Commands/Push.swift +++ b/Sources/tart/Commands/Push.swift @@ -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)") diff --git a/Sources/tart/Commands/Rename.swift b/Sources/tart/Commands/Rename.swift index 1f2c0680..70ae351f 100644 --- a/Sources/tart/Commands/Rename.swift +++ b/Sources/tart/Commands/Rename.swift @@ -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") diff --git a/Sources/tart/Commands/Set.swift b/Sources/tart/Commands/Set.swift index 0f69abc2..bfaceee4 100644 --- a/Sources/tart/Commands/Set.swift +++ b/Sources/tart/Commands/Set.swift @@ -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") diff --git a/Sources/tart/Commands/Stop.swift b/Sources/tart/Commands/Stop.swift index 7f46f4ed..0cc83216 100644 --- a/Sources/tart/Commands/Stop.swift +++ b/Sources/tart/Commands/Stop.swift @@ -6,7 +6,7 @@ import SwiftDate struct Stop: AsyncParsableCommand { static var configuration = CommandConfiguration(commandName: "stop", abstract: "Stop a VM") - @Argument(help: "VM name", completion: .custom(completeLocalMachines)) + @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") From 2d103e0ac0a5cc8bee596fdefc76c98dd744d477 Mon Sep 17 00:00:00 2001 From: Bartek Pacia Date: Wed, 10 Apr 2024 23:23:49 +0100 Subject: [PATCH 09/10] remove unnecessary `try` --- Sources/tart/Commands/IP.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/tart/Commands/IP.swift b/Sources/tart/Commands/IP.swift index 77d93d2d..f271c6dc 100644 --- a/Sources/tart/Commands/IP.swift +++ b/Sources/tart/Commands/IP.swift @@ -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 } } From 0e4f2b2bb8a084062e6cb056394168531c56a020 Mon Sep 17 00:00:00 2001 From: Bartek Pacia Date: Wed, 10 Apr 2024 23:33:00 +0100 Subject: [PATCH 10/10] refactor ShellCompletions file --- .../ShellCompletions/ShellCompletions.swift | 51 +++---------------- 1 file changed, 6 insertions(+), 45 deletions(-) diff --git a/Sources/tart/ShellCompletions/ShellCompletions.swift b/Sources/tart/ShellCompletions/ShellCompletions.swift index c478465c..39aa3c77 100644 --- a/Sources/tart/ShellCompletions/ShellCompletions.swift +++ b/Sources/tart/ShellCompletions/ShellCompletions.swift @@ -16,52 +16,13 @@ func completeMachines(_ arguments: [String]) -> [String] { } func completeLocalMachines(_ arguments: [String]) -> [String] { - if let vms = try? VMStorageLocal().list() { - return vms.map { name, vmDir in - return name - } - } - return [] + let localVMs = (try? VMStorageLocal().list()) ?? [] + return localVMs.map { name, _ in normalizeName(name) } } -//func completeMachines(_ arguments: [String]) -> [String] { -// if let vms = try? VMStorageLocal().list() { -// return vms.enumerated().map { (index, data) in -// let (name, vmDir) = data -// return "name\(String(describing: name.first!))" -// } -// } -// return ["siemka"] -//} - func completeRunningMachines(_ arguments: [String]) -> [String] { - if let vms = try? VMStorageLocal().list() { - return vms - .filter { (_, vm) in - if let state = try? vm.state() { - return state == "running" - } - return false - } - .map { (name, _) in - return name - } - } - return [] -} - -func completeActiveMachines(_ arguments: [String]) -> [String] { - if let vms = try? VMStorageLocal().list() { - return vms - .filter { (_, vm) in - if let state = try? vm.state() { - return state == "suspended" || state == "running" - } - return false - } - .map { (name, _) in - return name - } - } - return [] + let localVMs = (try? VMStorageLocal().list()) ?? [] + return localVMs + .filter { _, vmDir in (try? vmDir.state() == .Running) ?? false} + .map { name, _ in normalizeName(name) } }