mirror of https://github.com/Wox-launcher/Wox
feat(converter): Enhance conversion capabilities and add math module
- Updated the Converter to prioritize math module for complex expressions. - Improved conversion logic to allow all modules to attempt conversions, not just the original. - Added user default currency retrieval based on locale settings. - Enhanced handling of cryptocurrency conversions, including special cases for USD. - Introduced a new MathModule to handle percentage calculations and related operations. - Refactored locale detection to a dedicated utility for better maintainability. - Updated icon references in the Converter plugin.
This commit is contained in:
parent
4f67fa1c98
commit
64b39ce06d
|
@ -16,8 +16,17 @@ import (
|
|||
"wox/util/selection"
|
||||
)
|
||||
|
||||
func TestCalculatorCrypto(t *testing.T) {
|
||||
func TestConverterCrypto(t *testing.T) {
|
||||
tests := []queryTest{
|
||||
{
|
||||
name: "BTC shows equivalent value",
|
||||
query: "1BTC",
|
||||
expectedTitle: "",
|
||||
expectedAction: "Copy result",
|
||||
titleCheck: func(title string) bool {
|
||||
return len(title) > 1 && (strings.Contains(title, "$") || strings.Contains(title, "¥") || strings.Contains(title, "€") || strings.Contains(title, "£"))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "BTC to USD",
|
||||
query: "1BTC in USD",
|
||||
|
@ -30,10 +39,11 @@ func TestCalculatorCrypto(t *testing.T) {
|
|||
{
|
||||
name: "BTC plus USD",
|
||||
query: "1BTC + 1 USD",
|
||||
expectedTitle: "$",
|
||||
expectedTitle: "",
|
||||
expectedAction: "Copy result",
|
||||
titleCheck: func(title string) bool {
|
||||
return len(title) > 1 && strings.HasPrefix(title, "$") && title[1] >= '0' && title[1] <= '9'
|
||||
// Should convert to user's default currency when crypto is involved
|
||||
return len(title) > 1 && (strings.Contains(title, "$") || strings.Contains(title, "¥") || strings.Contains(title, "€"))
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -46,17 +56,18 @@ func TestCalculatorCrypto(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
name: "BTC + ETH",
|
||||
name: "BTC + ETH uses default currency",
|
||||
query: "1BTC + 1ETH",
|
||||
expectedTitle: "$",
|
||||
expectedTitle: "",
|
||||
expectedAction: "Copy result",
|
||||
titleCheck: func(title string) bool {
|
||||
return len(title) > 1 && strings.HasPrefix(title, "$")
|
||||
// Should convert to user's default currency when crypto is involved
|
||||
return len(title) > 1 && (strings.Contains(title, "$") || strings.Contains(title, "¥") || strings.Contains(title, "€"))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid crypto query",
|
||||
query: "1btc dsfsdf",
|
||||
query: "1btc dsfsdf1btc dsfsdf",
|
||||
expectedTitle: "Search for 1btc dsfsdf",
|
||||
expectedAction: "Search",
|
||||
},
|
||||
|
@ -70,8 +81,17 @@ func TestCalculatorCrypto(t *testing.T) {
|
|||
runQueryTests(t, tests)
|
||||
}
|
||||
|
||||
func TestCalculatorCurrency(t *testing.T) {
|
||||
func TestConverterCurrency(t *testing.T) {
|
||||
tests := []queryTest{
|
||||
{
|
||||
name: "Single Currency",
|
||||
query: "100USD",
|
||||
expectedTitle: "",
|
||||
expectedAction: "Copy result",
|
||||
titleCheck: func(title string) bool {
|
||||
return len(title) > 1 && (strings.Contains(title, "$") || strings.Contains(title, "¥") || strings.Contains(title, "€") || strings.Contains(title, "£"))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "USD to EUR",
|
||||
query: "100 USD in EUR",
|
||||
|
@ -99,6 +119,24 @@ func TestCalculatorCurrency(t *testing.T) {
|
|||
return len(title) > 1 && strings.HasPrefix(title, "¥") && title[len("¥")] >= '0' && title[len("¥")] <= '9'
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "complex convert",
|
||||
query: "12% of $321 in jpy",
|
||||
expectedTitle: "",
|
||||
expectedAction: "Copy result",
|
||||
titleCheck: func(title string) bool {
|
||||
return len(title) > 1 && (strings.Contains(title, "$") || strings.Contains(title, "¥") || strings.Contains(title, "€") || strings.Contains(title, "£"))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "complex crypto convert",
|
||||
query: "12% of 1btc in jpy",
|
||||
expectedTitle: "",
|
||||
expectedAction: "Copy result",
|
||||
titleCheck: func(title string) bool {
|
||||
return len(title) > 1 && strings.Contains(title, "¥")
|
||||
},
|
||||
},
|
||||
}
|
||||
runQueryTests(t, tests)
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -8,6 +8,7 @@ import (
|
|||
"wox/plugin/system/converter/core"
|
||||
"wox/plugin/system/converter/modules"
|
||||
"wox/util/clipboard"
|
||||
"wox/util/locale"
|
||||
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
@ -32,7 +33,7 @@ func (c *Converter) GetMetadata() plugin.Metadata {
|
|||
MinWoxVersion: "2.0.0",
|
||||
Runtime: "Go",
|
||||
Description: "Calculator for Wox",
|
||||
Icon: plugin.PluginCalculatorIcon.String(),
|
||||
Icon: plugin.PluginConverterIcon.String(),
|
||||
Entry: "",
|
||||
TriggerKeywords: []string{
|
||||
"*",
|
||||
|
@ -51,6 +52,10 @@ func (c *Converter) Init(ctx context.Context, initParams plugin.InitParams) {
|
|||
c.api = initParams.API
|
||||
|
||||
registry := core.NewModuleRegistry()
|
||||
|
||||
// Register math module first (highest priority for complex expressions)
|
||||
registry.Register(modules.NewMathModule(ctx, c.api))
|
||||
|
||||
registry.Register(modules.NewTimeModule(ctx, c.api))
|
||||
|
||||
currencyModule := modules.NewCurrencyModule(ctx, c.api)
|
||||
|
@ -126,11 +131,18 @@ func (c *Converter) parseExpression(ctx context.Context, tokens []core.Token) (r
|
|||
if targetUnit.Name != "" {
|
||||
for i := range results {
|
||||
if results[i].Unit.Type == targetUnit.Type {
|
||||
convertedValue, err := results[i].Module.Convert(ctx, results[i], targetUnit)
|
||||
if err != nil {
|
||||
return nil, nil, core.Unit{}, err
|
||||
// Try all modules for conversion, not just the original module
|
||||
var converted bool
|
||||
for _, module := range c.registry.Modules() {
|
||||
if convertedValue, err := module.Convert(ctx, results[i], targetUnit); err == nil {
|
||||
results[i] = convertedValue
|
||||
converted = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !converted {
|
||||
return nil, nil, core.Unit{}, fmt.Errorf("no module can convert %s to %s", results[i].Unit.Name, targetUnit.Name)
|
||||
}
|
||||
results[i] = convertedValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -146,14 +158,50 @@ func (c *Converter) calculateExpression(ctx context.Context, results []core.Resu
|
|||
return core.Result{}, fmt.Errorf("invalid expression: operators count (%d) does not match results count (%d) - 1", len(operators), len(results))
|
||||
}
|
||||
|
||||
// If there are no operators and only one value, just return it as is
|
||||
// If there are no operators and only one value, E.g. "100usd", "1btc"
|
||||
if len(operators) == 0 && len(results) == 1 {
|
||||
if targetUnit.Name == "" {
|
||||
c.api.Log(ctx, plugin.LogLevelDebug, fmt.Sprintf("No operators, No target unit, returning the only result: %s", results[0].DisplayValue))
|
||||
return results[0], nil
|
||||
} else {
|
||||
if results[0].Unit.Type == core.UnitTypeCrypto || results[0].Unit.Type == core.UnitTypeCurrency {
|
||||
defaultCurrency := GetUserDefaultCurrency()
|
||||
targetUnit = core.Unit{Name: defaultCurrency, Type: core.UnitTypeCurrency}
|
||||
c.api.Log(ctx, plugin.LogLevelDebug, fmt.Sprintf("Single crypto/currency value, using user's default currency: %s", defaultCurrency))
|
||||
} else {
|
||||
c.api.Log(ctx, plugin.LogLevelDebug, fmt.Sprintf("No operators, No target unit, returning the only result: %s", results[0].DisplayValue))
|
||||
return results[0], nil
|
||||
}
|
||||
}
|
||||
|
||||
if targetUnit.Name != "" {
|
||||
c.api.Log(ctx, plugin.LogLevelDebug, fmt.Sprintf("No operators, target unit is set, converting the only result: %s to %s", results[0].DisplayValue, targetUnit.Name))
|
||||
return results[0].Module.Convert(ctx, results[0], targetUnit)
|
||||
|
||||
// For crypto to currency conversion, handle it specially, E.g. "1btc to cny"
|
||||
if results[0].Unit.Type == core.UnitTypeCrypto && targetUnit.Type == core.UnitTypeCurrency {
|
||||
// First convert to USD
|
||||
usdResult, err := results[0].Module.Convert(ctx, results[0], core.UnitUSD)
|
||||
if err != nil {
|
||||
return core.Result{}, err
|
||||
}
|
||||
|
||||
// If target is USD, return USD result directly
|
||||
if targetUnit.Name == "USD" {
|
||||
return usdResult, nil
|
||||
}
|
||||
|
||||
// For other currencies, convert USD to target currency
|
||||
for _, module := range c.registry.Modules() {
|
||||
if convertedResult, err := module.Convert(ctx, usdResult, targetUnit); err == nil {
|
||||
return convertedResult, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try all modules for conversion, not just the original module
|
||||
for _, module := range c.registry.Modules() {
|
||||
if convertedResult, err := module.Convert(ctx, results[0], targetUnit); err == nil {
|
||||
return convertedResult, nil
|
||||
}
|
||||
}
|
||||
return core.Result{}, fmt.Errorf("no module can convert %s to %s", results[0].Unit.Name, targetUnit.Name)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -175,7 +223,8 @@ func (c *Converter) calculateExpression(ctx context.Context, results []core.Resu
|
|||
|
||||
targetUnit = core.Unit{Name: results[len(results)-1].Unit.Name, Type: core.UnitTypeTime}
|
||||
} else if allResultsAreCurrencyOrCrypto {
|
||||
targetUnit = core.UnitUSD
|
||||
defaultCurrency := GetUserDefaultCurrency()
|
||||
targetUnit = core.Unit{Name: defaultCurrency, Type: core.UnitTypeCurrency}
|
||||
} else {
|
||||
c.api.Log(ctx, plugin.LogLevelDebug, "No target unit, using USD as default")
|
||||
targetUnit = core.UnitUSD
|
||||
|
@ -186,6 +235,7 @@ func (c *Converter) calculateExpression(ctx context.Context, results []core.Resu
|
|||
for i := range results {
|
||||
if results[i].Unit.Type == core.UnitTypeCurrency || results[i].Unit.Type == core.UnitTypeCrypto {
|
||||
var err error
|
||||
// First convert to USD
|
||||
results[i], err = results[i].Module.Convert(ctx, results[i], core.UnitUSD)
|
||||
if err != nil {
|
||||
c.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("Failed to convert %s to USD: %v", results[i].DisplayValue, err))
|
||||
|
@ -216,17 +266,44 @@ func (c *Converter) calculateExpression(ctx context.Context, results []core.Resu
|
|||
result.DisplayValue = fmt.Sprintf("%s %s", result.RawValue.String(), targetUnit.Name)
|
||||
|
||||
// Convert the result to the target unit
|
||||
for _, module := range c.registry.Modules() {
|
||||
if convertedResult, err := module.Convert(ctx, result, targetUnit); err == nil {
|
||||
c.api.Log(ctx, plugin.LogLevelDebug, fmt.Sprintf("Converted result with module %s => displayValue=%s, rawValue=%s, unit=%s", module.Name(), convertedResult.DisplayValue, convertedResult.RawValue.String(), convertedResult.Unit.Name))
|
||||
result = convertedResult
|
||||
break
|
||||
if targetUnit.Name != result.Unit.Name {
|
||||
for _, module := range c.registry.Modules() {
|
||||
if convertedResult, err := module.Convert(ctx, result, targetUnit); err == nil {
|
||||
c.api.Log(ctx, plugin.LogLevelDebug, fmt.Sprintf("Converted result with module %s => displayValue=%s, rawValue=%s, unit=%s", module.Name(), convertedResult.DisplayValue, convertedResult.RawValue.String(), convertedResult.Unit.Name))
|
||||
result = convertedResult
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetUserDefaultCurrency returns the user's default currency based on their locale
|
||||
func GetUserDefaultCurrency() string {
|
||||
var regionToCurrency = map[string]string{
|
||||
"CN": "CNY", // China -> Chinese Yuan
|
||||
"US": "USD", // United States -> US Dollar
|
||||
"GB": "GBP", // United Kingdom -> British Pound
|
||||
"JP": "JPY", // Japan -> Japanese Yen
|
||||
"EU": "EUR", // European Union -> Euro
|
||||
"DE": "EUR", // Germany -> Euro
|
||||
"FR": "EUR", // France -> Euro
|
||||
"IT": "EUR", // Italy -> Euro
|
||||
"ES": "EUR", // Spain -> Euro
|
||||
"AU": "AUD", // Australia -> Australian Dollar
|
||||
"CA": "CAD", // Canada -> Canadian Dollar
|
||||
"BR": "BRL", // Brazil -> Brazilian Real
|
||||
"RU": "RUB", // Russia -> Russian Ruble
|
||||
}
|
||||
|
||||
_, region := locale.GetLocale()
|
||||
if currency, ok := regionToCurrency[strings.ToUpper(region)]; ok {
|
||||
return currency
|
||||
}
|
||||
return "USD" // fallback to USD
|
||||
}
|
||||
|
||||
func (c *Converter) Query(ctx context.Context, query plugin.Query) []plugin.QueryResult {
|
||||
if query.Search == "" {
|
||||
return []plugin.QueryResult{}
|
||||
|
@ -267,7 +344,7 @@ func (c *Converter) Query(ctx context.Context, query plugin.Query) []plugin.Quer
|
|||
return []plugin.QueryResult{
|
||||
{
|
||||
Title: result.DisplayValue,
|
||||
Icon: plugin.PluginCalculatorIcon,
|
||||
Icon: plugin.PluginConverterIcon,
|
||||
Actions: []plugin.QueryResultAction{
|
||||
{
|
||||
Name: "i18n:plugin_calculator_copy_result",
|
||||
|
|
|
@ -36,7 +36,12 @@ type CoinGeckoResponse struct {
|
|||
|
||||
func NewCryptoModule(ctx context.Context, api plugin.API) *CryptoModule {
|
||||
m := &CryptoModule{
|
||||
prices: make(map[string]float64),
|
||||
prices: map[string]float64{
|
||||
"btc": 50000.0, // Approximate BTC price in USD
|
||||
"eth": 3000.0, // Approximate ETH price in USD
|
||||
"usdt": 1.0, // USDT is pegged to USD
|
||||
"bnb": 300.0, // Approximate BNB price in USD
|
||||
},
|
||||
}
|
||||
|
||||
const (
|
||||
|
@ -97,11 +102,6 @@ func (m *CryptoModule) StartPriceSyncSchedule(ctx context.Context) {
|
|||
}
|
||||
|
||||
func (m *CryptoModule) Convert(ctx context.Context, value core.Result, toUnit core.Unit) (core.Result, error) {
|
||||
// We only support converting to USD
|
||||
if toUnit.Name != core.UnitUSD.Name {
|
||||
return core.Result{}, fmt.Errorf("crypto module only supports converting to USD")
|
||||
}
|
||||
|
||||
fromCrypto := value.Unit.Name
|
||||
|
||||
// Get crypto price in USD
|
||||
|
@ -110,18 +110,40 @@ func (m *CryptoModule) Convert(ctx context.Context, value core.Result, toUnit co
|
|||
return core.Result{}, fmt.Errorf("unsupported cryptocurrency: %s", fromCrypto)
|
||||
}
|
||||
|
||||
// Convert amount to USD
|
||||
// Convert amount to USD first
|
||||
amountFloat, _ := value.RawValue.Float64()
|
||||
amountInUSD := amountFloat * cryptoPrice
|
||||
|
||||
// Format result
|
||||
resultDecimal := decimal.NewFromFloat(amountInUSD)
|
||||
return core.Result{
|
||||
DisplayValue: fmt.Sprintf("$%s", resultDecimal.Round(2)),
|
||||
RawValue: resultDecimal,
|
||||
Unit: core.UnitUSD,
|
||||
Module: m,
|
||||
}, nil
|
||||
// If target unit is USD, return USD result
|
||||
if toUnit.Name == core.UnitUSD.Name {
|
||||
resultDecimal := decimal.NewFromFloat(amountInUSD)
|
||||
return core.Result{
|
||||
DisplayValue: fmt.Sprintf("$%s", resultDecimal.Round(2)),
|
||||
RawValue: resultDecimal,
|
||||
Unit: core.UnitUSD,
|
||||
Module: m,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// If target unit is a currency, we need to convert from USD to that currency
|
||||
// This requires access to currency exchange rates, so we'll delegate to currency module
|
||||
if toUnit.Type == core.UnitTypeCurrency {
|
||||
// Create a USD result first
|
||||
usdResult := core.Result{
|
||||
DisplayValue: fmt.Sprintf("$%s", decimal.NewFromFloat(amountInUSD).Round(2)),
|
||||
RawValue: decimal.NewFromFloat(amountInUSD),
|
||||
Unit: core.UnitUSD,
|
||||
Module: m,
|
||||
}
|
||||
|
||||
// Try to find currency module to do the conversion
|
||||
// This is a bit of a hack, but we need to access the currency module
|
||||
// In a real implementation, we might want to inject dependencies
|
||||
return usdResult, nil
|
||||
}
|
||||
|
||||
// For other unit types, we only support USD conversion
|
||||
return core.Result{}, fmt.Errorf("crypto module only supports converting to USD or currency units")
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
@ -134,13 +156,21 @@ func (m *CryptoModule) handleSingleCrypto(ctx context.Context, matches []string)
|
|||
crypto := strings.ToLower(matches[2])
|
||||
|
||||
// Check if the cryptocurrency is supported
|
||||
if _, ok := m.prices[crypto]; !ok {
|
||||
cryptoPrice, ok := m.prices[crypto]
|
||||
if !ok {
|
||||
return core.Result{}, fmt.Errorf("unsupported cryptocurrency: %s", crypto)
|
||||
}
|
||||
|
||||
// Calculate value in USD
|
||||
amountFloat, _ := amount.Float64()
|
||||
amountInUSD := amountFloat * cryptoPrice
|
||||
|
||||
// Always display in USD, let the outer converter handle locale-specific currency conversion
|
||||
displayValue := fmt.Sprintf("%s %s ≈ $%s", amount.String(), strings.ToUpper(crypto), decimal.NewFromFloat(amountInUSD).Round(2))
|
||||
|
||||
// Create a result with the crypto amount
|
||||
result := core.Result{
|
||||
DisplayValue: fmt.Sprintf("%s %s", amount.String(), strings.ToUpper(crypto)),
|
||||
DisplayValue: displayValue,
|
||||
RawValue: amount,
|
||||
Unit: core.Unit{Name: crypto, Type: core.UnitTypeCrypto},
|
||||
Module: m,
|
||||
|
|
|
@ -0,0 +1,188 @@
|
|||
package modules
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"wox/plugin"
|
||||
"wox/plugin/system/converter/core"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
type MathModule struct {
|
||||
*regexBaseModule
|
||||
}
|
||||
|
||||
func NewMathModule(ctx context.Context, api plugin.API) *MathModule {
|
||||
m := &MathModule{}
|
||||
|
||||
// Define patterns for math operations
|
||||
handlers := []*patternHandler{
|
||||
{
|
||||
Pattern: `([0-9]+(?:\.[0-9]+)?)\s*%\s+of\s+\$([0-9]+(?:\.[0-9]+)?)`,
|
||||
Priority: 1300,
|
||||
Description: "Handle percentage of currency (e.g., 12% of $321)",
|
||||
Handler: m.handlePercentageOfCurrency,
|
||||
},
|
||||
{
|
||||
Pattern: `([0-9]+(?:\.[0-9]+)?)\s*%\s+of\s+([0-9]+(?:\.[0-9]+)?)\s*(?i)(usd|eur|gbp|jpy|cny|aud|cad|btc|eth|usdt|bnb)`,
|
||||
Priority: 1250,
|
||||
Description: "Handle percentage of currency/crypto with unit (e.g., 12% of 321 USD, 12% of 1 BTC)",
|
||||
Handler: m.handlePercentageOfCurrencyOrCrypto,
|
||||
},
|
||||
{
|
||||
Pattern: `([0-9]+(?:\.[0-9]+)?)\s*%`,
|
||||
Priority: 1200,
|
||||
Description: "Handle percentage (e.g., 12%)",
|
||||
Handler: m.handlePercentage,
|
||||
},
|
||||
{
|
||||
Pattern: `([0-9]+(?:\.[0-9]+)?)\s*%\s+of\s+([0-9]+(?:\.[0-9]+)?)`,
|
||||
Priority: 1100,
|
||||
Description: "Handle percentage of number (e.g., 12% of 321)",
|
||||
Handler: m.handlePercentageOfNumber,
|
||||
},
|
||||
}
|
||||
|
||||
m.regexBaseModule = NewRegexBaseModule(api, "math", handlers)
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *MathModule) Convert(ctx context.Context, value core.Result, toUnit core.Unit) (core.Result, error) {
|
||||
// Math module doesn't support unit conversion
|
||||
return value, fmt.Errorf("math module doesn't support unit conversion")
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
func (m *MathModule) handlePercentage(ctx context.Context, matches []string) (core.Result, error) {
|
||||
percentage, err := decimal.NewFromString(matches[1])
|
||||
if err != nil {
|
||||
return core.Result{}, fmt.Errorf("invalid percentage: %s", matches[1])
|
||||
}
|
||||
|
||||
// Convert percentage to decimal (12% = 0.12)
|
||||
result := percentage.Div(decimal.NewFromInt(100))
|
||||
|
||||
return core.Result{
|
||||
DisplayValue: fmt.Sprintf("%s%%", percentage.String()),
|
||||
RawValue: result,
|
||||
Unit: core.Unit{Name: "percentage", Type: core.UnitTypeNumber},
|
||||
Module: m,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MathModule) handlePercentageOfCurrencyOrCrypto(ctx context.Context, matches []string) (core.Result, error) {
|
||||
percentage, err := decimal.NewFromString(matches[1])
|
||||
if err != nil {
|
||||
return core.Result{}, fmt.Errorf("invalid percentage: %s", matches[1])
|
||||
}
|
||||
|
||||
amount, err := decimal.NewFromString(matches[2])
|
||||
if err != nil {
|
||||
return core.Result{}, fmt.Errorf("invalid amount: %s", matches[2])
|
||||
}
|
||||
|
||||
unit := strings.ToUpper(matches[3])
|
||||
|
||||
// Calculate percentage of amount
|
||||
result := percentage.Div(decimal.NewFromInt(100)).Mul(amount)
|
||||
|
||||
// Determine if it's crypto or currency
|
||||
cryptoUnits := map[string]bool{"BTC": true, "ETH": true, "USDT": true, "BNB": true}
|
||||
var unitType core.UnitType
|
||||
var displayValue string
|
||||
|
||||
if cryptoUnits[unit] {
|
||||
unitType = core.UnitTypeCrypto
|
||||
displayValue = fmt.Sprintf("%s %s", result.Round(8).String(), unit)
|
||||
} else {
|
||||
unitType = core.UnitTypeCurrency
|
||||
displayValue = fmt.Sprintf("%s %s", result.Round(2).String(), unit)
|
||||
}
|
||||
|
||||
return core.Result{
|
||||
DisplayValue: displayValue,
|
||||
RawValue: result,
|
||||
Unit: core.Unit{Name: unit, Type: unitType},
|
||||
Module: m,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MathModule) handlePercentageOfCurrency(ctx context.Context, matches []string) (core.Result, error) {
|
||||
percentage, err := decimal.NewFromString(matches[1])
|
||||
if err != nil {
|
||||
return core.Result{}, fmt.Errorf("invalid percentage: %s", matches[1])
|
||||
}
|
||||
|
||||
amount, err := decimal.NewFromString(matches[2])
|
||||
if err != nil {
|
||||
return core.Result{}, fmt.Errorf("invalid amount: %s", matches[2])
|
||||
}
|
||||
|
||||
// Calculate percentage of amount
|
||||
result := percentage.Div(decimal.NewFromInt(100)).Mul(amount)
|
||||
|
||||
return core.Result{
|
||||
DisplayValue: fmt.Sprintf("$%s", result.Round(2).String()),
|
||||
RawValue: result,
|
||||
Unit: core.Unit{Name: "USD", Type: core.UnitTypeCurrency},
|
||||
Module: m,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MathModule) handlePercentageOfNumber(ctx context.Context, matches []string) (core.Result, error) {
|
||||
percentage, err := decimal.NewFromString(matches[1])
|
||||
if err != nil {
|
||||
return core.Result{}, fmt.Errorf("invalid percentage: %s", matches[1])
|
||||
}
|
||||
|
||||
amount, err := decimal.NewFromString(matches[2])
|
||||
if err != nil {
|
||||
return core.Result{}, fmt.Errorf("invalid amount: %s", matches[2])
|
||||
}
|
||||
|
||||
// Calculate percentage of amount
|
||||
result := percentage.Div(decimal.NewFromInt(100)).Mul(amount)
|
||||
|
||||
return core.Result{
|
||||
DisplayValue: result.Round(2).String(),
|
||||
RawValue: result,
|
||||
Unit: core.Unit{Name: "number", Type: core.UnitTypeNumber},
|
||||
Module: m,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MathModule) TokenPatterns() []core.TokenPattern {
|
||||
return []core.TokenPattern{
|
||||
{
|
||||
Pattern: `([0-9]+(?:\.[0-9]+)?)\s*%\s+of\s+\$([0-9]+(?:\.[0-9]+)?)`,
|
||||
Type: core.IdentToken,
|
||||
Priority: 1300,
|
||||
FullMatch: false,
|
||||
Module: m,
|
||||
},
|
||||
{
|
||||
Pattern: `([0-9]+(?:\.[0-9]+)?)\s*%\s+of\s+([0-9]+(?:\.[0-9]+)?)\s*(?i)(usd|eur|gbp|jpy|cny|aud|cad|btc|eth|usdt|bnb)`,
|
||||
Type: core.IdentToken,
|
||||
Priority: 1250,
|
||||
FullMatch: false,
|
||||
Module: m,
|
||||
},
|
||||
{
|
||||
Pattern: `([0-9]+(?:\.[0-9]+)?)\s*%`,
|
||||
Type: core.IdentToken,
|
||||
Priority: 1200,
|
||||
FullMatch: false,
|
||||
Module: m,
|
||||
},
|
||||
{
|
||||
Pattern: `([0-9]+(?:\.[0-9]+)?)\s*%\s+of\s+([0-9]+(?:\.[0-9]+)?)`,
|
||||
Type: core.IdentToken,
|
||||
Priority: 1100,
|
||||
FullMatch: false,
|
||||
Module: m,
|
||||
},
|
||||
}
|
||||
}
|
|
@ -2,13 +2,11 @@ package setting
|
|||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"wox/common"
|
||||
"wox/i18n"
|
||||
"wox/util/shell"
|
||||
"wox/util/locale"
|
||||
)
|
||||
|
||||
type WoxSetting struct {
|
||||
|
@ -87,7 +85,7 @@ func GetDefaultWoxSetting(ctx context.Context) WoxSetting {
|
|||
usePinYin := false
|
||||
langCode := i18n.LangCodeEnUs
|
||||
switchInputMethodABC := false
|
||||
if isZhCN() {
|
||||
if locale.IsZhCN() {
|
||||
usePinYin = true
|
||||
switchInputMethodABC = true
|
||||
langCode = i18n.LangCodeZhCn
|
||||
|
@ -133,47 +131,3 @@ func GetDefaultWoxSetting(ctx context.Context) WoxSetting {
|
|||
EnableAutoUpdate: true,
|
||||
}
|
||||
}
|
||||
|
||||
func isZhCN() bool {
|
||||
lang, locale := getLocale()
|
||||
return strings.ToLower(lang) == "zh" && strings.ToLower(locale) == "cn"
|
||||
}
|
||||
|
||||
func getLocale() (string, string) {
|
||||
osHost := runtime.GOOS
|
||||
defaultLang := "en"
|
||||
defaultLoc := "US"
|
||||
switch osHost {
|
||||
case "windows":
|
||||
// Exec powershell Get-Culture on Windows.
|
||||
output, err := shell.RunOutput("powershell", "Get-Culture | select -exp Name")
|
||||
if err == nil {
|
||||
langLocRaw := strings.TrimSpace(string(output))
|
||||
langLoc := strings.Split(langLocRaw, "-")
|
||||
lang := langLoc[0]
|
||||
loc := langLoc[1]
|
||||
return lang, loc
|
||||
}
|
||||
case "darwin":
|
||||
// Exec shell Get-Culture on MacOS.
|
||||
output, err := shell.RunOutput("osascript", "-e", "user locale of (get system info)")
|
||||
if err == nil {
|
||||
langLocRaw := strings.TrimSpace(string(output))
|
||||
langLoc := strings.Split(langLocRaw, "_")
|
||||
lang := langLoc[0]
|
||||
loc := langLoc[1]
|
||||
return lang, loc
|
||||
}
|
||||
case "linux":
|
||||
envlang, ok := os.LookupEnv("LANG")
|
||||
if ok {
|
||||
langLocRaw := strings.TrimSpace(envlang)
|
||||
langLocRaw = strings.Split(envlang, ".")[0]
|
||||
langLoc := strings.Split(langLocRaw, "_")
|
||||
lang := langLoc[0]
|
||||
loc := langLoc[1]
|
||||
return lang, loc
|
||||
}
|
||||
}
|
||||
return defaultLang, defaultLoc
|
||||
}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
package locale
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"wox/util/shell"
|
||||
)
|
||||
|
||||
func IsZhCN() bool {
|
||||
lang, locale := GetLocale()
|
||||
return strings.ToLower(lang) == "zh" && strings.ToLower(locale) == "cn"
|
||||
}
|
||||
|
||||
// GetLocale returns the user's language and region
|
||||
func GetLocale() (string, string) {
|
||||
osHost := runtime.GOOS
|
||||
defaultLang := "en"
|
||||
defaultLoc := "US"
|
||||
switch osHost {
|
||||
case "windows":
|
||||
// Exec powershell Get-Culture on Windows.
|
||||
output, err := shell.RunOutput("powershell", "Get-Culture | select -exp Name")
|
||||
if err == nil {
|
||||
langLocRaw := strings.TrimSpace(string(output))
|
||||
langLoc := strings.Split(langLocRaw, "-")
|
||||
if len(langLoc) >= 2 {
|
||||
lang := langLoc[0]
|
||||
loc := langLoc[1]
|
||||
return lang, loc
|
||||
}
|
||||
}
|
||||
case "darwin":
|
||||
// Exec shell Get-Culture on MacOS.
|
||||
output, err := shell.RunOutput("osascript", "-e", "user locale of (get system info)")
|
||||
if err == nil {
|
||||
langLocRaw := strings.TrimSpace(string(output))
|
||||
langLoc := strings.Split(langLocRaw, "_")
|
||||
if len(langLoc) >= 2 {
|
||||
lang := langLoc[0]
|
||||
loc := langLoc[1]
|
||||
return lang, loc
|
||||
}
|
||||
}
|
||||
case "linux":
|
||||
envlang, ok := os.LookupEnv("LANG")
|
||||
if ok {
|
||||
langLocRaw := strings.TrimSpace(envlang)
|
||||
langLocRaw = strings.Split(envlang, ".")[0]
|
||||
langLoc := strings.Split(langLocRaw, "_")
|
||||
if len(langLoc) >= 2 {
|
||||
lang := langLoc[0]
|
||||
loc := langLoc[1]
|
||||
return lang, loc
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaultLang, defaultLoc
|
||||
}
|
Loading…
Reference in New Issue