ArcanePlugins / Treasury

🏦 A powerful multi-platform library for next-level plugin integrations.
https://hangar.papermc.io/ArcanePlugins/Treasury
Other
57 stars 13 forks source link

Parsing sums of money #40

Closed Geolykt closed 2 years ago

Geolykt commented 2 years ago

Unlike vault, Treasury provides multi-currency support. Given that this multi-currency support is a thing in the backend, API users may be tempted to expose currencies to the frontend. Implementing /balance is implemented easily, albeit expensively by iterating over each and every currency and doing other trivial operations. However commands such as /pay would be a lot more complicated to implement as now you'd have to parse the amount. It is rather easy if you have a syntax that operates like /pay <reciever> <amount> [currency], where as currency has to be a currency name; easy enough to implement given that the currencies name can be looked up easily via Treasury API. However what if the User wants to format the amount to something like 15€? As far as I see there is no sane API to resolve that currency. The same applies to $ 15, $15, 15 USD or 15 €. An API user would not like to do this parsing hell manually so I propose an API to allow parsing strings such as the previous examples. This is aided by the fact that Currencies are only provided by the economy provider, so 3rd party currencies would not prove to be much of an issue.

What would the best course of action be here?

lokka30 commented 2 years ago

Unlike vault, Treasury provides multi-currency support. Given that this multi-currency support is a thing in the backend, API users may be tempted to expose currencies to the frontend. Implementing /balance is implemented easily, albeit expensively by iterating over each and every currency and doing other trivial operations. However commands such as /pay would be a lot more complicated to implement as now you'd have to parse the amount. It is rather easy if you have a syntax that operates like /pay <reciever> <amount> [currency], where as currency has to be a currency name; easy enough to implement given that the currencies name can be looked up easily via Treasury API. However what if the User wants to format the amount to something like 15€? As far as I see there is no sane API to resolve that currency. The same applies to $ 15, $15, 15 USD or 15 €. An API user would not like to do this parsing hell manually so I propose an API to allow parsing strings such as the previous examples. This is aided by the fact that Currencies are only provided by the economy provider, so 3rd party currencies would not prove to be much of an issue.

What would the best course of action be here?

Currency#formatBalance - is this what you are looking for? Or something else? :)

MrNemo64 commented 2 years ago

Unlike vault, Treasury provides multi-currency support. Given that this multi-currency support is a thing in the backend, API users may be tempted to expose currencies to the frontend. Implementing /balance is implemented easily, albeit expensively by iterating over each and every currency and doing other trivial operations. However commands such as /pay would be a lot more complicated to implement as now you'd have to parse the amount. It is rather easy if you have a syntax that operates like /pay <reciever> <amount> [currency], where as currency has to be a currency name; easy enough to implement given that the currencies name can be looked up easily via Treasury API. However what if the User wants to format the amount to something like 15€? As far as I see there is no sane API to resolve that currency. The same applies to $ 15, $15, 15 USD or 15 €. An API user would not like to do this parsing hell manually so I propose an API to allow parsing strings such as the previous examples. This is aided by the fact that Currencies are only provided by the economy provider, so 3rd party currencies would not prove to be much of an issue. What would the best course of action be here?

Currency#formatBalance - is this what you are looking for? Or something else? :)

I think he refers to a player doing something like /pay lokka30 30€ or /pay lokka30 30$. The player may write that instead of /pay lokka30 30 euro or /pay lokka30 30 dolar or /pay lokka30 30 euros or /pay lokka30 30 dolars

lokka30 commented 2 years ago

Unlike vault, Treasury provides multi-currency support. Given that this multi-currency support is a thing in the backend, API users may be tempted to expose currencies to the frontend. Implementing /balance is implemented easily, albeit expensively by iterating over each and every currency and doing other trivial operations. However commands such as /pay would be a lot more complicated to implement as now you'd have to parse the amount. It is rather easy if you have a syntax that operates like /pay <reciever> <amount> [currency], where as currency has to be a currency name; easy enough to implement given that the currencies name can be looked up easily via Treasury API. However what if the User wants to format the amount to something like 15€? As far as I see there is no sane API to resolve that currency. The same applies to $ 15, $15, 15 USD or 15 €. An API user would not like to do this parsing hell manually so I propose an API to allow parsing strings such as the previous examples. This is aided by the fact that Currencies are only provided by the economy provider, so 3rd party currencies would not prove to be much of an issue. What would the best course of action be here?

Currency#formatBalance - is this what you are looking for? Or something else? :)

I think he refers to a player doing something like /pay lokka30 30€ or /pay lokka30 30$. The player may write that instead of /pay lokka30 30 euro or /pay lokka30 30 dolar or /pay lokka30 30 euros or /pay lokka30 30 dolars

Shouldn't the economy provider handle all of that? Or do you also want a method in Treasury for other plugins to put in a String and get a Double, Currency out?

Geolykt commented 2 years ago

There is 0 point in having it being implementation-specific API, let's say I have a bank plugin where people get interest on their money they deposit. I would need to query each and every implementation I know of in order to implement the commands. This defeats the whole point of using an API to begin with

It should be a feature within the Treasury API. To parse a String into a Currency and Double.

MrIvanPlays commented 2 years ago

I have an idea in my head about doing the code of parsing currency symbols and such into 2 objects with a combined result object, but I'll wait on #37 to be pulled. :)

MrNemo64 commented 2 years ago

I have an idea in my head about doing the code of parsing currency symbols and such into 2 objects with a combined result object, but I'll wait on #37 to be pulled. :)

I suppose some regex must be used here. We can match for the number part and then search in the remaining string for names/symbols

MrIvanPlays commented 2 years ago

this is what I've written for ~30 minutes.

the code

```java package com.mrivanplays; import com.mrivanplays.ParsingTest.ParseResult.State; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class ParsingTest { // stfu I couldn't name it otherwise and this is an example so stfu public static record WaysToTypeCurrency(char byChar, String byCountryCode) {} // assume this is the plugin's logic of handling currencies // i cba to make it better just for an example public static class CurrencyRegistrar { private Map char2Name; public CurrencyRegistrar() { char2Name = new ConcurrentHashMap<>(); // assume some logic of currencies char2Name.put(new WaysToTypeCurrency('$', "USD"), "dollar"); char2Name.put(new WaysToTypeCurrency('€', "EUR"), "euro"); } public String getByChar(char currChar) { for (var entry : char2Name.entrySet()) { var currency = entry.getKey(); if (currency.byChar() == currChar) { return entry.getValue(); } } return null; } public String getByCountryCode(String code) { for (var entry : char2Name.entrySet()) { var currency = entry.getKey(); if (currency.byCountryCode().equalsIgnoreCase(code)) { return entry.getValue(); } } return null; } } public static record ParseResult(State state, double val, String currency) {public enum State {SUCCESS, FAILURE}} public static void main(String[] args) { var currencyRegistrar = new CurrencyRegistrar(); List toParse = Arrays.asList("$2", "2$", "€2", "2€", "2USD", "USD2", "EUR2", "2EUR", "2.5EUR", "5GBP"); for (var parse : toParse) { var result = parse(parse, currencyRegistrar); if (result.state() == State.FAILURE) { System.out.println("Failure: " + parse + " (unknown/invalid currency)"); } else { System.out.println("Parsed as: " + result.val() + " ( currency: " + result.currency() + " )"); } } } private static ParseResult parse(String s, CurrencyRegistrar registrar) { var currency = new StringBuilder(); var value = new StringBuilder(); for (var c : s.toCharArray()) { if (Character.isWhitespace(c)) { continue; } if (!Character.isDigit(c) && c != '.') { currency.append(c); } else if (Character.isDigit(c) || c == '.') { value.append(c); } } if (currency.length() == 1) { char byChar = currency.charAt(0); var curr = registrar.getByChar(byChar); if (curr == null) { return new ParseResult(State.FAILURE, -1, null); } var val = Double.parseDouble(value.toString()); return new ParseResult(State.SUCCESS, val, curr); } else { var byCode = currency.toString(); var curr = registrar.getByCountryCode(byCode); if (curr == null) { return new ParseResult(State.FAILURE, -1, null); } var val = Double.parseDouble(value.toString()); return new ParseResult(State.SUCCESS, val, curr); } } } ```

Currently not at it's best, my results show that it can't parse double values properly although I added a special case for them. Not anymore. Edited with working code. image

MrNemo64 commented 2 years ago

I suppose Currency#getAliases would be useful here I will also try to write something latter

MrIvanPlays commented 2 years ago

I suppose Currency#getAliases would be useful here I will also try to write something latter

my example is plain java the reason why i dont use plugin specific methods in it and create some stuff by myself.

Geolykt commented 2 years ago

I'd like if that parser would be changeable (even if it is only changeable for the economy provider) as I'd want to have a much more overkill solution to this problem

MrIvanPlays commented 2 years ago

I'd like if that parser would be changeable (even if it is only changeable for the economy provider) as I'd want to have a much more overkill solution to this problem

wait a minute so you want a solution integrated into treasury yet you want it to run with a different logic with what is (going to be) implemented !? why don't you just parse it yourself? in such case use my code and adapt it to work with treasury's currency registrar and your problem is basically solved.

MrNemo64 commented 2 years ago

I'd like if that parser would be changeable (even if it is only changeable for the economy provider) as I'd want to have a much more overkill solution to this problem

wait a minute so you want a solution integrated into treasury yet you want it to run with a different logic with what is (going to be) implemented !? why don't you just parse it yourself? in such case use my code and adapt it to work with treasury's currency registrar and your problem is basically solved.

A solution for the parser. A plugin that manages all the currencies (the provider) may want to use its own parser, but a plugin that adds a /pay command, he just doesn't care, might use the default one or whatever

I've came up with this, if someone wants to review it

Code public class CurrencyParser { private final Set currencies = new HashSet<>(); public CurrencyAmmount parse(String str) { // find ammount String ammount = findAmmount(str); if (ammount == null) { return null; } Currency currency = findCurrency(str); if(currency == null) return null; return new CurrencyAmmount(Double.parseDouble(ammount), currency); } private Currency findCurrency(String str) { // start wit hcapacity to avoid growing StringBuilder bCur = new StringBuilder(str.length()); if (str.length() == 0) return null; char c = str.charAt(0); if (Character.isDigit(c) || isComa(c)) { // first number, then currency for (int i = str.length() - 1; i >= 0; i--) { c = str.charAt(i); if (!Character.isDigit(c) && !isComa(c)) { bCur.append(c); } else { break; } } bCur.reverse(); } else { // first currency, then number for (int i = 0; i < str.length(); i++) { c = str.charAt(i); if (!Character.isDigit(c) && !isComa(c)) { bCur.append(c); } else { break; } } } String cur = bCur.toString().trim(); for (Currency cu : currencies) for(String cus : cu.getNames()) if(cus.equalsIgnoreCase(cur)) return cu; return null; } private String findAmmount(String str) { // start wit hcapacity to avoid growing StringBuilder bAmm = new StringBuilder(str.length()); boolean dot = false; // did we find a dot already? int start = -1; // start of the ammount. Inclusive for (int i = 0; i < str.length(); i++) { char c = str.charAt(i); if (Character.isDigit(c)) { bAmm.append(c); if (start == -1) { // is the first digit start = i; } } else if (isComa(c)) { if (dot) { return null; // 2 dots?? } if (start == -1) { // is the first char of the ammount start = i; bAmm.append('0'); } bAmm.append('.'); dot = true; } else { if (start != -1) { // first non-digit char afther we found a first one break; } } } if (start == -1) return null; return bAmm.toString(); } private boolean isComa(char c) { return c == '.' || c == ',' || c == '\''; } public void addCurrency(Currency c) { currencies.add(c); } public static final class CurrencyAmmount { private final double ammount; private final Currency currency; public CurrencyAmmount(double ammount, Currency currency) { this.ammount = ammount; this.currency = currency; } public double getAmmount() { return ammount; } public Currency getCurrency() { return currency; } @Override public String toString() { return "(" + currency.getName() + ", " + ammount + ")"; } } }
Running it public static void main(String[] args) { CurrencyParser parser = new CurrencyParser(); parser.addCurrency(new Currency(Arrays.asList("euro", "euros", "eur", "€"), "Euro")); parser.addCurrency(new Currency(Arrays.asList("dolar", "dolars", "usd", "$"), "Dolar")); System.out.println(parser.parse("500 euros")); // (Euro, 500.0) System.out.println(parser.parse(".500euros")); // (Euro, 0.5) System.out.println(parser.parse("1.500 euro")); // (Euro, 1.5) System.out.println(parser.parse("5.00euro")); // (Euro, 5.0) System.out.println(parser.parse("euros519 ")); // (Euro, 519.0) System.out.println(parser.parse("€ 500.0909 ")); // (Euro, 500.0909) System.out.println(parser.parse("9.9.9 usd")); // null System.out.println(parser.parse("5,00$")); // (Dolar, 5.0) System.out.println(parser.parse("$50'0")); // (Dolar, 50.0) System.out.println(parser.parse("euros")); // null System.out.println(parser.parse("not a currency 1000")); // null System.out.println(parser.parse("1000")); // null }
MrIvanPlays commented 2 years ago

@MrNemo64 nice one! We can do a combination of mine and your solution and it will be perfect. I'll write such tomorrow.

MrIvanPlays commented 2 years ago

I think this is the best of both mine and @MrNemo64's version:

MrIvanPlays commented 2 years ago

I think this is the best of both worlds (my first and @MrNemo64 's version)

the code

```java package com.mrivanplays; import com.mrivanplays.ParsingTest.ParseResult.State; import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class ParsingTest { // stfu I couldn't name it otherwise and this is an example so stfu public static record WaysToTypeCurrency(char byChar, String byCountryCode) {} // a way to store multiple names easy in a map public static record AcceptableCurrencyNames(String... names) { public String base() { return names[0]; } } // assume this is the plugin's logic of handling currencies // i cba to make it better just for an example public static class CurrencyRegistrar { private Map char2Name; public CurrencyRegistrar() { char2Name = new ConcurrentHashMap<>(); // assume some logic of currencies char2Name.put(new WaysToTypeCurrency('$', "USD"), new AcceptableCurrencyNames("dollar", "dolar", "dolla", "dola", "dollars", "dollas", "dolas") ); char2Name.put(new WaysToTypeCurrency('€', "EUR"), new AcceptableCurrencyNames("euro", "euros") ); } public String getByChar(char currChar) { for (var entry : char2Name.entrySet()) { var currency = entry.getKey(); if (currency.byChar() == currChar) { return entry.getValue().base(); } } return null; } public String getByCountryCode(String code) { for (var entry : char2Name.entrySet()) { var currency = entry.getKey(); var name = entry.getValue(); if (currency.byCountryCode().equalsIgnoreCase(code)) { return name.base(); } // couldn't find by code? test by name for (var currentName : name.names()) { if (code.equalsIgnoreCase(currentName)) { return name.base(); } } } return null; } } public static record ParseResult(State state, double val, String currency) {public enum State {SUCCESS, FAILURE}} public static void main(String[] args) { var currencyRegistrar = new CurrencyRegistrar(); List toParse = Arrays.asList("$2", "2$", "€2", "2€", "2USD", "USD2", "EUR2", "2EUR", "2.5EUR", ".5euro", "2 dollars", "5 euros", "euro5", "dollar2", "dola3", "euros6", "7euros", "5GBP"); for (var parse : toParse) { var result = parse(parse, currencyRegistrar); if (result.state() == State.FAILURE) { System.out.println("Failure: " + parse + " (unknown/invalid currency)"); } else { System.out.println("Parsed value: " + parse + " as: " + result.val() + " ( currency: " + result.currency() + " )"); } } } private static boolean isSeparator(char c) { return c == '.' || c == ',' || c == '\''; } private static ParseResult parse(String s, CurrencyRegistrar registrar) { var currency = new StringBuilder(); var value = new StringBuilder(); for (var c : s.toCharArray()) { if (Character.isWhitespace(c)) { continue; } if (!Character.isDigit(c) && !isSeparator(c)) { currency.append(c); } else if (Character.isDigit(c) || isSeparator(c)) { value.append(c); } } if (currency.length() == 1) { char byChar = currency.charAt(0); var curr = registrar.getByChar(byChar); if (curr == null) { return new ParseResult(State.FAILURE, -1, null); } var val = Double.parseDouble(value.toString()); return new ParseResult(State.SUCCESS, val, curr); } else { var byCode = currency.toString().toUpperCase(Locale.ROOT); var curr = registrar.getByCountryCode(byCode); if (curr == null) { return new ParseResult(State.FAILURE, -1, null); } var val = Double.parseDouble(value.toString()); return new ParseResult(State.SUCCESS, val, curr); } } } ```

Result: image

MrNemo64 commented 2 years ago

How would this code work with inputs like "1.56.7 euro" or "1e2u3r4o"

MrIvanPlays commented 2 years ago

Oh fuck forgot to add detection for multiple dots... will add this l8r

lokka30 commented 2 years ago

"1e2u3r4o"

Surely, that's beyond the scope of this issue :smile: of course no worries if it is implemented but I don't think anyone should bother making an algorithm that works with that

MrIvanPlays commented 2 years ago

"1e2u3r4o"

Surely, that's beyond the scope of this issue :smile: of course no worries if it is implemented but I don't think anyone should bother making an algorithm that works with that

Actually thats pretty simple to parse and it is already implemented in my code.

MrNemo64 commented 2 years ago

"1e2u3r4o"

Surely, that's beyond the scope of this issue 😄 of course no worries if it is implemented but I don't think anyone should bother making an algorithm that works with that

Actually thats pretty simple to parse and it is already implemented in my code.

If I'm not mistaken your code would parse it like 1234 ( currency: euro ) and, in my opinion, anything that doesn't match ammount currency or currency ammount should not parse

MrIvanPlays commented 2 years ago

We need @lokka30 's opinion about whether to only parse 'currency amount' or 'amount currency' (space (in/ex)cluded) or in any way it can.

MrIvanPlays commented 2 years ago

@MrNemo64 turning on the computer and I have an idea about it. What if we create a custom pattern class specifically about parsing such values? That way developers will be able to choose whether to support currency value, value currency, both, everything or a custom pattern. On the other hand tho, this makes this API kinda bloated, but we can specify it to parse everything by default.

MrIvanPlays commented 2 years ago

Now invalidates values with more than 1 dot

code

```java package com.mrivanplays; import com.mrivanplays.ParsingTest.ParseResult.State; import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class ParsingTest { // stfu I couldn't name it otherwise and this is an example so stfu public static record WaysToTypeCurrency(char byChar, String byCountryCode) {} // a way to store multiple names easy in a map public static record AcceptableCurrencyNames(String... names) { public String base() { return names[0]; } } // assume this is the plugin's logic of handling currencies // i cba to make it better just for an example public static class CurrencyRegistrar { private Map char2Name; public CurrencyRegistrar() { char2Name = new ConcurrentHashMap<>(); // assume some logic of currencies char2Name.put(new WaysToTypeCurrency('$', "USD"), new AcceptableCurrencyNames("dollar", "dolar", "dolla", "dola", "dollars", "dollas", "dolas") ); char2Name.put(new WaysToTypeCurrency('€', "EUR"), new AcceptableCurrencyNames("euro", "euros") ); } public String getByChar(char currChar) { for (var entry : char2Name.entrySet()) { var currency = entry.getKey(); if (currency.byChar() == currChar) { return entry.getValue().base(); } } return null; } public String getByCountryCode(String code) { for (var entry : char2Name.entrySet()) { var currency = entry.getKey(); var name = entry.getValue(); if (currency.byCountryCode().equalsIgnoreCase(code)) { return name.base(); } // couldn't find by code? test by name for (var currentName : name.names()) { if (code.equalsIgnoreCase(currentName)) { return name.base(); } } } return null; } } public static record ParseResult(State state, double val, String currency) { public enum State { SUCCESS, INVALID_VALUE, INVALID_OR_UNKNOWN_CURRENCY } } public static void main(String[] args) { var currencyRegistrar = new CurrencyRegistrar(); List toParse = Arrays.asList("$2", "2$", "€2", "2€", "2USD", "USD2", "EUR2", "2EUR", "2.5EUR", ".5euro", "2 dollars", "5 euros", "euro5", "dollar2", "dola3", "euros6", "7euros", "1e2u3r4o", "1.1.1USD", "5GBP"); for (var parse : toParse) { var result = parse(parse, currencyRegistrar); if (result.state() == State.INVALID_OR_UNKNOWN_CURRENCY) { System.out.println("Failure: " + parse + " (unknown/invalid currency)"); } else if (result.state() == State.INVALID_VALUE) { System.out.println("Failure: " + parse + " (invalid value)"); } else { System.out.println("Parsed value: " + parse + " as: " + result.val() + " ( currency: " + result.currency() + " )"); } } } private static boolean isSeparator(char c) { return c == '.' || c == ',' || c == '\''; } private static ParseResult parse(String s, CurrencyRegistrar registrar) { var currency = new StringBuilder(); var value = new StringBuilder(); var hadDot = false; for (var c : s.toCharArray()) { if (Character.isWhitespace(c)) { continue; } if (!Character.isDigit(c) && !isSeparator(c)) { currency.append(c); } else if (Character.isDigit(c)) { value.append(c); } else if (isSeparator(c)) { if (c == '.') { var nowChanged = false; if (!hadDot) { hadDot = true; nowChanged = true; } if (!nowChanged) { value = new StringBuilder(); break; } } value.append(c); } } var curr = ""; if (currency.length() == 1) { curr = registrar.getByChar(currency.charAt(0)); } else { curr = registrar.getByCountryCode(currency.toString().toUpperCase(Locale.ROOT)); } var state = State.SUCCESS; if (value.isEmpty()) { state = State.INVALID_VALUE; } else if (curr == null || curr.isEmpty()) { state = State.INVALID_OR_UNKNOWN_CURRENCY; } if (state == State.SUCCESS) { var val = Double.parseDouble(value.toString()); return new ParseResult(state, val, curr); } else { return new ParseResult(state, -1, null); } } } ```

In this test version I also replaced State.FAILURE with more descriptive enum constants. image

Geolykt commented 2 years ago

A small layer of safety can do no harm, but I don't think that "1e2u3r4o" should not error out.

MrNemo64 commented 2 years ago

@MrNemo64 turning on the computer and I have an idea about it. What if we create a custom pattern class specifically about parsing such values? That way developers will be able to choose whether to support currency value, value currency, both, everything or a custom pattern.

Sorry for late response, was bussy. I don't see much use in that. Take england for example, they write currency value : £20 £60, so if a server has the pound as currency they will most likely use that format, in other places you put first the value then the currency: 9€ So just allowing one doesn't feel right

MrIvanPlays commented 2 years ago

I mean we can make it so that multiple patterns can be inserted.

MrNemo64 commented 2 years ago

I supose, but having already some functions that parse strings into ammount an currency why do this? The implementing plugin shouldn't have to worry about parsing, that's what the method is for, so why make the pattern, it gives the idea that you have to worry about it

MrIvanPlays commented 2 years ago

It won't be mandatory to do that. We will have a couple of default patterns that will be specified when a method which doesn't accept them is called.

So we can have

parse(String foo, ParsePattern... patterns)

and

parse(String foo)

We can also make it so that the default patterns are controllable by the economy provider.

MrNemo64 commented 2 years ago

That makes some more sense but how would a ParsePattern look like and why can you specify more than one?

MrIvanPlays commented 2 years ago
public class ParsePattern {

  private final String pattern;

  // constructor and getter and other helpful methods perhaps

}

And on the parse method we could do something like

public /* insert return object here */ parse(String val, ParsePattern... patterns) {
  for (var patternObj : patterns) {
    var pattern = patternObj.getPattern();
    if (pattern.startsWith("%c") {
      // first read the currency, then the value
    } else if (pattern.startsWith("%v") {
      // first read the value, then the currency
    }
    // of course if the pattern doesn't match we can call continue and go to the 
    // next one if there is such. 
  }
}

This is just an example it shouldn't be thought of as a final version. Pretty sure I can think of something a little bit more robust whenever it comes to implementing that.

MrNemo64 commented 2 years ago

I see now. So maybe if someone only wants the ammount they can do pattern=%v, only the currency pattern=%c currency and value no matter the order maybe pattern=%c%v%c or something like that. Maybe instead of ParsePatten having a pattern object we can make it so it's kinda like a Function<String, CurrencyAndAmmount> and provide default implementations for some common uses. For example CurrencyParsePattern.AMMOUNT_ONLY would be a Function<String, Double> and only extracts the ammount CurrencyParsePattern.CURRENCY_ONLY would be a Function<String, Currency> and only extracts the currency CurrencyParsePattern.AMMOUNT_AND_CURRENCY would be a Function<String, AmmountAndCurrency (dont know how to call it)> and extracts both

MrIvanPlays commented 2 years ago

We'll think how it will be the best both for the economy provider and the developer utilizing the economy provider.

MrNemo64 commented 2 years ago

Maybe in the EconomyProvider interface there is default method for these so the implementation can change them if it wants @Geolykt was concerned about this

MrIvanPlays commented 2 years ago

It's been 1 month with no activity on this so... What shall we do? parse patterns or have it hardcoded like the example above?

MrNemo64 commented 2 years ago

It's been 1 month with no activity on this so... What shall we do? parse patterns or have it hardcoded like the example above?

Seeing what you said in #49 it may be better to wait