diff --git a/api/test/window_post.go b/api/test/window_post.go index 1c5c201d9..a31d17825 100644 --- a/api/test/window_post.go +++ b/api/test/window_post.go @@ -4,7 +4,6 @@ import ( "context" "fmt" - "os" "strings" "testing" @@ -81,7 +80,7 @@ func pledgeSectors(t *testing.T, ctx context.Context, miner TestStorageNode, n, s, err := miner.SectorsList(ctx) // Note - the test builder doesn't import genesis sectors into FSM require.NoError(t, err) fmt.Printf("Sectors: %d\n", len(s)) - if len(s) >= n + existing { + if len(s) >= n+existing { break } diff --git a/cli/client.go b/cli/client.go index ab30df6b8..f2cf72f97 100644 --- a/cli/client.go +++ b/cli/client.go @@ -30,6 +30,7 @@ import ( lapi "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/lib/tablewriter" ) var CidBaseFlag = cli.StringFlag{ @@ -986,8 +987,8 @@ var clientListDeals = &cli.Command{ color := cctx.Bool("color") - w := tabwriter.NewWriter(os.Stdout, 2, 4, 2, ' ', 0) if cctx.Bool("verbose") { + w := tabwriter.NewWriter(os.Stdout, 2, 4, 2, ' ', 0) fmt.Fprintf(w, "DealCid\tDealId\tProvider\tState\tOn Chain?\tSlashed?\tPieceCID\tSize\tPrice\tDuration\tMessage\n") for _, d := range deals { onChain := "N" @@ -1003,8 +1004,19 @@ var clientListDeals = &cli.Command{ price := types.FIL(types.BigMul(d.LocalDeal.PricePerEpoch, types.NewInt(d.LocalDeal.Duration))) fmt.Fprintf(w, "%s\t%d\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%d\t%s\n", d.LocalDeal.ProposalCid, d.LocalDeal.DealID, d.LocalDeal.Provider, dealStateString(color, d.LocalDeal.State), onChain, slashed, d.LocalDeal.PieceCID, types.SizeStr(types.NewInt(d.LocalDeal.Size)), price, d.LocalDeal.Duration, d.LocalDeal.Message) } + return w.Flush() } else { - fmt.Fprintf(w, "DealCid\tDealId\tProvider\tState\tOn Chain?\tSlashed?\tPieceCID\tSize\tPrice\tDuration\n") + w := tablewriter.New(tablewriter.Col("DealCid"), + tablewriter.Col("DealId"), + tablewriter.Col("Provider"), + tablewriter.Col("State"), + tablewriter.Col("On Chain?"), + tablewriter.Col("Slashed?"), + tablewriter.Col("PieceCID"), + tablewriter.Col("Size"), + tablewriter.Col("Price"), + tablewriter.Col("Duration"), + tablewriter.NewLineCol("Message")) for _, d := range deals { propcid := d.LocalDeal.ProposalCid.String() @@ -1024,11 +1036,24 @@ var clientListDeals = &cli.Command{ piece = "..." + piece[len(piece)-8:] price := types.FIL(types.BigMul(d.LocalDeal.PricePerEpoch, types.NewInt(d.LocalDeal.Duration))) - fmt.Fprintf(w, "%s\t%d\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%d\n", propcid, d.LocalDeal.DealID, d.LocalDeal.Provider, dealStateString(color, d.LocalDeal.State), onChain, slashed, piece, types.SizeStr(types.NewInt(d.LocalDeal.Size)), price, d.LocalDeal.Duration) + + w.Write(map[string]interface{}{ + "DealCid": propcid, + "DealId": d.LocalDeal.DealID, + "Provider": d.LocalDeal.Provider, + "State": dealStateString(color, d.LocalDeal.State), + "On Chain?": onChain, + "Slashed?": slashed, + "PieceCID": piece, + "Size": types.SizeStr(types.NewInt(d.LocalDeal.Size)), + "Price": price, + "Duration": d.LocalDeal.Duration, + "Message": d.LocalDeal.Message, + }) } + return w.Flush(os.Stdout) } - return w.Flush() }, } diff --git a/go.mod b/go.mod index d9c5df181..b6d92701a 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/GeertJohan/go.rice v1.0.0 github.com/Gurpartap/async v0.0.0-20180927173644-4f7f499dd9ee github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect + github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d github.com/coreos/go-systemd/v22 v22.0.0 github.com/dgraph-io/badger/v2 v2.0.3 github.com/docker/go-units v0.4.0 diff --git a/go.sum b/go.sum index b2fd03c3c..af8c4f2a2 100644 --- a/go.sum +++ b/go.sum @@ -58,6 +58,8 @@ github.com/Stebalien/go-bitfield v0.0.0-20180330043415-076a62f9ce6e/go.mod h1:3o github.com/Stebalien/go-bitfield v0.0.1 h1:X3kbSSPUaJK60wV2hjOPZwmpljr6VGCqdq4cBLhbQBo= github.com/Stebalien/go-bitfield v0.0.1/go.mod h1:GNjFpasyUVkHMsfEOk8EFLJ9syQ6SI+XWrX9Wf2XH0s= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/akavel/rsrc v0.8.0 h1:zjWn7ukO9Kc5Q62DOJCcxGpXC18RawVtYAGdz2aLlfw= diff --git a/lib/tablewriter/tablewiter_test.go b/lib/tablewriter/tablewiter_test.go new file mode 100644 index 000000000..9c5f9d37d --- /dev/null +++ b/lib/tablewriter/tablewiter_test.go @@ -0,0 +1,34 @@ +package tablewriter + +import ( + "os" + "testing" + + "github.com/fatih/color" +) + +func TestTableWriter(t *testing.T) { + tw := New(Col("C1"), Col("X"), Col("C333"), NewLineCol("Thing")) + tw.Write(map[string]interface{}{ + "C1": "234", + "C333": "ou", + }) + tw.Write(map[string]interface{}{ + "C1": "23uieui4", + "C333": "ou", + "X": color.GreenString("#"), + "Thing": "a very long thing, annoyingly so", + }) + tw.Write(map[string]interface{}{ + "C1": "ttttttttt", + "C333": "eui", + }) + tw.Write(map[string]interface{}{ + "C1": "1", + "C333": "2", + "SurpriseColumn": "42", + }) + if err := tw.Flush(os.Stdout); err != nil { + t.Fatal(err) + } +} diff --git a/lib/tablewriter/tablewriter.go b/lib/tablewriter/tablewriter.go new file mode 100644 index 000000000..d77611390 --- /dev/null +++ b/lib/tablewriter/tablewriter.go @@ -0,0 +1,129 @@ +package tablewriter + +import ( + "fmt" + "io" + "strings" + "unicode/utf8" + + "github.com/acarl005/stripansi" +) + +type Column struct { + Name string + SeparateLine bool +} + +type TableWriter struct { + cols []Column + rows []map[int]string +} + +func Col(name string) Column { + return Column{ + Name: name, + SeparateLine: false, + } +} + +func NewLineCol(name string) Column { + return Column{ + Name: name, + SeparateLine: true, + } +} + +// Unlike text/tabwriter, this works with CLI escape codes, and allows for info +// in separate lines +func New(cols ...Column) *TableWriter { + return &TableWriter{ + cols: cols, + } +} + +func (w *TableWriter) Write(r map[string]interface{}) { + // this can cause columns to be out of order, but will at least work + byColID := map[int]string{} + +cloop: + for col, val := range r { + for i, column := range w.cols { + if column.Name == col { + byColID[i] = fmt.Sprint(val) + continue cloop + } + } + + byColID[len(w.cols)] = fmt.Sprint(val) + w.cols = append(w.cols, Column{ + Name: col, + SeparateLine: false, + }) + } + + w.rows = append(w.rows, byColID) +} + +func (w *TableWriter) Flush(out io.Writer) error { + colLengths := make([]int, len(w.cols)) + + header := map[int]string{} + for i, col := range w.cols { + if col.SeparateLine { + continue + } + header[i] = col.Name + } + + w.rows = append([]map[int]string{header}, w.rows...) + + for col := range w.cols { + for _, row := range w.rows { + val, found := row[col] + if !found { + continue + } + + if cliStringLength(val) > colLengths[col] { + colLengths[col] = cliStringLength(val) + } + } + } + + for _, row := range w.rows { + cols := make([]string, len(w.cols)) + + for ci, col := range w.cols { + e, _ := row[ci] + pad := colLengths[ci] - cliStringLength(e) + 2 + if !col.SeparateLine { + e = e + strings.Repeat(" ", pad) + if _, err := fmt.Fprint(out, e); err != nil { + return err + } + } + + cols[ci] = e + } + + if _, err := fmt.Fprintln(out); err != nil { + return err + } + + for ci, col := range w.cols { + if !col.SeparateLine || len(cols[ci]) == 0 { + continue + } + + if _, err := fmt.Fprintf(out, " %s: %s\n", col.Name, cols[ci]); err != nil { + return err + } + } + } + + return nil +} + +func cliStringLength(s string) (n int) { + return utf8.RuneCountInString(stripansi.Strip(s)) +}