adding new output formats to cli: csv, column, json. fixes 1419
parent
05d50f3014
commit
d3c98de563
|
@ -1,15 +1,18 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/csv"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"text/tabwriter"
|
||||||
|
|
||||||
"github.com/influxdb/influxdb/client"
|
"github.com/influxdb/influxdb/client"
|
||||||
"github.com/peterh/liner"
|
"github.com/peterh/liner"
|
||||||
|
@ -18,6 +21,7 @@ import (
|
||||||
const (
|
const (
|
||||||
default_host = "localhost"
|
default_host = "localhost"
|
||||||
default_port = 8086
|
default_port = 8086
|
||||||
|
default_format = "column"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CommandLine struct {
|
type CommandLine struct {
|
||||||
|
@ -29,6 +33,7 @@ type CommandLine struct {
|
||||||
Database string
|
Database string
|
||||||
Version string
|
Version string
|
||||||
Pretty bool // controls pretty print for json
|
Pretty bool // controls pretty print for json
|
||||||
|
Format string // controls the output format. Valid values are json, csv, or column
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -40,6 +45,7 @@ func main() {
|
||||||
fs.StringVar(&c.Username, "username", c.Username, "username to connect to the server. can be blank if authorization is not required")
|
fs.StringVar(&c.Username, "username", c.Username, "username to connect to the server. can be blank if authorization is not required")
|
||||||
fs.StringVar(&c.Password, "password", c.Password, "password to connect to the server. can be blank if authorization is not required")
|
fs.StringVar(&c.Password, "password", c.Password, "password to connect to the server. can be blank if authorization is not required")
|
||||||
fs.StringVar(&c.Database, "database", c.Database, "database to connect to the server.")
|
fs.StringVar(&c.Database, "database", c.Database, "database to connect to the server.")
|
||||||
|
fs.StringVar(&c.Format, "output", default_format, "format specifies the format of the server responses: json, csv, or column")
|
||||||
fs.Parse(os.Args[1:])
|
fs.Parse(os.Args[1:])
|
||||||
|
|
||||||
// TODO Determine if we are an ineractive shell or running commands
|
// TODO Determine if we are an ineractive shell or running commands
|
||||||
|
@ -90,6 +96,10 @@ func (c *CommandLine) ParseCommand(cmd string) bool {
|
||||||
c.connect(cmd)
|
c.connect(cmd)
|
||||||
case strings.HasPrefix(lcmd, "help"):
|
case strings.HasPrefix(lcmd, "help"):
|
||||||
help()
|
help()
|
||||||
|
case strings.HasPrefix(lcmd, "format"):
|
||||||
|
c.SetFormat(cmd)
|
||||||
|
case strings.HasPrefix(lcmd, "settings"):
|
||||||
|
c.Settings()
|
||||||
case strings.HasPrefix(lcmd, "pretty"):
|
case strings.HasPrefix(lcmd, "pretty"):
|
||||||
c.Pretty = !c.Pretty
|
c.Pretty = !c.Pretty
|
||||||
if c.Pretty {
|
if c.Pretty {
|
||||||
|
@ -177,27 +187,160 @@ func (c *CommandLine) use(cmd string) {
|
||||||
fmt.Printf("Using database %s\n", d)
|
fmt.Printf("Using database %s\n", d)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *CommandLine) SetFormat(cmd string) {
|
||||||
|
// Remove the "format" keyword if it exists
|
||||||
|
cmd = strings.TrimSpace(strings.Replace(cmd, "format", "", -1))
|
||||||
|
// normalize cmd
|
||||||
|
cmd = strings.ToLower(cmd)
|
||||||
|
|
||||||
|
switch cmd {
|
||||||
|
case "json", "csv", "column":
|
||||||
|
c.Format = cmd
|
||||||
|
default:
|
||||||
|
fmt.Printf("Unknown format %q. Please use json, csv, or column.\n", cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *CommandLine) executeQuery(query string) {
|
func (c *CommandLine) executeQuery(query string) {
|
||||||
results, err := c.Client.Query(client.Query{Command: query, Database: c.Database})
|
results, err := c.Client.Query(client.Query{Command: query, Database: c.Database})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("ERR: %s\n", err)
|
fmt.Printf("ERR: %s\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("ERR: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.FormatResults(results, os.Stdout)
|
||||||
|
if results.Error() != nil && c.Database == "" {
|
||||||
|
fmt.Println("Warning: It is possible this error is due to not setting a database.")
|
||||||
|
fmt.Println(`Please set a database with the command "use <database>".`)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommandLine) FormatResults(results *client.Results, w io.Writer) {
|
||||||
|
switch c.Format {
|
||||||
|
case "json":
|
||||||
|
WriteJSON(results, c.Pretty, w)
|
||||||
|
case "csv":
|
||||||
|
WriteCSV(results, w)
|
||||||
|
case "column":
|
||||||
|
WriteColumns(results, w)
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(w, "Unknown output format %q.\n", c.Format)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteJSON(results *client.Results, pretty bool, w io.Writer) {
|
||||||
var data []byte
|
var data []byte
|
||||||
if c.Pretty {
|
var err error
|
||||||
|
if pretty {
|
||||||
data, err = json.MarshalIndent(results, "", " ")
|
data, err = json.MarshalIndent(results, "", " ")
|
||||||
} else {
|
} else {
|
||||||
data, err = json.Marshal(results)
|
data, err = json.Marshal(results)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("ERR: %s\n", err)
|
fmt.Fprintf(w, "Unable to parse json: %s\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Fprintln(os.Stdout, string(data))
|
fmt.Fprintln(w, string(data))
|
||||||
if results.Error() != nil && c.Database == "" {
|
|
||||||
fmt.Println("Warning: It is possible this error is due to not setting a database.")
|
|
||||||
fmt.Println(`Please set a database with the command "use <database>".`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WriteCSV(results *client.Results, w io.Writer) {
|
||||||
|
csvw := csv.NewWriter(w)
|
||||||
|
for _, result := range results.Results {
|
||||||
|
// Create a tabbed writer for each result a they won't always line up
|
||||||
|
rows := resultToCSV(result, "\t", false)
|
||||||
|
for _, r := range rows {
|
||||||
|
csvw.Write(strings.Split(r, "\t"))
|
||||||
|
}
|
||||||
|
csvw.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteColumns(results *client.Results, w io.Writer) {
|
||||||
|
for _, result := range results.Results {
|
||||||
|
// Create a tabbed writer for each result a they won't always line up
|
||||||
|
w := new(tabwriter.Writer)
|
||||||
|
w.Init(os.Stdout, 0, 8, 1, '\t', 0)
|
||||||
|
csv := resultToCSV(result, "\t", true)
|
||||||
|
for _, r := range csv {
|
||||||
|
fmt.Fprintln(w, r)
|
||||||
|
}
|
||||||
|
w.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resultToCSV(result *client.Result, seperator string, headerLines bool) []string {
|
||||||
|
rows := []string{}
|
||||||
|
// Create a tabbed writer for each result a they won't always line up
|
||||||
|
columnNames := []string{"name", "tags"}
|
||||||
|
|
||||||
|
for i, row := range result.Rows {
|
||||||
|
// Output the column headings
|
||||||
|
if i == 0 {
|
||||||
|
for _, column := range row.Columns {
|
||||||
|
columnNames = append(columnNames, column)
|
||||||
|
}
|
||||||
|
rows = append(rows, strings.Join(columnNames, seperator))
|
||||||
|
}
|
||||||
|
if headerLines {
|
||||||
|
// create column underscores
|
||||||
|
lines := []string{}
|
||||||
|
for _, columnName := range columnNames {
|
||||||
|
lines = append(lines, strings.Repeat("-", len(columnName)))
|
||||||
|
}
|
||||||
|
rows = append(rows, strings.Join(lines, seperator))
|
||||||
|
}
|
||||||
|
// gather tags
|
||||||
|
tags := []string{}
|
||||||
|
for k, v := range row.Tags {
|
||||||
|
tags = append(tags, fmt.Sprintf("%s=%s", k, v))
|
||||||
|
}
|
||||||
|
for _, v := range row.Values {
|
||||||
|
values := []string{row.Name}
|
||||||
|
values = append(values, strings.Join(tags, ","))
|
||||||
|
|
||||||
|
for _, vv := range v {
|
||||||
|
values = append(values, interfaceToString(vv))
|
||||||
|
}
|
||||||
|
rows = append(rows, strings.Join(values, seperator))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
|
||||||
|
func interfaceToString(v interface{}) string {
|
||||||
|
switch t := v.(type) {
|
||||||
|
case nil:
|
||||||
|
return ""
|
||||||
|
case bool:
|
||||||
|
return fmt.Sprintf("%v", v)
|
||||||
|
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr:
|
||||||
|
return fmt.Sprintf("%d", t)
|
||||||
|
case float32, float64:
|
||||||
|
return fmt.Sprintf("%v", t)
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("%v", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommandLine) Settings() {
|
||||||
|
w := new(tabwriter.Writer)
|
||||||
|
w.Init(os.Stdout, 0, 8, 1, '\t', 0)
|
||||||
|
if c.Port > 0 {
|
||||||
|
fmt.Fprintf(w, "Host\t%s:%d\n", c.Host, c.Port)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(w, "Host\t%s\n", c.Host)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "Username\t%s\n", c.Username)
|
||||||
|
fmt.Fprintf(w, "Database\t%s\n", c.Database)
|
||||||
|
fmt.Fprintf(w, "Pretty\t%v\n", c.Pretty)
|
||||||
|
fmt.Fprintf(w, "Format\t%s\n", c.Format)
|
||||||
|
fmt.Fprintln(w)
|
||||||
|
w.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
func help() {
|
func help() {
|
||||||
|
@ -205,6 +348,8 @@ func help() {
|
||||||
connect <host:port> connect to another node
|
connect <host:port> connect to another node
|
||||||
pretty toggle pretty print
|
pretty toggle pretty print
|
||||||
use <db_name> set current databases
|
use <db_name> set current databases
|
||||||
|
format <format> set the output format: json, csv, or column
|
||||||
|
settings output the current settings for the shell
|
||||||
exit quit the influx shell
|
exit quit the influx shell
|
||||||
|
|
||||||
show databases show database names
|
show databases show database names
|
||||||
|
|
Loading…
Reference in New Issue