Skip to content

Commit b8f9444

Browse files
committed
Semi-automatic test module & fixes for colors, commands, disk, fs, gps, keys
1 parent ad09602 commit b8f9444

File tree

9 files changed

+640
-57
lines changed

9 files changed

+640
-57
lines changed

computercraft/server.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def __init__(self, nid, program, cleanup_callback):
5454
self._event_to_tids = {}
5555
self._tid_to_event = {}
5656

57-
self.colors = ColorsAPI
57+
self.colors = ColorsAPI(self)
5858
self.commands = CommandsAPI(self)
5959
self.disk = DiskAPI(self)
6060
self.fs = FSAPI(self)
@@ -96,7 +96,7 @@ async def prog_wrap():
9696

9797
self._task = asyncio.create_task(prog_wrap())
9898

99-
def _new_task_id(self):
99+
def _new_task_id(self) -> str:
100100
task_id = base36(self._task_autoid)
101101
self._task_autoid += 1
102102
return task_id
@@ -150,7 +150,7 @@ class CCApplication(web.Application):
150150
async def _sender(ws, api):
151151
while not ws.closed:
152152
cmd = await api._cmd.get()
153-
print(f'_sender: {cmd}')
153+
# print(f'_sender: {cmd}')
154154
if not ws.closed:
155155
await ws.send_json(cmd)
156156
if cmd['action'] == 'close':
@@ -159,7 +159,7 @@ async def _sender(ws, api):
159159
@staticmethod
160160
async def _json_messages(ws):
161161
async for msg in ws:
162-
print('ws received', msg)
162+
# print('ws received', msg)
163163
if msg.type != WSMsgType.TEXT:
164164
continue
165165
# print('ws received', msg.data)

computercraft/subapis/base.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,12 @@ def op_fn(v):
8585
return op_fn
8686

8787

88-
def make_single_value_return(cond):
88+
def make_single_value_return(validator, converter=None):
8989
def fn(v):
9090
assert len(v) == 1
91-
assert cond(v[0])
91+
if converter is not None:
92+
v[0] = converter(v[0])
93+
assert validator(v[0])
9294
return v[0]
9395
return fn
9496

@@ -97,7 +99,11 @@ def fn(v):
9799
int_return = make_single_value_return(lambda v: isinstance(v, int) and not isinstance(v, bool))
98100
number_return = make_single_value_return(lambda v: isinstance(v, (int, float)) and not isinstance(v, bool))
99101
str_return = make_single_value_return(lambda v: isinstance(v, str))
100-
list_return = make_single_value_return(lambda v: isinstance(v, list))
102+
str_bool_return = make_single_value_return(lambda v: isinstance(v, (str, bool)))
103+
list_return = make_single_value_return(
104+
lambda v: isinstance(v, list),
105+
lambda v: [] if v == {} else v,
106+
)
101107
dict_return = make_single_value_return(lambda v: isinstance(v, dict))
102108

103109

@@ -110,6 +116,7 @@ def nil_return(v):
110116
opt_int_return = make_optional(int_return)
111117
opt_number_return = make_optional(number_return)
112118
opt_str_return = make_optional(str_return)
119+
opt_str_bool_return = make_optional(str_bool_return)
113120
opt_list_return = make_optional(list_return)
114121
opt_dict_return = make_optional(dict_return)
115122

computercraft/subapis/colors.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
class ColorsAPI:
1+
from typing import Tuple
2+
3+
from .base import BaseSubAPI, bool_return, int_return
4+
5+
6+
class ColorsAPI(BaseSubAPI):
7+
_API = 'colors'
8+
29
white = 0x1
310
orange = 0x2
411
magenta = 0x4
@@ -34,3 +41,25 @@ class ColorsAPI:
3441
'e': red,
3542
'f': black,
3643
}
44+
45+
async def combine(self, *colors: int) -> int:
46+
return int_return(await self._send('combine', *colors))
47+
48+
async def subtract(self, color_set: int, *colors: int) -> int:
49+
return int_return(await self._send('subtract', color_set, *colors))
50+
51+
async def test(self, colors: int, color: int) -> bool:
52+
return bool_return(await self._send('test', colors, color))
53+
54+
async def packRGB(self, r: float, g: float, b: float) -> int:
55+
return int_return(await self._send('packRGB', r, g, b))
56+
57+
async def unpackRGB(self, rgb: int) -> Tuple[float, float, float]:
58+
ret = await self._send('unpackRGB', rgb)
59+
assert isinstance(ret, list)
60+
assert len(ret) == 3
61+
return tuple(ret)
62+
63+
async def rgb8(self, *args):
64+
r = await self._send('rgb8', *args)
65+
return r[0] if len(r) == 1 else r

computercraft/subapis/commands.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,29 @@
1-
from typing import Tuple, List
1+
from typing import Tuple, List, Optional
22
from .base import BaseSubAPI, int_return, list_return, dict_return
3+
from ..errors import CommandException
34

45

56
class CommandsAPI(BaseSubAPI):
67
_API = 'commands'
78

8-
async def exec(self, command: str):
9+
async def exec(self, command: str) -> Tuple[str, Optional[int]]:
910
r = await self._send('exec', command)
10-
assert len(r) == 2
11+
assert len(r) == 3
1112
assert isinstance(r[0], bool)
13+
14+
if r[1] == {}:
15+
r[1] = []
1216
assert isinstance(r[1], list)
13-
return r[0], r[1]
17+
r[1] = '\n'.join(r[1])
18+
19+
if r[2] is None:
20+
r[2] = 0
21+
assert isinstance(r[2], int)
22+
23+
if r[0] is False:
24+
raise CommandException(r[1])
25+
26+
return r[1], r[2]
1427

1528
async def execAsync(self, command: str) -> int:
1629
return int_return(await self._send('execAsync', command))
@@ -19,7 +32,10 @@ async def list(self) -> List[str]:
1932
return list_return(await self._send('list'))
2033

2134
async def getBlockPosition(self) -> Tuple[int, int, int]:
22-
return tuple(await self._send('getBlockPosition'))
35+
ret = await self._send('getBlockPosition')
36+
assert isinstance(ret, list)
37+
assert len(ret) == 3
38+
return tuple(ret)
2339

2440
async def getBlockInfo(self, x: int, y: int, z: int) -> dict:
2541
return dict_return(await self._send('getBlockInfo', x, y, z))

computercraft/subapis/disk.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
from typing import Optional
2-
from .base import BaseSubAPI, nil_return, bool_return, opt_str_return, opt_int_return
1+
from typing import Optional, Union
2+
from .base import BaseSubAPI, nil_return, bool_return, opt_str_return, opt_int_return, opt_str_bool_return
33

44

55
class DiskAPI(BaseSubAPI):
@@ -26,8 +26,8 @@ async def getID(self, side: str) -> Optional[int]:
2626
async def hasAudio(self, side: str) -> bool:
2727
return bool_return(await self._send('hasAudio', side))
2828

29-
async def getAudioTitle(self, side: str) -> Optional[str]:
30-
return opt_str_return(await self._send('getAudioTitle', side))
29+
async def getAudioTitle(self, side: str) -> Optional[Union[bool, str]]:
30+
return opt_str_bool_return(await self._send('getAudioTitle', side))
3131

3232
async def playAudio(self, side: str):
3333
return nil_return(await self._send('playAudio', side))

computercraft/subapis/fs.py

Lines changed: 47 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,29 @@
1-
from typing import Optional, Union, List
1+
from contextlib import asynccontextmanager
2+
from typing import Optional, List
3+
24
from .base import (
35
BaseSubAPI, lua_string,
4-
nil_return, opt_str_return, str_return, bool_return, int_return, list_return, opt_int_return,
6+
nil_return, opt_str_return, str_return, bool_return,
7+
int_return, list_return, dict_return,
58
)
6-
from uuid import uuid4
79

810

9-
class CCFile(BaseSubAPI):
10-
def __init__(self, cc, path, mode):
11+
class BaseHandle(BaseSubAPI):
12+
def __init__(self, cc, var):
1113
super().__init__(cc)
12-
self._path = path
13-
self._mode = mode
14-
15-
async def __aenter__(self):
16-
self._id = str(uuid4())
17-
self._API = 'temp[{}]'.format(lua_string(self._id))
18-
await self._cc._send_cmd('{} = fs.open({}, {})'.format(self._API, *map(lua_string, [
19-
self._path, self._mode
20-
])))
21-
return self
14+
self._API = var
2215

23-
async def __aexit__(self, exc_type, exc, tb):
24-
await self._cc._send_cmd('{}.close(); {} = nil'.format(self._API, self._API))
2516

26-
async def read(self) -> Optional[int]:
27-
return opt_int_return(await self._send('read'))
17+
class ReadHandle(BaseHandle):
18+
async def read(self, count: int) -> Optional[str]:
19+
return opt_str_return(await self._send('read', count))
2820

2921
async def readLine(self) -> Optional[str]:
3022
return opt_str_return(await self._send('readLine'))
3123

3224
async def readAll(self) -> str:
3325
return str_return(await self._send('readAll'))
3426

35-
async def write(self, data: Union[str, int]):
36-
return nil_return(await self._send('write', data))
37-
38-
async def writeLine(self, data: str):
39-
return nil_return(await self._send('writeLine', data))
40-
41-
async def flush(self):
42-
return nil_return(await self._send('flush'))
43-
4427
def __aiter__(self):
4528
return self
4629

@@ -51,6 +34,17 @@ async def __anext__(self):
5134
return line
5235

5336

37+
class WriteHandle(BaseHandle):
38+
async def write(self, text: str):
39+
return nil_return(await self._send('write', text))
40+
41+
async def writeLine(self, text: str):
42+
return nil_return(await self._send('writeLine', text))
43+
44+
async def flush(self):
45+
return nil_return(await self._send('flush'))
46+
47+
5448
class FSAPI(BaseSubAPI):
5549
_API = 'fs'
5650

@@ -66,9 +60,6 @@ async def isDir(self, path: str) -> bool:
6660
async def isReadOnly(self, path: str) -> bool:
6761
return bool_return(await self._send('isReadOnly', path))
6862

69-
async def getName(self, path: str) -> str:
70-
return str_return(await self._send('getName', path))
71-
7263
async def getDrive(self, path: str) -> Optional[str]:
7364
return opt_str_return(await self._send('getDrive', path))
7465

@@ -78,6 +69,9 @@ async def getSize(self, path: str) -> int:
7869
async def getFreeSpace(self, path: str) -> int:
7970
return int_return(await self._send('getFreeSpace', path))
8071

72+
async def getCapacity(self, path: str) -> int:
73+
return int_return(await self._send('getCapacity', path))
74+
8175
async def makeDir(self, path: str):
8276
return nil_return(await self._send('makeDir', path))
8377

@@ -93,7 +87,8 @@ async def delete(self, path: str):
9387
async def combine(self, basePath: str, localPath: str) -> str:
9488
return str_return(await self._send('combine', basePath, localPath))
9589

96-
def open(self, path: str, mode: str) -> CCFile:
90+
@asynccontextmanager
91+
async def open(self, path: str, mode: str):
9792
'''
9893
Usage:
9994
@@ -104,15 +99,32 @@ def open(self, path: str, mode: str) -> CCFile:
10499
async for line in f:
105100
...
106101
'''
107-
return CCFile(self._cc, path, mode)
102+
fid = self._cc._new_task_id()
103+
var = 'temp[{}]'.format(lua_string(fid))
104+
await self._cc._send_cmd('{} = fs.open({}, {})'.format(
105+
var, *map(lua_string, [path, mode])))
106+
try:
107+
yield (ReadHandle if mode == 'r' else WriteHandle)(self._cc, var)
108+
finally:
109+
await self._cc._send_cmd('{}.close(); {} = nil'.format(var, var))
108110

109111
async def find(self, wildcard: str) -> List[str]:
110112
return list_return(await self._send('find', wildcard))
111113

112114
async def getDir(self, path: str) -> str:
113115
return str_return(await self._send('getDir', path))
114116

117+
async def getName(self, path: str) -> str:
118+
return str_return(await self._send('getName', path))
119+
120+
async def isDriveRoot(self, path: str) -> bool:
121+
return bool_return(await self._send('isDriveRoot', path))
122+
115123
async def complete(
116-
self, partialName: str, path: str, includeFiles: bool = None, includeSlashes: bool = None,
124+
self, partialName: str, path: str, includeFiles: bool = None, includeDirs: bool = None,
117125
) -> List[str]:
118-
return list_return(await self._send('complete', partialName, path, includeFiles, includeSlashes))
126+
return list_return(await self._send(
127+
'complete', partialName, path, includeFiles, includeDirs, omit_nulls=False))
128+
129+
async def attributes(self, path: str) -> dict:
130+
return dict_return(await self._send('attributes', path))

computercraft/subapis/gps.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,12 @@
55
class GpsAPI(BaseSubAPI):
66
_API = 'gps'
77

8+
CHANNEL_GPS = 65534
9+
810
async def locate(self, timeout: LuaNum = None, debug: bool = None) -> Optional[Tuple[int, int, int]]:
9-
r = await self._send('locate', timeout, debug)
10-
if r == [None]:
11+
r = await self._send('locate', timeout, debug, omit_nulls=False)
12+
if r == []:
1113
return None
14+
assert isinstance(r, list)
15+
assert len(r) == 3
1216
return tuple(r)

computercraft/subapis/keys.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
from .base import BaseSubAPI, str_return
1+
from typing import Optional
2+
3+
from .base import BaseSubAPI, opt_str_return
24

35

46
class KeysAPI(BaseSubAPI):
57
_API = 'keys'
68

7-
async def getName(self, code: int) -> str:
8-
return str_return(await self._send('getName', code))
9+
async def getName(self, code: int) -> Optional[str]:
10+
return opt_str_return(await self._send('getName', code))

0 commit comments

Comments
 (0)