diff options
| author | Florian Fischer <florian.fischer@muhq.space> | 2022-07-06 09:48:14 +0200 |
|---|---|---|
| committer | Florian Fischer <florian.fischer@muhq.space> | 2022-07-06 10:18:39 +0200 |
| commit | 2fe86be4e3b999ec4941284274b586dd0f2fecb7 (patch) | |
| tree | 4784c380304d8d84809a4568dfcbb96da93665fd | |
| parent | 24c54bdc6a319bca554223b77851acd3e801e0c7 (diff) | |
| download | geldschieberbot-2fe86be4e3b999ec4941284274b586dd0f2fecb7.tar.gz geldschieberbot-2fe86be4e3b999ec4941284274b586dd0f2fecb7.zip | |
add alias support
Aliases can be used where one or multiple persons names can be used.
An alias is expanded to the list of users it represents.
For commands implicitly including one user (e.g., 'split') the expansion
of the implicit user can be prevented.
| -rw-r--r-- | geldschieberbot.py | 80 | ||||
| -rwxr-xr-x | test.py | 96 |
2 files changed, 175 insertions, 1 deletions
diff --git a/geldschieberbot.py b/geldschieberbot.py index 0a912c3..79cb643 100644 --- a/geldschieberbot.py +++ b/geldschieberbot.py @@ -69,6 +69,7 @@ class Geldschieberbot: "cars": {}, "scheduled_cmds": {}, "changes": {}, + "aliases": {}, } self.balance = self.state["balance"] @@ -77,6 +78,7 @@ class Geldschieberbot: self.available_cars = self.state["cars"] self.scheduled_cmds = self.state["scheduled_cmds"] self.changes = self.state["changes"] + self.aliases = self.state.setdefault("aliases", {}) def save_state(self, state_path=STATE_FILE): """Load state from disk""" @@ -168,6 +170,33 @@ class Geldschieberbot: for m in self.balance: del self.balance[m][name] + def expand_aliases(self, + users: list[str], + exclude_users=None) -> list[str]: + """Expand any alias + + Any user name in exclude_users will not be expanded. This is usefull + when using aliases with commands implicitly adding a user like !split. + """ + ret = [] + for user in users: + if not user in self.aliases and user != 'all': + ret.append(user) + continue + + expanded_users = [] + if user in self.aliases: + expanded_users += self.aliases[user] + else: + expanded_users += list(self.name2num.keys()) + + ret.extend([ + u for u in expanded_users + if not exclude_users or u not in exclude_users + ]) + + return ret + @classmethod def create_help(cls) -> str: return """ @@ -179,6 +208,10 @@ class Geldschieberbot: sum [name] - print summary of specific users full-sum - print summary of all users + alias - list registered aliases + alias name person [persons] - create an alias for one or multiple persons + alias remove name - remove an alias + split amount person [persons] - split amount between the sender and persons teil amount person [persons] - split amount between the sender and persons @@ -247,7 +280,7 @@ class Geldschieberbot: return {'msg': f'Summary:\n{self.create_summary(name)}'} msg = "Summary:\n" - for name in args[1:]: + for name in self.expand_aliases(args[1:]): if name in self.name2num or name in self.available_cars: msg += self.create_summary(name) + "\n" else: @@ -293,6 +326,8 @@ class Geldschieberbot: return {'err': 'amount must be a positive number'} recipient = self.num2name[sender] + # exclude the implicit recipient from alias expension + persons = self.expand_aliases(persons, exclude_users=[recipient]) # persons + sender npersons = len(persons) + 1 amount_per_person = int(amount / npersons) @@ -821,6 +856,48 @@ class Geldschieberbot: return {'msg': msg} + def alias(self, sender, args, msg) -> dict[str, str]: # pylint: disable=unused-argument + """List or create aliases""" + + if len(args) == 1: + msg = '' + for alias, users in self.aliases.items(): + msg += f'\n\t{alias}: {" ".join(users)}' + return {'msg': f'Aliases:\n\tall: {" ".join(self.name2num)}{msg}'} + + if len(args) == 2: + return {'err': 'Not a valid alias command'} + + if args[1] == 'remove': + alias = args[2] + if alias not in self.aliases: + return {'err': f'Alias "{alias}" not registered'} + + del self.aliases[alias] + return {'msg': f'Alias "{alias}" removed'} + + # create a new alias + alias = args[1] + if alias in self.aliases or alias == 'all': + return {'err': f'Alias "{alias}" is already registered'} + + if alias in self.name2num: + return {'err': f'A user "{alias}" is already registered'} + + try: + to_cent(alias) + return {'err': 'Pure numerical aliases are not allowed'} + except (ValueError, TypeError): + pass + + users = args[2:] + for user in users: + if user not in self.name2num: + return {'err': f'User {user} is not registered'} + + self.aliases[alias] = users + return {'msg': f'New alias "{alias}" registered'} + def __init__(self, dry_run=False): self.dry_run = dry_run # Run without changing the stored state self.load_state() @@ -831,6 +908,7 @@ class Geldschieberbot: self.cmds = { 'reg': self.register, 'register': self.register, + 'alias': self.alias, 'sum': self.summary, 'summary': self.summary, 'full-sum': self.full_summary, @@ -419,6 +419,102 @@ alice: self.assertEqual(res.stdout, msg) +class TestAliasAdd(unittest.TestCase): + + def setUp(self): + reset_state("test/state_3users.json") + + def test_add_success(self): + res = run_bot(self, num[alice], f"!alias foo {alice} {bob}") + self.assertEqual(res.stdout, 'New alias "foo" registered') + + res = run_bot(self, num[alice], f"!alias bar {charlie}") + self.assertEqual(res.stdout, 'New alias "bar" registered') + + def test_add_invalid_aliases(self): + res = run_bot(self, num[alice], f"!alias foo {alice} {bob}") + self.assertEqual(res.stdout, 'New alias "foo" registered') + res = run_bot(self, num[alice], f"!alias foo {alice} {bob}") + self.assertEqual(res.stdout, + 'ERROR: Alias "foo" is already registered') + + res = run_bot(self, num[alice], f"!alias {alice} {alice} {bob}") + self.assertEqual(res.stdout, + f'ERROR: A user "{alice}" is already registered') + + res = run_bot(self, num[alice], f"!alias 13.23 {charlie}") + self.assertEqual(res.stdout, + 'ERROR: Pure numerical aliases are not allowed') + + res = run_bot(self, num[alice], "!alias bar ingo") + self.assertEqual(res.stdout, 'ERROR: User ingo is not registered') + + res = run_bot(self, num[alice], f"!alias all {alice}") + self.assertEqual(res.stdout, + 'ERROR: Alias "all" is already registered') + + +class TestAlias(unittest.TestCase): + + def setUp(self): + reset_state("test/state_3users.json") + + def test_list_aliases_empty_state(self): + res = run_bot(self, num[alice], "!alias") + self.assertEqual(res.stdout, f"""Aliases: +\tall: {alice} {bob} {charlie}""") + + def test_list_aliases(self): + run_bot(self, num[alice], f"!alias alob {alice} {bob}") + res = run_bot(self, num[alice], "!alias") + self.assertEqual( + res.stdout, f"""Aliases: +\tall: {alice} {bob} {charlie} +\talob: {alice} {bob}""") + + def test_add_invalid_aliases(self): + run_bot(self, num[alice], f"!alias foo {alice} {bob}") + res = run_bot(self, num[alice], f"!alias foo {alice} {bob}") + self.assertEqual(res.stdout, + 'ERROR: Alias "foo" is already registered') + + res = run_bot(self, num[alice], f"!alias {alice} {alice} {bob}") + self.assertEqual(res.stdout, + f'ERROR: A user "{alice}" is already registered') + + res = run_bot(self, num[alice], f"!alias 13.23 {charlie}") + self.assertEqual(res.stdout, + 'ERROR: Pure numerical aliases are not allowed') + + res = run_bot(self, num[alice], "!alias bar ingo") + self.assertEqual(res.stdout, 'ERROR: User ingo is not registered') + + res = run_bot(self, num[alice], f"!alias all {alice}") + self.assertEqual(res.stdout, + 'ERROR: Alias "all" is already registered') + + def test_split_all(self): + res = run_bot(self, num[alice], "!split 9 all") + self.assertEqual(res.stdout, \ +"""Split 9.00 between 3 -> 3.00 each +New Balance: +alice: +\t<- bob 3.00 +\t<- charlie 3.00 +\tBalance: 6.00""") + + def test_split_alias(self): + run_bot(self, num[alice], f"!alias alob {alice} {bob}") + res = run_bot(self, num[charlie], "!split 9 alob") + self.assertEqual(res.stdout, \ +"""Split 9.00 between 3 -> 3.00 each +New Balance: +charlie: +\t<- alice 3.00 +\t<- bob 3.00 +\tBalance: 6.00""") + + class TestCarsAdd(unittest.TestCase): def setUp(self): |
