Skip to content
This repository was archived by the owner on Dec 16, 2025. It is now read-only.

Commit 3c97b58

Browse files
committed
Add column filter option to holdings command
1 parent 6e3b9d4 commit 3c97b58

3 files changed

Lines changed: 131 additions & 38 deletions

File tree

cmd/commands/holdings.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@ func HoldingsCmd() *cobra.Command {
1212
var help bool
1313
var total bool
1414
var noCache bool
15+
var noHeader bool
1516
var config string
1617
var sortBy string
1718
var sortDesc bool
1819
var format string = "table"
1920
var humanReadable bool
2021
var filter []string
22+
var cols []string
2123
var convert string
2224

2325
holdingsCmd := &cobra.Command{
@@ -52,7 +54,9 @@ func HoldingsCmd() *cobra.Command {
5254
HumanReadable: humanReadable,
5355
Format: format,
5456
Filter: filter,
57+
Cols: cols,
5558
Convert: convert,
59+
NoHeader: noHeader,
5660
})
5761
},
5862
}
@@ -61,11 +65,13 @@ func HoldingsCmd() *cobra.Command {
6165
holdingsCmd.Flags().BoolVarP(&total, "total", "t", total, "Show total only")
6266
holdingsCmd.Flags().BoolVarP(&noCache, "no-cache", "", noCache, "No cache")
6367
holdingsCmd.Flags().BoolVarP(&humanReadable, "human", "h", humanReadable, "Human readable output")
68+
holdingsCmd.Flags().BoolVarP(&noHeader, "no-header", "", noHeader, "Don't display header columns")
6469
holdingsCmd.Flags().StringVarP(&config, "config", "c", "", fmt.Sprintf("Config filepath. (default %s)", cointop.DefaultConfigFilepath))
6570
holdingsCmd.Flags().StringVarP(&sortBy, "sort-by", "s", sortBy, `Sort by column. Options are "name", "symbol", "price", "holdings", "balance", "24h"`)
6671
holdingsCmd.Flags().BoolVarP(&sortDesc, "sort-desc", "d", sortDesc, "Sort in descending order")
6772
holdingsCmd.Flags().StringVarP(&format, "format", "", format, `Ouput format. Options are "table", "csv", "json"`)
68-
holdingsCmd.Flags().StringSliceVarP(&filter, "filter", "", filter, `Filter portfolio entries by coin name or symbol, comma separated. Example: "btc,eth,doge"`)
73+
holdingsCmd.Flags().StringSliceVarP(&filter, "filter", "", filter, `Filter portfolio entries by coin name or symbol, comma separated without spaces. Example: "btc,eth,doge"`)
74+
holdingsCmd.Flags().StringSliceVarP(&cols, "cols", "", cols, `Filter portfolio columns, comma separated without spaces. Example: "symbol,holdings,balance"`)
6975
holdingsCmd.Flags().StringVarP(&convert, "convert", "f", convert, "The currency to convert to")
7076

7177
return holdingsCmd

cointop/portfolio.go

Lines changed: 104 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -591,7 +591,9 @@ type TablePrintOptions struct {
591591
HumanReadable bool
592592
Format string
593593
Filter []string
594+
Cols []string
594595
Convert string
596+
NoHeader bool
595597
}
596598

597599
// outputFormats is list of valid output formats
@@ -628,8 +630,10 @@ func (ct *Cointop) PrintHoldingsTable(options *TablePrintOptions) error {
628630
sortDesc := options.SortDesc
629631
format := options.Format
630632
humanReadable := options.HumanReadable
631-
filter := options.Filter
633+
filterCoins := options.Filter
634+
filterCols := options.Cols
632635
holdings := ct.GetPortfolioSlice()
636+
noHeader := options.NoHeader
633637

634638
if format == "" {
635639
format = "table"
@@ -651,10 +655,41 @@ func (ct *Cointop) PrintHoldingsTable(options *TablePrintOptions) error {
651655
records := make([][]string, len(holdings))
652656
symbol := ct.CurrencySymbol()
653657

658+
headers := []string{"name", "symbol", "price", "holdings", "balance", "24h%", "%holdings"}
659+
if len(filterCols) > 0 {
660+
for _, col := range filterCols {
661+
valid := false
662+
for _, h := range headers {
663+
if col == h {
664+
valid = true
665+
break
666+
}
667+
}
668+
switch col {
669+
case "amount":
670+
return fmt.Errorf("did you mean %q?", "balance")
671+
case "24H":
672+
fallthrough
673+
case "24H%":
674+
fallthrough
675+
case "24h":
676+
fallthrough
677+
case "24h_change":
678+
return fmt.Errorf("did you mean %q?", "24h%")
679+
case "percent_holdings":
680+
return fmt.Errorf("did you mean %q?", "%holdings")
681+
}
682+
if !valid {
683+
return fmt.Errorf("unsupported column value %q", col)
684+
}
685+
}
686+
headers = filterCols
687+
}
688+
654689
for i, entry := range holdings {
655-
if len(filter) > 0 {
690+
if len(filterCoins) > 0 {
656691
found := false
657-
for _, item := range filter {
692+
for _, item := range filterCoins {
658693
item = strings.ToLower(strings.TrimSpace(item))
659694
if strings.ToLower(entry.Symbol) == item || strings.ToLower(entry.Name) == item {
660695
found = true
@@ -671,35 +706,54 @@ func (ct *Cointop) PrintHoldingsTable(options *TablePrintOptions) error {
671706
percentHoldings = 0
672707
}
673708

674-
if humanReadable {
675-
records[i] = []string{
676-
entry.Name,
677-
entry.Symbol,
678-
fmt.Sprintf("%s%s", symbol, humanize.Commaf(entry.Price)),
679-
humanize.Commaf(entry.Holdings),
680-
fmt.Sprintf("%s%s", symbol, humanize.Commaf(entry.Balance)),
681-
fmt.Sprintf("%.2f%%", entry.PercentChange24H),
682-
fmt.Sprintf("%.2f%%", percentHoldings),
683-
}
684-
} else {
685-
records[i] = []string{
686-
entry.Name,
687-
entry.Symbol,
688-
strconv.FormatFloat(entry.Price, 'f', -1, 64),
689-
strconv.FormatFloat(entry.Holdings, 'f', -1, 64),
690-
strconv.FormatFloat(entry.Balance, 'f', -1, 64),
691-
fmt.Sprintf("%.2f", entry.PercentChange24H),
692-
fmt.Sprintf("%.2f", percentHoldings),
709+
item := make([]string, len(headers))
710+
for i, header := range headers {
711+
switch header {
712+
case "name":
713+
item[i] = entry.Name
714+
case "symbol":
715+
item[i] = entry.Symbol
716+
case "price":
717+
if humanReadable {
718+
item[i] = fmt.Sprintf("%s%s", symbol, humanize.Commaf(entry.Price))
719+
} else {
720+
item[i] = strconv.FormatFloat(entry.Price, 'f', -1, 64)
721+
}
722+
case "holdings":
723+
if humanReadable {
724+
item[i] = humanize.Commaf(entry.Holdings)
725+
} else {
726+
item[i] = strconv.FormatFloat(entry.Holdings, 'f', -1, 64)
727+
}
728+
case "balance":
729+
if humanReadable {
730+
item[i] = fmt.Sprintf("%s%s", symbol, humanize.Commaf(entry.Balance))
731+
} else {
732+
item[i] = strconv.FormatFloat(entry.Balance, 'f', -1, 64)
733+
}
734+
case "24h%":
735+
if humanReadable {
736+
item[i] = fmt.Sprintf("%.2f%%", entry.PercentChange24H)
737+
} else {
738+
item[i] = fmt.Sprintf("%.2f", entry.PercentChange24H)
739+
}
740+
case "%holdings":
741+
if humanReadable {
742+
item[i] = fmt.Sprintf("%.2f%%", percentHoldings)
743+
} else {
744+
item[i] = fmt.Sprintf("%.2f", percentHoldings)
745+
}
693746
}
694747
}
748+
records[i] = item
695749
}
696750

697-
headers := []string{"name", "symbol", "price", "holdings", "balance", "24h%", "%holdings"}
698-
699751
if format == "csv" {
700752
csvWriter := csv.NewWriter(os.Stdout)
701-
if err := csvWriter.Write(headers); err != nil {
702-
return err
753+
if !noHeader {
754+
if err := csvWriter.Write(headers); err != nil {
755+
return err
756+
}
703757
}
704758

705759
for _, record := range records {
@@ -715,29 +769,42 @@ func (ct *Cointop) PrintHoldingsTable(options *TablePrintOptions) error {
715769

716770
return nil
717771
} else if format == "json" {
718-
list := make([]map[string]string, len(records))
719-
for i, record := range records {
720-
obj := make(map[string]string, len(record))
721-
for j, column := range record {
722-
obj[headers[j]] = column
772+
var output []byte
773+
var err error
774+
if noHeader {
775+
output, err = json.Marshal(records)
776+
if err != nil {
777+
return err
723778
}
779+
} else {
780+
list := make([]map[string]string, len(records))
781+
for i, record := range records {
782+
obj := make(map[string]string, len(record))
783+
for j, column := range record {
784+
obj[headers[j]] = column
785+
}
724786

725-
list[i] = obj
726-
}
787+
list[i] = obj
788+
}
727789

728-
output, err := json.Marshal(list)
729-
if err != nil {
730-
return err
790+
output, err = json.Marshal(list)
791+
if err != nil {
792+
return err
793+
}
731794
}
732795

733796
fmt.Println(string(output))
734797
return nil
735798
}
736799

737800
alignment := []int{-1, -1, 1, 1, 1, 1, 1}
801+
var tableHeaders []string
802+
if !noHeader {
803+
tableHeaders = headers
804+
}
738805
table := asciitable.NewAsciiTable(&asciitable.Input{
739806
Data: records,
740-
Headers: headers,
807+
Headers: tableHeaders,
741808
Alignment: alignment,
742809
})
743810

docs/content/portfolio.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,19 @@ Ethereum ETH 394.48 100 39448 -0.18
8787

8888
```bash
8989
$ cointop holdings --cols symbol,holdings,balance
90+
symbol holdings balance
91+
BTC 10 118331.6
92+
ETH 100 39490
93+
DOGE 500000 1779.3
94+
```
95+
96+
### Output without headers
97+
98+
```bash
99+
$ cointop holdings --no-header
100+
Bitcoin BTC $11,833.16 10 $118,331.6 -1.02% 74.14%
101+
Ethereum ETH $394.9 100 $39,490 0.02% 24.74%
102+
Dogecoin DOGE $0.00355861 500,000 $1,779.3 1.46% 1.11%
90103
```
91104

92105
### Convert to a different fiat currency
@@ -97,6 +110,13 @@ $ cointop holdings -h --convert eur
97110
Ethereum ETH €278.49 100 €27,849 -15.87% 100.00%
98111
```
99112

113+
### Total portfolio value
114+
115+
```bash
116+
$ cointop holdings --total
117+
3671.32
118+
```
119+
100120
### Combining flags
101121

102122
```bash

0 commit comments

Comments
 (0)