X-Git-Url: https://plomlompom.com/repos/berlin_corona.txt?a=blobdiff_plain;f=client.py;h=c9958be1637a9fba9891666444995fbffbbd1589;hb=956b2469d5537f1d0863e5a2811d37fa5220b0f9;hp=f356442390d22e2bb53d98521481b7da48d28a4b;hpb=7e119240b273588b4dce81ad211aa1548a0ca7f6;p=plomrogue2-experiments diff --git a/client.py b/client.py index f356442..c9958be 100755 --- a/client.py +++ b/client.py @@ -3,125 +3,7 @@ import urwid import plom_socket_io import socket import threading -from functools import partial -import unittest - - -class ArgumentError(Exception): - pass - - -class ParseError(Exception): - pass - - -class Parser: - - def __init__(self, game): - self.game = game - - def tokenize(self, msg): - """Parse msg string into tokens. - - Separates by ' ' and '\n', but allows whitespace in tokens quoted by - '"', and allows escaping within quoted tokens by a prefixed backslash. - """ - tokens = [] - token = '' - quoted = False - escaped = False - for c in msg: - if quoted: - if escaped: - token += c - escaped = False - elif c == '\\': - escaped = True - elif c == '"': - quoted = False - else: - token += c - elif c == '"': - quoted = True - elif c in {' ', '\n'}: - if len(token) > 0: - tokens += [token] - token = '' - else: - token += c - if len(token) > 0: - tokens += [token] - return tokens - - def parse(self, msg): - """Parse msg as call to self.game method, return method with arguments. - - Respects method signatures defined in methods' .argtypes attributes. - """ - tokens = self.tokenize(msg) - if len(tokens) == 0: - return None - method_candidate = 'cmd_' + tokens[0] - if not hasattr(self.game, method_candidate): - return None - method = getattr(self.game, method_candidate) - if len(tokens) == 1: - if not hasattr(method, 'argtypes'): - return method - else: - raise ParseError('Command expects argument(s).') - args_candidates = tokens[1:] - if not hasattr(method, 'argtypes'): - raise ParseError('Command expects no argument(s).') - args, kwargs = self.argsparse(method.argtypes, args_candidates) - return partial(method, *args, **kwargs) - - def parse_yx_tuple(self, yx_string): - """Parse yx_string as yx_tuple:nonneg argtype, return result.""" - - def get_axis_position_from_argument(axis, token): - if len(token) < 3 or token[:2] != axis + ':' or \ - not token[2:].isdigit(): - raise ParseError('Non-int arg for ' + axis + ' position.') - n = int(token[2:]) - if n < 1: - raise ParseError('Arg for ' + axis + ' position < 1.') - return n - - tokens = yx_string.split(',') - if len(tokens) != 2: - raise ParseError('Wrong number of yx-tuple arguments.') - y = get_axis_position_from_argument('Y', tokens[0]) - x = get_axis_position_from_argument('X', tokens[1]) - return (y, x) - - def argsparse(self, signature, args_tokens): - """Parse into / return args_tokens as args/kwargs defined by signature. - - Expects signature to be a ' '-delimited sequence of any of the strings - 'int:nonneg', 'yx_tuple:nonneg', 'string', defining the respective - argument types. - """ - tmpl_tokens = signature.split() - if len(tmpl_tokens) != len(args_tokens): - raise ParseError('Number of arguments (' + str(len(args_tokens)) + - ') not expected number (' + str(len(tmpl_tokens)) - + ').') - args = [] - for i in range(len(tmpl_tokens)): - tmpl = tmpl_tokens[i] - arg = args_tokens[i] - if tmpl == 'int:nonneg': - if not arg.isdigit(): - raise ParseError('Argument must be non-negative integer.') - args += [int(arg)] - elif tmpl == 'yx_tuple:nonneg': - args += [self.parse_yx_tuple(arg)] - elif tmpl == 'string': - args += [arg] - else: - raise ParseError('Unknown argument type.') - return args, {} +from parser import ArgError, Parser class Game: @@ -175,10 +57,10 @@ class Game: """Reset self.terrain_map from terrain_map.""" lines = terrain_map.split('\n') if len(lines) != self.map_size[0]: - raise ArgumentError('wrong map height') + raise ArgError('wrong map height') for line in lines: if len(line) != self.map_size[1]: - raise ArgumentError('wrong map width') + raise ArgError('wrong map width') self.terrain_map = terrain_map cmd_TERRAIN.argtypes = 'string' @@ -285,7 +167,7 @@ class PlomRogueClient: self.game.log('UNHANDLED INPUT: ' + msg) else: command() - except (ArgumentError, ParseError) as e: + except ArgError as e: self.game.log('ARGUMENT ERROR: ' + msg + '\n' + str(e)) self.widget_manager.update() del self.server_output[0] @@ -312,52 +194,6 @@ class PlomRogueClient: self.recv_loop_thread.join() -class TestParser(unittest.TestCase): - - def test_tokenizer(self): - p = Parser(Game()) - self.assertEqual(p.tokenize(''), []) - self.assertEqual(p.tokenize(' '), []) - self.assertEqual(p.tokenize('abc'), ['abc']) - self.assertEqual(p.tokenize('a b\nc "d"'), ['a', 'b', 'c', 'd']) - self.assertEqual(p.tokenize('a "b\nc d"'), ['a', 'b\nc d']) - self.assertEqual(p.tokenize('a"b"c'), ['abc']) - self.assertEqual(p.tokenize('a\\b'), ['a\\b']) - self.assertEqual(p.tokenize('"a\\b"'), ['ab']) - self.assertEqual(p.tokenize('a"b'), ['ab']) - self.assertEqual(p.tokenize('a"\\"b'), ['a"b']) - - def test_unhandled(self): - p = Parser(Game()) - self.assertEqual(p.parse(''), None) - self.assertEqual(p.parse(' '), None) - self.assertEqual(p.parse('x'), None) - - def test_argsparse(self): - p = Parser(Game()) - assertErr = partial(self.assertRaises, ParseError, p.argsparse) - assertErr('', ['foo']) - assertErr('string', []) - assertErr('string string', ['foo']) - self.assertEqual(p.argsparse('string', ('foo',)), - (['foo'], {})) - self.assertEqual(p.argsparse('string string', ('foo', 'bar')), - (['foo', 'bar'], {})) - assertErr('int:nonneg', ['']) - assertErr('int:nonneg', ['x']) - assertErr('int:nonneg', ['-1']) - assertErr('int:nonneg', ['0.1']) - self.assertEqual(p.argsparse('int:nonneg', ('0',)), - ([0], {})) - assertErr('yx_tuple:nonneg', ['x']) - assertErr('yx_tuple:nonneg', ['Y:0,X:1']) - assertErr('yx_tuple:nonneg', ['Y:1,X:0']) - assertErr('yx_tuple:nonneg', ['Y:1.1,X:1']) - assertErr('yx_tuple:nonneg', ['Y:1,X:1.1']) - self.assertEqual(p.argsparse('yx_tuple:nonneg', ('Y:1,X:2',)), - ([(1, 2)], {})) - - if __name__ == '__main__': game = Game() s = socket.create_connection(('127.0.0.1', 5000))