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

JSON output for get and list commands #394

Merged
merged 11 commits into from
Feb 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,15 @@
"revision" : "da637c398c5d08896521b737f2868ddc2e7996ae",
"version" : "0.50.6"
}
},
{
"identity" : "texttable",
"kind" : "remoteSourceControl",
"location" : "https:/cfilipov/TextTable",
"state" : {
"branch" : "master",
"revision" : "e03289289155b4e7aa565e32862f9cb42140596a"
}
}
],
"version" : 2
Expand Down
3 changes: 2 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ let package = Package(
.package(url: "https:/apple/swift-atomics.git", .upToNextMajor(from: "1.0.0")),
.package(url: "https:/nicklockwood/SwiftFormat", from: "0.50.6"),
.package(url: "https:/getsentry/sentry-cocoa", from: "7.31.3"),
.package(url: "https:/cfilipov/TextTable", branch: "master"),
],
targets: [
.executableTarget(name: "tart", dependencies: [
Expand All @@ -32,7 +33,7 @@ let package = Package(
.product(name: "Antlr4Static", package: "Antlr4"),
.product(name: "Atomics", package: "swift-atomics"),
.product(name: "Sentry", package: "sentry-cocoa"),

.product(name: "TextTable", package: "TextTable"),
], exclude: [
"OCI/Reference/Makefile",
"OCI/Reference/Reference.g4",
Expand Down
51 changes: 15 additions & 36 deletions Sources/tart/Commands/Get.swift
Original file line number Diff line number Diff line change
@@ -1,52 +1,31 @@
import ArgumentParser
import Foundation

fileprivate struct VMInfo: Encodable {
let CPU: Int
let Memory: UInt64
let Disk: Int
let Display: String
let Running: Bool
}

struct Get: AsyncParsableCommand {
static var configuration = CommandConfiguration(commandName: "get", abstract: "Get a VM's configuration")

@Argument(help: "VM name.")
var name: String

@Flag(help: "Number of VM CPUs.")
var cpu: Bool = false

@Flag(help: "VM memory size in megabytes.")
var memory: Bool = false

@Flag(help: "Disk size in gigabytes.")
var diskSize: Bool = false

@Flag(help: "VM display resolution in a format of <width>x<height>. For example, 1200x800.")
var display: Bool = false

func validate() throws {
if [cpu, memory, diskSize, display].filter({$0}).count > 1 {
throw ValidationError("Options --cpu, --memory, --disk-size and --display are mutually exclusive")
}
}
@Option(help: "Output format: text or json")
var format: Format = .text

func run() async throws {
let vmDir = try VMStorageLocal().open(name)
let vmConfig = try VMConfig(fromURL: vmDir.configURL)
let diskSizeInGb = try vmDir.sizeBytes() / 1000 / 1000 / 1000
let memorySizeInMb = vmConfig.memorySize / 1024 / 1024
let diskSizeInGb = try vmDir.sizeGB()
let memorySizeInMb = vmConfig.memorySize / 1024 / 1024
let running = try PIDLock(lockURL: vmDir.configURL).pid() > 0

if cpu {
print(vmConfig.cpuCount)
} else if memory {
print(memorySizeInMb)
} else if diskSize {
print(diskSizeInGb)
} else if display {
print("\(vmConfig.display.width)x\(vmConfig.display.height)")
} else {
print(
"CPU\tMemory\tDisk\tDisplay\n" +
"\(vmConfig.cpuCount)\t" +
"\(memorySizeInMb) MB\t" +
"\(diskSizeInGb) GB\t" +
"\(vmConfig.display)"
)
}
let info = VMInfo(CPU: vmConfig.cpuCount, Memory: memorySizeInMb, Disk: diskSizeInGb, Display: vmConfig.display.description, Running: running)
print(format.renderSingle(info))
}
}
46 changes: 28 additions & 18 deletions Sources/tart/Commands/List.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,24 @@ import ArgumentParser
import Dispatch
import SwiftUI

fileprivate struct VMInfo: Encodable {
let Source: String
let Name: String
let Size: Int
}

struct List: AsyncParsableCommand {
static var configuration = CommandConfiguration(abstract: "List created VMs")

@Flag(name: [.short, .long], help: ArgumentHelp("Only display VM names."))
var quiet: Bool = false

@Option(help: ArgumentHelp("Only display VMs from the specified source (e.g. --source local, --source oci)."))
var source: String?

@Option(help: "Output format: text or json")
var format: Format = .text

@Flag(name: [.short, .long], help: ArgumentHelp("Only display VM names."))
var quiet: Bool = false

func validate() throws {
guard let source = source else {
return
Expand All @@ -22,27 +31,28 @@ struct List: AsyncParsableCommand {
}

func run() async throws {
if !quiet {
print("Source\tName")
}

var infos: [VMInfo] = []
if source == nil || source == "local" {
displayTable("local", try VMStorageLocal().list())
infos += sortedInfos(try VMStorageLocal().list().map { (name, vmDir) in
try VMInfo(Source: "local", Name: name, Size: vmDir.sizeGB())
})
}

if source == nil || source == "oci" {
displayTable("oci", try VMStorageOCI().list().map { (name, vmDir, _) in (name, vmDir) })
infos += sortedInfos(try VMStorageOCI().list().map { (name, vmDir, _) in
try VMInfo(Source: "oci", Name: name, Size: vmDir.sizeGB())
})
}
}

private func displayTable(_ source: String, _ vms: [(String, VMDirectory)]) {
for (name, _) in vms.sorted(by: { left, right in left.0 < right.0 }) {
if quiet {
print(name)
} else {
let source = source.padding(toLength: "Source".count, withPad: " ", startingAt: 0)
print("\(source)\t\(name)")
if (quiet) {
for info in infos {
print(info.Name)
}
} else {
print(format.renderList(infos))
}
}

private func sortedInfos(_ infos: [VMInfo]) -> [VMInfo] {
infos.sorted(by: { left, right in left.Name < right.Name })
}
}
41 changes: 41 additions & 0 deletions Sources/tart/Formatter/Format.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import ArgumentParser
import Foundation
import TextTable

enum Format: String, ExpressibleByArgument, CaseIterable {
case text, json

private(set) static var allValueStrings: [String] = Format.allCases.map { "\($0)"}

func renderSingle<T>(_ data: T) -> String where T: Encodable {
switch self {
case .text:
return renderList([data])
case .json:
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
return try! encoder.encode(data).asText()
}
}

func renderList<T>(_ data: Array<T>) -> String where T: Encodable {
switch self {
case .text:
if (data.count == 0) {
return ""
}
let table = TextTable<T> { (item: T) in
let mirroredObject = Mirror(reflecting: item)
return mirroredObject.children.enumerated().map { (_, element) in
let fieldName = element.label!
return Column(title: fieldName, value: element.value)
}
}
return table.string(for: data, style: Style.plain)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
case .json:
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
return try! encoder.encode(data).asText()
}
}
}
4 changes: 4 additions & 0 deletions Sources/tart/VMDirectory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ struct VMDirectory: Prunable {
try configURL.sizeBytes() + diskURL.sizeBytes() + nvramURL.sizeBytes()
}

func sizeGB() throws -> Int {
try sizeBytes() / 1000 / 1000 / 1000
}

func markExplicitlyPulled() {
FileManager.default.createFile(atPath: explicitlyPulledMark.path, contents: nil)
}
Expand Down