Implement alt-link and bind it to ^o

pull/284/head
Simon Roberts 2 years ago
parent fdc9664842
commit 9cc10ccdd0
No known key found for this signature in database
GPG Key ID: 0F30F99E6B771FD4

@ -95,6 +95,7 @@ type State struct {
favoritesCompactNotation bool
portfolioCompactNotation bool
enableMouse bool
altCoinLinkCode string
}
// Cointop cointop
@ -195,6 +196,9 @@ var DefaultCompactNotation = false
// DefaultEnableMouse ...
var DefaultEnableMouse = true
// DefaultAltCoinLinkCode
var DefaultAltCoinLinkCode = "sprintf(\"https://www.tradingview.com/chart/?symbol=%sUSDT\", coin.Symbol)"
// DefaultMaxChartWidth ...
var DefaultMaxChartWidth = 175
@ -302,6 +306,7 @@ func NewCointop(config *Config) (*Cointop, error) {
},
compactNotation: DefaultCompactNotation,
enableMouse: DefaultEnableMouse,
altCoinLinkCode: DefaultAltCoinLinkCode,
tableCompactNotation: DefaultCompactNotation,
favoritesCompactNotation: DefaultCompactNotation,
portfolioCompactNotation: DefaultCompactNotation,
@ -412,9 +417,9 @@ func NewCointop(config *Config) (*Cointop, error) {
}
if ct.apiChoice == CoinMarketCap {
ct.api = api.NewCMC(ct.apiKeys.cmc)
ct.api = api.NewCMC(ct.apiKeys.cmc, ct.State.altCoinLinkCode)
} else if ct.apiChoice == CoinGecko {
ct.api = api.NewCG(perPage, maxPages)
ct.api = api.NewCG(perPage, maxPages, ct.State.altCoinLinkCode)
} else {
return nil, ErrInvalidAPIChoice
}

@ -50,6 +50,7 @@ type ConfigFileConfig struct {
CacheDir interface{} `toml:"cache_dir"`
CompactNotation interface{} `toml:"compact_notation"`
EnableMouse interface{} `toml:"enable_mouse"`
AltCoinLinkCode interface{} `toml:"alt_link_code"` // TODO: should really be in API-specific section
Table map[string]interface{} `toml:"table"`
Chart map[string]interface{} `toml:"chart"`
}
@ -74,6 +75,7 @@ func (ct *Cointop) SetupConfig() error {
ct.loadCacheDirFromConfig,
ct.loadCompactNotationFromConfig,
ct.loadEnableMouseFromConfig,
ct.loadAltCoinLinkCodeFromConfig,
ct.loadPriceAlertsFromConfig,
ct.loadPortfolioFromConfig,
}
@ -295,6 +297,7 @@ func (ct *Cointop) ConfigToToml() ([]byte, error) {
Chart: chartMapIfc,
CompactNotation: ct.State.compactNotation,
EnableMouse: ct.State.enableMouse,
AltCoinLinkCode: ct.State.altCoinLinkCode,
}
var b bytes.Buffer
@ -522,6 +525,16 @@ func (ct *Cointop) loadEnableMouseFromConfig() error {
return nil
}
// loadCompactNotationFromConfig loads compact-notation setting from config file to struct
func (ct *Cointop) loadAltCoinLinkCodeFromConfig() error {
log.Debug("loadAltCoinLinkCodeFromConfig()")
if altCoinLinkCode, ok := ct.config.AltCoinLinkCode.(string); ok {
ct.State.altCoinLinkCode = altCoinLinkCode
}
return nil
}
// LoadAPIChoiceFromConfig loads API choices from config file to struct
func (ct *Cointop) loadAPIChoiceFromConfig() error {
log.Debug("loadAPIKeysFromConfig()")

@ -20,6 +20,7 @@ func DefaultShortcuts() map[string]string {
"ctrl+d": "page_down",
"ctrl+f": "open_search",
"ctrl+n": "next_page",
"ctrl+o": "open_alt_link",
"ctrl+p": "previous_page",
"ctrl+r": "refresh",
"ctrl+R": "refresh",

@ -20,9 +20,9 @@ func PrintBitcoinDominance(config *DominanceConfig) error {
var coinAPI api.Interface
if config.APIChoice == CoinMarketCap {
coinAPI = api.NewCMC("")
coinAPI = api.NewCMC("", DefaultAltCoinLinkCode)
} else if config.APIChoice == CoinGecko {
coinAPI = api.NewCG(0, 0)
coinAPI = api.NewCG(0, 0, DefaultAltCoinLinkCode)
} else {
return ErrInvalidAPIChoice
}

@ -130,6 +130,8 @@ func (ct *Cointop) SetKeybindingAction(shortcutKey string, action string) error
fn = ct.Keyfn(ct.NavigateLastLine)
case "open_link":
fn = ct.Keyfn(ct.OpenLink)
case "open_alt_link":
fn = ct.Keyfn(ct.OpenAltLink)
case "refresh":
fn = ct.Keyfn(ct.Refresh)
case "sort_column_asc":

@ -55,9 +55,9 @@ func GetCoinPrices(config *PricesConfig) ([]string, error) {
}
var priceAPI api.Interface
if config.APIChoice == CoinMarketCap {
priceAPI = api.NewCMC("")
priceAPI = api.NewCMC("", DefaultAltCoinLinkCode)
} else if config.APIChoice == CoinGecko {
priceAPI = api.NewCG(0, 0)
priceAPI = api.NewCG(0, 0, DefaultAltCoinLinkCode)
} else {
return nil, ErrInvalidAPIChoice
}

@ -2,6 +2,7 @@ package cointop
import (
"fmt"
apitypes "github.com/cointop-sh/cointop/pkg/api/types"
"net/url"
"strings"
@ -209,6 +210,25 @@ func (ct *Cointop) RowLink() string {
return ct.api.CoinLink(coin.Slug)
}
// RowLink returns the row url link
func (ct *Cointop) RowAltLink() string {
log.Debug("RowAltLink()")
coin := ct.HighlightedRowCoin()
if coin == nil {
return ""
}
apiCoin := apitypes.Coin{
ID: coin.ID,
Name: coin.Name,
Symbol: coin.Symbol,
Rank: coin.Rank,
Slug: coin.Slug,
}
return ct.api.AltCoinLink(apiCoin)
}
// RowLinkShort returns a shortened version of the row url link
func (ct *Cointop) RowLinkShort() string {
log.Debug("RowLinkShort()")

@ -19,6 +19,13 @@ func (ct *Cointop) OpenLink() error {
return nil
}
// OpenLink opens the alternate url in a browser
func (ct *Cointop) OpenAltLink() error {
log.Debug("OpenAltLink()")
open.URL(ct.RowAltLink())
return nil
}
// GetBytes returns the interface in bytes form
func GetBytes(key interface{}) ([]byte, error) {
var buf bytes.Buffer

@ -6,8 +6,8 @@ import (
)
// NewCMC new CoinMarketCap API
func NewCMC(apiKey string) Interface {
return cmc.NewCMC(apiKey)
func NewCMC(apiKey string, altCoinLinkCode string) Interface {
return cmc.NewCMC(apiKey, altCoinLinkCode)
}
// NewCC new CryptoCompare API
@ -16,9 +16,10 @@ func NewCC() {
}
// NewCG new CoinGecko API
func NewCG(perPage, maxPages uint) Interface {
func NewCG(perPage, maxPages uint, altCoinLinkCode string) Interface {
return cg.NewCoinGecko(&cg.Config{
PerPage: perPage,
MaxPages: maxPages,
PerPage: perPage,
MaxPages: maxPages,
AltCoinLinkCode: altCoinLinkCode,
})
}

@ -14,6 +14,8 @@ import (
gecko "github.com/cointop-sh/cointop/pkg/api/vendors/coingecko/v3"
"github.com/cointop-sh/cointop/pkg/api/vendors/coingecko/v3/types"
geckoTypes "github.com/cointop-sh/cointop/pkg/api/vendors/coingecko/v3/types"
"github.com/cointop-sh/cointop/pkg/eval"
log "github.com/sirupsen/logrus"
)
// ErrPingFailed is the error for when pinging the API fails
@ -24,8 +26,9 @@ var ErrNotFound = errors.New("not found")
// Config config
type Config struct {
PerPage uint
MaxPages uint
PerPage uint
MaxPages uint
AltCoinLinkCode string
}
// Service service
@ -33,6 +36,7 @@ type Service struct {
client *gecko.Client
maxResultsPerPage uint
maxPages uint
altCoinLinkCode string
cacheMap sync.Map
cachedRates *types.ExchangeRatesItem
}
@ -57,6 +61,7 @@ func NewCoinGecko(config *Config) *Service {
client: client,
maxResultsPerPage: uint(math.Min(float64(maxResults), float64(maxResultsPerPage))),
maxPages: maxPages,
altCoinLinkCode: config.AltCoinLinkCode,
cacheMap: sync.Map{},
}
svc.cacheCoinsIDList()
@ -272,6 +277,23 @@ func (s *Service) CoinLink(slug string) string {
return fmt.Sprintf("https://www.coingecko.com/en/coins/%s", slug)
}
func (s *Service) AltCoinLink(coin apitypes.Coin) string {
if s.altCoinLinkCode == "" {
return s.CoinLink(coin.Slug)
}
env := map[string]interface{}{
"sprintf": fmt.Sprintf,
"coin": coin,
}
if s, err := eval.EvaluateExpressionToString(s.altCoinLinkCode, env); err == nil {
return s
} else {
log.Warnf("Error evaluating AltCoinLink: %v", err)
return ""
}
}
// SupportedCurrencies returns a list of supported currencies
func (s *Service) SupportedCurrencies() []string {
// keep these in alphabetical order

@ -4,6 +4,8 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/cointop-sh/cointop/pkg/eval"
log "github.com/sirupsen/logrus"
"io/ioutil"
"net/http"
"os"
@ -28,11 +30,12 @@ var ErrFetchGraphData = errors.New("graph data fetch error")
// Service service
type Service struct {
client *cmc.Client
client *cmc.Client
altCoinLinkCode string
}
// NewCMC new service
func NewCMC(apiKey string) *Service {
func NewCMC(apiKey string, altCoinLinkCode string) *Service {
if apiKey == "" {
apiKey = os.Getenv("CMC_PRO_API_KEY")
}
@ -40,7 +43,8 @@ func NewCMC(apiKey string) *Service {
ProAPIKey: apiKey,
})
return &Service{
client: client,
client: client,
altCoinLinkCode: altCoinLinkCode,
}
}
@ -337,6 +341,24 @@ func (s *Service) CoinLink(slug string) string {
return fmt.Sprintf("https://coinmarketcap.com/currencies/%s/", slug)
}
func (s *Service) AltCoinLink(coin apitypes.Coin) string {
if s.altCoinLinkCode == "" {
return s.CoinLink(coin.Slug)
}
// code := `sprintf("https://www.coingecko.com/en/coins/%s", coin.Slug)`
env := map[string]interface{}{
"sprintf": fmt.Sprintf,
"coin": coin,
}
if s, err := eval.EvaluateExpressionToString(s.altCoinLinkCode, env); err == nil {
return s
} else {
log.Warnf("Error evaluating AltCoinLink: %v", err)
return ""
}
}
// SupportedCurrencies returns a list of supported currencies
func (s *Service) SupportedCurrencies() []string {
// keep these in alphabetical order

@ -14,6 +14,7 @@ type Interface interface {
GetCoinData(name string, convert string) (types.Coin, error)
GetCoinDataBatch(names []string, convert string) ([]types.Coin, error)
CoinLink(slug string) string
AltCoinLink(coin types.Coin) string
SupportedCurrencies() []string
Price(name string, convert string) (float64, error)
GetExchangeRate(convertFrom, convertTo string, cached bool) (float64, error) // I don't love this caching

@ -43,3 +43,23 @@ func EvaluateExpressionToFloat64(input string, env interface{}) (float64, error)
}
return f64, nil
}
func EvaluateExpressionToString(input string, env interface{}) (string, error) {
input = strings.TrimSpace(input)
if input == "" {
return "", nil
}
program, err := expr.Compile(input, expr.Env(env))
if err != nil {
return "", err
}
result, err := expr.Run(program, env)
if err != nil {
return "", err
}
s, ok := result.(string)
if !ok {
return "", errors.New("expression did not return string type")
}
return s, nil
}

Loading…
Cancel
Save