Skip to content

Commit

Permalink
Merge pull request #42 from yjinjo/v0
Browse files Browse the repository at this point in the history
Add watch option
  • Loading branch information
yjinjo authored Nov 21, 2024
2 parents f57c00d + 8d9197e commit 1f39fec
Show file tree
Hide file tree
Showing 3 changed files with 205 additions and 30 deletions.
100 changes: 72 additions & 28 deletions cmd/common/fetchService.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"fmt"
"log"
"os"
"sort"
"strings"

"github.com/atotto/clipboard"
Expand Down Expand Up @@ -36,26 +37,29 @@ type Environment struct {
}

// FetchService handles the execution of gRPC commands for all services
func FetchService(serviceName string, verb string, resourceName string, options *FetchOptions) error {
func FetchService(serviceName string, verb string, resourceName string, options *FetchOptions) (map[string]interface{}, error) {
config, err := loadConfig()
if err != nil {
return fmt.Errorf("failed to load config: %v", err)
return nil, fmt.Errorf("failed to load config: %v", err)
}

jsonBytes, err := fetchJSONResponse(config, serviceName, verb, resourceName, options)
if err != nil {
return err
return nil, err
}

// Unmarshal JSON bytes to a map
var respMap map[string]interface{}
if err := json.Unmarshal(jsonBytes, &respMap); err != nil {
return fmt.Errorf("failed to unmarshal JSON: %v", err)
return nil, fmt.Errorf("failed to unmarshal JSON: %v", err)
}

printData(respMap, options)
// Print the data if not in watch mode
if options.OutputFormat != "" {
printData(respMap, options)
}

return nil
return respMap, nil
}

func loadConfig() (*Config, error) {
Expand Down Expand Up @@ -262,9 +266,11 @@ func printData(data map[string]interface{}, options *FetchOptions) {
}

func printTable(data map[string]interface{}) string {
var output string
if results, ok := data["results"].([]interface{}); ok {
tableData := pterm.TableData{}
pageSize := 5
currentPage := 0
totalItems := len(results)
totalPages := (totalItems + pageSize - 1) / pageSize

// Extract headers
headers := []string{}
Expand All @@ -273,42 +279,80 @@ func printTable(data map[string]interface{}) string {
for key := range row {
headers = append(headers, key)
}
sort.Strings(headers)
}
}

// Append headers to table data
tableData = append(tableData, headers)
for {
tableData := pterm.TableData{headers}

// Extract rows
for _, result := range results {
if row, ok := result.(map[string]interface{}); ok {
rowData := []string{}
for _, key := range headers {
rowData = append(rowData, formatTableValue(row[key]))
// Calculate current page items
startIdx := currentPage * pageSize
endIdx := startIdx + pageSize
if endIdx > totalItems {
endIdx = totalItems
}

// Add rows for current page
for _, result := range results[startIdx:endIdx] {
if row, ok := result.(map[string]interface{}); ok {
rowData := make([]string, len(headers))
for i, key := range headers {
rowData[i] = formatTableValue(row[key])
}
tableData = append(tableData, rowData)
}
tableData = append(tableData, rowData)
}
}

// Disable styling only for the table output
pterm.DisableStyling()
renderedOutput, err := pterm.DefaultTable.WithHasHeader(true).WithData(tableData).Srender()
pterm.EnableStyling() // Re-enable styling for other outputs
if err != nil {
log.Fatalf("Failed to render table: %v", err)
// Clear screen
fmt.Print("\033[H\033[2J")

// Print table
pterm.DefaultTable.WithHasHeader().WithData(tableData).Render()

// Print pagination info and controls
fmt.Printf("\nPage %d of %d (Total items: %d)\n", currentPage+1, totalPages, totalItems)
fmt.Println("Navigation: [p]revious page, [n]ext page, [q]uit")

// Get user input
var input string
fmt.Scanln(&input)

switch strings.ToLower(input) {
case "n":
if currentPage < totalPages-1 {
currentPage++
}
case "p":
if currentPage > 0 {
currentPage--
}
case "q":
return ""
}
}
output = renderedOutput
fmt.Println(output) // Print to console
}
return output
return ""
}

func formatTableValue(val interface{}) string {
switch v := val.(type) {
case nil:
return ""
case string:
return v
// Add colors for status values
switch strings.ToUpper(v) {
case "SUCCESS":
return pterm.FgGreen.Sprint(v)
case "FAILURE":
return pterm.FgRed.Sprint(v)
case "PENDING":
return pterm.FgYellow.Sprint(v)
case "RUNNING":
return pterm.FgBlue.Sprint(v)
default:
return v
}
case float64, float32, int, int32, int64, uint, uint32, uint64:
return fmt.Sprintf("%v", v)
case bool:
Expand Down
133 changes: 132 additions & 1 deletion cmd/common/fetchVerb.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ package common

import (
"fmt"
"os"
"os/signal"
"sort"
"strings"
"time"

"github.com/pterm/pterm"

Expand Down Expand Up @@ -91,7 +94,16 @@ func AddVerbCommands(parentCmd *cobra.Command, serviceName string, groupID strin
CopyToClipboard: copyToClipboard,
}

err = FetchService(serviceName, currentVerb, resource, options)
if currentVerb == "list" && !cmd.Flags().Changed("output") {
options.OutputFormat = "table"
}

watch, _ := cmd.Flags().GetBool("watch")
if watch && currentVerb == "list" {
return watchResource(serviceName, currentVerb, resource, options)
}

_, err = FetchService(serviceName, currentVerb, resource, options)
if err != nil {
// Use pterm to display the error message
pterm.Error.Println(err.Error())
Expand All @@ -105,6 +117,10 @@ func AddVerbCommands(parentCmd *cobra.Command, serviceName string, groupID strin
},
}

if currentVerb == "list" {
verbCmd.Flags().BoolP("watch", "w", false, "Watch for changes")
}

// Define flags for verbCmd
verbCmd.Flags().StringArrayP("parameter", "p", []string{}, "Input Parameter (-p <key>=<value> -p ...)")
verbCmd.Flags().StringP("json-parameter", "j", "", "JSON type parameter")
Expand All @@ -121,3 +137,118 @@ func AddVerbCommands(parentCmd *cobra.Command, serviceName string, groupID strin

return nil
}

// watchResource monitors a resource for changes and prints updates
func watchResource(serviceName, verb, resource string, options *FetchOptions) error {
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()

sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt)

// Map to store seen results
seenResults := make(map[string]bool)

// Create a copy of options for initial fetch
initialOptions := *options

// Fetch and display initial data
initialData, err := FetchService(serviceName, verb, resource, &initialOptions)
if err != nil {
return err
}

// Process initial results
if results, ok := initialData["results"].([]interface{}); ok {
for _, item := range results {
if m, ok := item.(map[string]interface{}); ok {
identifier := generateIdentifier(m)
seenResults[identifier] = true
}
}
}

fmt.Printf("\nWatching for changes... (Ctrl+C to quit)\n")

// Create options for subsequent fetches without output
watchOptions := *options
watchOptions.OutputFormat = ""

for {
select {
case <-ticker.C:
newData, err := FetchService(serviceName, verb, resource, &watchOptions)
if err != nil {
continue
}

if results, ok := newData["results"].([]interface{}); ok {
newItems := []map[string]interface{}{}

for _, item := range results {
if m, ok := item.(map[string]interface{}); ok {
identifier := generateIdentifier(m)
if !seenResults[identifier] {
seenResults[identifier] = true
newItems = append(newItems, m)
}
}
}

if len(newItems) > 0 {
printNewItems(newItems)
}
}

case <-sigChan:
fmt.Println("\nStopping watch...")
return nil
}
}
}

// generateIdentifier creates a unique identifier for an item based on its contents
func generateIdentifier(item map[string]interface{}) string {
var keys []string
for k := range item {
keys = append(keys, k)
}
sort.Strings(keys)

var parts []string
for _, k := range keys {
parts = append(parts, fmt.Sprintf("%v=%v", k, item[k]))
}
return strings.Join(parts, ",")
}

// printNewItems displays new items in table format
func printNewItems(items []map[string]interface{}) {
if len(items) == 0 {
return
}

// Prepare table data
tableData := pterm.TableData{}

// Extract headers from first item
headers := make([]string, 0)
for key := range items[0] {
headers = append(headers, key)
}
sort.Strings(headers)

// Convert each item to a table row
for _, item := range items {
row := make([]string, len(headers))
for i, header := range headers {
if val, ok := item[header]; ok {
row[i] = formatTableValue(val)
}
}
tableData = append(tableData, row)
}

// Render the table
pterm.DefaultTable.WithData(tableData).Render()
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ go 1.23.1
require (
github.com/atotto/clipboard v0.1.4
github.com/jhump/protoreflect v1.17.0
github.com/mattn/go-runewidth v0.0.15
github.com/pterm/pterm v0.12.79
github.com/sashabaranov/go-openai v1.35.6
github.com/spf13/cobra v1.8.1
Expand All @@ -29,6 +28,7 @@ require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/lithammer/fuzzysearch v1.1.8 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
Expand Down

0 comments on commit 1f39fec

Please sign in to comment.