From 2f0aae7b4ce4f426fd344d29825993937beed85a Mon Sep 17 00:00:00 2001 From: Matthias Tafelmeier Date: Sun, 11 Jan 2026 10:51:49 +0100 Subject: [PATCH] cli: add --output option for formatted responses Add --output option supporting text/json/yaml formats. Text mode shows human-readable messages (default), while json/yaml modes output raw structured data. Fix windmillVariable label and add UUID to delete confirmations. --- src/godon/credential.nim | 2 +- src/godon_cli.nim | 199 ++++++++++++++++++++++++--------------- 2 files changed, 125 insertions(+), 76 deletions(-) diff --git a/src/godon/credential.nim b/src/godon/credential.nim index 5737d23..d6f9363 100644 --- a/src/godon/credential.nim +++ b/src/godon/credential.nim @@ -1,4 +1,4 @@ -import std/[httpclient, json, strutils, uri, tables] +import std/[httpclient, json, uri, tables] import yaml import client, types diff --git a/src/godon_cli.nim b/src/godon_cli.nim index 031a825..eb7edec 100644 --- a/src/godon_cli.nim +++ b/src/godon_cli.nim @@ -1,6 +1,13 @@ import std/[parseopt, strutils, os, json] +import yaml, yaml/presenter, yaml/dumping import godon/[client, breeder, credential, types] +type + OutputFormat* = enum + Text = "text" + Json = "json" + Yaml = "yaml" + proc writeHelp() = echo """Godon CLI - Command line interface for Godon API @@ -23,8 +30,9 @@ Global Options: --hostname, -h Godon hostname (default: localhost) --port, -p Godon port (default: 8080) --api-version, -v API version (default: v0) + --output, -o Output format: text, json, or yaml (default: text) --insecure Skip SSL certificate verification (HTTPS only) - --help, -h Show this help message + --help Show this help message Examples: godon_cli breeder list @@ -40,12 +48,13 @@ proc writeError(message: string) = stderr.writeLine("Error: " & message) quit(1) -proc parseArgs(): (string, string, int, string, bool, seq[string]) = +proc parseArgs(): (string, string, int, string, bool, OutputFormat, seq[string]) = var command = "" var hostname = "localhost" var port = 8080 var apiVersion = "v0" var insecure = false + var outputFormat = OutputFormat.Text var args: seq[string] = @[] var p = initOptParser(commandLineParams()) @@ -82,7 +91,19 @@ proc parseArgs(): (string, string, int, string, bool, seq[string]) = args.add("--id=" & val) of "insecure": insecure = true - of "help", "h": + of "output", "o": + if val.len == 0: + writeError("Output option requires a value (text, json, or yaml)") + case val.normalize() + of "text": + outputFormat = OutputFormat.Text + of "json": + outputFormat = OutputFormat.Json + of "yaml": + outputFormat = OutputFormat.Yaml + else: + writeError("Unknown output format: " & val & ". Use text, json, or yaml") + of "help": writeHelp() quit(0) else: @@ -95,23 +116,39 @@ proc parseArgs(): (string, string, int, string, bool, seq[string]) = writeHelp() quit(0) - (command, hostname, port, apiVersion, insecure, args) + (command, hostname, port, apiVersion, insecure, outputFormat, args) + +proc formatOutput*[T](data: T, outputFormat: OutputFormat) = + ## Format data according to output format preference + case outputFormat + of OutputFormat.Text: + # Text mode is handled by caller with custom formatting + discard + of OutputFormat.Json: + echo pretty(%*data) + of OutputFormat.Yaml: + # Convert to YAML by dumping JSON as text and transforming it + let jsonString = pretty(%*data) + var dumper = blockOnlyDumper() + echo dumper.transform(jsonString) -proc handleBreederCommand(client: GodonClient, command: string, args: seq[string]) = +proc handleBreederCommand(client: GodonClient, command: string, args: seq[string], outputFormat: OutputFormat) = let subCommand = if args.len > 0: args[0] else: "" case subCommand: of "list": - echo "Listing breeders..." let response = client.listBreeders() if response.success: - echo "Breeders:" - for breeder in response.data: - echo " ID: ", breeder.id - echo " Name: ", breeder.name - echo " Status: ", breeder.status - echo " Created: ", breeder.createdAt - echo " ---" + if outputFormat == OutputFormat.Text: + echo "Breeders:" + for breeder in response.data: + echo " ID: ", breeder.id + echo " Name: ", breeder.name + echo " Status: ", breeder.status + echo " Created: ", breeder.createdAt + echo " ---" + else: + formatOutput(response.data, outputFormat) else: writeError(response.error) @@ -133,14 +170,16 @@ proc handleBreederCommand(client: GodonClient, command: string, args: seq[string if not fileExists(file): writeError("File not found: " & file) - echo "Creating breeder '", name, "' from file: ", file let content = readFile(file) let response = client.createBreederFromYamlWithName(content, name) if response.success: - echo "Breeder created successfully:" - echo " ID: ", response.data.id - echo " Name: ", response.data.name - echo " Status: ", response.data.status + if outputFormat == OutputFormat.Text: + echo "Breeder created successfully:" + echo " ID: ", response.data.id + echo " Name: ", response.data.name + echo " Status: ", response.data.status + else: + formatOutput(response.data, outputFormat) else: writeError(response.error) @@ -153,16 +192,18 @@ proc handleBreederCommand(client: GodonClient, command: string, args: seq[string if id.len == 0: writeError("breeder show requires --id ") - - echo "Getting breeder details for ID: ", id + let response = client.getBreeder(id) if response.success: - echo "Breeder Details:" - echo " ID: ", response.data.id - echo " Name: ", response.data.name - echo " Status: ", response.data.status - echo " Config: ", pretty(response.data.config) - echo " Created: ", response.data.createdAt + if outputFormat == OutputFormat.Text: + echo "Breeder Details:" + echo " ID: ", response.data.id + echo " Name: ", response.data.name + echo " Status: ", response.data.status + echo " Config: ", pretty(response.data.config) + echo " Created: ", response.data.createdAt + else: + formatOutput(response.data, outputFormat) else: writeError(response.error) @@ -178,15 +219,17 @@ proc handleBreederCommand(client: GodonClient, command: string, args: seq[string if not fileExists(file): writeError("File not found: " & file) - - echo "Updating breeder from file: ", file + let content = readFile(file) let response = client.updateBreederFromYaml(content) if response.success: - echo "Breeder updated successfully:" - echo " ID: ", response.data.id - echo " Name: ", response.data.name - echo " Status: ", response.data.status + if outputFormat == OutputFormat.Text: + echo "Breeder updated successfully:" + echo " ID: ", response.data.id + echo " Name: ", response.data.name + echo " Status: ", response.data.status + else: + formatOutput(response.data, outputFormat) else: writeError(response.error) @@ -199,36 +242,38 @@ proc handleBreederCommand(client: GodonClient, command: string, args: seq[string if id.len == 0: writeError("breeder purge requires --id ") - - echo "Deleting breeder with ID: ", id + let response = client.deleteBreeder(id) if response.success: - echo "Breeder deleted successfully" - if response.data != nil: - echo "Response: ", pretty(response.data) + if outputFormat == OutputFormat.Text: + echo "Breeder deleted successfully: ", id + else: + formatOutput(response.data, outputFormat) else: writeError(response.error) else: writeError("Unknown breeder command: " & subCommand) -proc handleCredentialCommand(client: GodonClient, command: string, args: seq[string]) = +proc handleCredentialCommand(client: GodonClient, command: string, args: seq[string], outputFormat: OutputFormat) = let subCommand = if args.len > 0: args[0] else: "" case subCommand: of "list": - echo "Listing credentials..." let response = client.listCredentials() if response.success: - echo "Credentials:" - for credential in response.data: - echo " ID: ", credential.id - echo " Name: ", credential.name - echo " Type: ", credential.credentialType - echo " Description: ", credential.description - echo " Windmill Variable: ", credential.windmillVariable - echo " Created: ", credential.createdAt - echo " ---" + if outputFormat == OutputFormat.Text: + echo "Credentials:" + for credential in response.data: + echo " ID: ", credential.id + echo " Name: ", credential.name + echo " Type: ", credential.credentialType + echo " Description: ", credential.description + echo " windmillVariable: ", credential.windmillVariable + echo " Created: ", credential.createdAt + echo " ---" + else: + formatOutput(response.data, outputFormat) else: writeError(response.error) @@ -244,16 +289,18 @@ proc handleCredentialCommand(client: GodonClient, command: string, args: seq[str if not fileExists(file): writeError("File not found: " & file) - - echo "Creating credential from file: ", file + let content = readFile(file) let response = client.createCredentialFromYaml(content) if response.success: - echo "Credential created successfully:" - echo " ID: ", response.data.id - echo " Name: ", response.data.name - echo " Type: ", response.data.credentialType - echo " Windmill Variable: ", response.data.windmillVariable + if outputFormat == OutputFormat.Text: + echo "Credential created successfully:" + echo " ID: ", response.data.id + echo " Name: ", response.data.name + echo " Type: ", response.data.credentialType + echo " windmillVariable: ", response.data.windmillVariable + else: + formatOutput(response.data, outputFormat) else: writeError(response.error) @@ -266,20 +313,22 @@ proc handleCredentialCommand(client: GodonClient, command: string, args: seq[str if id.len == 0: writeError("credential show requires --id ") - - echo "Getting credential details for ID: ", id + let response = client.getCredential(id) if response.success: - echo "Credential Details:" - echo " ID: ", response.data.id - echo " Name: ", response.data.name - echo " Type: ", response.data.credentialType - echo " Description: ", response.data.description - echo " Windmill Variable: ", response.data.windmillVariable - echo " Created: ", response.data.createdAt - echo " Last Used: ", response.data.lastUsedAt - echo " Content:" - echo " ", response.data.content # Show actual credential content + if outputFormat == OutputFormat.Text: + echo "Credential Details:" + echo " ID: ", response.data.id + echo " Name: ", response.data.name + echo " Type: ", response.data.credentialType + echo " Description: ", response.data.description + echo " windmillVariable: ", response.data.windmillVariable + echo " Created: ", response.data.createdAt + echo " Last Used: ", response.data.lastUsedAt + echo " Content:" + echo " ", response.data.content + else: + formatOutput(response.data, outputFormat) else: writeError(response.error) @@ -292,20 +341,20 @@ proc handleCredentialCommand(client: GodonClient, command: string, args: seq[str if id.len == 0: writeError("credential delete requires --id ") - - echo "Deleting credential with ID: ", id + let response = client.deleteCredential(id) if response.success: - echo "Credential deleted successfully" - if response.data != nil: - echo "Response: ", pretty(response.data) + if outputFormat == OutputFormat.Text: + echo "Credential deleted successfully: ", id + else: + formatOutput(response.data, outputFormat) else: writeError(response.error) else: writeError("Unknown credential command: " & subCommand) -let (command, hostname, port, apiVersion, insecure, args) = parseArgs() +let (command, hostname, port, apiVersion, insecure, outputFormat, args) = parseArgs() let godonClient = newGodonClient(hostname, port, apiVersion, insecure) @@ -313,12 +362,12 @@ case command: of "breeder": if args.len == 0: writeError("breeder command requires a subcommand (list, create, show, update, purge)") - handleBreederCommand(godonClient, command, args) + handleBreederCommand(godonClient, command, args, outputFormat) of "credential": if args.len == 0: writeError("credential command requires a subcommand (list, create, show, delete)") - handleCredentialCommand(godonClient, command, args) + handleCredentialCommand(godonClient, command, args, outputFormat) else: writeError("Unknown command: " & command) \ No newline at end of file