package jsontable import ( "fmt" "io" "strings" "github.com/olekukonko/tablewriter" ) // A quick-and-dirty proxy/emulator of tablewriter to enable more easy machine readable output // - Does not strictly support types, just quoted or unquoted values // - Does not support nested values. // If a datalabel is set with SetDataLabel(true, "..."), that will be used as the key for teh data of the table, // otherwise if the caption is set with SetCaption(true, "..."), the data label will be set to the default of // "rows", otherwise the table will output as a JSON list. // // Proxys all actions through to the tablewriter except addrow and addbatch, which it does at render time // type JSONTable struct { out io.Writer colsize int rows [][]string keys []string quoted []bool // hack to do output typing, quoted vs. unquoted hasDataLabel bool dataLabel string hasCaption bool caption string // the actual caption hasCaptionLabel bool captionLabel string // the key in the dictionary for the caption tbl *tablewriter.Table } func writeChar(w io.Writer, c byte) { w.Write([]byte{c}) } func NewJSONTable(writer io.Writer) *JSONTable { t := &JSONTable{ out: writer, colsize: 0, rows: [][]string{}, keys: []string{}, quoted: []bool{}, hasDataLabel: false, dataLabel: "rows", hasCaption: false, caption: "", hasCaptionLabel: false, captionLabel: "caption", tbl: tablewriter.NewWriter(writer), } return t } func (t *JSONTable) NumLines() int { // JSON only but reflects a shared state. return len(t.rows) } func (t *JSONTable) SetHeader(keys []string) { // Set the keys value which will assign each column to the keys. // Note that we'll ignore values that are beyond the length of the keys list t.colsize = len(keys) t.keys = []string{} for _, k := range keys { t.keys = append(t.keys, k) t.quoted = append(t.quoted, true) } t.tbl.SetHeader(keys) } func (t *JSONTable) SetColumnQuoting(quoting []bool) { // Specify which columns are quoted or unquoted in output // JSON only for i := 0; i < t.colsize; i++ { t.quoted[i] = quoting[i] } } func (t *JSONTable) Append(row []string) { // We'll just append whatever to the rows list. If they fix the keys after appending rows, it'll work as // expected. // We should detect if the row is narrower than the key list tho. // JSON only (but we use the rows later when rendering a regular table) t.rows = append(t.rows, row) } func (t *JSONTable) Render() { // Load the table with rows and render. // Proxy only for _, row := range t.rows { t.tbl.Append(row) } t.tbl.Render() } func (t *JSONTable) _JSONRenderInner() { // JSON only // Render the list of dictionaries to the writer. //// inner render loop writeChar(t.out, '[') for rowidx, row := range t.rows { if rowidx != 0 { writeChar(t.out, ',') } writeChar(t.out, '{') for keyidx, key := range t.keys { key := strings.ToLower(key) key = strings.ReplaceAll(key, " ", "-") value := "nil" if keyidx < len(row) { value = row[keyidx] } if keyidx != 0 { writeChar(t.out, ',') } if t.quoted[keyidx] { fmt.Fprintf(t.out, "\"%s\":\"%s\"", key, value) } else { fmt.Fprintf(t.out, "\"%s\":%s", key, value) } } writeChar(t.out, '}') } writeChar(t.out, ']') } func (t *JSONTable) JSONRender() { // write JSON table to output // JSON only if t.hasDataLabel || t.hasCaption { // dict mode writeChar(t.out, '{') if t.hasCaption { fmt.Fprintf(t.out, "\"%s\":\"%s\",", t.captionLabel, t.caption) } fmt.Fprintf(t.out, "\"%s\":", t.dataLabel) } // write list t._JSONRenderInner() if t.hasDataLabel || t.hasCaption { // dict mode writeChar(t.out, '}') } } func (t *JSONTable) SetCaption(caption bool, captionText ...string) { t.hasCaption = caption if len(captionText) == 1 { t.caption = captionText[0] } t.tbl.SetCaption(caption, captionText...) } func (t *JSONTable) SetCaptionLabel(captionLabel bool, captionLabelText ...string) { // JSON only t.hasCaptionLabel = captionLabel if len(captionLabelText) == 1 { t.captionLabel = captionLabelText[0] } } func (t *JSONTable) SetDataLabel(dataLabel bool, dataLabelText ...string) { // JSON only t.hasDataLabel = dataLabel if len(dataLabelText) == 1 { t.dataLabel = dataLabelText[0] } } func (t *JSONTable) AppendBulk(rows [][]string) { // JSON only but reflects shared state for _, row := range rows { t.Append(row) } } // Stuff we should implement but we just proxy for now. func (t *JSONTable) SetAutoMergeCellsByColumnIndex(cols []int) { // FIXME t.tbl.SetAutoMergeCellsByColumnIndex(cols) } // Stuff we should implement but we just proxy for now. func (t *JSONTable) SetAlignment(align int) { // FIXME t.tbl.SetAlignment(align) } func (t *JSONTable) SetAutoMergeCells(auto bool) { // FIXME t.tbl.SetAutoMergeCells(auto) } // Stub functions func (t *JSONTable) SetAutoWrapText(auto bool) { t.tbl.SetAutoWrapText(auto) return }