From 3c97b58e09bc0206e344fdabea3fece65039a495 Mon Sep 17 00:00:00 2001 From: Miguel Mota Date: Sun, 18 Apr 2021 12:29:28 -0700 Subject: [PATCH] Add column filter option to holdings command --- cmd/commands/holdings.go | 8 ++- cointop/portfolio.go | 143 ++++++++++++++++++++++++++++---------- docs/content/portfolio.md | 20 ++++++ 3 files changed, 132 insertions(+), 39 deletions(-) diff --git a/cmd/commands/holdings.go b/cmd/commands/holdings.go index bdc90aa..d56e16e 100644 --- a/cmd/commands/holdings.go +++ b/cmd/commands/holdings.go @@ -12,12 +12,14 @@ func HoldingsCmd() *cobra.Command { var help bool var total bool var noCache bool + var noHeader bool var config string var sortBy string var sortDesc bool var format string = "table" var humanReadable bool var filter []string + var cols []string var convert string holdingsCmd := &cobra.Command{ @@ -52,7 +54,9 @@ func HoldingsCmd() *cobra.Command { HumanReadable: humanReadable, Format: format, Filter: filter, + Cols: cols, Convert: convert, + NoHeader: noHeader, }) }, } @@ -61,11 +65,13 @@ func HoldingsCmd() *cobra.Command { holdingsCmd.Flags().BoolVarP(&total, "total", "t", total, "Show total only") holdingsCmd.Flags().BoolVarP(&noCache, "no-cache", "", noCache, "No cache") holdingsCmd.Flags().BoolVarP(&humanReadable, "human", "h", humanReadable, "Human readable output") + holdingsCmd.Flags().BoolVarP(&noHeader, "no-header", "", noHeader, "Don't display header columns") holdingsCmd.Flags().StringVarP(&config, "config", "c", "", fmt.Sprintf("Config filepath. (default %s)", cointop.DefaultConfigFilepath)) holdingsCmd.Flags().StringVarP(&sortBy, "sort-by", "s", sortBy, `Sort by column. Options are "name", "symbol", "price", "holdings", "balance", "24h"`) holdingsCmd.Flags().BoolVarP(&sortDesc, "sort-desc", "d", sortDesc, "Sort in descending order") holdingsCmd.Flags().StringVarP(&format, "format", "", format, `Ouput format. Options are "table", "csv", "json"`) - holdingsCmd.Flags().StringSliceVarP(&filter, "filter", "", filter, `Filter portfolio entries by coin name or symbol, comma separated. Example: "btc,eth,doge"`) + holdingsCmd.Flags().StringSliceVarP(&filter, "filter", "", filter, `Filter portfolio entries by coin name or symbol, comma separated without spaces. Example: "btc,eth,doge"`) + holdingsCmd.Flags().StringSliceVarP(&cols, "cols", "", cols, `Filter portfolio columns, comma separated without spaces. Example: "symbol,holdings,balance"`) holdingsCmd.Flags().StringVarP(&convert, "convert", "f", convert, "The currency to convert to") return holdingsCmd diff --git a/cointop/portfolio.go b/cointop/portfolio.go index 7a3f0a6..b398fae 100644 --- a/cointop/portfolio.go +++ b/cointop/portfolio.go @@ -591,7 +591,9 @@ type TablePrintOptions struct { HumanReadable bool Format string Filter []string + Cols []string Convert string + NoHeader bool } // outputFormats is list of valid output formats @@ -628,8 +630,10 @@ func (ct *Cointop) PrintHoldingsTable(options *TablePrintOptions) error { sortDesc := options.SortDesc format := options.Format humanReadable := options.HumanReadable - filter := options.Filter + filterCoins := options.Filter + filterCols := options.Cols holdings := ct.GetPortfolioSlice() + noHeader := options.NoHeader if format == "" { format = "table" @@ -651,10 +655,41 @@ func (ct *Cointop) PrintHoldingsTable(options *TablePrintOptions) error { records := make([][]string, len(holdings)) symbol := ct.CurrencySymbol() + headers := []string{"name", "symbol", "price", "holdings", "balance", "24h%", "%holdings"} + if len(filterCols) > 0 { + for _, col := range filterCols { + valid := false + for _, h := range headers { + if col == h { + valid = true + break + } + } + switch col { + case "amount": + return fmt.Errorf("did you mean %q?", "balance") + case "24H": + fallthrough + case "24H%": + fallthrough + case "24h": + fallthrough + case "24h_change": + return fmt.Errorf("did you mean %q?", "24h%") + case "percent_holdings": + return fmt.Errorf("did you mean %q?", "%holdings") + } + if !valid { + return fmt.Errorf("unsupported column value %q", col) + } + } + headers = filterCols + } + for i, entry := range holdings { - if len(filter) > 0 { + if len(filterCoins) > 0 { found := false - for _, item := range filter { + for _, item := range filterCoins { item = strings.ToLower(strings.TrimSpace(item)) if strings.ToLower(entry.Symbol) == item || strings.ToLower(entry.Name) == item { found = true @@ -671,35 +706,54 @@ func (ct *Cointop) PrintHoldingsTable(options *TablePrintOptions) error { percentHoldings = 0 } - if humanReadable { - records[i] = []string{ - entry.Name, - entry.Symbol, - fmt.Sprintf("%s%s", symbol, humanize.Commaf(entry.Price)), - humanize.Commaf(entry.Holdings), - fmt.Sprintf("%s%s", symbol, humanize.Commaf(entry.Balance)), - fmt.Sprintf("%.2f%%", entry.PercentChange24H), - fmt.Sprintf("%.2f%%", percentHoldings), - } - } else { - records[i] = []string{ - entry.Name, - entry.Symbol, - strconv.FormatFloat(entry.Price, 'f', -1, 64), - strconv.FormatFloat(entry.Holdings, 'f', -1, 64), - strconv.FormatFloat(entry.Balance, 'f', -1, 64), - fmt.Sprintf("%.2f", entry.PercentChange24H), - fmt.Sprintf("%.2f", percentHoldings), + item := make([]string, len(headers)) + for i, header := range headers { + switch header { + case "name": + item[i] = entry.Name + case "symbol": + item[i] = entry.Symbol + case "price": + if humanReadable { + item[i] = fmt.Sprintf("%s%s", symbol, humanize.Commaf(entry.Price)) + } else { + item[i] = strconv.FormatFloat(entry.Price, 'f', -1, 64) + } + case "holdings": + if humanReadable { + item[i] = humanize.Commaf(entry.Holdings) + } else { + item[i] = strconv.FormatFloat(entry.Holdings, 'f', -1, 64) + } + case "balance": + if humanReadable { + item[i] = fmt.Sprintf("%s%s", symbol, humanize.Commaf(entry.Balance)) + } else { + item[i] = strconv.FormatFloat(entry.Balance, 'f', -1, 64) + } + case "24h%": + if humanReadable { + item[i] = fmt.Sprintf("%.2f%%", entry.PercentChange24H) + } else { + item[i] = fmt.Sprintf("%.2f", entry.PercentChange24H) + } + case "%holdings": + if humanReadable { + item[i] = fmt.Sprintf("%.2f%%", percentHoldings) + } else { + item[i] = fmt.Sprintf("%.2f", percentHoldings) + } } } + records[i] = item } - headers := []string{"name", "symbol", "price", "holdings", "balance", "24h%", "%holdings"} - if format == "csv" { csvWriter := csv.NewWriter(os.Stdout) - if err := csvWriter.Write(headers); err != nil { - return err + if !noHeader { + if err := csvWriter.Write(headers); err != nil { + return err + } } for _, record := range records { @@ -715,19 +769,28 @@ func (ct *Cointop) PrintHoldingsTable(options *TablePrintOptions) error { return nil } else if format == "json" { - list := make([]map[string]string, len(records)) - for i, record := range records { - obj := make(map[string]string, len(record)) - for j, column := range record { - obj[headers[j]] = column + var output []byte + var err error + if noHeader { + output, err = json.Marshal(records) + if err != nil { + return err + } + } else { + list := make([]map[string]string, len(records)) + for i, record := range records { + obj := make(map[string]string, len(record)) + for j, column := range record { + obj[headers[j]] = column + } + + list[i] = obj } - list[i] = obj - } - - output, err := json.Marshal(list) - if err != nil { - return err + output, err = json.Marshal(list) + if err != nil { + return err + } } fmt.Println(string(output)) @@ -735,9 +798,13 @@ func (ct *Cointop) PrintHoldingsTable(options *TablePrintOptions) error { } alignment := []int{-1, -1, 1, 1, 1, 1, 1} + var tableHeaders []string + if !noHeader { + tableHeaders = headers + } table := asciitable.NewAsciiTable(&asciitable.Input{ Data: records, - Headers: headers, + Headers: tableHeaders, Alignment: alignment, }) diff --git a/docs/content/portfolio.md b/docs/content/portfolio.md index 49f4c31..515c936 100644 --- a/docs/content/portfolio.md +++ b/docs/content/portfolio.md @@ -87,6 +87,19 @@ Ethereum ETH 394.48 100 39448 -0.18 ```bash $ cointop holdings --cols symbol,holdings,balance +symbol holdings balance +BTC 10 118331.6 +ETH 100 39490 +DOGE 500000 1779.3 +``` + +### Output without headers + +```bash +$ cointop holdings --no-header +Bitcoin BTC $11,833.16 10 $118,331.6 -1.02% 74.14% +Ethereum ETH $394.9 100 $39,490 0.02% 24.74% +Dogecoin DOGE $0.00355861 500,000 $1,779.3 1.46% 1.11% ``` ### Convert to a different fiat currency @@ -97,6 +110,13 @@ $ cointop holdings -h --convert eur Ethereum ETH €278.49 100 €27,849 -15.87% 100.00% ``` +### Total portfolio value + +```bash +$ cointop holdings --total +3671.32 +``` + ### Combining flags ```bash