Rewrite to greenlets (remove awaits from user programs)
This commit is contained in:
parent
a948091980
commit
c4c6523c55
120
README.md
120
README.md
|
@ -1,6 +1,16 @@
|
|||
# Pythonized CC Tweaked (ComputerCraft) API
|
||||
|
||||
1. Enable localhost in mod server config
|
||||
**Warning**: CPython can't build safe sandboxes for arbitrary untrusted code
|
||||
[(read more)](https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html).
|
||||
Never use code in this repo if you don't trust your players!
|
||||
|
||||
1. Download and install wheel from github releases
|
||||
|
||||
```sh
|
||||
pip install computercraft-*.whl
|
||||
```
|
||||
|
||||
2. Enable localhost in mod server config
|
||||
|
||||
In case of singleplayer it's located inside your saves folder.
|
||||
In case of multiplayer check your server folder.
|
||||
|
@ -13,80 +23,90 @@
|
|||
action = "allow" # change here deny to allow
|
||||
```
|
||||
|
||||
2. Create module named `examplemod.py`:
|
||||
3. Start python server:
|
||||
|
||||
```python
|
||||
async def hello(api):
|
||||
await api.print('Hello world!')
|
||||
```
|
||||
|
||||
3. Start a server:
|
||||
|
||||
```bash
|
||||
python -m computercraft.server examplemod
|
||||
```sh
|
||||
python -m computercraft.server
|
||||
```
|
||||
|
||||
4. In minecraft, open up any computer and type:
|
||||
|
||||
```bash
|
||||
```sh
|
||||
wget http://127.0.0.1:8080/ py
|
||||
py hello
|
||||
py
|
||||
```
|
||||
|
||||
Now you have python REPL in computercraft!
|
||||
To quit REPL type `exit()` and press enter.
|
||||
|
||||
`py` is short Lua program that interacts with the server.
|
||||
Argument is the name of coroutine inside the module.
|
||||
`api` object contains almost everything *as is* in ComputerCraft documentation:
|
||||
`cc` module contains almost everything *as is* in ComputerCraft documentation:
|
||||
|
||||
```python
|
||||
async def program(api):
|
||||
await api.disk.eject('right')
|
||||
await api.print(await api.os.getComputerLabel())
|
||||
# ...
|
||||
```
|
||||
from cc import disk, os
|
||||
|
||||
Using python coroutines allows launching commands in parallel, effectively replacing `parallel` API:
|
||||
|
||||
```python
|
||||
async def program(api):
|
||||
# Since os.sleep is mostly waiting for events, it doesn't block execution of parallel threads
|
||||
# and this snippet takes approximately 2 seconds to complete.
|
||||
await asyncio.gather(api.os.sleep(2), api.os.sleep(2))
|
||||
disk.eject('right')
|
||||
print(os.getComputerLabel())
|
||||
```
|
||||
|
||||
Opening a file:
|
||||
|
||||
```python
|
||||
async def program(api):
|
||||
async with api.fs.open('filename', 'r') as f:
|
||||
async for line in f:
|
||||
await api.print(line)
|
||||
from cc import fs
|
||||
|
||||
with fs.open('filename', 'r') as f:
|
||||
for line in f:
|
||||
print(line)
|
||||
```
|
||||
|
||||
Capturing event:
|
||||
Waiting for event:
|
||||
|
||||
```python
|
||||
async def program(api):
|
||||
async with api.os.captureEvent('timer') as timer_event_queue:
|
||||
timer_id = await api.os.startTimer(2)
|
||||
async for etid, *_ in timer_event_queue:
|
||||
if etid == timer_id:
|
||||
await api.print('Timer reached')
|
||||
break
|
||||
from cc import os
|
||||
|
||||
timer_id = os.startTimer(2)
|
||||
while True:
|
||||
e = os.pullEvent('timer')
|
||||
if e[1] == timer_id:
|
||||
print('Timer reached')
|
||||
break
|
||||
```
|
||||
|
||||
Using modems:
|
||||
|
||||
```python
|
||||
async def program(api):
|
||||
modem = await api.peripheral.wrap('back')
|
||||
listen_channel = 5
|
||||
async with modem.receive(listen_channel) as q:
|
||||
async for msg in q:
|
||||
await api.print(repr(msg))
|
||||
if msg.content == 'stop':
|
||||
break
|
||||
else:
|
||||
await modem.transmit(msg.reply_channel, listen_channel, msg.content)
|
||||
from cc import peripheral
|
||||
|
||||
modem = peripheral.wrap('back')
|
||||
listen_channel = 5
|
||||
# this automatically opens and closes modem on listen_channel
|
||||
for msg in modem.receive(listen_channel):
|
||||
print(repr(msg))
|
||||
if msg.content == 'stop':
|
||||
break
|
||||
else:
|
||||
modem.transmit(msg.reply_channel, listen_channel, msg.content)
|
||||
```
|
||||
|
||||
More examples can be found in `testmod.py`.
|
||||
Using parallel:
|
||||
|
||||
```python
|
||||
from cc import parallel, os
|
||||
|
||||
def fn():
|
||||
os.sleep(2)
|
||||
print('done')
|
||||
|
||||
parallel.waitForAll(fn, fn, fn)
|
||||
```
|
||||
|
||||
Importing in-game files as modules:
|
||||
|
||||
```python
|
||||
from cc import import_file
|
||||
|
||||
p = import_file('/disk/program.py') # absolute
|
||||
m = import_file('lib.py', __file__) # relative to current file
|
||||
```
|
||||
|
||||
More examples can be found in repository.
|
||||
|
|
|
@ -56,6 +56,11 @@ while true do
|
|||
ycounts[msg.task_id] = 0
|
||||
end
|
||||
end
|
||||
elseif msg.action == 'drop' then
|
||||
for _, task_id in ipairs(msg.task_ids) do
|
||||
tasks[task_id] = nil
|
||||
ycounts[task_id] = nil
|
||||
end
|
||||
elseif msg.action == 'sub' then
|
||||
event_sub[msg.event] = true
|
||||
elseif msg.action == 'unsub' then
|
||||
|
@ -88,12 +93,12 @@ while true do
|
|||
})
|
||||
del_tasks[task_id] = true
|
||||
else
|
||||
ycounts[msg.task_id] = ycounts[msg.task_id] + 1
|
||||
ycounts[task_id] = ycounts[task_id] + 1
|
||||
end
|
||||
end
|
||||
for task_id in pairs(del_tasks) do
|
||||
tasks[task_id] = nil
|
||||
ycounts[msg.task_id] = nil
|
||||
ycounts[task_id] = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -73,3 +73,11 @@ def lua_args(*params):
|
|||
idx = -1
|
||||
params = params[:idx + 1]
|
||||
return ', '.join(lua_value(p) for p in params)
|
||||
|
||||
|
||||
def lua_call(name, *params):
|
||||
return '{}({})'.format(name, lua_args(*params))
|
||||
|
||||
|
||||
def return_lua_call(name, *params):
|
||||
return 'return ' + lua_call(name, *params)
|
||||
|
|
|
@ -1,173 +1,15 @@
|
|||
import argparse
|
||||
import asyncio
|
||||
import json
|
||||
import string
|
||||
from aiohttp import web, WSMsgType
|
||||
from contextlib import asynccontextmanager
|
||||
from traceback import print_exc
|
||||
from os.path import join, dirname, abspath
|
||||
from importlib import import_module
|
||||
import argparse
|
||||
|
||||
from .subapis.root import RootAPIMixin
|
||||
from .lua import lua_string
|
||||
from . import rproc
|
||||
from aiohttp import web, WSMsgType
|
||||
|
||||
from .subapis.colors import ColorsAPI
|
||||
from .subapis.commands import CommandsAPI
|
||||
from .subapis.disk import DiskAPI
|
||||
from .subapis.fs import FSAPI
|
||||
from .subapis.gps import GpsAPI
|
||||
from .subapis.help import HelpAPI
|
||||
from .subapis.keys import KeysAPI
|
||||
from .subapis.multishell import MultishellAPI
|
||||
from .subapis.os import OSAPI
|
||||
from .subapis.paintutils import PaintutilsAPI
|
||||
from .subapis.peripheral import PeripheralAPI
|
||||
from .subapis.pocket import PocketAPI
|
||||
from .subapis.rednet import RednetAPI
|
||||
from .subapis.redstone import RedstoneAPI
|
||||
from .subapis.settings import SettingsAPI
|
||||
from .subapis.shell import ShellAPI
|
||||
from .subapis.term import TermAPI
|
||||
from .subapis.textutils import TextutilsAPI
|
||||
from .subapis.turtle import TurtleAPI
|
||||
from .subapis.window import WindowAPI
|
||||
from .sess import CCSession
|
||||
|
||||
|
||||
THIS_DIR = dirname(abspath(__file__))
|
||||
LUA_FILE = join(THIS_DIR, 'back.lua')
|
||||
DIGITS = string.digits + string.ascii_lowercase
|
||||
|
||||
|
||||
def base36(n):
|
||||
r = ''
|
||||
while n:
|
||||
r += DIGITS[n % 36]
|
||||
n //= 36
|
||||
return r[::-1]
|
||||
|
||||
|
||||
class CCAPI(RootAPIMixin):
|
||||
def __init__(self, nid, program, sender):
|
||||
self._id = nid
|
||||
self._task_autoid = 1
|
||||
self._result_locks = {}
|
||||
self._result_values = {}
|
||||
self._result_queues = {}
|
||||
self._event_to_tids = {}
|
||||
self._tid_to_event = {}
|
||||
self._sender = sender
|
||||
|
||||
self.colors = ColorsAPI(self, 'colors')
|
||||
self.commands = CommandsAPI(self, 'commands')
|
||||
self.disk = DiskAPI(self, 'disk')
|
||||
self.fs = FSAPI(self, 'fs')
|
||||
self.gps = GpsAPI(self, 'gps')
|
||||
self.help = HelpAPI(self, 'help')
|
||||
self.keys = KeysAPI(self, 'keys')
|
||||
self.multishell = MultishellAPI(self, 'multishell')
|
||||
self.os = OSAPI(self, 'os')
|
||||
self.paintutils = PaintutilsAPI(self, 'paintutils')
|
||||
self.peripheral = PeripheralAPI(self, 'peripheral')
|
||||
self.pocket = PocketAPI(self, 'pocket')
|
||||
self.rednet = RednetAPI(self, 'rednet')
|
||||
self.redstone = RedstoneAPI(self, 'redstone')
|
||||
self.settings = SettingsAPI(self, 'settings')
|
||||
self.shell = ShellAPI(self, 'shell')
|
||||
self.term = TermAPI(self, 'term')
|
||||
self.textutils = TextutilsAPI(self, 'textutils')
|
||||
self.turtle = TurtleAPI(self, 'turtle')
|
||||
self.window = WindowAPI(self, 'window')
|
||||
|
||||
async def prog_wrap():
|
||||
err = None
|
||||
cancel = False
|
||||
try:
|
||||
await program(self)
|
||||
except asyncio.CancelledError:
|
||||
print('program {} cancelled'.format(self._id))
|
||||
print_exc()
|
||||
err = 'program has been cancelled'
|
||||
cancel = True
|
||||
except Exception as e:
|
||||
print('program {} crashed: {} {}'.format(self._id, type(e), e))
|
||||
print_exc()
|
||||
err = type(e).__name__ + ': ' + str(e)
|
||||
else:
|
||||
print('program {} finished'.format(self._id))
|
||||
finally:
|
||||
if not cancel:
|
||||
c = {'action': 'close'}
|
||||
if err is not None:
|
||||
c['error'] = err
|
||||
await self._sender(c)
|
||||
|
||||
self._task = asyncio.create_task(prog_wrap())
|
||||
|
||||
def cancel(self):
|
||||
self._task.cancel()
|
||||
|
||||
def _new_task_id(self) -> str:
|
||||
task_id = base36(self._task_autoid)
|
||||
self._task_autoid += 1
|
||||
return task_id
|
||||
|
||||
async def _eval(self, lua_code, immediate=False):
|
||||
task_id = self._new_task_id()
|
||||
self._result_locks[task_id] = asyncio.Event()
|
||||
await self._sender({
|
||||
'action': 'task',
|
||||
'task_id': task_id,
|
||||
'code': lua_code,
|
||||
'immediate': immediate,
|
||||
})
|
||||
await self._result_locks[task_id].wait()
|
||||
del self._result_locks[task_id]
|
||||
result = self._result_values.pop(task_id)
|
||||
print('{} → {}'.format(lua_code, repr(result)))
|
||||
return result
|
||||
|
||||
async def eval(self, lua_code):
|
||||
return await self._eval(lua_code, True)
|
||||
|
||||
async def eval_coro(self, lua_code):
|
||||
return rproc.coro(await self._eval(lua_code, False))
|
||||
|
||||
async def _start_queue(self, event):
|
||||
task_id = self._new_task_id()
|
||||
self._result_queues[task_id] = asyncio.Queue()
|
||||
es = self._event_to_tids.setdefault(event, set())
|
||||
if not es:
|
||||
await self._sender({
|
||||
'action': 'sub',
|
||||
'event': event,
|
||||
})
|
||||
es.add(task_id)
|
||||
self._tid_to_event[task_id] = event
|
||||
return self._result_queues[task_id], task_id
|
||||
|
||||
async def _stop_queue(self, task_id):
|
||||
event = self._tid_to_event[task_id]
|
||||
del self._result_queues[task_id]
|
||||
del self._tid_to_event[task_id]
|
||||
self._event_to_tids[event].discard(task_id)
|
||||
if not self._event_to_tids[event]:
|
||||
await self._sender({
|
||||
'action': 'unsub',
|
||||
'event': event,
|
||||
})
|
||||
|
||||
@asynccontextmanager
|
||||
async def _create_temp_object(self, create_expr: str, finalizer_template: str = ''):
|
||||
fid = self._new_task_id()
|
||||
var = 'temp[{}]'.format(lua_string(fid))
|
||||
await self.eval_coro('{} = {}'.format(var, create_expr))
|
||||
try:
|
||||
yield var
|
||||
finally:
|
||||
finalizer_template += '; {e} = nil'
|
||||
finalizer_template = finalizer_template.lstrip(' ;')
|
||||
await self.eval_coro(finalizer_template.format(e=var))
|
||||
|
||||
|
||||
class CCApplication(web.Application):
|
||||
|
@ -188,46 +30,34 @@ class CCApplication(web.Application):
|
|||
'error': 'protocol error',
|
||||
})
|
||||
return None
|
||||
if len(msg['args']) == 0:
|
||||
await ws.send_json({
|
||||
'action': 'close',
|
||||
'error': 'arguments required',
|
||||
})
|
||||
return None
|
||||
module = import_module(self['source_module'])
|
||||
program = getattr(module, msg['args'][0], None)
|
||||
if program is None:
|
||||
await ws.send_json({
|
||||
'action': 'close',
|
||||
'error': "program doesn't exist",
|
||||
})
|
||||
return None
|
||||
|
||||
return CCAPI(msg['computer'], program, ws.send_json)
|
||||
def sender(data):
|
||||
asyncio.create_task(ws.send_json(data))
|
||||
|
||||
sess = CCSession(msg['computer'], sender)
|
||||
if msg['args']:
|
||||
sess.run_program(msg['args'][0])
|
||||
else:
|
||||
sess.run_repl()
|
||||
return sess
|
||||
|
||||
async def ws(self, request):
|
||||
ws = web.WebSocketResponse()
|
||||
await ws.prepare(request)
|
||||
|
||||
api = await self._launch_program(ws)
|
||||
if api is not None:
|
||||
try:
|
||||
async for msg in self._json_messages(ws):
|
||||
if msg['action'] == 'event':
|
||||
for task_id in api._event_to_tids.get(msg['event'], ()):
|
||||
await api._result_queues[task_id].put(msg['params'])
|
||||
elif msg['action'] == 'task_result':
|
||||
api._result_values[msg['task_id']] = msg['result']
|
||||
api._result_locks[msg['task_id']].set()
|
||||
# print(msg['task_id'], msg['yields'])
|
||||
else:
|
||||
await ws.send_json({
|
||||
'action': 'close',
|
||||
'error': 'protocol error',
|
||||
})
|
||||
break
|
||||
finally:
|
||||
api.cancel()
|
||||
sess = await self._launch_program(ws)
|
||||
if sess is not None:
|
||||
async for msg in self._json_messages(ws):
|
||||
if msg['action'] == 'event':
|
||||
pass
|
||||
elif msg['action'] == 'task_result':
|
||||
sess.on_task_result(msg['task_id'], msg['result'])
|
||||
else:
|
||||
await ws.send_json({
|
||||
'action': 'close',
|
||||
'error': 'protocol error',
|
||||
})
|
||||
break
|
||||
|
||||
return ws
|
||||
|
||||
|
@ -245,16 +75,13 @@ class CCApplication(web.Application):
|
|||
)
|
||||
return web.Response(text=fcont)
|
||||
|
||||
def initialize(self, source_module):
|
||||
self['source_module'] = source_module
|
||||
self['exchange'] = {}
|
||||
def initialize(self):
|
||||
self.router.add_get('/', self.backdoor)
|
||||
self.router.add_get('/ws/', self.ws)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('module', help='Module used as source for programs')
|
||||
parser.add_argument('--host')
|
||||
parser.add_argument('--port', type=int, default=8080)
|
||||
args = parser.parse_args()
|
||||
|
@ -266,7 +93,7 @@ def main():
|
|||
|
||||
app = CCApplication()
|
||||
app['port'] = args.port
|
||||
app.initialize(args.module)
|
||||
app.initialize()
|
||||
web.run_app(app, **app_kw)
|
||||
|
||||
|
||||
|
|
315
computercraft/sess.py
Normal file
315
computercraft/sess.py
Normal file
|
@ -0,0 +1,315 @@
|
|||
import asyncio
|
||||
import string
|
||||
import sys
|
||||
from code import InteractiveConsole
|
||||
from contextlib import contextmanager
|
||||
from functools import partial
|
||||
from importlib import import_module
|
||||
from importlib.abc import MetaPathFinder, Loader
|
||||
from importlib.machinery import ModuleSpec
|
||||
from itertools import count
|
||||
from traceback import format_exc
|
||||
from types import ModuleType
|
||||
|
||||
from greenlet import greenlet, getcurrent as get_current_greenlet
|
||||
|
||||
from .lua import lua_string, lua_call, return_lua_call
|
||||
from . import rproc
|
||||
|
||||
|
||||
__all__ = (
|
||||
'CCSession',
|
||||
'get_current_session',
|
||||
'eval_lua',
|
||||
'lua_context_object',
|
||||
)
|
||||
|
||||
|
||||
def debug(*args):
|
||||
sys.__stdout__.write(' '.join(map(str, args)) + '\n')
|
||||
sys.__stdout__.flush()
|
||||
|
||||
|
||||
DIGITS = string.digits + string.ascii_lowercase
|
||||
|
||||
|
||||
def base36(n):
|
||||
r = ''
|
||||
while n:
|
||||
r += DIGITS[n % 36]
|
||||
n //= 36
|
||||
return r[::-1]
|
||||
|
||||
|
||||
def _is_global_greenlet():
|
||||
return not hasattr(get_current_greenlet(), 'cc_greenlet')
|
||||
|
||||
|
||||
def get_current_session():
|
||||
try:
|
||||
return get_current_greenlet().cc_greenlet._sess
|
||||
except AttributeError:
|
||||
raise RuntimeError('Computercraft function was called outside context')
|
||||
|
||||
|
||||
class StdFileProxy:
|
||||
def __init__(self, native):
|
||||
self._native = native
|
||||
|
||||
def read(self, size=-1):
|
||||
if _is_global_greenlet():
|
||||
return self._native.read(size)
|
||||
else:
|
||||
raise RuntimeError(
|
||||
"Computercraft environment doesn't support stdin read method")
|
||||
|
||||
def readline(self, size=-1):
|
||||
if _is_global_greenlet():
|
||||
return self._native.readline(size)
|
||||
else:
|
||||
if size is not None and size >= 0:
|
||||
raise RuntimeError(
|
||||
"Computercraft environment doesn't support "
|
||||
"stdin readline method with parameter")
|
||||
return rproc.string(eval_lua(
|
||||
return_lua_call('io.read')
|
||||
))
|
||||
|
||||
def write(self, s):
|
||||
if _is_global_greenlet():
|
||||
return self._native.write(s)
|
||||
else:
|
||||
return rproc.nil(eval_lua(
|
||||
lua_call('io.write', s)
|
||||
))
|
||||
|
||||
def fileno(self):
|
||||
if _is_global_greenlet():
|
||||
return self._native.fileno()
|
||||
else:
|
||||
# preventing use of gnu readline here
|
||||
# https://github.com/python/cpython/blob/master/Python/bltinmodule.c#L1970
|
||||
raise AttributeError
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._native, name)
|
||||
|
||||
|
||||
class ComputerCraftFinder(MetaPathFinder):
|
||||
@staticmethod
|
||||
def find_spec(fullname, path, target=None):
|
||||
if fullname == 'cc':
|
||||
return ModuleSpec(fullname, ComputerCraftLoader, is_package=True)
|
||||
if fullname.startswith('cc.'):
|
||||
return ModuleSpec(fullname, ComputerCraftLoader, is_package=False)
|
||||
|
||||
|
||||
class ComputerCraftLoader(Loader):
|
||||
@staticmethod
|
||||
def create_module(spec):
|
||||
sn = spec.name.split('.', 1)
|
||||
assert sn[0] == 'cc'
|
||||
if len(sn) == 1:
|
||||
sn.append('_pkg')
|
||||
rawmod = import_module('.' + sn[1], 'computercraft.subapis')
|
||||
mod = ModuleType(spec.name)
|
||||
for k in rawmod.__all__:
|
||||
setattr(mod, k, getattr(rawmod, k))
|
||||
return mod
|
||||
|
||||
@staticmethod
|
||||
def exec_module(module):
|
||||
pass
|
||||
|
||||
|
||||
def install_import_hook():
|
||||
import sys
|
||||
sys.meta_path.append(ComputerCraftFinder)
|
||||
|
||||
|
||||
install_import_hook()
|
||||
sys.stdin = StdFileProxy(sys.__stdin__)
|
||||
sys.stdout = StdFileProxy(sys.__stdout__)
|
||||
sys.stderr = StdFileProxy(sys.__stderr__)
|
||||
|
||||
|
||||
def eval_lua(lua_code, immediate=False):
|
||||
result = get_current_session()._server_greenlet.switch({
|
||||
'code': lua_code,
|
||||
'immediate': immediate,
|
||||
})
|
||||
# do not uncomment this, or use sys.__stdout__.write
|
||||
# print('{} → {}'.format(lua_code, repr(result)))
|
||||
if not immediate:
|
||||
result = rproc.coro(result)
|
||||
return result
|
||||
|
||||
|
||||
@contextmanager
|
||||
def lua_context_object(create_expr: str, finalizer_template: str = ''):
|
||||
sess = get_current_session()
|
||||
fid = sess.create_task_id()
|
||||
var = 'temp[{}]'.format(lua_string(fid))
|
||||
eval_lua('{} = {}'.format(var, create_expr))
|
||||
try:
|
||||
yield var
|
||||
finally:
|
||||
finalizer_template += '; {e} = nil'
|
||||
finalizer_template = finalizer_template.lstrip(' ;')
|
||||
eval_lua(finalizer_template.format(e=var))
|
||||
|
||||
|
||||
def eval_lua_method_factory(obj):
|
||||
def method(name, *params):
|
||||
return eval_lua(return_lua_call(obj + name, *params))
|
||||
return method
|
||||
|
||||
|
||||
class CCGreenlet:
|
||||
def __init__(self, body_fn, sess=None):
|
||||
if sess is None:
|
||||
self._sess = get_current_session()
|
||||
else:
|
||||
self._sess = sess
|
||||
|
||||
self._task_id = self._sess.create_task_id()
|
||||
self._sess._greenlets[self._task_id] = self
|
||||
|
||||
parent_g = get_current_greenlet()
|
||||
if parent_g is self._sess._server_greenlet:
|
||||
self._parent = None
|
||||
debug(self._task_id, 'G.__init__', 'parent None')
|
||||
else:
|
||||
self._parent = parent_g.cc_greenlet
|
||||
self._parent._children.add(self._task_id)
|
||||
debug(self._task_id, 'G.__init__', 'parent', self._parent._task_id)
|
||||
|
||||
self._children = set()
|
||||
self._g = greenlet(body_fn)
|
||||
self._g.cc_greenlet = self
|
||||
|
||||
def detach_children(self):
|
||||
if self._children:
|
||||
debug(self._task_id, 'G.detach_children', self._children)
|
||||
ch = list(self._children)
|
||||
self._children.clear()
|
||||
self._sess.drop(ch)
|
||||
|
||||
def _on_death(self, error=None):
|
||||
debug(self._task_id, 'G._on_death', error)
|
||||
self._sess._greenlets.pop(self._task_id, None)
|
||||
self.detach_children()
|
||||
if error is not None:
|
||||
if error is True:
|
||||
error = {}
|
||||
self._sess._sender({'action': 'close', **error})
|
||||
if self._parent is not None:
|
||||
self._parent._children.discard(self._task_id)
|
||||
|
||||
def defer_switch(self, *args, **kwargs):
|
||||
asyncio.get_running_loop().call_soon(
|
||||
partial(self.switch, *args, **kwargs))
|
||||
|
||||
def switch(self, *args, **kwargs):
|
||||
# switch must be called from server greenlet
|
||||
assert get_current_greenlet() is self._sess._server_greenlet
|
||||
debug(self._task_id, 'G.switch', args, kwargs)
|
||||
try:
|
||||
task = self._g.switch(*args, **kwargs)
|
||||
except SystemExit:
|
||||
debug(self._task_id, 'G.switch: ', 'SystemExit')
|
||||
self._on_death(True)
|
||||
return
|
||||
except Exception as e:
|
||||
debug(self._task_id, 'G.switch: ', 'Exception', e, type(e), format_exc(limit=None, chain=False))
|
||||
self._on_death({'error': format_exc(limit=None, chain=False)})
|
||||
return
|
||||
|
||||
debug(self._task_id, 'G.switch: result', repr(task))
|
||||
|
||||
# lua_eval call or simply idle
|
||||
if isinstance(task, dict):
|
||||
x = self
|
||||
while x._g.dead:
|
||||
x = x._parent
|
||||
tid = x._task_id
|
||||
debug(self._task_id, 'G.switch: start task for', tid)
|
||||
self._sess._sender({
|
||||
'action': 'task',
|
||||
'task_id': tid,
|
||||
**task,
|
||||
})
|
||||
else:
|
||||
debug(self._task_id, f'G.switch: {"dead" if self._g.dead else "idle"}')
|
||||
|
||||
if self._g.dead:
|
||||
if self._parent is None:
|
||||
self._on_death(True)
|
||||
else:
|
||||
self._on_death()
|
||||
|
||||
|
||||
class CCSession:
|
||||
def __init__(self, computer_id, sender):
|
||||
# computer_id is unique identifier of a CCSession
|
||||
self._computer_id = computer_id
|
||||
self._tid_allocator = map(base36, count(start=1))
|
||||
self._sender = sender
|
||||
self._greenlets = {}
|
||||
self._server_greenlet = get_current_greenlet()
|
||||
self._program_greenlet = None
|
||||
|
||||
def on_task_result(self, task_id, result):
|
||||
assert get_current_greenlet() is self._server_greenlet
|
||||
if task_id not in self._greenlets:
|
||||
# ignore for dropped tasks
|
||||
return
|
||||
debug('on_task_result', task_id, result)
|
||||
self._greenlets[task_id].switch(result)
|
||||
|
||||
def create_task_id(self):
|
||||
return next(self._tid_allocator)
|
||||
|
||||
def drop(self, task_ids):
|
||||
def collect(task_id):
|
||||
yield task_id
|
||||
g = self._greenlets.pop(task_id)
|
||||
for tid in g._children:
|
||||
yield from collect(tid)
|
||||
|
||||
all_tids = []
|
||||
for task_id in task_ids:
|
||||
all_tids.extend(collect(task_id))
|
||||
|
||||
debug('Sess.drop', all_tids)
|
||||
|
||||
self._sender({
|
||||
'action': 'drop',
|
||||
'task_ids': all_tids,
|
||||
})
|
||||
|
||||
def _run_sandboxed_greenlet(self, fn):
|
||||
self._program_greenlet = CCGreenlet(fn, sess=self)
|
||||
self._program_greenlet.switch()
|
||||
|
||||
def run_program(self, program):
|
||||
def _run_program():
|
||||
p, code = eval_lua('''
|
||||
local p = fs.combine(shell.dir(), {})
|
||||
if not fs.exists(p) then return nil end
|
||||
if fs.isDir(p) then return nil end
|
||||
local f = fs.open(p, 'r')
|
||||
local code = f.readAll()
|
||||
f.close()
|
||||
return p, code
|
||||
'''.lstrip().format(lua_string(program)))
|
||||
cc = compile(code, p, 'exec')
|
||||
exec(cc, {'__file__': p})
|
||||
|
||||
self._run_sandboxed_greenlet(_run_program)
|
||||
|
||||
def run_repl(self):
|
||||
def _repl():
|
||||
InteractiveConsole(locals={}).interact()
|
||||
|
||||
self._run_sandboxed_greenlet(_repl)
|
60
computercraft/subapis/_pkg.py
Normal file
60
computercraft/subapis/_pkg.py
Normal file
|
@ -0,0 +1,60 @@
|
|||
from types import ModuleType
|
||||
|
||||
from ..errors import LuaException
|
||||
from ..lua import lua_string
|
||||
from ..rproc import boolean, option_string
|
||||
from ..sess import eval_lua
|
||||
|
||||
|
||||
__all__ = (
|
||||
'import_file',
|
||||
'is_commands',
|
||||
'is_multishell',
|
||||
'is_turtle',
|
||||
'is_pocket',
|
||||
'eval_lua',
|
||||
'LuaException',
|
||||
)
|
||||
|
||||
|
||||
def import_file(path: str, relative_to: str = None):
|
||||
mod = ModuleType(path)
|
||||
mod.__file__ = path
|
||||
path_expr = lua_string(path)
|
||||
if relative_to is not None:
|
||||
path_expr = 'fs.combine(fs.getDir({}), {})'.format(
|
||||
lua_string(relative_to),
|
||||
path_expr,
|
||||
)
|
||||
source = option_string(eval_lua('''
|
||||
local p = {}
|
||||
if not fs.exists(p) then return nil end
|
||||
if fs.isDir(p) then return nil end
|
||||
f = fs.open(p, "r")
|
||||
local src = f.readAll()
|
||||
f.close()
|
||||
return src
|
||||
'''.lstrip().format(
|
||||
path_expr,
|
||||
)))
|
||||
if source is None:
|
||||
raise ImportError('File not found: {}'.format(path))
|
||||
cc = compile(source, mod.__name__, 'exec')
|
||||
exec(cc, vars(mod))
|
||||
return mod
|
||||
|
||||
|
||||
def is_commands() -> bool:
|
||||
return boolean(eval_lua('return commands ~= nil'))
|
||||
|
||||
|
||||
def is_multishell() -> bool:
|
||||
return boolean(eval_lua('return multishell ~= nil'))
|
||||
|
||||
|
||||
def is_turtle() -> bool:
|
||||
return boolean(eval_lua('return turtle ~= nil'))
|
||||
|
||||
|
||||
def is_pocket() -> bool:
|
||||
return boolean(eval_lua('return pocket ~= nil'))
|
|
@ -1,18 +1,15 @@
|
|||
from ..lua import LuaExpr, lua_args
|
||||
from ..sess import eval_lua
|
||||
|
||||
|
||||
class BaseSubAPI(LuaExpr):
|
||||
def __init__(self, cc, lua_expr):
|
||||
self._cc = cc
|
||||
def __init__(self, lua_expr):
|
||||
self._lua_expr = lua_expr
|
||||
|
||||
def get_expr_code(self):
|
||||
return self._lua_expr
|
||||
|
||||
async def _send(self, method, *params):
|
||||
return await self._method(method, *params)
|
||||
|
||||
async def _method(self, name, *params):
|
||||
return await self._cc.eval_coro('return {}.{}({})'.format(
|
||||
self._lua_expr, name, lua_args(*params),
|
||||
def _method(self, name, *params):
|
||||
return eval_lua('return {}.{}({})'.format(
|
||||
self.get_expr_code(), name, lua_args(*params),
|
||||
))
|
||||
|
|
|
@ -1,64 +1,100 @@
|
|||
from typing import Tuple
|
||||
|
||||
from .base import BaseSubAPI
|
||||
from ..rproc import boolean, integer, tuple3_number
|
||||
from ..sess import eval_lua_method_factory
|
||||
|
||||
|
||||
class ColorsAPI(BaseSubAPI):
|
||||
white = 0x1
|
||||
orange = 0x2
|
||||
magenta = 0x4
|
||||
lightBlue = 0x8
|
||||
yellow = 0x10
|
||||
lime = 0x20
|
||||
pink = 0x40
|
||||
gray = 0x80
|
||||
lightGray = 0x100
|
||||
cyan = 0x200
|
||||
purple = 0x400
|
||||
blue = 0x800
|
||||
brown = 0x1000
|
||||
green = 0x2000
|
||||
red = 0x4000
|
||||
black = 0x8000
|
||||
method = eval_lua_method_factory('colors.')
|
||||
|
||||
# use these chars for term.blit
|
||||
chars = {
|
||||
'0': white,
|
||||
'1': orange,
|
||||
'2': magenta,
|
||||
'3': lightBlue,
|
||||
'4': yellow,
|
||||
'5': lime,
|
||||
'6': pink,
|
||||
'7': gray,
|
||||
'8': lightGray,
|
||||
'9': cyan,
|
||||
'a': purple,
|
||||
'b': blue,
|
||||
'c': brown,
|
||||
'd': green,
|
||||
'e': red,
|
||||
'f': black,
|
||||
}
|
||||
|
||||
def __iter__(self):
|
||||
for c in self.chars.values():
|
||||
yield c
|
||||
__all__ = (
|
||||
'white',
|
||||
'orange',
|
||||
'magenta',
|
||||
'lightBlue',
|
||||
'yellow',
|
||||
'lime',
|
||||
'pink',
|
||||
'gray',
|
||||
'lightGray',
|
||||
'cyan',
|
||||
'purple',
|
||||
'blue',
|
||||
'brown',
|
||||
'green',
|
||||
'red',
|
||||
'black',
|
||||
'combine',
|
||||
'subtract',
|
||||
'test',
|
||||
'packRGB',
|
||||
'unpackRGB',
|
||||
'chars',
|
||||
'iter_colors',
|
||||
)
|
||||
|
||||
# combine, subtract and test are mostly for redstone.setBundledOutput
|
||||
|
||||
async def combine(self, *colors: int) -> int:
|
||||
return integer(await self._send('combine', *colors))
|
||||
white = 0x1
|
||||
orange = 0x2
|
||||
magenta = 0x4
|
||||
lightBlue = 0x8
|
||||
yellow = 0x10
|
||||
lime = 0x20
|
||||
pink = 0x40
|
||||
gray = 0x80
|
||||
lightGray = 0x100
|
||||
cyan = 0x200
|
||||
purple = 0x400
|
||||
blue = 0x800
|
||||
brown = 0x1000
|
||||
green = 0x2000
|
||||
red = 0x4000
|
||||
black = 0x8000
|
||||
|
||||
async def subtract(self, color_set: int, *colors: int) -> int:
|
||||
return integer(await self._send('subtract', color_set, *colors))
|
||||
|
||||
async def test(self, colors: int, color: int) -> bool:
|
||||
return boolean(await self._send('test', colors, color))
|
||||
# combine, subtract and test are mostly for redstone.setBundledOutput
|
||||
|
||||
async def packRGB(self, r: float, g: float, b: float) -> int:
|
||||
return integer(await self._send('packRGB', r, g, b))
|
||||
def combine(*colors: int) -> int:
|
||||
return integer(method('combine', *colors))
|
||||
|
||||
async def unpackRGB(self, rgb: int) -> Tuple[float, float, float]:
|
||||
return tuple3_number(await self._send('unpackRGB', rgb))
|
||||
|
||||
def subtract(color_set: int, *colors: int) -> int:
|
||||
return integer(method('subtract', color_set, *colors))
|
||||
|
||||
|
||||
def test(colors: int, color: int) -> bool:
|
||||
return boolean(method('test', colors, color))
|
||||
|
||||
|
||||
def packRGB(r: float, g: float, b: float) -> int:
|
||||
return integer(method('packRGB', r, g, b))
|
||||
|
||||
|
||||
def unpackRGB(rgb: int) -> Tuple[float, float, float]:
|
||||
return tuple3_number(method('unpackRGB', rgb))
|
||||
|
||||
|
||||
# use these chars for term.blit
|
||||
chars = {
|
||||
'0': white,
|
||||
'1': orange,
|
||||
'2': magenta,
|
||||
'3': lightBlue,
|
||||
'4': yellow,
|
||||
'5': lime,
|
||||
'6': pink,
|
||||
'7': gray,
|
||||
'8': lightGray,
|
||||
'9': cyan,
|
||||
'a': purple,
|
||||
'b': blue,
|
||||
'c': brown,
|
||||
'd': green,
|
||||
'e': red,
|
||||
'f': black,
|
||||
}
|
||||
|
||||
|
||||
def iter_colors(self):
|
||||
for c in self.chars.values():
|
||||
yield c
|
||||
|
|
|
@ -1,24 +1,37 @@
|
|||
from typing import Tuple, List, Optional
|
||||
|
||||
from .base import BaseSubAPI
|
||||
from ..rproc import tuple3_integer, any_dict, any_list, array_string, fact_tuple, boolean, option_integer
|
||||
from ..sess import eval_lua_method_factory
|
||||
|
||||
|
||||
command_result = fact_tuple(boolean, array_string, option_integer, tail_nils=1)
|
||||
method = eval_lua_method_factory('commands.')
|
||||
|
||||
|
||||
class CommandsAPI(BaseSubAPI):
|
||||
async def exec(self, command: str) -> Tuple[bool, List[str], Optional[int]]:
|
||||
return command_result(await self._send('exec', command))
|
||||
__all__ = (
|
||||
'exec',
|
||||
'list',
|
||||
'getBlockPosition',
|
||||
'getBlockInfo',
|
||||
'getBlockInfos',
|
||||
)
|
||||
|
||||
async def list(self) -> List[str]:
|
||||
return array_string(await self._send('list'))
|
||||
|
||||
async def getBlockPosition(self) -> Tuple[int, int, int]:
|
||||
return tuple3_integer(await self._send('getBlockPosition'))
|
||||
def exec(command: str) -> Tuple[bool, List[str], Optional[int]]:
|
||||
return command_result(method('exec', command))
|
||||
|
||||
async def getBlockInfo(self, x: int, y: int, z: int) -> dict:
|
||||
return any_dict(await self._send('getBlockInfo', x, y, z))
|
||||
|
||||
async def getBlockInfos(self, x1: int, y1: int, z1: int, x2: int, y2: int, z2: int) -> List[dict]:
|
||||
return any_list(await self._send('getBlockInfos', x1, y1, z1, x2, y2, z2))
|
||||
def list() -> List[str]:
|
||||
return array_string(method('list'))
|
||||
|
||||
|
||||
def getBlockPosition() -> Tuple[int, int, int]:
|
||||
return tuple3_integer(method('getBlockPosition'))
|
||||
|
||||
|
||||
def getBlockInfo(x: int, y: int, z: int) -> dict:
|
||||
return any_dict(method('getBlockInfo', x, y, z))
|
||||
|
||||
|
||||
def getBlockInfos(x1: int, y1: int, z1: int, x2: int, y2: int, z2: int) -> List[dict]:
|
||||
return any_list(method('getBlockInfos', x1, y1, z1, x2, y2, z2))
|
||||
|
|
|
@ -1,39 +1,66 @@
|
|||
from typing import Optional, Union
|
||||
|
||||
from .base import BaseSubAPI
|
||||
from ..rproc import boolean, nil, option_integer, option_string, option_string_bool
|
||||
from ..sess import eval_lua_method_factory
|
||||
|
||||
|
||||
class DiskAPI(BaseSubAPI):
|
||||
async def isPresent(self, side: str) -> bool:
|
||||
return boolean(await self._send('isPresent', side))
|
||||
method = eval_lua_method_factory('disk.')
|
||||
|
||||
async def hasData(self, side: str) -> bool:
|
||||
return boolean(await self._send('hasData', side))
|
||||
|
||||
async def getMountPath(self, side: str) -> Optional[str]:
|
||||
return option_string(await self._send('getMountPath', side))
|
||||
__all__ = (
|
||||
'isPresent',
|
||||
'hasData',
|
||||
'getMountPath',
|
||||
'setLabel',
|
||||
'getLabel',
|
||||
'getID',
|
||||
'hasAudio',
|
||||
'getAudioTitle',
|
||||
'playAudio',
|
||||
'stopAudio',
|
||||
'eject',
|
||||
)
|
||||
|
||||
async def setLabel(self, side: str, label: str):
|
||||
return nil(await self._send('setLabel', side, label))
|
||||
|
||||
async def getLabel(self, side: str) -> Optional[str]:
|
||||
return option_string(await self._send('getLabel', side))
|
||||
def isPresent(side: str) -> bool:
|
||||
return boolean(method('isPresent', side))
|
||||
|
||||
async def getID(self, side: str) -> Optional[int]:
|
||||
return option_integer(await self._send('getID', side))
|
||||
|
||||
async def hasAudio(self, side: str) -> bool:
|
||||
return boolean(await self._send('hasAudio', side))
|
||||
def hasData(side: str) -> bool:
|
||||
return boolean(method('hasData', side))
|
||||
|
||||
async def getAudioTitle(self, side: str) -> Optional[Union[bool, str]]:
|
||||
return option_string_bool(await self._send('getAudioTitle', side))
|
||||
|
||||
async def playAudio(self, side: str):
|
||||
return nil(await self._send('playAudio', side))
|
||||
def getMountPath(side: str) -> Optional[str]:
|
||||
return option_string(method('getMountPath', side))
|
||||
|
||||
async def stopAudio(self, side: str):
|
||||
return nil(await self._send('stopAudio', side))
|
||||
|
||||
async def eject(self, side: str):
|
||||
return nil(await self._send('eject', side))
|
||||
def setLabel(side: str, label: str):
|
||||
return nil(method('setLabel', side, label))
|
||||
|
||||
|
||||
def getLabel(side: str) -> Optional[str]:
|
||||
return option_string(method('getLabel', side))
|
||||
|
||||
|
||||
def getID(side: str) -> Optional[int]:
|
||||
return option_integer(method('getID', side))
|
||||
|
||||
|
||||
def hasAudio(side: str) -> bool:
|
||||
return boolean(method('hasAudio', side))
|
||||
|
||||
|
||||
def getAudioTitle(side: str) -> Optional[Union[bool, str]]:
|
||||
return option_string_bool(method('getAudioTitle', side))
|
||||
|
||||
|
||||
def playAudio(side: str):
|
||||
return nil(method('playAudio', side))
|
||||
|
||||
|
||||
def stopAudio(side: str):
|
||||
return nil(method('stopAudio', side))
|
||||
|
||||
|
||||
def eject(side: str):
|
||||
return nil(method('eject', side))
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
from contextlib import asynccontextmanager
|
||||
from contextlib import contextmanager
|
||||
from typing import Optional, List, Union
|
||||
|
||||
from .base import BaseSubAPI
|
||||
from ..errors import LuaException
|
||||
from ..lua import lua_args
|
||||
from ..lua import lua_call
|
||||
from ..rproc import boolean, string, integer, nil, array_string, option_string, option_integer, fact_scheme_dict
|
||||
from ..sess import eval_lua_method_factory, lua_context_object
|
||||
|
||||
|
||||
attribute = fact_scheme_dict({
|
||||
|
@ -16,9 +17,9 @@ attribute = fact_scheme_dict({
|
|||
|
||||
|
||||
class SeekMixin:
|
||||
async def seek(self, whence: str = None, offset: int = None) -> int:
|
||||
def seek(self, whence: str = None, offset: int = None) -> int:
|
||||
# whence: set, cur, end
|
||||
r = await self._send('seek', whence, offset)
|
||||
r = self._method('seek', whence, offset)
|
||||
if isinstance(r, list):
|
||||
assert r[0] is False
|
||||
raise LuaException(r[1])
|
||||
|
@ -26,23 +27,25 @@ class SeekMixin:
|
|||
|
||||
|
||||
class ReadHandle(BaseSubAPI):
|
||||
async def read(self, count: int = None) -> Optional[Union[str, int]]:
|
||||
r = await self._send('read', count)
|
||||
# TODO: binary handle must return bytes instead string
|
||||
|
||||
def read(self, count: int = None) -> Optional[Union[str, int]]:
|
||||
r = self._method('read', count)
|
||||
return option_integer(r) if count is None else option_string(r)
|
||||
|
||||
async def readLine(self) -> Optional[str]:
|
||||
return option_string(await self._send('readLine'))
|
||||
def readLine(self) -> Optional[str]:
|
||||
return option_string(self._method('readLine'))
|
||||
|
||||
async def readAll(self) -> str:
|
||||
return string(await self._send('readAll'))
|
||||
def readAll(self) -> str:
|
||||
return string(self._method('readAll'))
|
||||
|
||||
def __aiter__(self):
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
async def __anext__(self):
|
||||
line = await self.readLine()
|
||||
def __next__(self):
|
||||
line = self.readLine()
|
||||
if line is None:
|
||||
raise StopAsyncIteration
|
||||
raise StopIteration
|
||||
return line
|
||||
|
||||
|
||||
|
@ -51,101 +54,144 @@ class BinaryReadHandle(ReadHandle, SeekMixin):
|
|||
|
||||
|
||||
class WriteHandle(BaseSubAPI):
|
||||
async def write(self, text: str):
|
||||
return nil(await self._send('write', text))
|
||||
def write(self, text: str):
|
||||
return nil(self._method('write', text))
|
||||
|
||||
async def writeLine(self, text: str):
|
||||
return nil(await self._send('writeLine', text))
|
||||
def writeLine(self, text: str):
|
||||
return nil(self._method('writeLine', text))
|
||||
|
||||
async def flush(self):
|
||||
return nil(await self._send('flush'))
|
||||
def flush(self):
|
||||
return nil(self._method('flush'))
|
||||
|
||||
|
||||
class BinaryWriteHandle(WriteHandle, SeekMixin):
|
||||
pass
|
||||
|
||||
|
||||
class FSAPI(BaseSubAPI):
|
||||
async def list(self, path: str) -> List[str]:
|
||||
return array_string(await self._send('list', path))
|
||||
method = eval_lua_method_factory('fs.')
|
||||
|
||||
async def exists(self, path: str) -> bool:
|
||||
return boolean(await self._send('exists', path))
|
||||
|
||||
async def isDir(self, path: str) -> bool:
|
||||
return boolean(await self._send('isDir', path))
|
||||
__all__ = (
|
||||
'list',
|
||||
'exists',
|
||||
'isDir',
|
||||
'isReadOnly',
|
||||
'getDrive',
|
||||
'getSize',
|
||||
'getFreeSpace',
|
||||
'getCapacity',
|
||||
'makeDir',
|
||||
'move',
|
||||
'copy',
|
||||
'delete',
|
||||
'combine',
|
||||
'open',
|
||||
'find',
|
||||
'getDir',
|
||||
'getName',
|
||||
'isDriveRoot',
|
||||
'complete',
|
||||
'attributes',
|
||||
)
|
||||
|
||||
async def isReadOnly(self, path: str) -> bool:
|
||||
return boolean(await self._send('isReadOnly', path))
|
||||
|
||||
async def getDrive(self, path: str) -> Optional[str]:
|
||||
return option_string(await self._send('getDrive', path))
|
||||
def list(path: str) -> List[str]:
|
||||
return array_string(method('list', path))
|
||||
|
||||
async def getSize(self, path: str) -> int:
|
||||
return integer(await self._send('getSize', path))
|
||||
|
||||
async def getFreeSpace(self, path: str) -> int:
|
||||
return integer(await self._send('getFreeSpace', path))
|
||||
def exists(path: str) -> bool:
|
||||
return boolean(method('exists', path))
|
||||
|
||||
async def getCapacity(self, path: str) -> int:
|
||||
return integer(await self._send('getCapacity', path))
|
||||
|
||||
async def makeDir(self, path: str):
|
||||
return nil(await self._send('makeDir', path))
|
||||
def isDir(path: str) -> bool:
|
||||
return boolean(method('isDir', path))
|
||||
|
||||
async def move(self, fromPath: str, toPath: str):
|
||||
return nil(await self._send('move', fromPath, toPath))
|
||||
|
||||
async def copy(self, fromPath: str, toPath: str):
|
||||
return nil(await self._send('copy', fromPath, toPath))
|
||||
def isReadOnly(path: str) -> bool:
|
||||
return boolean(method('isReadOnly', path))
|
||||
|
||||
async def delete(self, path: str):
|
||||
return nil(await self._send('delete', path))
|
||||
|
||||
async def combine(self, basePath: str, localPath: str) -> str:
|
||||
return string(await self._send('combine', basePath, localPath))
|
||||
def getDrive(path: str) -> Optional[str]:
|
||||
return option_string(method('getDrive', path))
|
||||
|
||||
@asynccontextmanager
|
||||
async def open(self, path: str, mode: str):
|
||||
'''
|
||||
Usage:
|
||||
|
||||
async with api.fs.open('filename', 'w') as f:
|
||||
await f.writeLine('textline')
|
||||
def getSize(path: str) -> int:
|
||||
return integer(method('getSize', path))
|
||||
|
||||
async with api.fs.open('filename', 'r') as f:
|
||||
async for line in f:
|
||||
...
|
||||
'''
|
||||
create_expr = '{}.open({})'.format(
|
||||
self.get_expr_code(),
|
||||
lua_args(path, mode),
|
||||
)
|
||||
fin_tpl = '{e}.close()'
|
||||
async with self._cc._create_temp_object(create_expr, fin_tpl) as var:
|
||||
if 'b' in mode:
|
||||
hcls = BinaryReadHandle if 'r' in mode else BinaryWriteHandle
|
||||
else:
|
||||
hcls = ReadHandle if 'r' in mode else WriteHandle
|
||||
yield hcls(self._cc, var)
|
||||
|
||||
async def find(self, wildcard: str) -> List[str]:
|
||||
return array_string(await self._send('find', wildcard))
|
||||
def getFreeSpace(path: str) -> int:
|
||||
return integer(method('getFreeSpace', path))
|
||||
|
||||
async def getDir(self, path: str) -> str:
|
||||
return string(await self._send('getDir', path))
|
||||
|
||||
async def getName(self, path: str) -> str:
|
||||
return string(await self._send('getName', path))
|
||||
def getCapacity(path: str) -> int:
|
||||
return integer(method('getCapacity', path))
|
||||
|
||||
async def isDriveRoot(self, path: str) -> bool:
|
||||
return boolean(await self._send('isDriveRoot', path))
|
||||
|
||||
async def complete(
|
||||
self, partialName: str, path: str, includeFiles: bool = None, includeDirs: bool = None,
|
||||
) -> List[str]:
|
||||
return array_string(await self._send(
|
||||
'complete', partialName, path, includeFiles, includeDirs))
|
||||
def makeDir(path: str):
|
||||
return nil(method('makeDir', path))
|
||||
|
||||
async def attributes(self, path: str) -> dict:
|
||||
return attribute(await self._send('attributes', path))
|
||||
|
||||
def move(fromPath: str, toPath: str):
|
||||
return nil(method('move', fromPath, toPath))
|
||||
|
||||
|
||||
def copy(fromPath: str, toPath: str):
|
||||
return nil(method('copy', fromPath, toPath))
|
||||
|
||||
|
||||
def delete(path: str):
|
||||
return nil(method('delete', path))
|
||||
|
||||
|
||||
def combine(basePath: str, localPath: str) -> str:
|
||||
return string(method('combine', basePath, localPath))
|
||||
|
||||
|
||||
@contextmanager
|
||||
def open(path: str, mode: str):
|
||||
'''
|
||||
Usage:
|
||||
|
||||
with fs.open('filename', 'w') as f:
|
||||
f.writeLine('textline')
|
||||
|
||||
with fs.open('filename', 'r') as f:
|
||||
for line in f:
|
||||
...
|
||||
'''
|
||||
with lua_context_object(
|
||||
lua_call('fs.open', path, mode),
|
||||
'{e}.close()',
|
||||
) as var:
|
||||
if 'b' in mode:
|
||||
hcls = BinaryReadHandle if 'r' in mode else BinaryWriteHandle
|
||||
else:
|
||||
hcls = ReadHandle if 'r' in mode else WriteHandle
|
||||
yield hcls(var)
|
||||
|
||||
|
||||
def find(wildcard: str) -> List[str]:
|
||||
return array_string(method('find', wildcard))
|
||||
|
||||
|
||||
def getDir(path: str) -> str:
|
||||
return string(method('getDir', path))
|
||||
|
||||
|
||||
def getName(path: str) -> str:
|
||||
return string(method('getName', path))
|
||||
|
||||
|
||||
def isDriveRoot(path: str) -> bool:
|
||||
return boolean(method('isDriveRoot', path))
|
||||
|
||||
|
||||
def complete(
|
||||
partialName: str, path: str, includeFiles: bool = None, includeDirs: bool = None,
|
||||
) -> List[str]:
|
||||
return array_string(method(
|
||||
'complete', partialName, path, includeFiles, includeDirs))
|
||||
|
||||
|
||||
def attributes(path: str) -> dict:
|
||||
return attribute(method('attributes', path))
|
||||
|
|
|
@ -1,15 +1,22 @@
|
|||
from typing import Tuple, Optional
|
||||
|
||||
from .base import BaseSubAPI
|
||||
from ..lua import LuaNum
|
||||
from ..rproc import tuple3_number, fact_option
|
||||
from ..sess import eval_lua_method_factory
|
||||
|
||||
|
||||
option_tuple3_number = fact_option(tuple3_number)
|
||||
method = eval_lua_method_factory('gps.')
|
||||
|
||||
|
||||
class GpsAPI(BaseSubAPI):
|
||||
CHANNEL_GPS = 65534
|
||||
__all__ = (
|
||||
'CHANNEL_GPS',
|
||||
'locate',
|
||||
)
|
||||
|
||||
async def locate(self, timeout: LuaNum = None, debug: bool = None) -> Optional[Tuple[LuaNum, LuaNum, LuaNum]]:
|
||||
return option_tuple3_number(await self._send('locate', timeout, debug))
|
||||
|
||||
CHANNEL_GPS = 65534
|
||||
|
||||
|
||||
def locate(timeout: LuaNum = None, debug: bool = None) -> Optional[Tuple[LuaNum, LuaNum, LuaNum]]:
|
||||
return option_tuple3_number(method('locate', timeout, debug))
|
||||
|
|
|
@ -1,21 +1,36 @@
|
|||
from typing import Optional, List
|
||||
|
||||
from .base import BaseSubAPI
|
||||
from ..rproc import string, nil, array_string, option_string
|
||||
from ..sess import eval_lua_method_factory
|
||||
|
||||
|
||||
class HelpAPI(BaseSubAPI):
|
||||
async def path(self) -> str:
|
||||
return string(await self._send('path'))
|
||||
method = eval_lua_method_factory('help.')
|
||||
|
||||
async def setPath(self, path: str):
|
||||
return nil(await self._send('setPath', path))
|
||||
|
||||
async def lookup(self, topic: str) -> Optional[str]:
|
||||
return option_string(await self._send('lookup', topic))
|
||||
__all__ = (
|
||||
'path',
|
||||
'setPath',
|
||||
'lookup',
|
||||
'topics',
|
||||
'completeTopic',
|
||||
)
|
||||
|
||||
async def topics(self) -> List[str]:
|
||||
return array_string(await self._send('topics'))
|
||||
|
||||
async def completeTopic(self, topicPrefix: str) -> List[str]:
|
||||
return array_string(await self._send('completeTopic', topicPrefix))
|
||||
def path() -> str:
|
||||
return string(method('path'))
|
||||
|
||||
|
||||
def setPath(path: str):
|
||||
return nil(method('setPath', path))
|
||||
|
||||
|
||||
def lookup(topic: str) -> Optional[str]:
|
||||
return option_string(method('lookup', topic))
|
||||
|
||||
|
||||
def topics() -> List[str]:
|
||||
return array_string(method('topics'))
|
||||
|
||||
|
||||
def completeTopic(topicPrefix: str) -> List[str]:
|
||||
return array_string(method('completeTopic', topicPrefix))
|
||||
|
|
|
@ -1,19 +1,28 @@
|
|||
from typing import Optional
|
||||
|
||||
from .base import BaseSubAPI
|
||||
from ..lua import lua_string
|
||||
from ..rproc import option_integer, option_string
|
||||
from ..sess import eval_lua, eval_lua_method_factory
|
||||
|
||||
|
||||
class KeysAPI(BaseSubAPI):
|
||||
async def getCode(self, name: str) -> Optional[int]:
|
||||
# replaces properties
|
||||
# keys.space → await api.keys.getCode('space')
|
||||
return option_integer(await self._cc.eval_coro('''
|
||||
if type({module}[{key}]) == 'number' then
|
||||
return {module}[{key}]
|
||||
method = eval_lua_method_factory('keys.')
|
||||
|
||||
|
||||
__all__ = (
|
||||
'getCode',
|
||||
'getName',
|
||||
)
|
||||
|
||||
|
||||
def getCode(name: str) -> Optional[int]:
|
||||
# replaces properties
|
||||
# keys.space → keys.getCode('space')
|
||||
return option_integer(eval_lua('''
|
||||
if type(keys[{key}]) == 'number' then
|
||||
return keys[{key}]
|
||||
end
|
||||
return nil'''.format(module=self.get_expr_code(), key=lua_string(name))))
|
||||
return nil'''.format(key=lua_string(name))))
|
||||
|
||||
async def getName(self, code: int) -> Optional[str]:
|
||||
return option_string(await self._send('getName', code))
|
||||
|
||||
def getName(code: int) -> Optional[str]:
|
||||
return option_string(method('getName', code))
|
||||
|
|
|
@ -5,56 +5,56 @@ from ..rproc import boolean, nil, integer, tuple3_number, tuple2_integer
|
|||
|
||||
|
||||
class TermMixin:
|
||||
async def write(self, text: str):
|
||||
return nil(await self._send('write', text))
|
||||
def write(self, text: str):
|
||||
return nil(self._method('write', text))
|
||||
|
||||
async def blit(self, text: str, textColors: str, backgroundColors: str):
|
||||
return nil(await self._send('blit', text, textColors, backgroundColors))
|
||||
def blit(self, text: str, textColors: str, backgroundColors: str):
|
||||
return nil(self._method('blit', text, textColors, backgroundColors))
|
||||
|
||||
async def clear(self):
|
||||
return nil(await self._send('clear'))
|
||||
def clear(self):
|
||||
return nil(self._method('clear'))
|
||||
|
||||
async def clearLine(self):
|
||||
return nil(await self._send('clearLine'))
|
||||
def clearLine(self):
|
||||
return nil(self._method('clearLine'))
|
||||
|
||||
async def getCursorPos(self) -> Tuple[int, int]:
|
||||
return tuple2_integer(await self._send('getCursorPos'))
|
||||
def getCursorPos(self) -> Tuple[int, int]:
|
||||
return tuple2_integer(self._method('getCursorPos'))
|
||||
|
||||
async def setCursorPos(self, x: int, y: int):
|
||||
return nil(await self._send('setCursorPos', x, y))
|
||||
def setCursorPos(self, x: int, y: int):
|
||||
return nil(self._method('setCursorPos', x, y))
|
||||
|
||||
async def getCursorBlink(self) -> bool:
|
||||
return boolean(await self._send('getCursorBlink'))
|
||||
def getCursorBlink(self) -> bool:
|
||||
return boolean(self._method('getCursorBlink'))
|
||||
|
||||
async def setCursorBlink(self, value: bool):
|
||||
return nil(await self._send('setCursorBlink', value))
|
||||
def setCursorBlink(self, value: bool):
|
||||
return nil(self._method('setCursorBlink', value))
|
||||
|
||||
async def isColor(self) -> bool:
|
||||
return boolean(await self._send('isColor'))
|
||||
def isColor(self) -> bool:
|
||||
return boolean(self._method('isColor'))
|
||||
|
||||
async def getSize(self) -> Tuple[int, int]:
|
||||
return tuple2_integer(await self._send('getSize'))
|
||||
def getSize(self) -> Tuple[int, int]:
|
||||
return tuple2_integer(self._method('getSize'))
|
||||
|
||||
async def scroll(self, lines: int):
|
||||
return nil(await self._send('scroll', lines))
|
||||
def scroll(self, lines: int):
|
||||
return nil(self._method('scroll', lines))
|
||||
|
||||
async def setTextColor(self, colorID: int):
|
||||
return nil(await self._send('setTextColor', colorID))
|
||||
def setTextColor(self, colorID: int):
|
||||
return nil(self._method('setTextColor', colorID))
|
||||
|
||||
async def getTextColor(self) -> int:
|
||||
return integer(await self._send('getTextColor'))
|
||||
def getTextColor(self) -> int:
|
||||
return integer(self._method('getTextColor'))
|
||||
|
||||
async def setBackgroundColor(self, colorID: int):
|
||||
return nil(await self._send('setBackgroundColor', colorID))
|
||||
def setBackgroundColor(self, colorID: int):
|
||||
return nil(self._method('setBackgroundColor', colorID))
|
||||
|
||||
async def getBackgroundColor(self) -> int:
|
||||
return integer(await self._send('getBackgroundColor'))
|
||||
def getBackgroundColor(self) -> int:
|
||||
return integer(self._method('getBackgroundColor'))
|
||||
|
||||
async def getPaletteColor(self, colorID: int) -> Tuple[float, float, float]:
|
||||
return tuple3_number(await self._send('getPaletteColor', colorID))
|
||||
def getPaletteColor(self, colorID: int) -> Tuple[float, float, float]:
|
||||
return tuple3_number(self._method('getPaletteColor', colorID))
|
||||
|
||||
async def setPaletteColor(self, colorID: int, r: float, g: float, b: float):
|
||||
return nil(await self._send('setPaletteColor', colorID, r, g, b))
|
||||
def setPaletteColor(self, colorID: int, r: float, g: float, b: float):
|
||||
return nil(self._method('setPaletteColor', colorID, r, g, b))
|
||||
|
||||
|
||||
class TermTarget(LuaExpr):
|
||||
|
|
|
@ -1,27 +1,46 @@
|
|||
from typing import Optional
|
||||
|
||||
from .base import BaseSubAPI
|
||||
from ..rproc import integer, nil, boolean, option_string
|
||||
from ..sess import eval_lua_method_factory
|
||||
|
||||
|
||||
class MultishellAPI(BaseSubAPI):
|
||||
async def getCurrent(self) -> int:
|
||||
return integer(await self._send('getCurrent'))
|
||||
method = eval_lua_method_factory('multishell.')
|
||||
|
||||
async def getCount(self) -> int:
|
||||
return integer(await self._send('getCount'))
|
||||
|
||||
async def launch(self, environment: dict, programPath: str, *args: str) -> int:
|
||||
return integer(await self._send('launch', environment, programPath, *args))
|
||||
__all__ = (
|
||||
'getCurrent',
|
||||
'getCount',
|
||||
'launch',
|
||||
'setTitle',
|
||||
'getTitle',
|
||||
'setFocus',
|
||||
'getFocus',
|
||||
)
|
||||
|
||||
async def setTitle(self, tabID: int, title: str):
|
||||
return nil(await self._send('setTitle', tabID, title))
|
||||
|
||||
async def getTitle(self, tabID: int) -> Optional[str]:
|
||||
return option_string(await self._send('getTitle', tabID))
|
||||
def getCurrent() -> int:
|
||||
return integer(method('getCurrent'))
|
||||
|
||||
async def setFocus(self, tabID: int) -> bool:
|
||||
return boolean(await self._send('setFocus', tabID))
|
||||
|
||||
async def getFocus(self) -> int:
|
||||
return integer(await self._send('getFocus'))
|
||||
def getCount() -> int:
|
||||
return integer(method('getCount'))
|
||||
|
||||
|
||||
def launch(environment: dict, programPath: str, *args: str) -> int:
|
||||
return integer(method('launch', environment, programPath, *args))
|
||||
|
||||
|
||||
def setTitle(tabID: int, title: str):
|
||||
return nil(method('setTitle', tabID, title))
|
||||
|
||||
|
||||
def getTitle(tabID: int) -> Optional[str]:
|
||||
return option_string(method('getTitle', tabID))
|
||||
|
||||
|
||||
def setFocus(tabID: int) -> bool:
|
||||
return boolean(method('setFocus', tabID))
|
||||
|
||||
|
||||
def getFocus() -> int:
|
||||
return integer(method('getFocus'))
|
||||
|
|
|
@ -1,114 +1,115 @@
|
|||
from contextlib import asynccontextmanager
|
||||
from typing import Optional, List
|
||||
|
||||
from .base import BaseSubAPI
|
||||
from ..lua import LuaNum
|
||||
from ..rproc import nil, string, option_string, number, integer, boolean
|
||||
from ..rproc import nil, string, option_string, number, integer, boolean, any_list
|
||||
from ..sess import eval_lua_method_factory
|
||||
|
||||
|
||||
class CCEventQueue:
|
||||
def __init__(self, q):
|
||||
self._q = q
|
||||
self.filter = self.default_filter
|
||||
|
||||
@staticmethod
|
||||
def default_filter(msg):
|
||||
return True, msg
|
||||
|
||||
async def __aiter__(self):
|
||||
while True:
|
||||
msg = await self._q.get()
|
||||
emit, msg = self.filter(msg)
|
||||
if emit:
|
||||
yield msg
|
||||
method = eval_lua_method_factory('os.')
|
||||
|
||||
|
||||
class OSAPI(BaseSubAPI):
|
||||
async def version(self) -> str:
|
||||
return string(await self._send('version'))
|
||||
__all__ = (
|
||||
'version',
|
||||
'getComputerID',
|
||||
'getComputerLabel',
|
||||
'setComputerLabel',
|
||||
'run',
|
||||
'pullEvent',
|
||||
'pullEventRaw',
|
||||
'queueEvent',
|
||||
'clock',
|
||||
'time',
|
||||
'day',
|
||||
'epoch',
|
||||
'sleep',
|
||||
'startTimer',
|
||||
'cancelTimer',
|
||||
'setAlarm',
|
||||
'cancelAlarm',
|
||||
'shutdown',
|
||||
'reboot',
|
||||
)
|
||||
|
||||
async def getComputerID(self) -> int:
|
||||
return integer(await self._send('getComputerID'))
|
||||
|
||||
async def getComputerLabel(self) -> Optional[str]:
|
||||
return option_string(await self._send('getComputerLabel'))
|
||||
def version() -> str:
|
||||
return string(method('version'))
|
||||
|
||||
async def setComputerLabel(self, label: Optional[str]):
|
||||
return nil(await self._send('setComputerLabel', label))
|
||||
|
||||
async def run(self, environment: dict, programPath: str, *args: List[str]):
|
||||
return boolean(await self._send('run', environment, programPath, *args))
|
||||
def getComputerID() -> int:
|
||||
return integer(method('getComputerID'))
|
||||
|
||||
@asynccontextmanager
|
||||
async def captureEvent(self, targetEvent: str) -> CCEventQueue:
|
||||
'''
|
||||
Use this function instead loop over pullEvent/pullEventRaw.
|
||||
|
||||
This makes a queue capable of effective event translation, excluding roundtrip delay.
|
||||
E.g.:
|
||||
1. You start short timer using os.startTimer().
|
||||
2. Then you call os.pullEvent() in loop to catch timer event.
|
||||
def getComputerLabel() -> Optional[str]:
|
||||
return option_string(method('getComputerLabel'))
|
||||
|
||||
There exist some dead intervals of time, while pullEvent is going to be transferred to python side.
|
||||
Lua side can receive and discard timer event since there's no consumer for it.
|
||||
|
||||
captureEvent gives you reliable way of receiving events without losses.
|
||||
Register queue before firing a timer, start a timer, listen for messages in queue.
|
||||
def setComputerLabel(label: Optional[str]):
|
||||
return nil(method('setComputerLabel', label))
|
||||
|
||||
# it's significant here: start queue before starting a timer
|
||||
async with api.os.captureEvent('timer') as timer_queue:
|
||||
myTimer = await api.os.startTimer(3)
|
||||
async for etid, in timer_queue:
|
||||
if etid == myTimer:
|
||||
await api.print('Timer reached')
|
||||
break
|
||||
'''
|
||||
q, tid = await self._cc._start_queue(targetEvent)
|
||||
try:
|
||||
yield CCEventQueue(q)
|
||||
finally:
|
||||
await self._cc._stop_queue(tid)
|
||||
|
||||
async def queueEvent(self, event: str, *params):
|
||||
return nil(await self._send('queueEvent', event, *params))
|
||||
def run(environment: dict, programPath: str, *args: List[str]):
|
||||
return boolean(method('run', environment, programPath, *args))
|
||||
|
||||
async def clock(self) -> LuaNum:
|
||||
# number of game ticks * 0.05, roughly seconds
|
||||
return number(await self._send('clock'))
|
||||
|
||||
# regarding ingame parameter below:
|
||||
# python has great stdlib to deal with real current time
|
||||
# we keep here only in-game time methods and parameters
|
||||
def pullEvent(event: str) -> tuple:
|
||||
return tuple(any_list(method('pullEvent', event)))
|
||||
|
||||
async def time(self) -> LuaNum:
|
||||
# in hours 0..24
|
||||
return number(await self._send('time', 'ingame'))
|
||||
|
||||
async def day(self) -> int:
|
||||
return integer(await self._send('day', 'ingame'))
|
||||
def pullEventRaw(event: str) -> tuple:
|
||||
return tuple(any_list(method('pullEventRaw', event)))
|
||||
|
||||
async def epoch(self) -> int:
|
||||
return integer(await self._send('epoch', 'ingame'))
|
||||
|
||||
async def sleep(self, seconds: LuaNum):
|
||||
return nil(await self._send('sleep', seconds))
|
||||
def queueEvent(event: str, *params):
|
||||
return nil(method('queueEvent', event, *params))
|
||||
|
||||
async def startTimer(self, timeout: LuaNum) -> int:
|
||||
return integer(await self._send('startTimer', timeout))
|
||||
|
||||
async def cancelTimer(self, timerID: int):
|
||||
return nil(await self._send('cancelTimer', timerID))
|
||||
def clock() -> LuaNum:
|
||||
# number of game ticks * 0.05, roughly seconds
|
||||
return number(method('clock'))
|
||||
|
||||
async def setAlarm(self, time: LuaNum) -> int:
|
||||
# takes time of the day in hours 0..24
|
||||
# returns integer alarmID
|
||||
return integer(await self._send('setAlarm', time))
|
||||
|
||||
async def cancelAlarm(self, alarmID: int):
|
||||
return nil(await self._send('cancelAlarm', alarmID))
|
||||
# regarding ingame parameter below:
|
||||
# python has great stdlib to deal with real current time
|
||||
# we keep here only in-game time methods and parameters
|
||||
|
||||
async def shutdown(self):
|
||||
return nil(await self._send('shutdown'))
|
||||
def time() -> LuaNum:
|
||||
# in hours 0..24
|
||||
return number(method('time', 'ingame'))
|
||||
|
||||
async def reboot(self):
|
||||
return nil(await self._send('reboot'))
|
||||
|
||||
def day() -> int:
|
||||
return integer(method('day', 'ingame'))
|
||||
|
||||
|
||||
def epoch() -> int:
|
||||
return integer(method('epoch', 'ingame'))
|
||||
|
||||
|
||||
def sleep(seconds: LuaNum):
|
||||
return nil(method('sleep', seconds))
|
||||
|
||||
|
||||
def startTimer(timeout: LuaNum) -> int:
|
||||
return integer(method('startTimer', timeout))
|
||||
|
||||
|
||||
def cancelTimer(timerID: int):
|
||||
return nil(method('cancelTimer', timerID))
|
||||
|
||||
|
||||
def setAlarm(time: LuaNum) -> int:
|
||||
# takes time of the day in hours 0..24
|
||||
# returns integer alarmID
|
||||
return integer(method('setAlarm', time))
|
||||
|
||||
|
||||
def cancelAlarm(alarmID: int):
|
||||
return nil(method('cancelAlarm', alarmID))
|
||||
|
||||
|
||||
def shutdown():
|
||||
return nil(method('shutdown'))
|
||||
|
||||
|
||||
def reboot():
|
||||
return nil(method('reboot'))
|
||||
|
|
|
@ -1,30 +1,47 @@
|
|||
from typing import List
|
||||
|
||||
from .base import BaseSubAPI
|
||||
from ..rproc import nil, integer, fact_array
|
||||
from ..sess import eval_lua_method_factory
|
||||
|
||||
|
||||
array_2d_integer = fact_array(fact_array(integer))
|
||||
method = eval_lua_method_factory('paintutils.')
|
||||
|
||||
|
||||
class PaintutilsAPI(BaseSubAPI):
|
||||
async def parseImage(self, data: str) -> List[List[int]]:
|
||||
return array_2d_integer(await self._send('parseImage', data))
|
||||
__all__ = (
|
||||
'parseImage',
|
||||
'loadImage',
|
||||
'drawPixel',
|
||||
'drawLine',
|
||||
'drawBox',
|
||||
'drawFilledBox',
|
||||
'drawImage',
|
||||
)
|
||||
|
||||
async def loadImage(self, path: str) -> List[List[int]]:
|
||||
return array_2d_integer(await self._send('loadImage', path))
|
||||
|
||||
async def drawPixel(self, x: int, y: int, color: int = None):
|
||||
return nil(await self._send('drawPixel', x, y, color))
|
||||
def parseImage(data: str) -> List[List[int]]:
|
||||
return array_2d_integer(method('parseImage', data))
|
||||
|
||||
async def drawLine(self, startX: int, startY: int, endX: int, endY: int, color: int = None):
|
||||
return nil(await self._send('drawLine', startX, startY, endX, endY, color))
|
||||
|
||||
async def drawBox(self, startX: int, startY: int, endX: int, endY: int, color: int = None):
|
||||
return nil(await self._send('drawBox', startX, startY, endX, endY, color))
|
||||
def loadImage(path: str) -> List[List[int]]:
|
||||
return array_2d_integer(method('loadImage', path))
|
||||
|
||||
async def drawFilledBox(self, startX: int, startY: int, endX: int, endY: int, color: int = None):
|
||||
return nil(await self._send('drawFilledBox', startX, startY, endX, endY, color))
|
||||
|
||||
async def drawImage(self, image: List[List[int]], xPos: int, yPos: int):
|
||||
return nil(await self._send('drawImage', image, xPos, yPos))
|
||||
def drawPixel(x: int, y: int, color: int = None):
|
||||
return nil(method('drawPixel', x, y, color))
|
||||
|
||||
|
||||
def drawLine(startX: int, startY: int, endX: int, endY: int, color: int = None):
|
||||
return nil(method('drawLine', startX, startY, endX, endY, color))
|
||||
|
||||
|
||||
def drawBox(startX: int, startY: int, endX: int, endY: int, color: int = None):
|
||||
return nil(method('drawBox', startX, startY, endX, endY, color))
|
||||
|
||||
|
||||
def drawFilledBox(startX: int, startY: int, endX: int, endY: int, color: int = None):
|
||||
return nil(method('drawFilledBox', startX, startY, endX, endY, color))
|
||||
|
||||
|
||||
def drawImage(image: List[List[int]], xPos: int, yPos: int):
|
||||
return nil(method('drawImage', image, xPos, yPos))
|
||||
|
|
39
computercraft/subapis/parallel.py
Normal file
39
computercraft/subapis/parallel.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
from ..sess import debug, CCGreenlet, get_current_greenlet
|
||||
|
||||
|
||||
__all__ = (
|
||||
'waitForAny',
|
||||
'waitForAll',
|
||||
)
|
||||
|
||||
|
||||
def waitForAny(*task_fns):
|
||||
pgl = get_current_greenlet().cc_greenlet
|
||||
sess = pgl._sess
|
||||
|
||||
gs = [CCGreenlet(fn) for fn in task_fns]
|
||||
for g in gs:
|
||||
g.defer_switch()
|
||||
|
||||
try:
|
||||
result = sess._server_greenlet.switch()
|
||||
debug('waitForAny switch result', result)
|
||||
finally:
|
||||
pgl.detach_children()
|
||||
|
||||
|
||||
def waitForAll(*task_fns):
|
||||
pgl = get_current_greenlet().cc_greenlet
|
||||
sess = pgl._sess
|
||||
|
||||
gs = [CCGreenlet(fn) for fn in task_fns]
|
||||
for g in gs:
|
||||
g.defer_switch()
|
||||
|
||||
try:
|
||||
for _ in range(len(task_fns)):
|
||||
result = sess._server_greenlet.switch()
|
||||
debug('waitForAll switch result', result)
|
||||
debug('waitForAll finish')
|
||||
finally:
|
||||
pgl.detach_children()
|
|
@ -1,96 +1,92 @@
|
|||
from contextlib import asynccontextmanager
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional, List, Tuple, Any, Union
|
||||
|
||||
from .base import BaseSubAPI
|
||||
from .mixins import TermMixin, TermTarget
|
||||
from .turtle import craft_result
|
||||
from ..lua import LuaNum, lua_args
|
||||
from ..lua import LuaNum, lua_args, return_lua_call
|
||||
from ..rproc import (
|
||||
boolean, nil, integer, string, option_integer, option_string,
|
||||
tuple2_integer, array_string, option_string_bool, try_result,
|
||||
)
|
||||
from ..sess import eval_lua, eval_lua_method_factory
|
||||
|
||||
|
||||
class BasePeripheral:
|
||||
# NOTE: is not LuaExpr, you can't pass peripheral as parameter
|
||||
# TODO: to fix this we can supply separate lua expr, result of .wrap()
|
||||
|
||||
def __init__(self, cc, lua_method_expr, *prepend_params):
|
||||
self._cc = cc
|
||||
def __init__(self, lua_method_expr, *prepend_params):
|
||||
self._lua_method_expr = lua_method_expr
|
||||
self._prepend_params = prepend_params
|
||||
|
||||
async def _send(self, method, *params):
|
||||
return await self._method(method, *params)
|
||||
|
||||
async def _method(self, name, *params):
|
||||
return await self._cc.eval_coro('return {}({})'.format(
|
||||
def _method(self, name, *params):
|
||||
return eval_lua(return_lua_call(
|
||||
self._lua_method_expr,
|
||||
lua_args(*self._prepend_params, name, *params),
|
||||
*self._prepend_params, name, *params,
|
||||
))
|
||||
|
||||
|
||||
class CCDrive(BasePeripheral):
|
||||
async def isDiskPresent(self) -> bool:
|
||||
return boolean(await self._send('isDiskPresent'))
|
||||
def isDiskPresent(self) -> bool:
|
||||
return boolean(self._method('isDiskPresent'))
|
||||
|
||||
async def getDiskLabel(self) -> Optional[str]:
|
||||
return option_string(await self._send('getDiskLabel'))
|
||||
def getDiskLabel(self) -> Optional[str]:
|
||||
return option_string(self._method('getDiskLabel'))
|
||||
|
||||
async def setDiskLabel(self, label: str):
|
||||
return nil(await self._send('setDiskLabel', label))
|
||||
def setDiskLabel(self, label: str):
|
||||
return nil(self._method('setDiskLabel', label))
|
||||
|
||||
async def hasData(self) -> bool:
|
||||
return boolean(await self._send('hasData'))
|
||||
def hasData(self) -> bool:
|
||||
return boolean(self._method('hasData'))
|
||||
|
||||
async def getMountPath(self) -> Optional[str]:
|
||||
return option_string(await self._send('getMountPath'))
|
||||
def getMountPath(self) -> Optional[str]:
|
||||
return option_string(self._method('getMountPath'))
|
||||
|
||||
async def hasAudio(self) -> bool:
|
||||
return boolean(await self._send('hasAudio'))
|
||||
def hasAudio(self) -> bool:
|
||||
return boolean(self._method('hasAudio'))
|
||||
|
||||
async def getAudioTitle(self) -> Optional[Union[bool, str]]:
|
||||
return option_string_bool(await self._send('getAudioTitle'))
|
||||
def getAudioTitle(self) -> Optional[Union[bool, str]]:
|
||||
return option_string_bool(self._method('getAudioTitle'))
|
||||
|
||||
async def playAudio(self):
|
||||
return nil(await self._send('playAudio'))
|
||||
def playAudio(self):
|
||||
return nil(self._method('playAudio'))
|
||||
|
||||
async def stopAudio(self):
|
||||
return nil(await self._send('stopAudio'))
|
||||
def stopAudio(self):
|
||||
return nil(self._method('stopAudio'))
|
||||
|
||||
async def ejectDisk(self):
|
||||
return nil(await self._send('ejectDisk'))
|
||||
def ejectDisk(self):
|
||||
return nil(self._method('ejectDisk'))
|
||||
|
||||
async def getDiskID(self) -> Optional[int]:
|
||||
return option_integer(await self._send('getDiskID'))
|
||||
def getDiskID(self) -> Optional[int]:
|
||||
return option_integer(self._method('getDiskID'))
|
||||
|
||||
|
||||
class CCMonitor(BasePeripheral, TermMixin):
|
||||
async def getTextScale(self) -> int:
|
||||
return integer(await self._send('getTextScale'))
|
||||
def getTextScale(self) -> int:
|
||||
return integer(self._method('getTextScale'))
|
||||
|
||||
async def setTextScale(self, scale: int):
|
||||
return nil(await self._send('setTextScale', scale))
|
||||
def setTextScale(self, scale: int):
|
||||
return nil(self._method('setTextScale', scale))
|
||||
|
||||
|
||||
class ComputerMixin:
|
||||
async def turnOn(self):
|
||||
return nil(await self._send('turnOn'))
|
||||
def turnOn(self):
|
||||
return nil(self._method('turnOn'))
|
||||
|
||||
async def shutdown(self):
|
||||
return nil(await self._send('shutdown'))
|
||||
def shutdown(self):
|
||||
return nil(self._method('shutdown'))
|
||||
|
||||
async def reboot(self):
|
||||
return nil(await self._send('reboot'))
|
||||
def reboot(self):
|
||||
return nil(self._method('reboot'))
|
||||
|
||||
async def getID(self) -> int:
|
||||
return integer(await self._send('getID'))
|
||||
def getID(self) -> int:
|
||||
return integer(self._method('getID'))
|
||||
|
||||
async def getLabel(self) -> Optional[str]:
|
||||
return option_string(await self._send('getLabel'))
|
||||
def getLabel(self) -> Optional[str]:
|
||||
return option_string(self._method('getLabel'))
|
||||
|
||||
async def isOn(self) -> bool:
|
||||
return boolean(await self._send('isOn'))
|
||||
def isOn(self) -> bool:
|
||||
return boolean(self._method('isOn'))
|
||||
|
||||
|
||||
class CCComputer(BasePeripheral, ComputerMixin):
|
||||
|
@ -109,48 +105,45 @@ class ModemMessage:
|
|||
|
||||
|
||||
class ModemMixin:
|
||||
async def isOpen(self, channel: int) -> bool:
|
||||
return boolean(await self._send('isOpen', channel))
|
||||
def isOpen(self, channel: int) -> bool:
|
||||
return boolean(self._method('isOpen', channel))
|
||||
|
||||
async def open(self, channel: int):
|
||||
return nil(await self._send('open', channel))
|
||||
def open(self, channel: int):
|
||||
return nil(self._method('open', channel))
|
||||
|
||||
async def close(self, channel: int):
|
||||
return nil(await self._send('close', channel))
|
||||
def close(self, channel: int):
|
||||
return nil(self._method('close', channel))
|
||||
|
||||
async def closeAll(self):
|
||||
return nil(await self._send('closeAll'))
|
||||
def closeAll(self):
|
||||
return nil(self._method('closeAll'))
|
||||
|
||||
async def transmit(self, channel: int, replyChannel: int, message: Any):
|
||||
return nil(await self._send('transmit', channel, replyChannel, message))
|
||||
def transmit(self, channel: int, replyChannel: int, message: Any):
|
||||
return nil(self._method('transmit', channel, replyChannel, message))
|
||||
|
||||
async def isWireless(self) -> bool:
|
||||
return boolean(await self._send('isWireless'))
|
||||
def isWireless(self) -> bool:
|
||||
return boolean(self._method('isWireless'))
|
||||
|
||||
@property
|
||||
def _side(self):
|
||||
return self._prepend_params[0]
|
||||
|
||||
def _mk_recv_filter(self, channel):
|
||||
def filter(msg):
|
||||
if msg[0] != self._side:
|
||||
return False, None
|
||||
if msg[1] != channel:
|
||||
return False, None
|
||||
return True, ModemMessage(*msg[2:])
|
||||
return filter
|
||||
def receive(self, channel: int):
|
||||
from .os import pullEvent
|
||||
|
||||
@asynccontextmanager
|
||||
async def receive(self, channel: int):
|
||||
if await self.isOpen(channel):
|
||||
if self.isOpen(channel):
|
||||
raise Exception('Channel is busy')
|
||||
await self.open(channel)
|
||||
|
||||
self.open(channel)
|
||||
try:
|
||||
async with self._cc.os.captureEvent('modem_message') as q:
|
||||
q.filter = self._mk_recv_filter(channel)
|
||||
yield q
|
||||
while True:
|
||||
evt = pullEvent('modem_message')
|
||||
if evt[0] != self._side:
|
||||
continue
|
||||
if evt[1] != channel:
|
||||
continue
|
||||
yield ModemMessage(*evt[2:])
|
||||
finally:
|
||||
await self.close(channel)
|
||||
self.close(channel)
|
||||
|
||||
|
||||
class CCWirelessModem(BasePeripheral, ModemMixin):
|
||||
|
@ -158,28 +151,27 @@ class CCWirelessModem(BasePeripheral, ModemMixin):
|
|||
|
||||
|
||||
class CCWiredModem(BasePeripheral, ModemMixin):
|
||||
async def getNameLocal(self) -> Optional[str]:
|
||||
return option_string(await self._send('getNameLocal'))
|
||||
def getNameLocal(self) -> Optional[str]:
|
||||
return option_string(self._method('getNameLocal'))
|
||||
|
||||
async def getNamesRemote(self) -> List[str]:
|
||||
return array_string(await self._send('getNamesRemote'))
|
||||
def getNamesRemote(self) -> List[str]:
|
||||
return array_string(self._method('getNamesRemote'))
|
||||
|
||||
async def getTypeRemote(self, peripheralName: str) -> Optional[str]:
|
||||
return option_string(await self._send('getTypeRemote', peripheralName))
|
||||
def getTypeRemote(self, peripheralName: str) -> Optional[str]:
|
||||
return option_string(self._method('getTypeRemote', peripheralName))
|
||||
|
||||
async def isPresentRemote(self, peripheralName: str) -> bool:
|
||||
return boolean(await self._send('isPresentRemote', peripheralName))
|
||||
def isPresentRemote(self, peripheralName: str) -> bool:
|
||||
return boolean(self._method('isPresentRemote', peripheralName))
|
||||
|
||||
async def wrapRemote(self, peripheralName: str) -> Optional[BasePeripheral]:
|
||||
def wrapRemote(self, peripheralName: str) -> Optional[BasePeripheral]:
|
||||
# use instead getMethodsRemote and callRemote
|
||||
# NOTE: you can also use peripheral.wrap(peripheralName)
|
||||
|
||||
ptype = await self.getTypeRemote(peripheralName)
|
||||
ptype = self.getTypeRemote(peripheralName)
|
||||
if ptype is None:
|
||||
return None
|
||||
|
||||
return TYPE_MAP[ptype](
|
||||
self._cc,
|
||||
self._lua_method_expr, *self._prepend_params,
|
||||
'callRemote', peripheralName,
|
||||
)
|
||||
|
@ -188,36 +180,36 @@ class CCWiredModem(BasePeripheral, ModemMixin):
|
|||
|
||||
|
||||
class CCPrinter(BasePeripheral):
|
||||
async def newPage(self) -> bool:
|
||||
return boolean(await self._send('newPage'))
|
||||
def newPage(self) -> bool:
|
||||
return boolean(self._method('newPage'))
|
||||
|
||||
async def endPage(self) -> bool:
|
||||
return boolean(await self._send('endPage'))
|
||||
def endPage(self) -> bool:
|
||||
return boolean(self._method('endPage'))
|
||||
|
||||
async def write(self, text: str):
|
||||
return nil(await self._send('write', text))
|
||||
def write(self, text: str):
|
||||
return nil(self._method('write', text))
|
||||
|
||||
async def setCursorPos(self, x: int, y: int):
|
||||
return nil(await self._send('setCursorPos', x, y))
|
||||
def setCursorPos(self, x: int, y: int):
|
||||
return nil(self._method('setCursorPos', x, y))
|
||||
|
||||
async def getCursorPos(self) -> Tuple[int, int]:
|
||||
return tuple2_integer(await self._send('getCursorPos'))
|
||||
def getCursorPos(self) -> Tuple[int, int]:
|
||||
return tuple2_integer(self._method('getCursorPos'))
|
||||
|
||||
async def getPageSize(self) -> Tuple[int, int]:
|
||||
return tuple2_integer(await self._send('getPageSize'))
|
||||
def getPageSize(self) -> Tuple[int, int]:
|
||||
return tuple2_integer(self._method('getPageSize'))
|
||||
|
||||
async def setPageTitle(self, title: str):
|
||||
return nil(await self._send('setPageTitle', title))
|
||||
def setPageTitle(self, title: str):
|
||||
return nil(self._method('setPageTitle', title))
|
||||
|
||||
async def getPaperLevel(self) -> int:
|
||||
return integer(await self._send('getPaperLevel'))
|
||||
def getPaperLevel(self) -> int:
|
||||
return integer(self._method('getPaperLevel'))
|
||||
|
||||
async def getInkLevel(self) -> int:
|
||||
return integer(await self._send('getInkLevel'))
|
||||
def getInkLevel(self) -> int:
|
||||
return integer(self._method('getInkLevel'))
|
||||
|
||||
|
||||
class CCSpeaker(BasePeripheral):
|
||||
async def playNote(self, instrument: str, volume: int = 1, pitch: int = 1) -> bool:
|
||||
def playNote(self, instrument: str, volume: int = 1, pitch: int = 1) -> bool:
|
||||
# instrument:
|
||||
# https://minecraft.gamepedia.com/Note_Block#Instruments
|
||||
# bass
|
||||
|
@ -238,28 +230,28 @@ class CCSpeaker(BasePeripheral):
|
|||
|
||||
# volume 0..3
|
||||
# pitch 0..24
|
||||
return boolean(await self._send('playNote', instrument, volume, pitch))
|
||||
return boolean(self._method('playNote', instrument, volume, pitch))
|
||||
|
||||
async def playSound(self, sound: str, volume: int = 1, pitch: int = 1):
|
||||
def playSound(self, sound: str, volume: int = 1, pitch: int = 1):
|
||||
# volume 0..3
|
||||
# pitch 0..2
|
||||
return boolean(await self._send('playSound', sound, volume, pitch))
|
||||
return boolean(self._method('playSound', sound, volume, pitch))
|
||||
|
||||
|
||||
class CCCommandBlock(BasePeripheral):
|
||||
async def getCommand(self) -> str:
|
||||
return string(await self._send('getCommand'))
|
||||
def getCommand(self) -> str:
|
||||
return string(self._method('getCommand'))
|
||||
|
||||
async def setCommand(self, command: str):
|
||||
return nil(await self._send('setCommand', command))
|
||||
def setCommand(self, command: str):
|
||||
return nil(self._method('setCommand', command))
|
||||
|
||||
async def runCommand(self):
|
||||
return try_result(await self._send('runCommand'))
|
||||
def runCommand(self):
|
||||
return try_result(self._method('runCommand'))
|
||||
|
||||
|
||||
class CCWorkbench(BasePeripheral):
|
||||
async def craft(self, quantity: int = 64) -> bool:
|
||||
return craft_result(await self._send('craft', quantity))
|
||||
def craft(self, quantity: int = 64) -> bool:
|
||||
return craft_result(self._method('craft', quantity))
|
||||
|
||||
|
||||
TYPE_MAP = {
|
||||
|
@ -274,34 +266,48 @@ TYPE_MAP = {
|
|||
}
|
||||
|
||||
|
||||
class PeripheralAPI(BaseSubAPI):
|
||||
async def isPresent(self, side: str) -> bool:
|
||||
return boolean(await self._send('isPresent', side))
|
||||
method = eval_lua_method_factory('peripheral.')
|
||||
|
||||
async def getType(self, side: str) -> Optional[str]:
|
||||
return option_string(await self._send('getType', side))
|
||||
|
||||
async def getNames(self) -> List[str]:
|
||||
return array_string(await self._send('getNames'))
|
||||
__all__ = (
|
||||
'isPresent',
|
||||
'getType',
|
||||
'getNames',
|
||||
'wrap',
|
||||
'get_term_target',
|
||||
)
|
||||
|
||||
# use instead getMethods and call
|
||||
async def wrap(self, side: str) -> Optional[BasePeripheral]:
|
||||
ptype = await self.getType(side)
|
||||
if ptype is None:
|
||||
return None
|
||||
|
||||
m = self.get_expr_code() + '.call'
|
||||
def isPresent(side: str) -> bool:
|
||||
return boolean(method('isPresent', side))
|
||||
|
||||
if ptype == 'modem':
|
||||
if boolean(await self._send('call', side, 'isWireless')):
|
||||
return CCWirelessModem(self._cc, m, side)
|
||||
else:
|
||||
return CCWiredModem(self._cc, m, side)
|
||||
|
||||
def getType(side: str) -> Optional[str]:
|
||||
return option_string(method('getType', side))
|
||||
|
||||
|
||||
def getNames() -> List[str]:
|
||||
return array_string(method('getNames'))
|
||||
|
||||
|
||||
# use instead getMethods and call
|
||||
def wrap(side: str) -> Optional[BasePeripheral]:
|
||||
ptype = getType(side)
|
||||
if ptype is None:
|
||||
return None
|
||||
|
||||
m = 'peripheral.call'
|
||||
|
||||
if ptype == 'modem':
|
||||
if boolean(method('call', side, 'isWireless')):
|
||||
return CCWirelessModem(m, side)
|
||||
else:
|
||||
return TYPE_MAP[ptype](self._cc, m, side)
|
||||
return CCWiredModem(m, side)
|
||||
else:
|
||||
return TYPE_MAP[ptype](m, side)
|
||||
|
||||
def get_term_target(self, side: str) -> TermTarget:
|
||||
return TermTarget('{}.wrap({})'.format(
|
||||
self.get_expr_code(),
|
||||
lua_args(side),
|
||||
))
|
||||
|
||||
def get_term_target(side: str) -> TermTarget:
|
||||
return TermTarget('peripheral.wrap({})'.format(
|
||||
lua_args(side),
|
||||
))
|
||||
|
|
|
@ -1,10 +1,19 @@
|
|||
from .base import BaseSubAPI
|
||||
from ..rproc import flat_try_result
|
||||
from ..sess import eval_lua_method_factory
|
||||
|
||||
|
||||
class PocketAPI(BaseSubAPI):
|
||||
async def equipBack(self):
|
||||
return flat_try_result(await self._send('equipBack'))
|
||||
method = eval_lua_method_factory('pocket.')
|
||||
|
||||
async def unequipBack(self) -> bool:
|
||||
return flat_try_result(await self._send('unequipBack'))
|
||||
|
||||
__all__ = (
|
||||
'equipBack',
|
||||
'unequipBack',
|
||||
)
|
||||
|
||||
|
||||
def equipBack():
|
||||
return flat_try_result(method('equipBack'))
|
||||
|
||||
|
||||
def unequipBack():
|
||||
return flat_try_result(method('unequipBack'))
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
from typing import Any, List, Optional, Tuple, Union
|
||||
|
||||
from .base import BaseSubAPI
|
||||
from ..lua import LuaNum
|
||||
from ..rproc import nil, integer, option_string, boolean, array_integer, option_integer, fact_option, fact_tuple
|
||||
from ..sess import eval_lua_method_factory
|
||||
|
||||
|
||||
recv_result = fact_option(fact_tuple(
|
||||
|
@ -11,45 +11,69 @@ recv_result = fact_option(fact_tuple(
|
|||
option_string,
|
||||
tail_nils=1,
|
||||
))
|
||||
method = eval_lua_method_factory('rednet.')
|
||||
|
||||
|
||||
class RednetAPI(BaseSubAPI):
|
||||
CHANNEL_REPEAT = 65533
|
||||
CHANNEL_BROADCAST = 65535
|
||||
__all__ = (
|
||||
'CHANNEL_REPEAT',
|
||||
'CHANNEL_BROADCAST',
|
||||
'open',
|
||||
'close',
|
||||
'send',
|
||||
'broadcast',
|
||||
'receive',
|
||||
'isOpen',
|
||||
'host',
|
||||
'unhost',
|
||||
'lookup',
|
||||
)
|
||||
|
||||
async def open(self, side: str):
|
||||
return nil(await self._send('open', side))
|
||||
|
||||
async def close(self, side: str = None):
|
||||
return nil(await self._send('close', side))
|
||||
CHANNEL_REPEAT = 65533
|
||||
CHANNEL_BROADCAST = 65535
|
||||
|
||||
async def send(self, receiverID: int, message: Any, protocol: str = None) -> bool:
|
||||
return boolean(await self._send('send', receiverID, message, protocol))
|
||||
|
||||
async def broadcast(self, message: Any, protocol: str = None):
|
||||
return nil(await self._send('broadcast', message, protocol))
|
||||
def open(side: str):
|
||||
return nil(method('open', side))
|
||||
|
||||
async def receive(
|
||||
self, protocolFilter: str = None, timeout: LuaNum = None,
|
||||
) -> Optional[Tuple[int, Any, Optional[str]]]:
|
||||
return recv_result(await self._send('receive', protocolFilter, timeout))
|
||||
|
||||
async def isOpen(self, side: str = None) -> bool:
|
||||
return boolean(await self._send('isOpen', side))
|
||||
def close(side: str = None):
|
||||
return nil(method('close', side))
|
||||
|
||||
async def host(self, protocol: str, hostname: str):
|
||||
return nil(await self._send('host', protocol, hostname))
|
||||
|
||||
async def unhost(self, protocol: str):
|
||||
return nil(await self._send('unhost', protocol))
|
||||
def send(receiverID: int, message: Any, protocol: str = None) -> bool:
|
||||
return boolean(method('send', receiverID, message, protocol))
|
||||
|
||||
async def lookup(self, protocol: str, hostname: str = None) -> Union[Optional[int], List[int]]:
|
||||
result = await self._send('lookup', protocol, hostname)
|
||||
if hostname is None:
|
||||
if result is None:
|
||||
return []
|
||||
if isinstance(result, list):
|
||||
return array_integer(result)
|
||||
return [integer(result)]
|
||||
else:
|
||||
return option_integer(result)
|
||||
|
||||
def broadcast(message: Any, protocol: str = None):
|
||||
return nil(method('broadcast', message, protocol))
|
||||
|
||||
|
||||
def receive(
|
||||
self, protocolFilter: str = None, timeout: LuaNum = None,
|
||||
) -> Optional[Tuple[int, Any, Optional[str]]]:
|
||||
return recv_result(method('receive', protocolFilter, timeout))
|
||||
|
||||
|
||||
def isOpen(side: str = None) -> bool:
|
||||
return boolean(method('isOpen', side))
|
||||
|
||||
|
||||
def host(protocol: str, hostname: str):
|
||||
return nil(method('host', protocol, hostname))
|
||||
|
||||
|
||||
def unhost(protocol: str):
|
||||
return nil(method('unhost', protocol))
|
||||
|
||||
|
||||
def lookup(protocol: str, hostname: str = None) -> Union[Optional[int], List[int]]:
|
||||
result = method('lookup', protocol, hostname)
|
||||
if hostname is None:
|
||||
if result is None:
|
||||
return []
|
||||
if isinstance(result, list):
|
||||
return array_integer(result)
|
||||
return [integer(result)]
|
||||
else:
|
||||
return option_integer(result)
|
||||
|
|
|
@ -1,41 +1,68 @@
|
|||
from typing import List
|
||||
|
||||
from .base import BaseSubAPI
|
||||
from ..rproc import boolean, nil, integer, array_string
|
||||
from ..sess import eval_lua_method_factory
|
||||
|
||||
|
||||
class RedstoneAPI(BaseSubAPI):
|
||||
async def getSides(self) -> List[str]:
|
||||
return array_string(await self._send('getSides'))
|
||||
method = eval_lua_method_factory('redstone.')
|
||||
|
||||
async def getInput(self, side: str) -> bool:
|
||||
return boolean(await self._send('getInput', side))
|
||||
|
||||
async def setOutput(self, side: str, value: bool):
|
||||
return nil(await self._send('setOutput', side, value))
|
||||
__all__ = (
|
||||
'getSides',
|
||||
'getInput',
|
||||
'setOutput',
|
||||
'getOutput',
|
||||
'getAnalogInput',
|
||||
'setAnalogOutput',
|
||||
'getAnalogOutput',
|
||||
'getBundledInput',
|
||||
'setBundledOutput',
|
||||
'getBundledOutput',
|
||||
'testBundledInput',
|
||||
)
|
||||
|
||||
async def getOutput(self, side: str) -> bool:
|
||||
return boolean(await self._send('getOutput', side))
|
||||
|
||||
async def getAnalogInput(self, side: str) -> int:
|
||||
return integer(await self._send('getAnalogInput', side))
|
||||
def getSides(self) -> List[str]:
|
||||
return array_string(method('getSides'))
|
||||
|
||||
async def setAnalogOutput(self, side: str, strength: int):
|
||||
return nil(await self._send('setAnalogOutput', side, strength))
|
||||
|
||||
async def getAnalogOutput(self, side: str) -> int:
|
||||
return integer(await self._send('getAnalogOutput', side))
|
||||
def getInput(side: str) -> bool:
|
||||
return boolean(method('getInput', side))
|
||||
|
||||
# bundled cables are not available in vanilla
|
||||
|
||||
async def getBundledInput(self, side: str) -> int:
|
||||
return integer(await self._send('getBundledInput', side))
|
||||
def setOutput(side: str, value: bool):
|
||||
return nil(method('setOutput', side, value))
|
||||
|
||||
async def setBundledOutput(self, side: str, colors: int):
|
||||
return nil(await self._send('setBundledOutput', side, colors))
|
||||
|
||||
async def getBundledOutput(self, side: str) -> int:
|
||||
return integer(await self._send('getBundledOutput', side))
|
||||
def getOutput(side: str) -> bool:
|
||||
return boolean(method('getOutput', side))
|
||||
|
||||
async def testBundledInput(self, side: str, color: int) -> bool:
|
||||
return boolean(await self._send('testBundledInput', side, color))
|
||||
|
||||
def getAnalogInput(side: str) -> int:
|
||||
return integer(method('getAnalogInput', side))
|
||||
|
||||
|
||||
def setAnalogOutput(side: str, strength: int):
|
||||
return nil(method('setAnalogOutput', side, strength))
|
||||
|
||||
|
||||
def getAnalogOutput(side: str) -> int:
|
||||
return integer(method('getAnalogOutput', side))
|
||||
|
||||
|
||||
# bundled cables are not available in vanilla
|
||||
|
||||
def getBundledInput(side: str) -> int:
|
||||
return integer(method('getBundledInput', side))
|
||||
|
||||
|
||||
def setBundledOutput(side: str, colors: int):
|
||||
return nil(method('setBundledOutput', side, colors))
|
||||
|
||||
|
||||
def getBundledOutput(side: str) -> int:
|
||||
return integer(method('getBundledOutput', side))
|
||||
|
||||
|
||||
def testBundledInput(side: str, color: int) -> bool:
|
||||
return boolean(method('testBundledInput', side, color))
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
from ..lua import lua_args
|
||||
from ..rproc import nil, boolean, string
|
||||
|
||||
|
||||
class RootAPIMixin:
|
||||
async def print(self, *args):
|
||||
return nil(await self.eval_coro('print({})'.format(lua_args(*args))))
|
||||
|
||||
async def read_line(self) -> str:
|
||||
return string(await self.eval_coro('return io.read()'))
|
||||
|
||||
async def has_commands_api(self) -> bool:
|
||||
return boolean(await self.eval_coro('return commands ~= nil'))
|
||||
|
||||
async def has_multishell_api(self) -> bool:
|
||||
return boolean(await self.eval_coro('return multishell ~= nil'))
|
||||
|
||||
async def has_turtle_api(self) -> bool:
|
||||
return boolean(await self.eval_coro('return turtle ~= nil'))
|
||||
|
||||
async def is_pocket(self) -> bool:
|
||||
return boolean(await self.eval_coro('return pocket ~= nil'))
|
|
@ -1,7 +1,7 @@
|
|||
from typing import Any, List
|
||||
|
||||
from .base import BaseSubAPI
|
||||
from ..rproc import nil, boolean, string, array_string, fact_scheme_dict
|
||||
from ..sess import eval_lua_method_factory
|
||||
|
||||
|
||||
setting = fact_scheme_dict({
|
||||
|
@ -12,42 +12,65 @@ setting = fact_scheme_dict({
|
|||
'type': string,
|
||||
'value': lambda v: v,
|
||||
})
|
||||
method = eval_lua_method_factory('settings.')
|
||||
|
||||
|
||||
class SettingsAPI(BaseSubAPI):
|
||||
async def define(self, name: str, description: str = None, default: Any = None, type: str = None):
|
||||
options = {}
|
||||
if description is not None:
|
||||
options['description'] = description
|
||||
if default is not None:
|
||||
options['default'] = default
|
||||
if type is not None:
|
||||
options['type'] = type
|
||||
return nil(await self._send('define', name, options))
|
||||
__all__ = (
|
||||
'define',
|
||||
'undefine',
|
||||
'getDetails',
|
||||
'set',
|
||||
'get',
|
||||
'unset',
|
||||
'clear',
|
||||
'getNames',
|
||||
'load',
|
||||
'save',
|
||||
)
|
||||
|
||||
async def undefine(self, name: str):
|
||||
return nil(await self._send('undefine', name))
|
||||
|
||||
async def getDetails(self, name: str) -> dict:
|
||||
return setting(await self._send('getDetails', name))
|
||||
def define(name: str, description: str = None, default: Any = None, type: str = None):
|
||||
options = {}
|
||||
if description is not None:
|
||||
options['description'] = description
|
||||
if default is not None:
|
||||
options['default'] = default
|
||||
if type is not None:
|
||||
options['type'] = type
|
||||
return nil(method('define', name, options))
|
||||
|
||||
async def set(self, name: str, value: Any):
|
||||
return nil(await self._send('set', name, value))
|
||||
|
||||
async def get(self, name: str, default: Any = None) -> Any:
|
||||
return await self._send('get', name, default)
|
||||
def undefine(name: str):
|
||||
return nil(method('undefine', name))
|
||||
|
||||
async def unset(self, name: str):
|
||||
return nil(await self._send('unset', name))
|
||||
|
||||
async def clear(self):
|
||||
return nil(await self._send('clear'))
|
||||
def getDetails(name: str) -> dict:
|
||||
return setting(method('getDetails', name))
|
||||
|
||||
async def getNames(self) -> List[str]:
|
||||
return array_string(await self._send('getNames'))
|
||||
|
||||
async def load(self, path: str = None) -> bool:
|
||||
return boolean(await self._send('load', path))
|
||||
def set(name: str, value: Any):
|
||||
return nil(method('set', name, value))
|
||||
|
||||
async def save(self, path: str = None) -> bool:
|
||||
return boolean(await self._send('save', path))
|
||||
|
||||
def get(name: str, default: Any = None) -> Any:
|
||||
return method('get', name, default)
|
||||
|
||||
|
||||
def unset(name: str):
|
||||
return nil(method('unset', name))
|
||||
|
||||
|
||||
def clear():
|
||||
return nil(method('clear'))
|
||||
|
||||
|
||||
def getNames() -> List[str]:
|
||||
return array_string(method('getNames'))
|
||||
|
||||
|
||||
def load(path: str = None) -> bool:
|
||||
return boolean(method('load', path))
|
||||
|
||||
|
||||
def save(path: str = None) -> bool:
|
||||
return boolean(method('save', path))
|
||||
|
|
|
@ -1,72 +1,112 @@
|
|||
from typing import List, Dict, Optional
|
||||
|
||||
from .base import BaseSubAPI
|
||||
from ..rproc import nil, string, boolean, integer, array_string, fact_mono_dict, option_string
|
||||
from ..sess import eval_lua_method_factory
|
||||
|
||||
|
||||
map_string_string = fact_mono_dict(string, string)
|
||||
method = eval_lua_method_factory('shell.')
|
||||
|
||||
|
||||
class ShellAPI(BaseSubAPI):
|
||||
async def exit(self):
|
||||
return nil(await self._send('exit'))
|
||||
__all__ = (
|
||||
'exit',
|
||||
'dir',
|
||||
'setDir',
|
||||
'path',
|
||||
'setPath',
|
||||
'resolve',
|
||||
'resolveProgram',
|
||||
'aliases',
|
||||
'setAlias',
|
||||
'clearAlias',
|
||||
'programs',
|
||||
'getRunningProgram',
|
||||
'run',
|
||||
'execute',
|
||||
'openTab',
|
||||
'switchTab',
|
||||
'complete',
|
||||
'completeProgram',
|
||||
)
|
||||
|
||||
async def dir(self) -> str:
|
||||
return string(await self._send('dir'))
|
||||
|
||||
async def setDir(self, path: str):
|
||||
return nil(await self._send('setDir', path))
|
||||
def exit(self):
|
||||
return nil(method('exit'))
|
||||
|
||||
async def path(self) -> str:
|
||||
return string(await self._send('path'))
|
||||
|
||||
async def setPath(self, path: str):
|
||||
return nil(await self._send('setPath', path))
|
||||
def dir(self) -> str:
|
||||
return string(method('dir'))
|
||||
|
||||
async def resolve(self, localPath: str) -> str:
|
||||
return string(await self._send('resolve', localPath))
|
||||
|
||||
async def resolveProgram(self, name: str) -> Optional[str]:
|
||||
return option_string(await self._send('resolveProgram', name))
|
||||
def setDir(path: str):
|
||||
return nil(method('setDir', path))
|
||||
|
||||
async def aliases(self) -> Dict[str, str]:
|
||||
return map_string_string(await self._send('aliases'))
|
||||
|
||||
async def setAlias(self, alias: str, program: str):
|
||||
return nil(await self._send('setAlias', alias, program))
|
||||
def path(self) -> str:
|
||||
return string(method('path'))
|
||||
|
||||
async def clearAlias(self, alias: str):
|
||||
return nil(await self._send('clearAlias', alias))
|
||||
|
||||
async def programs(self, showHidden: bool = None) -> List[str]:
|
||||
return array_string(await self._send('programs', showHidden))
|
||||
def setPath(path: str):
|
||||
return nil(method('setPath', path))
|
||||
|
||||
async def getRunningProgram(self) -> str:
|
||||
return string(await self._send('getRunningProgram'))
|
||||
|
||||
async def run(self, command: str, *args: str) -> bool:
|
||||
return boolean(await self._send('run', command, *args))
|
||||
def resolve(localPath: str) -> str:
|
||||
return string(method('resolve', localPath))
|
||||
|
||||
async def execute(self, command: str, *args: str) -> bool:
|
||||
return boolean(await self._send('execute', command, *args))
|
||||
|
||||
async def openTab(self, command: str, *args: str) -> int:
|
||||
return integer(await self._send('openTab', command, *args))
|
||||
def resolveProgram(name: str) -> Optional[str]:
|
||||
return option_string(method('resolveProgram', name))
|
||||
|
||||
async def switchTab(self, tabID: int):
|
||||
return nil(await self._send('switchTab', tabID))
|
||||
|
||||
async def complete(self, prefix: str) -> List[str]:
|
||||
return array_string(await self._send('complete', prefix))
|
||||
def aliases(self) -> Dict[str, str]:
|
||||
return map_string_string(method('aliases'))
|
||||
|
||||
async def completeProgram(self, prefix: str) -> List[str]:
|
||||
return array_string(await self._send('completeProgram', prefix))
|
||||
|
||||
# these functions won't be implemented
|
||||
# it's far better to keep this in lua code
|
||||
def setAlias(alias: str, program: str):
|
||||
return nil(method('setAlias', alias, program))
|
||||
|
||||
# setCompletionFunction
|
||||
# getCompletionInfo
|
||||
|
||||
# we can create callbacks to python code, but this will require
|
||||
# connection to python, and will break the shell if python disconnects
|
||||
def clearAlias(alias: str):
|
||||
return nil(method('clearAlias', alias))
|
||||
|
||||
|
||||
def programs(showHidden: bool = None) -> List[str]:
|
||||
return array_string(method('programs', showHidden))
|
||||
|
||||
|
||||
def getRunningProgram(self) -> str:
|
||||
return string(method('getRunningProgram'))
|
||||
|
||||
|
||||
def run(command: str, *args: str) -> bool:
|
||||
return boolean(method('run', command, *args))
|
||||
|
||||
|
||||
def execute(command: str, *args: str) -> bool:
|
||||
return boolean(method('execute', command, *args))
|
||||
|
||||
|
||||
def openTab(command: str, *args: str) -> int:
|
||||
return integer(method('openTab', command, *args))
|
||||
|
||||
|
||||
def switchTab(tabID: int):
|
||||
return nil(method('switchTab', tabID))
|
||||
|
||||
|
||||
def complete(prefix: str) -> List[str]:
|
||||
return array_string(method('complete', prefix))
|
||||
|
||||
|
||||
def completeProgram(prefix: str) -> List[str]:
|
||||
return array_string(method('completeProgram', prefix))
|
||||
|
||||
# TODO: ?
|
||||
# these functions won't be implemented
|
||||
# it's far better to keep this in lua code
|
||||
|
||||
# setCompletionFunction
|
||||
# getCompletionInfo
|
||||
|
||||
# we can create callbacks to python code, but this will require
|
||||
# connection to python, and will break the shell if python disconnects
|
||||
|
|
|
@ -1,30 +1,75 @@
|
|||
from contextlib import asynccontextmanager
|
||||
from contextlib import contextmanager
|
||||
from typing import Tuple
|
||||
|
||||
from .base import BaseSubAPI
|
||||
from .mixins import TermMixin, TermTarget
|
||||
from ..lua import lua_args
|
||||
from ..lua import lua_call
|
||||
from ..rproc import tuple3_number
|
||||
from ..sess import eval_lua_method_factory, lua_context_object
|
||||
|
||||
|
||||
class TermAPI(BaseSubAPI, TermMixin):
|
||||
async def nativePaletteColor(self, colorID: int) -> Tuple[float, float, float]:
|
||||
return tuple3_number(await self._send('nativePaletteColor', colorID))
|
||||
pass
|
||||
|
||||
@asynccontextmanager
|
||||
async def redirect(self, target: TermTarget):
|
||||
create_expr = '{}.redirect({})'.format(
|
||||
self.get_expr_code(),
|
||||
lua_args(target),
|
||||
)
|
||||
fin_tpl = '{}.redirect({{e}})'.format(
|
||||
self.get_expr_code(),
|
||||
)
|
||||
async with self._cc._create_temp_object(create_expr, fin_tpl):
|
||||
yield
|
||||
|
||||
def get_current_target(self) -> TermTarget:
|
||||
return TermTarget('{}.current()'.format(self.get_expr_code()))
|
||||
method = eval_lua_method_factory('term.')
|
||||
tapi = TermAPI('term')
|
||||
|
||||
def get_native_target(self) -> TermTarget:
|
||||
return TermTarget('{}.native()'.format(self.get_expr_code()))
|
||||
|
||||
__all__ = (
|
||||
'write',
|
||||
'blit',
|
||||
'clear',
|
||||
'clearLine',
|
||||
'getCursorPos',
|
||||
'setCursorPos',
|
||||
'getCursorBlink',
|
||||
'isColor',
|
||||
'getSize',
|
||||
'scroll',
|
||||
'setTextColor',
|
||||
'getTextColor',
|
||||
'setBackgroundColor',
|
||||
'getBackgroundColor',
|
||||
'getPaletteColor',
|
||||
'setPaletteColor',
|
||||
)
|
||||
|
||||
|
||||
write = tapi.write
|
||||
blit = tapi.blit
|
||||
clear = tapi.clear
|
||||
clearLine = tapi.clearLine
|
||||
getCursorPos = tapi.getCursorPos
|
||||
setCursorPos = tapi.setCursorPos
|
||||
getCursorBlink = tapi.getCursorBlink
|
||||
isColor = tapi.isColor
|
||||
getSize = tapi.getSize
|
||||
scroll = tapi.scroll
|
||||
setTextColor = tapi.setTextColor
|
||||
getTextColor = tapi.getTextColor
|
||||
setBackgroundColor = tapi.setBackgroundColor
|
||||
getBackgroundColor = tapi.getBackgroundColor
|
||||
getPaletteColor = tapi.getPaletteColor
|
||||
setPaletteColor = tapi.setPaletteColor
|
||||
|
||||
|
||||
def nativePaletteColor(colorID: int) -> Tuple[float, float, float]:
|
||||
return tuple3_number(method('nativePaletteColor', colorID))
|
||||
|
||||
|
||||
@contextmanager
|
||||
def redirect(target: TermTarget):
|
||||
with lua_context_object(
|
||||
lua_call('term.redirect', target),
|
||||
'term.redirect({e})'
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
def get_current_target() -> TermTarget:
|
||||
return TermTarget('term.current()')
|
||||
|
||||
|
||||
def get_native_target() -> TermTarget:
|
||||
return TermTarget('term.native()')
|
||||
|
|
|
@ -1,39 +1,59 @@
|
|||
from typing import List, Union
|
||||
|
||||
from .base import BaseSubAPI
|
||||
from ..lua import LuaNum
|
||||
from ..rproc import nil, string, integer
|
||||
from ..sess import eval_lua_method_factory
|
||||
|
||||
|
||||
class TextutilsAPI(BaseSubAPI):
|
||||
async def slowWrite(self, text: str, rate: LuaNum = None):
|
||||
return nil(await self._send('slowWrite', text, rate))
|
||||
method = eval_lua_method_factory('textutils.')
|
||||
|
||||
async def slowPrint(self, text: str, rate: LuaNum = None):
|
||||
return nil(await self._send('slowPrint', text, rate))
|
||||
|
||||
async def formatTime(self, time: LuaNum, twentyFourHour: bool = None) -> str:
|
||||
return string(await self._send('formatTime', time, twentyFourHour))
|
||||
__all__ = (
|
||||
'slowWrite',
|
||||
'slowPrint',
|
||||
'formatTime',
|
||||
'tabulate',
|
||||
'pagedTabulate',
|
||||
'pagedPrint',
|
||||
'complete',
|
||||
)
|
||||
|
||||
async def tabulate(self, *rows_and_colors: Union[list, int]):
|
||||
return nil(await self._send('tabulate', *rows_and_colors))
|
||||
|
||||
async def pagedTabulate(self, *rows_and_colors: Union[list, int]):
|
||||
return nil(await self._send('pagedTabulate', *rows_and_colors))
|
||||
def slowWrite(text: str, rate: LuaNum = None):
|
||||
return nil(method('slowWrite', text, rate))
|
||||
|
||||
async def pagedPrint(self, text: str, freeLines: int = None) -> int:
|
||||
return integer(await self._send('pagedPrint', text, freeLines))
|
||||
|
||||
def complete(self, partial: str, possible: List[str]) -> List[str]:
|
||||
return [p[len(partial):] for p in possible if p.startswith(partial)]
|
||||
def slowPrint(text: str, rate: LuaNum = None):
|
||||
return nil(method('slowPrint', text, rate))
|
||||
|
||||
# Questionable to implement
|
||||
# serialize
|
||||
# unserialize
|
||||
|
||||
# Will not implement, use pythonic equivalents
|
||||
# serializeJSON
|
||||
# unserializeJSON
|
||||
# urlEncode
|
||||
# json_null
|
||||
# empty_json_array
|
||||
def formatTime(time: LuaNum, twentyFourHour: bool = None) -> str:
|
||||
return string(method('formatTime', time, twentyFourHour))
|
||||
|
||||
|
||||
def tabulate(*rows_and_colors: Union[list, int]):
|
||||
return nil(method('tabulate', *rows_and_colors))
|
||||
|
||||
|
||||
def pagedTabulate(*rows_and_colors: Union[list, int]):
|
||||
return nil(method('pagedTabulate', *rows_and_colors))
|
||||
|
||||
|
||||
def pagedPrint(text: str, freeLines: int = None) -> int:
|
||||
return integer(method('pagedPrint', text, freeLines))
|
||||
|
||||
|
||||
def complete(partial: str, possible: List[str]) -> List[str]:
|
||||
return [p[len(partial):] for p in possible if p.startswith(partial)]
|
||||
|
||||
|
||||
# Questionable to implement
|
||||
# serialize
|
||||
# unserialize
|
||||
|
||||
# Will not implement, use pythonic equivalents
|
||||
# serializeJSON
|
||||
# unserializeJSON
|
||||
# urlEncode
|
||||
# json_null
|
||||
# empty_json_array
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
from typing import Optional
|
||||
|
||||
from .base import BaseSubAPI
|
||||
from ..errors import LuaException
|
||||
from ..rproc import integer, boolean, fact_option, any_dict, flat_try_result
|
||||
from ..sess import eval_lua_method_factory
|
||||
|
||||
|
||||
method = eval_lua_method_factory('turtle.')
|
||||
option_any_dict = fact_option(any_dict)
|
||||
|
||||
|
||||
|
@ -54,132 +55,220 @@ def always_true(r):
|
|||
return None
|
||||
|
||||
|
||||
class TurtleAPI(BaseSubAPI):
|
||||
async def craft(self, quantity: int = 64) -> bool:
|
||||
return craft_result(await self._send('craft', quantity))
|
||||
__all__ = (
|
||||
'craft',
|
||||
'forward',
|
||||
'back',
|
||||
'up',
|
||||
'down',
|
||||
'turnLeft',
|
||||
'turnRight',
|
||||
'select',
|
||||
'getSelectedSlot',
|
||||
'getItemCount',
|
||||
'getItemSpace',
|
||||
'getItemDetail',
|
||||
'equipLeft',
|
||||
'equipRight',
|
||||
'attack',
|
||||
'attackUp',
|
||||
'attackDown',
|
||||
'dig',
|
||||
'digUp',
|
||||
'digDown',
|
||||
'place',
|
||||
'placeUp',
|
||||
'placeDown',
|
||||
'detect',
|
||||
'detectUp',
|
||||
'detectDown',
|
||||
'inspect',
|
||||
'inspectUp',
|
||||
'inspectDown',
|
||||
'compare',
|
||||
'compareUp',
|
||||
'compareDown',
|
||||
'compareTo',
|
||||
'drop',
|
||||
'dropUp',
|
||||
'dropDown',
|
||||
'suck',
|
||||
'suckUp',
|
||||
'suckDown',
|
||||
'refuel',
|
||||
'getFuelLevel',
|
||||
'getFuelLimit',
|
||||
'transferTo',
|
||||
)
|
||||
|
||||
async def forward(self) -> bool:
|
||||
return move_result(await self._send('forward'))
|
||||
|
||||
async def back(self) -> bool:
|
||||
return move_result(await self._send('back'))
|
||||
def craft(quantity: int = 64) -> bool:
|
||||
return craft_result(method('craft', quantity))
|
||||
|
||||
async def up(self) -> bool:
|
||||
return move_result(await self._send('up'))
|
||||
|
||||
async def down(self) -> bool:
|
||||
return move_result(await self._send('down'))
|
||||
def forward() -> bool:
|
||||
return move_result(method('forward'))
|
||||
|
||||
async def turnLeft(self):
|
||||
return always_true(await self._send('turnLeft'))
|
||||
|
||||
async def turnRight(self):
|
||||
return always_true(await self._send('turnRight'))
|
||||
def back() -> bool:
|
||||
return move_result(method('back'))
|
||||
|
||||
async def select(self, slotNum: int):
|
||||
return always_true(await self._send('select', slotNum))
|
||||
|
||||
async def getSelectedSlot(self) -> int:
|
||||
return integer(await self._send('getSelectedSlot'))
|
||||
def up() -> bool:
|
||||
return move_result(method('up'))
|
||||
|
||||
async def getItemCount(self, slotNum: int = None) -> int:
|
||||
return integer(await self._send('getItemCount', slotNum))
|
||||
|
||||
async def getItemSpace(self, slotNum: int = None) -> int:
|
||||
return integer(await self._send('getItemSpace', slotNum))
|
||||
def down() -> bool:
|
||||
return move_result(method('down'))
|
||||
|
||||
async def getItemDetail(self, slotNum: int = None) -> dict:
|
||||
return option_any_dict(await self._send('getItemDetail', slotNum))
|
||||
|
||||
async def equipLeft(self):
|
||||
return always_true(await self._send('equipLeft'))
|
||||
def turnLeft():
|
||||
return always_true(method('turnLeft'))
|
||||
|
||||
async def equipRight(self):
|
||||
return always_true(await self._send('equipRight'))
|
||||
|
||||
async def attack(self) -> bool:
|
||||
return attack_result(await self._send('attack'))
|
||||
def turnRight():
|
||||
return always_true(method('turnRight'))
|
||||
|
||||
async def attackUp(self) -> bool:
|
||||
return attack_result(await self._send('attackUp'))
|
||||
|
||||
async def attackDown(self) -> bool:
|
||||
return attack_result(await self._send('attackDown'))
|
||||
def select(slotNum: int):
|
||||
return always_true(method('select', slotNum))
|
||||
|
||||
async def dig(self) -> bool:
|
||||
return dig_result(await self._send('dig'))
|
||||
|
||||
async def digUp(self) -> bool:
|
||||
return dig_result(await self._send('digUp'))
|
||||
def getSelectedSlot() -> int:
|
||||
return integer(method('getSelectedSlot'))
|
||||
|
||||
async def digDown(self) -> bool:
|
||||
return dig_result(await self._send('digDown'))
|
||||
|
||||
async def place(self, signText: str = None) -> bool:
|
||||
return place_result(await self._send('place', signText))
|
||||
def getItemCount(slotNum: int = None) -> int:
|
||||
return integer(method('getItemCount', slotNum))
|
||||
|
||||
async def placeUp(self) -> bool:
|
||||
return place_result(await self._send('placeUp'))
|
||||
|
||||
async def placeDown(self) -> bool:
|
||||
return place_result(await self._send('placeDown'))
|
||||
def getItemSpace(slotNum: int = None) -> int:
|
||||
return integer(method('getItemSpace', slotNum))
|
||||
|
||||
async def detect(self) -> bool:
|
||||
return boolean(await self._send('detect'))
|
||||
|
||||
async def detectUp(self) -> bool:
|
||||
return boolean(await self._send('detectUp'))
|
||||
def getItemDetail(slotNum: int = None) -> dict:
|
||||
return option_any_dict(method('getItemDetail', slotNum))
|
||||
|
||||
async def detectDown(self) -> bool:
|
||||
return boolean(await self._send('detectDown'))
|
||||
|
||||
async def inspect(self) -> Optional[dict]:
|
||||
return inspect_result(await self._send('inspect'))
|
||||
def equipLeft():
|
||||
return always_true(method('equipLeft'))
|
||||
|
||||
async def inspectUp(self) -> Optional[dict]:
|
||||
return inspect_result(await self._send('inspectUp'))
|
||||
|
||||
async def inspectDown(self) -> Optional[dict]:
|
||||
return inspect_result(await self._send('inspectDown'))
|
||||
def equipRight():
|
||||
return always_true(method('equipRight'))
|
||||
|
||||
async def compare(self) -> bool:
|
||||
return boolean(await self._send('compare'))
|
||||
|
||||
async def compareUp(self) -> bool:
|
||||
return boolean(await self._send('compareUp'))
|
||||
def attack() -> bool:
|
||||
return attack_result(method('attack'))
|
||||
|
||||
async def compareDown(self) -> bool:
|
||||
return boolean(await self._send('compareDown'))
|
||||
|
||||
async def compareTo(self, slot: int) -> bool:
|
||||
return boolean(await self._send('compareTo', slot))
|
||||
def attackUp() -> bool:
|
||||
return attack_result(method('attackUp'))
|
||||
|
||||
async def drop(self, count: int = None) -> bool:
|
||||
return drop_result(await self._send('drop', count))
|
||||
|
||||
async def dropUp(self, count: int = None) -> bool:
|
||||
return drop_result(await self._send('dropUp', count))
|
||||
def attackDown() -> bool:
|
||||
return attack_result(method('attackDown'))
|
||||
|
||||
async def dropDown(self, count: int = None) -> bool:
|
||||
return drop_result(await self._send('dropDown', count))
|
||||
|
||||
async def suck(self, amount: int = None) -> bool:
|
||||
return suck_result(await self._send('suck', amount))
|
||||
def dig() -> bool:
|
||||
return dig_result(method('dig'))
|
||||
|
||||
async def suckUp(self, amount: int = None) -> bool:
|
||||
return suck_result(await self._send('suckUp', amount))
|
||||
|
||||
async def suckDown(self, amount: int = None) -> bool:
|
||||
return suck_result(await self._send('suckDown', amount))
|
||||
def digUp() -> bool:
|
||||
return dig_result(method('digUp'))
|
||||
|
||||
async def refuel(self, quantity: int = None):
|
||||
return flat_try_result(await self._send('refuel', quantity))
|
||||
|
||||
async def getFuelLevel(self) -> int:
|
||||
return integer(await self._send('getFuelLevel'))
|
||||
def digDown() -> bool:
|
||||
return dig_result(method('digDown'))
|
||||
|
||||
async def getFuelLimit(self) -> int:
|
||||
return integer(await self._send('getFuelLimit'))
|
||||
|
||||
async def transferTo(self, slot: int, quantity: int = None) -> bool:
|
||||
return transfer_result(await self._send('transferTo', slot, quantity))
|
||||
def place(signText: str = None) -> bool:
|
||||
return place_result(method('place', signText))
|
||||
|
||||
|
||||
def placeUp() -> bool:
|
||||
return place_result(method('placeUp'))
|
||||
|
||||
|
||||
def placeDown() -> bool:
|
||||
return place_result(method('placeDown'))
|
||||
|
||||
|
||||
def detect() -> bool:
|
||||
return boolean(method('detect'))
|
||||
|
||||
|
||||
def detectUp() -> bool:
|
||||
return boolean(method('detectUp'))
|
||||
|
||||
|
||||
def detectDown() -> bool:
|
||||
return boolean(method('detectDown'))
|
||||
|
||||
|
||||
def inspect() -> Optional[dict]:
|
||||
return inspect_result(method('inspect'))
|
||||
|
||||
|
||||
def inspectUp() -> Optional[dict]:
|
||||
return inspect_result(method('inspectUp'))
|
||||
|
||||
|
||||
def inspectDown() -> Optional[dict]:
|
||||
return inspect_result(method('inspectDown'))
|
||||
|
||||
|
||||
def compare() -> bool:
|
||||
return boolean(method('compare'))
|
||||
|
||||
|
||||
def compareUp() -> bool:
|
||||
return boolean(method('compareUp'))
|
||||
|
||||
|
||||
def compareDown() -> bool:
|
||||
return boolean(method('compareDown'))
|
||||
|
||||
|
||||
def compareTo(slot: int) -> bool:
|
||||
return boolean(method('compareTo', slot))
|
||||
|
||||
|
||||
def drop(count: int = None) -> bool:
|
||||
return drop_result(method('drop', count))
|
||||
|
||||
|
||||
def dropUp(count: int = None) -> bool:
|
||||
return drop_result(method('dropUp', count))
|
||||
|
||||
|
||||
def dropDown(count: int = None) -> bool:
|
||||
return drop_result(method('dropDown', count))
|
||||
|
||||
|
||||
def suck(amount: int = None) -> bool:
|
||||
return suck_result(method('suck', amount))
|
||||
|
||||
|
||||
def suckUp(amount: int = None) -> bool:
|
||||
return suck_result(method('suckUp', amount))
|
||||
|
||||
|
||||
def suckDown(amount: int = None) -> bool:
|
||||
return suck_result(method('suckDown', amount))
|
||||
|
||||
|
||||
def refuel(quantity: int = None):
|
||||
return flat_try_result(method('refuel', quantity))
|
||||
|
||||
|
||||
def getFuelLevel() -> int:
|
||||
return integer(method('getFuelLevel'))
|
||||
|
||||
|
||||
def getFuelLimit() -> int:
|
||||
return integer(method('getFuelLimit'))
|
||||
|
||||
|
||||
def transferTo(slot: int, quantity: int = None) -> bool:
|
||||
return transfer_result(method('transferTo', slot, quantity))
|
||||
|
|
|
@ -1,43 +1,49 @@
|
|||
from contextlib import asynccontextmanager
|
||||
from contextlib import contextmanager
|
||||
from typing import Tuple
|
||||
|
||||
from ..lua import lua_args
|
||||
from ..lua import lua_call
|
||||
from ..rproc import nil, tuple2_integer, tuple3_string
|
||||
from ..sess import eval_lua_method_factory, lua_context_object
|
||||
from .base import BaseSubAPI
|
||||
from .mixins import TermMixin, TermTarget
|
||||
|
||||
|
||||
class CCWindow(BaseSubAPI, TermMixin):
|
||||
async def setVisible(self, visibility: bool):
|
||||
return nil(await self._send('setVisible', visibility))
|
||||
def setVisible(self, visibility: bool):
|
||||
return nil(self._method('setVisible', visibility))
|
||||
|
||||
async def redraw(self):
|
||||
return nil(await self._send('redraw'))
|
||||
def redraw(self):
|
||||
return nil(self._method('redraw'))
|
||||
|
||||
async def restoreCursor(self):
|
||||
return nil(await self._send('restoreCursor'))
|
||||
def restoreCursor(self):
|
||||
return nil(self._method('restoreCursor'))
|
||||
|
||||
async def getPosition(self) -> Tuple[int, int]:
|
||||
return tuple2_integer(await self._send('getPosition'))
|
||||
def getPosition(self) -> Tuple[int, int]:
|
||||
return tuple2_integer(self._method('getPosition'))
|
||||
|
||||
async def reposition(self, x: int, y: int, width: int = None, height: int = None, parent: TermTarget = None):
|
||||
return nil(await self._send('reposition', x, y, width, height, parent))
|
||||
def reposition(self, x: int, y: int, width: int = None, height: int = None, parent: TermTarget = None):
|
||||
return nil(self._method('reposition', x, y, width, height, parent))
|
||||
|
||||
async def getLine(self, y: int) -> Tuple[str, str, str]:
|
||||
return tuple3_string(await self._send('getLine', y))
|
||||
def getLine(self, y: int) -> Tuple[str, str, str]:
|
||||
return tuple3_string(self._method('getLine', y))
|
||||
|
||||
def get_term_target(self) -> TermTarget:
|
||||
return TermTarget(self.get_expr_code())
|
||||
|
||||
|
||||
class WindowAPI(BaseSubAPI):
|
||||
@asynccontextmanager
|
||||
async def create(
|
||||
self, parentTerm: TermTarget, x: int, y: int, width: int, height: int, visible: bool = None,
|
||||
) -> CCWindow:
|
||||
create_expr = '{}.create({})'.format(
|
||||
self.get_expr_code(),
|
||||
lua_args(parentTerm, x, y, width, height, visible),
|
||||
)
|
||||
async with self._cc._create_temp_object(create_expr) as var:
|
||||
yield CCWindow(self._cc, var)
|
||||
method = eval_lua_method_factory('window.')
|
||||
|
||||
|
||||
__all__ = (
|
||||
'create',
|
||||
)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def create(
|
||||
parentTerm: TermTarget, x: int, y: int, width: int, height: int, visible: bool = None,
|
||||
) -> CCWindow:
|
||||
with lua_context_object(
|
||||
lua_call('window.create', parentTerm, x, y, width, height, visible),
|
||||
) as var:
|
||||
yield CCWindow(var)
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
from .hello import program as hello
|
||||
|
||||
|
||||
__all__ = (hello, )
|
135
examples/_lib.py
Normal file
135
examples/_lib.py
Normal file
|
@ -0,0 +1,135 @@
|
|||
from contextlib import contextmanager
|
||||
from time import monotonic
|
||||
from types import FunctionType
|
||||
|
||||
from cc import eval_lua
|
||||
|
||||
|
||||
@contextmanager
|
||||
def assert_raises(etype, message=None):
|
||||
try:
|
||||
yield
|
||||
except Exception as e:
|
||||
assert isinstance(e, etype), repr(e)
|
||||
if message is not None:
|
||||
assert e.args == (message, )
|
||||
else:
|
||||
raise AssertionError(f'Exception of type {etype} was not raised')
|
||||
|
||||
|
||||
@contextmanager
|
||||
def assert_takes_time(at_least, at_most):
|
||||
t = monotonic()
|
||||
yield
|
||||
dt = monotonic() - t
|
||||
# print(at_least, '<=', dt, '<=', at_most)
|
||||
assert at_least <= dt <= at_most
|
||||
|
||||
|
||||
class AnyInstanceOf:
|
||||
def __init__(self, cls):
|
||||
self.c = cls
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, self.c)
|
||||
|
||||
|
||||
def step(text):
|
||||
input(f'{text} [enter]')
|
||||
|
||||
|
||||
def get_object_table(objname):
|
||||
r = eval_lua(f"""
|
||||
local r = {{}}
|
||||
for k in pairs({objname}) do
|
||||
local t = type({objname}[k])
|
||||
if r[t] == nil then r[t] = {{}} end
|
||||
if t == 'number' or t == 'boolean' or t == 'string' then
|
||||
r[t][k] = {objname}[k]
|
||||
else
|
||||
r[t][k] = true
|
||||
end
|
||||
end
|
||||
return r""", immediate=True)
|
||||
assert len(r) == 1
|
||||
return r[0]
|
||||
|
||||
|
||||
def get_class_table(cls):
|
||||
items = {
|
||||
k: v for k, v in vars(cls).items()
|
||||
if not k.startswith('_')
|
||||
}
|
||||
nums = {
|
||||
k: v for k, v in items.items()
|
||||
if isinstance(v, (int, float))
|
||||
}
|
||||
methods = {
|
||||
k: True for k, v in items.items()
|
||||
if isinstance(v, FunctionType)
|
||||
}
|
||||
r = {}
|
||||
if nums:
|
||||
r['number'] = nums
|
||||
if methods:
|
||||
r['function'] = methods
|
||||
return r
|
||||
|
||||
|
||||
def get_multiclass_table(*cls):
|
||||
result = {}
|
||||
for c in cls:
|
||||
for k, v in get_class_table(c).items():
|
||||
result.setdefault(k, {}).update(v)
|
||||
return result
|
||||
|
||||
|
||||
def term_step(text):
|
||||
from cc import colors, term
|
||||
|
||||
for color in colors.iter_colors():
|
||||
r, g, b = term.nativePaletteColor(color)
|
||||
term.setPaletteColor(color, r, g, b)
|
||||
term.setBackgroundColor(colors.black)
|
||||
term.setTextColor(colors.white)
|
||||
term.clear()
|
||||
term.setCursorPos(1, 1)
|
||||
term.setCursorBlink(True)
|
||||
step(text)
|
||||
|
||||
|
||||
def _computer_peri(place_thing, thing):
|
||||
from cc import peripheral
|
||||
|
||||
side = 'left'
|
||||
|
||||
step(
|
||||
f'Place {place_thing} on {side} side of computer\n'
|
||||
"Don't turn it on!",
|
||||
)
|
||||
|
||||
c = peripheral.wrap(side)
|
||||
assert c is not None
|
||||
|
||||
from computercraft.subapis.peripheral import ComputerMixin
|
||||
tbl = get_object_table(f'peripheral.wrap("{side}")')
|
||||
assert get_class_table(ComputerMixin) == tbl
|
||||
|
||||
assert c.isOn() is False
|
||||
assert isinstance(c.getID(), int)
|
||||
assert c.getLabel() is None
|
||||
assert c.turnOn() is None
|
||||
|
||||
step(f'{thing.capitalize()} must be turned on now')
|
||||
|
||||
assert c.shutdown() is None
|
||||
|
||||
step(f'{thing.capitalize()} must shutdown')
|
||||
|
||||
step(f'Now turn on {thing} manually and enter some commands')
|
||||
|
||||
assert c.reboot() is None
|
||||
|
||||
step(f'{thing.capitalize()} must reboot')
|
||||
|
||||
print('Test finished successfully')
|
|
@ -1,2 +1 @@
|
|||
async def program(api):
|
||||
await api.print('Hello world!')
|
||||
print('Hello world!')
|
||||
|
|
5
examples/id.py
Normal file
5
examples/id.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from cc import os
|
||||
|
||||
print('ID', os.getComputerID())
|
||||
print('Label', os.getComputerLabel())
|
||||
print('Version', os.version())
|
12
examples/modem_server.py
Normal file
12
examples/modem_server.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
from cc import peripheral
|
||||
|
||||
|
||||
side = 'back'
|
||||
m = peripheral.wrap(side)
|
||||
listen_channel = 5
|
||||
for msg in m.receive(listen_channel):
|
||||
print(repr(msg))
|
||||
if msg.content == 'stop':
|
||||
break
|
||||
else:
|
||||
m.transmit(msg.reply_channel, listen_channel, msg.content)
|
9
examples/move.py
Normal file
9
examples/move.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
from cc import is_turtle, turtle
|
||||
|
||||
if not is_turtle():
|
||||
print('Turtle required!')
|
||||
exit()
|
||||
|
||||
for _ in range(4):
|
||||
turtle.forward()
|
||||
turtle.turnLeft()
|
2
examples/read.py
Normal file
2
examples/read.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
line = input()
|
||||
print(f'Entered line: {line}')
|
38
examples/test_colors.py
Normal file
38
examples/test_colors.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
from cc import import_file, colors
|
||||
|
||||
_lib = import_file('_lib.py', __file__)
|
||||
|
||||
|
||||
tbl = _lib.get_object_table('colors')
|
||||
|
||||
# use packRGB and unpackRGB
|
||||
del tbl['function']['rgb8']
|
||||
|
||||
tbl['function']['iter_colors'] = True
|
||||
|
||||
assert _lib.get_class_table(colors) == tbl
|
||||
|
||||
cs = colors.combine(
|
||||
colors.orange,
|
||||
colors.cyan,
|
||||
colors.pink,
|
||||
colors.brown,
|
||||
)
|
||||
assert isinstance(cs, int)
|
||||
cs = colors.subtract(cs, colors.brown, colors.green)
|
||||
assert isinstance(cs, int)
|
||||
assert cs == colors.combine(
|
||||
colors.orange,
|
||||
colors.cyan,
|
||||
colors.pink,
|
||||
)
|
||||
assert colors.test(cs, colors.red) is False
|
||||
assert colors.test(cs, colors.cyan) is True
|
||||
|
||||
assert colors.packRGB(0.7, 0.2, 0.6) == 0xb23399
|
||||
r, g, b = colors.unpackRGB(0xb23399)
|
||||
assert 0.68 < r < 0.72
|
||||
assert 0.18 < g < 0.22
|
||||
assert 0.58 < b < 0.62
|
||||
|
||||
print('Test finished successfully')
|
55
examples/test_commands.py
Normal file
55
examples/test_commands.py
Normal file
|
@ -0,0 +1,55 @@
|
|||
from cc import import_file, commands
|
||||
|
||||
_lib = import_file('_lib.py', __file__)
|
||||
AnyInstanceOf = _lib.AnyInstanceOf
|
||||
|
||||
|
||||
tbl = _lib.get_object_table('commands.native')
|
||||
# remove in favor of exec
|
||||
del tbl['function']['execAsync']
|
||||
assert _lib.get_class_table(commands) == tbl
|
||||
|
||||
xyz = commands.getBlockPosition()
|
||||
|
||||
assert len(xyz) == 3
|
||||
for c in xyz:
|
||||
assert isinstance(c, int)
|
||||
|
||||
expected_binfo = {
|
||||
'state': {
|
||||
'state': AnyInstanceOf(str),
|
||||
'facing': AnyInstanceOf(str),
|
||||
},
|
||||
'name': 'computercraft:computer_command',
|
||||
'nbt': {
|
||||
'x': xyz[0],
|
||||
'y': xyz[1],
|
||||
'z': xyz[2],
|
||||
'ComputerId': AnyInstanceOf(int),
|
||||
'id': 'computercraft:computer_command',
|
||||
'On': 1,
|
||||
},
|
||||
'tags': {},
|
||||
}
|
||||
|
||||
assert commands.getBlockInfo(*xyz) == expected_binfo
|
||||
assert commands.getBlockInfos(*xyz, *xyz) == [expected_binfo]
|
||||
|
||||
cmdlist = commands.list()
|
||||
|
||||
assert len(cmdlist) > 0
|
||||
for c in cmdlist:
|
||||
assert isinstance(c, str)
|
||||
|
||||
assert commands.exec('say Hello!') == (True, [], AnyInstanceOf(int))
|
||||
|
||||
d = commands.exec('tp hajejndlasksdkelefsns fjeklaskslekffjslas')
|
||||
assert d[0] is False
|
||||
|
||||
d = commands.exec('difficulty')
|
||||
assert d[0] is True
|
||||
assert len(d[1]) == 1
|
||||
assert d[1][0].startswith('The difficulty is ')
|
||||
assert isinstance(d[2], int)
|
||||
|
||||
print('Test finished successfully')
|
86
examples/test_disk.py
Normal file
86
examples/test_disk.py
Normal file
|
@ -0,0 +1,86 @@
|
|||
from cc import LuaException, import_file, disk
|
||||
|
||||
_lib = import_file('_lib.py', __file__)
|
||||
step, assert_raises = _lib.step, _lib.assert_raises
|
||||
|
||||
|
||||
s = 'right'
|
||||
|
||||
assert _lib.get_class_table(disk) == _lib.get_object_table('disk')
|
||||
|
||||
step(f'Make sure there is no disk drive at {s} side')
|
||||
|
||||
assert disk.isPresent(s) is False
|
||||
assert disk.hasData(s) is False
|
||||
assert disk.getMountPath(s) is None
|
||||
assert disk.setLabel(s, 'text') is None
|
||||
assert disk.getLabel(s) is None
|
||||
assert disk.getID(s) is None
|
||||
assert disk.hasAudio(s) is False
|
||||
assert disk.getAudioTitle(s) is None
|
||||
assert disk.playAudio(s) is None
|
||||
assert disk.stopAudio(s) is None
|
||||
assert disk.eject(s) is None
|
||||
|
||||
step(f'Place empty disk drive at {s} side')
|
||||
|
||||
assert disk.isPresent(s) is False
|
||||
assert disk.hasData(s) is False
|
||||
assert disk.getMountPath(s) is None
|
||||
assert disk.setLabel(s, 'text') is None
|
||||
assert disk.getLabel(s) is None
|
||||
assert disk.getID(s) is None
|
||||
assert disk.hasAudio(s) is False
|
||||
assert disk.getAudioTitle(s) is False # False instead None!
|
||||
assert disk.playAudio(s) is None
|
||||
assert disk.stopAudio(s) is None
|
||||
assert disk.eject(s) is None
|
||||
|
||||
step('Put new CC diskette into disk drive')
|
||||
|
||||
assert disk.isPresent(s) is True
|
||||
assert disk.hasData(s) is True
|
||||
assert isinstance(disk.getMountPath(s), str)
|
||||
assert isinstance(disk.getID(s), int)
|
||||
|
||||
assert disk.getLabel(s) is None
|
||||
assert disk.setLabel(s, 'label') is None
|
||||
assert disk.getLabel(s) == 'label'
|
||||
assert disk.setLabel(s, None) is None
|
||||
assert disk.getLabel(s) is None
|
||||
|
||||
assert disk.hasAudio(s) is False
|
||||
assert disk.getAudioTitle(s) is None
|
||||
assert disk.playAudio(s) is None
|
||||
assert disk.stopAudio(s) is None
|
||||
|
||||
assert disk.eject(s) is None
|
||||
|
||||
step('Put any audio disk into disk drive')
|
||||
|
||||
assert disk.isPresent(s) is True
|
||||
assert disk.hasData(s) is False
|
||||
assert disk.getMountPath(s) is None
|
||||
assert disk.getID(s) is None
|
||||
assert disk.hasAudio(s) is True
|
||||
|
||||
label = disk.getAudioTitle(s)
|
||||
assert isinstance(label, str)
|
||||
assert label != 'label'
|
||||
print(f'Label is {label}')
|
||||
assert disk.getLabel(s) == label
|
||||
with assert_raises(LuaException):
|
||||
assert disk.setLabel(s, 'label') is None
|
||||
with assert_raises(LuaException):
|
||||
assert disk.setLabel(s, None) is None
|
||||
# no effect
|
||||
assert disk.getLabel(s) == label
|
||||
|
||||
assert disk.playAudio(s) is None
|
||||
|
||||
step('Audio must be playing now')
|
||||
|
||||
assert disk.stopAudio(s) is None
|
||||
assert disk.eject(s) is None
|
||||
|
||||
print('Test finished successfully')
|
231
examples/test_fs.py
Normal file
231
examples/test_fs.py
Normal file
|
@ -0,0 +1,231 @@
|
|||
from cc import LuaException, import_file, fs
|
||||
|
||||
_lib = import_file('_lib.py', __file__)
|
||||
assert_raises, AnyInstanceOf = _lib.assert_raises, _lib.AnyInstanceOf
|
||||
|
||||
|
||||
assert _lib.get_class_table(fs) == _lib.get_object_table('fs')
|
||||
|
||||
for name in ('tdir', 'tfile'):
|
||||
if fs.exists(name):
|
||||
fs.delete(name)
|
||||
|
||||
assert fs.makeDir('tdir') is None
|
||||
with fs.open('tfile', 'w') as f:
|
||||
f.writeLine('textline')
|
||||
|
||||
dlist = set(fs.list('.'))
|
||||
|
||||
assert {'tdir', 'tfile', 'rom'}.issubset(dlist)
|
||||
assert fs.list('tdir') == []
|
||||
|
||||
capacity = fs.getCapacity('.')
|
||||
free = fs.getFreeSpace('.')
|
||||
assert isinstance(capacity, int)
|
||||
assert isinstance(free, int)
|
||||
assert free < capacity
|
||||
assert free > 0
|
||||
assert capacity > 0
|
||||
|
||||
assert fs.exists('tdir') is True
|
||||
assert fs.exists('tfile') is True
|
||||
assert fs.exists('doesnotexist') is False
|
||||
|
||||
assert fs.isDir('tdir') is True
|
||||
assert fs.isDir('tfile') is False
|
||||
assert fs.isDir('doesnotexist') is False
|
||||
|
||||
assert fs.isReadOnly('rom') is True
|
||||
assert fs.isReadOnly('tdir') is False
|
||||
assert fs.isReadOnly('tfile') is False
|
||||
assert fs.isReadOnly('doesnotexist') is False
|
||||
|
||||
assert fs.getDrive('rom') == 'rom'
|
||||
assert fs.getDrive('tdir') == 'hdd'
|
||||
assert fs.getDrive('tfile') == 'hdd'
|
||||
assert fs.getDrive('doesnotexist') is None
|
||||
|
||||
assert fs.isDriveRoot('/') is True
|
||||
assert fs.isDriveRoot('rom') is True
|
||||
assert fs.isDriveRoot('tdir') is False
|
||||
assert fs.isDriveRoot('tfile') is False
|
||||
assert fs.isDriveRoot('doesnotexist') is True # wtf?
|
||||
|
||||
assert fs.getName('a/b/c/d') == 'd'
|
||||
assert fs.getName('a/b/c/') == 'c'
|
||||
assert fs.getName('/a/b/c/d') == 'd'
|
||||
assert fs.getName('///a/b/c/d') == 'd'
|
||||
assert fs.getName('') == 'root' # wtf?
|
||||
assert fs.getName('/') == 'root'
|
||||
assert fs.getName('///') == 'root'
|
||||
assert fs.getName('.') == 'root'
|
||||
assert fs.getName('..') == '..'
|
||||
assert fs.getName('../../..') == '..'
|
||||
|
||||
assert fs.getDir('a/b/c/d') == 'a/b/c'
|
||||
assert fs.getDir('a/b/c/') == 'a/b'
|
||||
assert fs.getDir('/a/b/c/d') == 'a/b/c'
|
||||
assert fs.getDir('///a/b/c/d') == 'a/b/c'
|
||||
assert fs.getDir('') == '..'
|
||||
assert fs.getDir('/') == '..'
|
||||
assert fs.getDir('///') == '..'
|
||||
assert fs.getDir('.') == '..'
|
||||
assert fs.getDir('..') == ''
|
||||
assert fs.getDir('../../..') == '../..'
|
||||
|
||||
assert fs.combine('a', 'b') == 'a/b'
|
||||
assert fs.combine('a/', 'b') == 'a/b'
|
||||
assert fs.combine('a//', 'b') == 'a/b'
|
||||
assert fs.combine('a/', '/b') == 'a/b'
|
||||
assert fs.combine('a/b/c', '..') == 'a/b'
|
||||
assert fs.combine('a/b/c', '../..') == 'a'
|
||||
assert fs.combine('a/b/c', '../../..') == ''
|
||||
assert fs.combine('a/b/c', '../../../..') == '..'
|
||||
assert fs.combine('a/b/c', '../../../../..') == '../..'
|
||||
assert fs.combine('/a/b/c', '../../../../..') == '../..'
|
||||
assert fs.combine('a/b/c', '////') == 'a/b/c'
|
||||
assert fs.combine('a/b/c', '.') == 'a/b/c'
|
||||
assert fs.combine('a/b/c', './.') == 'a/b/c'
|
||||
assert fs.combine('a/b/c', './../.') == 'a/b'
|
||||
|
||||
assert fs.getSize('tfile') == 9
|
||||
assert fs.getSize('tdir') == 0
|
||||
with assert_raises(LuaException):
|
||||
fs.getSize('doesnotexist')
|
||||
|
||||
assert fs.move('tfile', 'tdir/apple') is None
|
||||
assert fs.list('tdir') == ['apple']
|
||||
assert fs.copy('tdir/apple', 'tdir/banana') is None
|
||||
assert fs.list('tdir/') == ['apple', 'banana']
|
||||
assert fs.copy('tdir/apple', 'tdir/cherry') is None
|
||||
|
||||
assert fs.getSize('tdir') == 0
|
||||
|
||||
dlist = set(fs.find('*'))
|
||||
assert 'tdir' in dlist
|
||||
assert 'rom' in dlist
|
||||
assert 'tfile' not in dlist
|
||||
assert 'tdir/apple' not in dlist
|
||||
|
||||
dlist = set(fs.find('tdir/*'))
|
||||
assert dlist == {'tdir/apple', 'tdir/banana', 'tdir/cherry'}
|
||||
|
||||
dlist = set(fs.find('tdir/*a*'))
|
||||
assert dlist == {'tdir/apple', 'tdir/banana'}
|
||||
|
||||
dlist = set(fs.find('**'))
|
||||
assert 'tdir' in dlist
|
||||
assert 'tdir/apple' not in dlist # not recursive
|
||||
|
||||
dlist = set(fs.list(''))
|
||||
assert 'tfile' not in dlist
|
||||
assert 'tdir' in dlist
|
||||
assert 'rom' in dlist
|
||||
|
||||
dlist = set(fs.list('tdir'))
|
||||
assert dlist == {'apple', 'banana', 'cherry'}
|
||||
|
||||
assert fs.attributes('tdir/banana') == {
|
||||
'created': AnyInstanceOf(int),
|
||||
'modification': AnyInstanceOf(int),
|
||||
'isDir': False,
|
||||
'size': 9,
|
||||
}
|
||||
assert fs.attributes('tdir') == {
|
||||
'created': AnyInstanceOf(int),
|
||||
'modification': AnyInstanceOf(int),
|
||||
'isDir': True,
|
||||
'size': 0,
|
||||
}
|
||||
with assert_raises(LuaException):
|
||||
fs.attributes('doesnotexist')
|
||||
|
||||
assert fs.complete('ba', 'tdir') == ['nana']
|
||||
assert fs.complete('ap', 'tdir') == ['ple']
|
||||
assert fs.complete('c', 'tdir') == ['herry']
|
||||
assert fs.complete('td', '') == ['ir/', 'ir']
|
||||
assert fs.complete('td', '', includeDirs=True) == ['ir/', 'ir']
|
||||
assert fs.complete('td', '', includeDirs=False) == ['ir/'] # wtf?
|
||||
assert fs.complete('ap', 'tdir', includeFiles=True) == ['ple']
|
||||
assert fs.complete('ap', 'tdir', includeFiles=False) == []
|
||||
|
||||
assert fs.getSize('tdir/banana') == 9
|
||||
with fs.open('tdir/banana', 'r') as f:
|
||||
assert _lib.get_object_table(f.get_expr_code()) == {'function': {
|
||||
'close': True,
|
||||
'read': True,
|
||||
'readLine': True,
|
||||
'readAll': True,
|
||||
}}
|
||||
assert f.read(4) == 'text'
|
||||
assert f.readLine() == 'line'
|
||||
assert f.read(1) is None
|
||||
assert f.readLine() is None
|
||||
assert f.readAll() == ''
|
||||
assert f.readAll() == ''
|
||||
assert fs.getSize('tdir/banana') == 9
|
||||
with fs.open('tdir/banana', 'a') as f:
|
||||
assert _lib.get_object_table(f.get_expr_code()) == {'function': {
|
||||
'close': True,
|
||||
'write': True,
|
||||
'writeLine': True,
|
||||
'flush': True,
|
||||
}}
|
||||
assert f.write('x') is None
|
||||
assert fs.getSize('tdir/banana') == 10
|
||||
with fs.open('tdir/banana', 'w') as f:
|
||||
pass
|
||||
assert fs.getSize('tdir/banana') == 0 # truncate
|
||||
with fs.open('tdir/banana', 'w') as f:
|
||||
assert _lib.get_object_table(f.get_expr_code()) == {'function': {
|
||||
'close': True,
|
||||
'write': True,
|
||||
'writeLine': True,
|
||||
'flush': True,
|
||||
}}
|
||||
assert f.write('Bro') is None
|
||||
assert f.writeLine('wn fox jumps') is None
|
||||
assert fs.getSize('tdir/banana') == 0 # changes are not on a disk
|
||||
assert f.flush() is None
|
||||
assert fs.getSize('tdir/banana') == len('Brown fox jumps\n')
|
||||
assert f.write('ov') is None
|
||||
assert f.write('er ') is None
|
||||
assert f.write('a lazy') is None
|
||||
assert f.writeLine(' dog.') is None
|
||||
assert fs.getSize('tdir/banana') > 9
|
||||
with fs.open('tdir/banana', 'r') as f:
|
||||
assert f.readAll() == 'Brown fox jumps\nover a lazy dog.' # no newline?
|
||||
with assert_raises(LuaException):
|
||||
with fs.open('tdir/banana', 'rw') as f:
|
||||
pass
|
||||
|
||||
assert fs.exists('tdir/banana') is True
|
||||
|
||||
with fs.open('tdir/binfile', 'wb') as f:
|
||||
assert f.write('a' * 9) is None
|
||||
assert f.seek() == 9
|
||||
assert f.seek('set', 0) == 0
|
||||
assert f.write('b' * 3) is None
|
||||
assert f.seek('cur', -1) == 2
|
||||
assert f.write('c' * 3) is None
|
||||
assert f.seek('end') == 9
|
||||
assert f.write('d' * 3) is None
|
||||
with assert_raises(LuaException):
|
||||
f.seek('set', -10)
|
||||
|
||||
with fs.open('tdir/binfile', 'rb') as f:
|
||||
assert f.readAll() == 'bbcccaaaaddd'
|
||||
|
||||
with fs.open('tdir/binfile', 'rb') as f:
|
||||
assert isinstance(f.read(), int)
|
||||
|
||||
with fs.open('tdir/binfile', 'r') as f:
|
||||
assert [line async for line in f] == ['bbcccaaaaddd']
|
||||
|
||||
assert fs.delete('tdir') is None
|
||||
assert fs.delete('tfile') is None
|
||||
assert fs.delete('doesnotexist') is None
|
||||
|
||||
assert fs.exists('tdir/banana') is False
|
||||
|
||||
print('Test finished successfully')
|
18
examples/test_gps_basic_computer.py
Normal file
18
examples/test_gps_basic_computer.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
from cc import import_file, gps
|
||||
|
||||
_lib = import_file('_lib.py', __file__)
|
||||
|
||||
|
||||
assert _lib.get_class_table(gps) == _lib.get_object_table('gps')
|
||||
|
||||
assert gps.locate() is None
|
||||
|
||||
_lib.step('Attach wireless modem to computer')
|
||||
|
||||
assert gps.locate() is None
|
||||
|
||||
assert gps.locate(debug=True) is None
|
||||
|
||||
assert gps.locate(timeout=5, debug=True) is None
|
||||
|
||||
print('Test finished successfully')
|
14
examples/test_gps_command_computer.py
Normal file
14
examples/test_gps_command_computer.py
Normal file
|
@ -0,0 +1,14 @@
|
|||
from cc import import_file, gps
|
||||
|
||||
_lib = import_file('_lib.py', __file__)
|
||||
|
||||
|
||||
assert _lib.get_class_table(gps) == _lib.get_object_table('gps')
|
||||
|
||||
assert gps.locate() == (
|
||||
_lib.AnyInstanceOf(int),
|
||||
_lib.AnyInstanceOf(int),
|
||||
_lib.AnyInstanceOf(int),
|
||||
)
|
||||
|
||||
print('Test finished successfully')
|
29
examples/test_help.py
Normal file
29
examples/test_help.py
Normal file
|
@ -0,0 +1,29 @@
|
|||
from cc import import_file, help
|
||||
|
||||
_lib = import_file('_lib.py', __file__)
|
||||
|
||||
|
||||
assert _lib.get_class_table(help) == _lib.get_object_table('help')
|
||||
|
||||
help.setPath('/rom/help')
|
||||
|
||||
assert help.path() == '/rom/help'
|
||||
|
||||
assert help.lookup('disk') == 'rom/help/disk.txt'
|
||||
assert help.lookup('abracadabra') is None
|
||||
|
||||
ts = help.topics()
|
||||
assert isinstance(ts, list)
|
||||
assert len(ts) > 2
|
||||
# print(ts)
|
||||
assert 'disk' in ts
|
||||
|
||||
assert help.completeTopic('di') == ['sk']
|
||||
assert help.completeTopic('abracadabra') == []
|
||||
|
||||
assert help.setPath('/kek') is None
|
||||
assert help.path() == '/kek'
|
||||
assert help.topics() == ['index']
|
||||
assert help.setPath('/rom/help') is None
|
||||
|
||||
print('Test finished successfully')
|
20
examples/test_keys.py
Normal file
20
examples/test_keys.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
from cc import keys
|
||||
|
||||
|
||||
a = keys.getCode('a')
|
||||
space = keys.getCode('space')
|
||||
enter = keys.getCode('enter')
|
||||
assert keys.getCode('doesnotexist') is None
|
||||
assert keys.getCode('getName') is None
|
||||
assert isinstance(a, int)
|
||||
assert isinstance(space, int)
|
||||
assert isinstance(enter, int)
|
||||
|
||||
assert keys.getName(a) == 'a'
|
||||
assert keys.getName(space) == 'space'
|
||||
assert keys.getName(enter) == 'enter'
|
||||
|
||||
# for i in range(255):
|
||||
# print(i, keys.getName(i))
|
||||
|
||||
print('Test finished successfully')
|
30
examples/test_multishell.py
Normal file
30
examples/test_multishell.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
import random
|
||||
|
||||
from cc import import_file, multishell
|
||||
|
||||
_lib = import_file('_lib.py', __file__)
|
||||
|
||||
|
||||
assert _lib.get_class_table(multishell) == _lib.get_object_table('multishell')
|
||||
|
||||
_lib.step('Close all additional shells')
|
||||
|
||||
assert multishell.getCount() == 1
|
||||
assert multishell.getCurrent() == 1
|
||||
assert multishell.getFocus() == 1
|
||||
assert isinstance(multishell.getTitle(1), str)
|
||||
|
||||
title = f'new title {random.randint(1, 1000000)}'
|
||||
assert multishell.setTitle(1, title) is None
|
||||
assert multishell.getTitle(1) == title
|
||||
|
||||
assert multishell.setFocus(1) is True
|
||||
assert multishell.setFocus(0) is False
|
||||
assert multishell.setFocus(2) is False
|
||||
|
||||
assert multishell.getTitle(2) is None
|
||||
|
||||
assert multishell.launch({}, 'rom/programs/fun/hello.lua') == 2
|
||||
assert isinstance(multishell.getTitle(2), str)
|
||||
|
||||
print('Test finished successfully')
|
61
examples/test_os.py
Normal file
61
examples/test_os.py
Normal file
|
@ -0,0 +1,61 @@
|
|||
from cc import import_file, os
|
||||
|
||||
_lib = import_file('_lib.py', __file__)
|
||||
|
||||
|
||||
tbl = _lib.get_object_table('os')
|
||||
|
||||
# use methods with get*
|
||||
del tbl['function']['computerID']
|
||||
del tbl['function']['computerLabel']
|
||||
|
||||
# we are in python world, loading lua modules is useless
|
||||
del tbl['function']['loadAPI']
|
||||
del tbl['function']['unloadAPI']
|
||||
|
||||
# remove complex date formatting function in favor of python stdlib
|
||||
del tbl['function']['date']
|
||||
|
||||
assert _lib.get_class_table(os) == tbl
|
||||
|
||||
|
||||
with _lib.assert_takes_time(1.5, 3):
|
||||
timer_id = os.startTimer(2)
|
||||
while True:
|
||||
e = os.pullEvent('timer')
|
||||
if e[1] == timer_id:
|
||||
print('Timer reached')
|
||||
break
|
||||
|
||||
|
||||
timer_id = os.startTimer(20)
|
||||
assert isinstance(timer_id, int)
|
||||
assert os.cancelTimer(timer_id) is None
|
||||
assert os.cancelTimer(timer_id) is None
|
||||
|
||||
alarm_id = os.setAlarm(0.0)
|
||||
assert isinstance(alarm_id, int)
|
||||
assert os.cancelAlarm(alarm_id) is None
|
||||
assert os.cancelAlarm(alarm_id) is None
|
||||
|
||||
with _lib.assert_takes_time(1.5, 3):
|
||||
assert os.sleep(2) is None
|
||||
|
||||
assert (os.version()).startswith('CraftOS ')
|
||||
assert isinstance(os.getComputerID(), int)
|
||||
|
||||
assert os.setComputerLabel(None) is None
|
||||
assert os.getComputerLabel() is None
|
||||
assert os.setComputerLabel('altair') is None
|
||||
assert os.getComputerLabel() == 'altair'
|
||||
assert os.setComputerLabel(None) is None
|
||||
assert os.getComputerLabel() is None
|
||||
|
||||
assert isinstance(os.epoch(), int)
|
||||
assert isinstance(os.day(), int)
|
||||
assert isinstance(os.time(), (int, float))
|
||||
assert isinstance(os.clock(), (int, float))
|
||||
|
||||
assert os.run({}, 'rom/programs/fun/hello.lua') is True
|
||||
|
||||
print('Test finished successfully')
|
72
examples/test_paintutils.py
Normal file
72
examples/test_paintutils.py
Normal file
|
@ -0,0 +1,72 @@
|
|||
from cc import import_file, fs, os, term, colors, paintutils
|
||||
|
||||
_lib = import_file('_lib.py', __file__)
|
||||
|
||||
|
||||
pixels = '''
|
||||
0000000030030033333333330000000003000000000000000
|
||||
0333300000000033333333300000000000333333000000330
|
||||
0803000000803033333333000000000000880330300003000
|
||||
0800800030330333333333000300883000888880000033000
|
||||
3333000000003333333333300080038880000080000888003
|
||||
33333ddd3333333333333333300000333330000000000d033
|
||||
333dddddd3333333333333333333333333333333333ddd333
|
||||
3333ccdd333333333333344444444333333333333dddddd33
|
||||
333cc33d3333333333334444444444333333333335d3cc33d
|
||||
5ddc33333333333333344444444444433333333333333cd55
|
||||
dddc555d3333333333344444444444433333333333d5dc5dd
|
||||
d5dd5dd4bbbbbbbbb999b00b00300b3bb9999bbbb4ddddddd
|
||||
ddd55444bb999993bbb33390b030bb9999bbbbbbb444ddddd
|
||||
55dd44bbbbbbbbbbbbb9bb3003003bbb339bbbbbbbb44444d
|
||||
dd444bbbbbbbbbbb99933bbb0030b999bbbbbbbbbbbbbbb44
|
||||
444bbbbbbbbbbbbbbb9bbb33b309933bbbbbbbbbbbbbbbbbb
|
||||
bbbbbbbbbbbbbbbbbbbb9bbbb3bbbb99bbbbbbbbbbbbbbbbb
|
||||
bbbbbbbbbbbbbbbbbbbbbb399399bbbbbbbbbbbbbbbbbbbbb
|
||||
'''.strip()
|
||||
|
||||
|
||||
assert _lib.get_class_table(paintutils) == _lib.get_object_table('paintutils')
|
||||
|
||||
with fs.open('img.nfp', 'w') as f:
|
||||
f.write(pixels)
|
||||
|
||||
int_pixels = paintutils.loadImage('img.nfp')
|
||||
assert len(int_pixels) > 0
|
||||
assert len(int_pixels[0]) > 0
|
||||
assert paintutils.parseImage(pixels) == int_pixels
|
||||
|
||||
assert paintutils.drawImage(int_pixels, 1, 1) is None
|
||||
|
||||
os.sleep(2)
|
||||
|
||||
term.setTextColor(colors.white)
|
||||
term.setBackgroundColor(colors.black)
|
||||
term.clear()
|
||||
term.setBackgroundColor(colors.green)
|
||||
|
||||
by = 3
|
||||
bx = 3
|
||||
|
||||
assert paintutils.drawPixel(bx, by) is None
|
||||
assert paintutils.drawPixel(bx + 1, by, colors.red) is None
|
||||
|
||||
bx += 5
|
||||
|
||||
assert paintutils.drawLine(bx, by, bx + 3, by + 3) is None
|
||||
assert paintutils.drawLine(bx + 3, by, bx, by + 3, colors.red) is None
|
||||
|
||||
bx += 5
|
||||
assert paintutils.drawBox(bx, by, bx + 3, by + 3) is None
|
||||
bx += 5
|
||||
assert paintutils.drawBox(bx, by, bx + 3, by + 3, colors.red) is None
|
||||
|
||||
bx += 5
|
||||
assert paintutils.drawFilledBox(bx, by, bx + 3, by + 3) is None
|
||||
bx += 5
|
||||
assert paintutils.drawFilledBox(bx, by, bx + 3, by + 3, colors.red) is None
|
||||
|
||||
term.setCursorPos(1, by + 6)
|
||||
|
||||
os.sleep(2)
|
||||
|
||||
print('Test finished successfully')
|
135
examples/test_parallel.py
Normal file
135
examples/test_parallel.py
Normal file
|
@ -0,0 +1,135 @@
|
|||
from cc import import_file, os, parallel
|
||||
|
||||
_lib = import_file('_lib.py', __file__)
|
||||
assert_takes_time, assert_raises = _lib.assert_takes_time, _lib.assert_raises
|
||||
|
||||
|
||||
tags = set()
|
||||
|
||||
|
||||
def partial(tag, fn, *args):
|
||||
def wrap():
|
||||
tags.add(tag)
|
||||
return fn(*args)
|
||||
return wrap
|
||||
|
||||
|
||||
all_parallels = [
|
||||
('waitForAll', parallel.waitForAll),
|
||||
('waitForAny', parallel.waitForAny),
|
||||
]
|
||||
|
||||
|
||||
for name, fn in all_parallels:
|
||||
tags.clear()
|
||||
with assert_takes_time(1.5, 3):
|
||||
# Since os.sleep is mostly waiting for events, it doesn't block execution of parallel threads
|
||||
# and this snippet takes approximately 2 seconds to complete.
|
||||
fn(
|
||||
partial('a', os.sleep, 2),
|
||||
partial('b', os.sleep, 2),
|
||||
partial('c', os.sleep, 2),
|
||||
)
|
||||
assert tags == {'a', 'b', 'c'}
|
||||
print(name, 'OK')
|
||||
|
||||
|
||||
for name, fn in all_parallels:
|
||||
tags.clear()
|
||||
tts = (0, 1) if name == 'waitForAny' else (1.5, 3)
|
||||
with assert_takes_time(*tts):
|
||||
fn(
|
||||
partial('fast', os.version),
|
||||
partial('s1', os.sleep, 2),
|
||||
partial('s2', os.sleep, 2),
|
||||
)
|
||||
assert tags == {'fast', 's1', 's2'}
|
||||
print(name, 'fast OK')
|
||||
|
||||
|
||||
def breaks_fast(etype):
|
||||
os.sleep(0.5)
|
||||
raise etype
|
||||
|
||||
|
||||
def breaks_slow(etype):
|
||||
os.sleep(3)
|
||||
raise etype
|
||||
|
||||
|
||||
tags.clear()
|
||||
with assert_takes_time(0, 1):
|
||||
parallel.waitForAny(
|
||||
partial('fast', os.version),
|
||||
partial('bomb', breaks_slow, IndexError),
|
||||
)
|
||||
assert tags == {'fast', 'bomb'}
|
||||
print('waitForAny fast success OK')
|
||||
|
||||
|
||||
tags.clear()
|
||||
with assert_takes_time(2.5, 3.8):
|
||||
with assert_raises(IndexError):
|
||||
parallel.waitForAll(
|
||||
partial('fast', os.version),
|
||||
partial('bomb', breaks_slow, IndexError),
|
||||
)
|
||||
assert tags == {'fast', 'bomb'}
|
||||
print('waitForAll waits for bomb OK')
|
||||
|
||||
|
||||
for name, fn in all_parallels:
|
||||
tags.clear()
|
||||
with assert_takes_time(0.4, 1.2):
|
||||
with assert_raises(ValueError):
|
||||
fn(
|
||||
partial('v', breaks_fast, ValueError),
|
||||
partial('s', os.sleep, 2),
|
||||
partial('i', breaks_slow, IndexError),
|
||||
)
|
||||
os.sleep(4)
|
||||
assert tags == {'v', 's', 'i'}
|
||||
print(name + ' handles error OK')
|
||||
|
||||
|
||||
for name, fn in all_parallels:
|
||||
tags.clear()
|
||||
with assert_takes_time(1.5, 3):
|
||||
fn(
|
||||
partial('1_s', os.sleep, 2),
|
||||
partial(
|
||||
'1_p',
|
||||
fn,
|
||||
partial('2_s', os.sleep, 2),
|
||||
partial(
|
||||
'2_p',
|
||||
fn,
|
||||
partial('3_s', os.sleep, 2),
|
||||
),
|
||||
),
|
||||
)
|
||||
assert tags == {'1_s', '1_p', '2_s', '2_p', '3_s'}
|
||||
print('Nested', name, 'OK')
|
||||
|
||||
|
||||
def nested_err():
|
||||
parallel.waitForAll(
|
||||
partial('n_v', breaks_fast, ValueError),
|
||||
partial('n_s', os.sleep, 2),
|
||||
partial('n_i', breaks_slow, IndexError),
|
||||
)
|
||||
|
||||
|
||||
tags.clear()
|
||||
with assert_takes_time(0.4, 1.2):
|
||||
with assert_raises(ValueError):
|
||||
parallel.waitForAll(
|
||||
nested_err,
|
||||
partial('s', os.sleep, 2),
|
||||
partial('i', breaks_slow, IndexError),
|
||||
)
|
||||
assert tags == {'s', 'i', 'n_v', 'n_s', 'n_i'}
|
||||
print('Nested errors OK')
|
||||
|
||||
|
||||
print('Test finished successfully')
|
38
examples/test_peripheral.py
Normal file
38
examples/test_peripheral.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
from cc import import_file, peripheral
|
||||
|
||||
_lib = import_file('_lib.py', __file__)
|
||||
|
||||
|
||||
tbl = _lib.get_object_table('peripheral')
|
||||
|
||||
# use wrap
|
||||
del tbl['function']['getMethods']
|
||||
del tbl['function']['call']
|
||||
|
||||
# TODO: support these methods
|
||||
del tbl['function']['getName']
|
||||
del tbl['function']['find']
|
||||
|
||||
assert _lib.get_class_table(peripheral) == tbl
|
||||
|
||||
_lib.step('Remove all peripherals')
|
||||
|
||||
side = 'top'
|
||||
|
||||
assert peripheral.getNames() == []
|
||||
assert peripheral.getType(side) is None
|
||||
assert peripheral.isPresent(side) is False
|
||||
assert peripheral.wrap(side) is None
|
||||
|
||||
_lib.step(f'Put disk drive on {side} side of computer')
|
||||
|
||||
assert peripheral.getNames() == [side]
|
||||
assert peripheral.getType(side) == 'drive'
|
||||
assert peripheral.isPresent(side) is True
|
||||
d = peripheral.wrap(side)
|
||||
assert d is not None
|
||||
assert d.isDiskPresent() is False
|
||||
|
||||
print('Remove disk drive')
|
||||
|
||||
print('Test finished successfully')
|
31
examples/test_peripheral_commandblock.py
Normal file
31
examples/test_peripheral_commandblock.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
from computercraft.subapis.peripheral import CCCommandBlock
|
||||
from cc import LuaException, import_file, peripheral
|
||||
|
||||
_lib = import_file('_lib.py', __file__)
|
||||
|
||||
|
||||
side = 'left'
|
||||
|
||||
_lib.step(f'Attach command block at {side} side of computer')
|
||||
|
||||
m = peripheral.wrap(side)
|
||||
|
||||
tbl = _lib.get_object_table(f'peripheral.wrap("{side}")')
|
||||
assert _lib.get_class_table(CCCommandBlock) == tbl
|
||||
|
||||
assert m.getCommand() == ''
|
||||
assert m.setCommand('say Hello from python side') is None
|
||||
assert m.getCommand() == 'say Hello from python side'
|
||||
assert m.runCommand() is None
|
||||
|
||||
assert m.setCommand('time query daytime') is None
|
||||
assert m.getCommand() == 'time query daytime'
|
||||
assert m.runCommand() is None
|
||||
|
||||
assert m.setCommand('') is None
|
||||
assert m.getCommand() == ''
|
||||
with _lib.assert_raises(LuaException):
|
||||
m.runCommand()
|
||||
|
||||
print('You must have seen chat message')
|
||||
print('Test finished successfully')
|
5
examples/test_peripheral_computer.py
Normal file
5
examples/test_peripheral_computer.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from cc import import_file
|
||||
|
||||
_lib = import_file('_lib.py', __file__)
|
||||
|
||||
_lib._computer_peri('another computer', 'computer')
|
76
examples/test_peripheral_disk.py
Normal file
76
examples/test_peripheral_disk.py
Normal file
|
@ -0,0 +1,76 @@
|
|||
from computercraft.subapis.peripheral import CCDrive
|
||||
from cc import LuaException, import_file, peripheral
|
||||
|
||||
_lib = import_file('_lib.py', __file__)
|
||||
|
||||
|
||||
side = 'left'
|
||||
|
||||
_lib.step(f'Put empty disk drive on {side} side of computer')
|
||||
|
||||
d = peripheral.wrap(side)
|
||||
assert d is not None
|
||||
|
||||
tbl = _lib.get_object_table(f'peripheral.wrap("{side}")')
|
||||
assert _lib.get_class_table(CCDrive) == tbl
|
||||
|
||||
assert d.isDiskPresent() is False
|
||||
assert d.hasData() is False
|
||||
assert d.getMountPath() is None
|
||||
assert d.setDiskLabel('text') is None
|
||||
assert d.getDiskLabel() is None
|
||||
assert d.getDiskID() is None
|
||||
assert d.hasAudio() is False
|
||||
assert d.getAudioTitle() is False # False instead None!
|
||||
assert d.playAudio() is None
|
||||
assert d.stopAudio() is None
|
||||
assert d.ejectDisk() is None
|
||||
|
||||
_lib.step('Put new CC diskette into disk drive')
|
||||
|
||||
assert d.isDiskPresent() is True
|
||||
assert d.hasData() is True
|
||||
assert isinstance(d.getMountPath(), str)
|
||||
assert isinstance(d.getDiskID(), int)
|
||||
|
||||
assert d.getDiskLabel() is None
|
||||
assert d.setDiskLabel('label') is None
|
||||
assert d.getDiskLabel() == 'label'
|
||||
assert d.setDiskLabel(None) is None
|
||||
assert d.getDiskLabel() is None
|
||||
|
||||
assert d.hasAudio() is False
|
||||
assert d.getAudioTitle() is None
|
||||
assert d.playAudio() is None
|
||||
assert d.stopAudio() is None
|
||||
|
||||
assert d.ejectDisk() is None
|
||||
|
||||
_lib.step('Put any audio disk into disk drive')
|
||||
|
||||
assert d.isDiskPresent() is True
|
||||
assert d.hasData() is False
|
||||
assert d.getMountPath() is None
|
||||
assert d.getDiskID() is None
|
||||
assert d.hasAudio() is True
|
||||
|
||||
label = d.getAudioTitle()
|
||||
assert isinstance(label, str)
|
||||
assert label != 'label'
|
||||
print(f'Label is {label}')
|
||||
assert d.getDiskLabel() == label
|
||||
with _lib.assert_raises(LuaException):
|
||||
d.setDiskLabel('label')
|
||||
with _lib.assert_raises(LuaException):
|
||||
d.setDiskLabel(None)
|
||||
# no effect
|
||||
assert d.getDiskLabel() == label
|
||||
|
||||
assert d.playAudio() is None
|
||||
|
||||
_lib.step('Audio must be playing now')
|
||||
|
||||
assert d.stopAudio() is None
|
||||
assert d.ejectDisk() is None
|
||||
|
||||
print('Test finished successfully')
|
49
examples/test_peripheral_modem.py
Normal file
49
examples/test_peripheral_modem.py
Normal file
|
@ -0,0 +1,49 @@
|
|||
from cc import import_file, parallel, peripheral
|
||||
|
||||
_lib = import_file('_lib.py', __file__)
|
||||
|
||||
|
||||
# do this test twice: for wired and wireless modems
|
||||
|
||||
side = 'back'
|
||||
|
||||
_lib.step(
|
||||
f'Attach modem to {side} side of computer\n'
|
||||
f'Place another computer with similar modem at {side} side\n'
|
||||
'In case of wired modems connect them\n'
|
||||
'On another computer start py modem_server.py'
|
||||
)
|
||||
|
||||
m = peripheral.wrap(side)
|
||||
|
||||
remote_channel = 5
|
||||
local_channel = 7
|
||||
messages = []
|
||||
|
||||
|
||||
def _send():
|
||||
m.transmit(remote_channel, local_channel, 1)
|
||||
m.transmit(remote_channel, local_channel, 'hi')
|
||||
m.transmit(remote_channel, local_channel, {'data': 5})
|
||||
m.transmit(remote_channel, local_channel, 'stop')
|
||||
|
||||
|
||||
def _recv():
|
||||
assert m.isOpen(local_channel) is False
|
||||
for msg in m.receive(local_channel):
|
||||
assert m.isOpen(local_channel) is True
|
||||
assert msg.reply_channel == remote_channel
|
||||
assert msg.distance > 0
|
||||
messages.append(msg.content)
|
||||
if len(messages) == 3:
|
||||
break
|
||||
|
||||
|
||||
parallel.waitForAll(_recv, _send)
|
||||
|
||||
assert messages == [1, 'hi', {'data': 5}]
|
||||
assert m.isOpen(local_channel) is False
|
||||
assert m.closeAll() is None
|
||||
assert isinstance(m.isWireless(), bool)
|
||||
|
||||
print('Test finished successfully')
|
144
examples/test_peripheral_monitor.py
Normal file
144
examples/test_peripheral_monitor.py
Normal file
|
@ -0,0 +1,144 @@
|
|||
from computercraft.subapis.peripheral import CCMonitor
|
||||
from computercraft.subapis.mixins import TermMixin
|
||||
from cc import import_file, colors, os, peripheral
|
||||
|
||||
_lib = import_file('_lib.py', __file__)
|
||||
|
||||
|
||||
side = 'left'
|
||||
|
||||
_lib.step(
|
||||
'Use advanced computer and monitor for colors\n'
|
||||
f'Place single block monitor on {side} side of computer',
|
||||
)
|
||||
|
||||
m = peripheral.wrap(side)
|
||||
assert m is not None
|
||||
|
||||
|
||||
tbl = _lib.get_object_table(f'peripheral.wrap("{side}")')
|
||||
|
||||
# remove British method names to make API lighter
|
||||
del tbl['function']['getBackgroundColour']
|
||||
del tbl['function']['getPaletteColour']
|
||||
del tbl['function']['getTextColour']
|
||||
del tbl['function']['isColour']
|
||||
del tbl['function']['setBackgroundColour']
|
||||
del tbl['function']['setPaletteColour']
|
||||
del tbl['function']['setTextColour']
|
||||
# NOTE: peripheral doesn't have nativePaletteColor method
|
||||
|
||||
assert _lib.get_multiclass_table(TermMixin, CCMonitor) == tbl
|
||||
|
||||
|
||||
assert m.getSize() == (7, 5)
|
||||
assert m.isColor() is True
|
||||
assert m.setTextColor(colors.white) is None
|
||||
assert m.setBackgroundColor(colors.black) is None
|
||||
assert m.clear() is None
|
||||
assert m.setCursorPos(1, 1) is None
|
||||
assert m.getCursorPos() == (1, 1)
|
||||
assert m.write('Alpha') is None
|
||||
assert m.getCursorPos() == (6, 1)
|
||||
assert m.setCursorBlink(False) is None
|
||||
assert m.getCursorBlink() is False
|
||||
assert m.setCursorBlink(True) is None
|
||||
assert m.getCursorBlink() is True
|
||||
|
||||
_lib.step('You must have seen word Alpha with blinking cursor')
|
||||
|
||||
assert m.clear() is None
|
||||
assert m.setCursorBlink(False) is None
|
||||
for offs, (tc, bc) in enumerate((
|
||||
(colors.lime, colors.green),
|
||||
(colors.yellow, colors.brown),
|
||||
(colors.red, colors.orange),
|
||||
), start=1):
|
||||
assert m.setTextColor(tc) is None
|
||||
assert m.getTextColor() == tc
|
||||
assert m.setBackgroundColor(bc) is None
|
||||
assert m.getBackgroundColor() == bc
|
||||
assert m.setCursorPos(offs, offs) is None
|
||||
assert m.getCursorPos() == (offs, offs)
|
||||
assert m.write('text') is None
|
||||
assert m.setBackgroundColor(colors.black) is None
|
||||
os.sleep(1)
|
||||
for i in range(2):
|
||||
assert m.scroll(-1) is None
|
||||
os.sleep(0.5)
|
||||
for i in range(2):
|
||||
assert m.scroll(1) is None
|
||||
os.sleep(0.5)
|
||||
|
||||
_lib.step('You must have seen three texts with different colors scrolling')
|
||||
|
||||
assert m.setTextColor(colors.white) is None
|
||||
assert m.setBackgroundColor(colors.black) is None
|
||||
assert m.clear() is None
|
||||
for i in range(1, 5):
|
||||
assert m.setCursorPos(1, i) is None
|
||||
assert m.write((str(i) + ' ') * 4) is None
|
||||
os.sleep(2)
|
||||
for i in range(2, 5, 2):
|
||||
assert m.setCursorPos(1, i) is None
|
||||
assert m.clearLine() is None
|
||||
|
||||
_lib.step('You must have seen some lines disappearing')
|
||||
|
||||
assert m.setBackgroundColor(colors.black) is None
|
||||
assert m.clear() is None
|
||||
assert m.setCursorPos(1, 1) is None
|
||||
assert m.blit(
|
||||
'rainbow',
|
||||
'e14d3ba',
|
||||
'fffffff',
|
||||
) is None
|
||||
assert m.setCursorPos(1, 2) is None
|
||||
assert m.blit(
|
||||
'rainbow',
|
||||
'0000000',
|
||||
'e14d3ba',
|
||||
) is None
|
||||
|
||||
_lib.step('You must have seen per-letter colored text')
|
||||
|
||||
assert m.setBackgroundColor(colors.black) is None
|
||||
assert m.setTextColor(colors.white) is None
|
||||
assert m.getTextScale() == 1
|
||||
assert m.setTextScale(5) is None
|
||||
assert m.getTextScale() == 5
|
||||
assert m.setCursorPos(1, 1) is None
|
||||
assert m.clear() is None
|
||||
assert m.getSize() == (1, 1)
|
||||
assert m.write('AAA') is None
|
||||
|
||||
_lib.step('You must have seen single large letter A')
|
||||
|
||||
assert m.setTextScale(1) is None
|
||||
assert m.setBackgroundColor(colors.white) is None
|
||||
assert m.clear() is None
|
||||
for i, color in enumerate(colors.iter_colors()):
|
||||
m.setPaletteColor(color, i / 15, 0, 0)
|
||||
assert m.setCursorPos(1, 1) is None
|
||||
assert m.blit(
|
||||
' redtex',
|
||||
'0123456',
|
||||
'0000000',
|
||||
) is None
|
||||
assert m.setCursorPos(1, 2) is None
|
||||
assert m.blit(
|
||||
'tappear',
|
||||
'789abcd',
|
||||
'0000000',
|
||||
) is None
|
||||
assert m.setCursorPos(1, 3) is None
|
||||
assert m.blit(
|
||||
's!',
|
||||
'ef',
|
||||
'00',
|
||||
) is None
|
||||
|
||||
_lib.step('You must have seen different shades of red made using palettes')
|
||||
|
||||
print('Remove monitor')
|
||||
print('Test finished successfully')
|
108
examples/test_peripheral_printer.py
Normal file
108
examples/test_peripheral_printer.py
Normal file
|
@ -0,0 +1,108 @@
|
|||
from computercraft.subapis.peripheral import CCPrinter
|
||||
from cc import LuaException, import_file, peripheral
|
||||
|
||||
_lib = import_file('_lib.py', __file__)
|
||||
assert_raises = _lib.assert_raises
|
||||
|
||||
|
||||
side = 'left'
|
||||
|
||||
_lib.step(f'Attach empty printer at {side} side of computer')
|
||||
|
||||
m = peripheral.wrap(side)
|
||||
|
||||
tbl = _lib.get_object_table(f'peripheral.wrap("{side}")')
|
||||
assert _lib.get_class_table(CCPrinter) == tbl
|
||||
|
||||
assert m.getPaperLevel() == 0
|
||||
assert m.getInkLevel() == 0
|
||||
|
||||
# no paper
|
||||
assert m.newPage() is False
|
||||
# page not started
|
||||
with assert_raises(LuaException):
|
||||
m.endPage()
|
||||
with assert_raises(LuaException):
|
||||
m.write('test')
|
||||
with assert_raises(LuaException):
|
||||
m.setCursorPos(2, 2)
|
||||
with assert_raises(LuaException):
|
||||
m.getCursorPos()
|
||||
with assert_raises(LuaException):
|
||||
m.getPageSize()
|
||||
with assert_raises(LuaException):
|
||||
m.setPageTitle('title')
|
||||
|
||||
_lib.step('Put paper into printer')
|
||||
paper_level = m.getPaperLevel()
|
||||
assert paper_level > 0
|
||||
# no ink
|
||||
assert m.newPage() is False
|
||||
|
||||
_lib.step('Put ink into printer')
|
||||
ink_level = m.getInkLevel()
|
||||
assert ink_level > 0
|
||||
|
||||
assert m.newPage() is True
|
||||
assert m.getPaperLevel() < paper_level
|
||||
assert m.getInkLevel() < ink_level
|
||||
|
||||
assert m.setCursorPos(2, 2) is None
|
||||
assert m.getCursorPos() == (2, 2)
|
||||
assert m.setCursorPos(1, 1) is None
|
||||
assert m.getCursorPos() == (1, 1)
|
||||
assert m.setPageTitle('Green bottles') is None
|
||||
assert m.getPageSize() == (25, 21)
|
||||
|
||||
|
||||
async def row(n=1):
|
||||
_, r = m.getCursorPos()
|
||||
m.setCursorPos(1, r + n)
|
||||
|
||||
|
||||
def split_text(text, max_width=25):
|
||||
for i in range(0, len(text), max_width):
|
||||
yield text[i:i + max_width]
|
||||
|
||||
|
||||
def split_by_words(text, max_width=25):
|
||||
stack = []
|
||||
stack_len = 0
|
||||
for word in text.split(' '):
|
||||
assert len(word) <= max_width
|
||||
with_word = len(word) if stack_len == 0 else stack_len + 1 + len(word)
|
||||
if with_word > max_width:
|
||||
yield ' '.join(stack)
|
||||
stack.clear()
|
||||
stack_len = 0
|
||||
else:
|
||||
stack.append(word)
|
||||
stack_len = with_word
|
||||
if stack:
|
||||
yield ' '.join(stack)
|
||||
|
||||
|
||||
def multiline_write(text):
|
||||
_, r = m.getCursorPos()
|
||||
for pt in split_by_words(text):
|
||||
assert m.setCursorPos(1, r) is None
|
||||
assert m.write(pt) is None
|
||||
r += 1
|
||||
assert m.setCursorPos(1, r) is None
|
||||
|
||||
|
||||
assert m.write('Green bottles'.center(25)) is None
|
||||
row(2)
|
||||
|
||||
x = 2
|
||||
while x > 0:
|
||||
multiline_write(f'{x} green bottles hanging on the wall')
|
||||
multiline_write(f'{x} green bottles hanging on the wall')
|
||||
multiline_write('if one green bottle accidently falls')
|
||||
x -= 1
|
||||
multiline_write(f'there will be {x} hanging on the wall')
|
||||
row()
|
||||
|
||||
assert m.endPage() is True
|
||||
|
||||
print('Test finished successfully')
|
43
examples/test_peripheral_remote.py
Normal file
43
examples/test_peripheral_remote.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
from cc import import_file, peripheral
|
||||
|
||||
_lib = import_file('_lib.py', __file__)
|
||||
|
||||
|
||||
side = 'back'
|
||||
|
||||
_lib.step(f'Attach and disable (right-click) wired modem at {side} side')
|
||||
|
||||
m = peripheral.wrap(side)
|
||||
assert m.isWireless() is False
|
||||
assert m.getNameLocal() is None
|
||||
|
||||
_lib.step(f'Enable (right-click) wired modem at {side} side')
|
||||
|
||||
assert isinstance(m.getNameLocal(), str)
|
||||
|
||||
_lib.step('Connect networked speaker peripheral & enable its modem')
|
||||
|
||||
names = m.getNamesRemote()
|
||||
assert isinstance(names, list)
|
||||
assert len(names) > 0
|
||||
speaker = []
|
||||
for n in names:
|
||||
assert isinstance(n, str)
|
||||
if n.startswith('speaker_'):
|
||||
speaker.append(n)
|
||||
assert len(speaker) == 1
|
||||
speaker = speaker[0]
|
||||
|
||||
assert m.isPresentRemote('doesnotexist') is False
|
||||
assert m.getTypeRemote('doesnotexist') is None
|
||||
|
||||
assert m.isPresentRemote(speaker) is True
|
||||
assert m.getTypeRemote(speaker) == 'speaker'
|
||||
|
||||
assert m.wrapRemote('doesnotexist') is None
|
||||
s = m.wrapRemote(speaker)
|
||||
|
||||
assert s.playSound('minecraft:entity.player.levelup') is True
|
||||
|
||||
print('You must have heard levelup sound')
|
||||
print('Test finished successfully')
|
34
examples/test_peripheral_speaker.py
Normal file
34
examples/test_peripheral_speaker.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
import random
|
||||
|
||||
from computercraft.subapis.peripheral import CCSpeaker
|
||||
from cc import import_file, os, peripheral
|
||||
|
||||
_lib = import_file('_lib.py', __file__)
|
||||
|
||||
|
||||
side = 'left'
|
||||
|
||||
_lib.step(f'Attach speaker at {side} side of computer')
|
||||
|
||||
m = peripheral.wrap(side)
|
||||
|
||||
|
||||
tbl = _lib.get_object_table(f'peripheral.wrap("{side}")')
|
||||
assert _lib.get_class_table(CCSpeaker) == tbl
|
||||
|
||||
for _ in range(48):
|
||||
assert m.playNote(
|
||||
random.choice([
|
||||
'bass', 'basedrum', 'bell', 'chime', 'flute', 'guitar', 'hat',
|
||||
'snare', 'xylophone', 'iron_xylophone', 'pling', 'banjo',
|
||||
'bit', 'didgeridoo', 'cow_bell',
|
||||
]),
|
||||
3,
|
||||
random.randint(0, 24)
|
||||
) is True
|
||||
os.sleep(0.2)
|
||||
|
||||
assert m.playSound('minecraft:entity.player.levelup') is True
|
||||
|
||||
print('You must have heard notes and sounds')
|
||||
print('Test finished successfully')
|
5
examples/test_peripheral_turtle.py
Normal file
5
examples/test_peripheral_turtle.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from cc import import_file
|
||||
|
||||
_lib = import_file('_lib.py', __file__)
|
||||
|
||||
_lib._computer_peri('turtle', 'turtle')
|
27
examples/test_pocket.py
Normal file
27
examples/test_pocket.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
from cc import LuaException, import_file, pocket, peripheral
|
||||
|
||||
_lib = import_file('_lib.py', __file__)
|
||||
|
||||
|
||||
assert peripheral.isPresent('back') is False
|
||||
|
||||
tbl = _lib.get_object_table('pocket')
|
||||
assert _lib.get_class_table(pocket) == tbl
|
||||
|
||||
_lib.step('Clean inventory from any pocket upgrades')
|
||||
|
||||
with _lib.assert_raises(LuaException):
|
||||
pocket.equipBack()
|
||||
with _lib.assert_raises(LuaException):
|
||||
pocket.unequipBack()
|
||||
assert peripheral.isPresent('back') is False
|
||||
|
||||
_lib.step('Put any pocket upgrade to inventory')
|
||||
|
||||
assert pocket.equipBack() is None
|
||||
assert peripheral.isPresent('back') is True
|
||||
|
||||
assert pocket.unequipBack() is None
|
||||
assert peripheral.isPresent('back') is False
|
||||
|
||||
print('Test finished successfully')
|
5
examples/test_reboot.py
Normal file
5
examples/test_reboot.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from cc import os
|
||||
|
||||
|
||||
assert os.reboot() is None
|
||||
print('Test finished successfully')
|
16
examples/test_redirect_to_local_monitor.py
Normal file
16
examples/test_redirect_to_local_monitor.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
from cc import import_file, colors, term, peripheral
|
||||
|
||||
_lib = import_file('_lib.py', __file__)
|
||||
|
||||
|
||||
side = 'left'
|
||||
_lib.step(f'Attach 3x3 color monitor to {side} side of computer')
|
||||
|
||||
with term.redirect(peripheral.get_term_target(side)):
|
||||
term.setBackgroundColor(colors.green)
|
||||
term.setTextColor(colors.white)
|
||||
term.clear()
|
||||
term.setCursorPos(1, 1)
|
||||
print('Redirected to monitor')
|
||||
|
||||
print('Test finished successfully')
|
26
examples/test_redirect_to_remote_monitor.py
Normal file
26
examples/test_redirect_to_remote_monitor.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
from cc import import_file, colors, term, peripheral
|
||||
|
||||
_lib = import_file('_lib.py', __file__)
|
||||
|
||||
|
||||
side = 'back'
|
||||
_lib.step(f'Attach wired modem to {side} side of computer')
|
||||
|
||||
mod = peripheral.wrap(side)
|
||||
|
||||
_lib.step('Connect remote monitor using wires, activate its modem')
|
||||
|
||||
for name in mod.getNamesRemote():
|
||||
if mod.getTypeRemote(name) == 'monitor':
|
||||
break
|
||||
else:
|
||||
assert False
|
||||
|
||||
with term.redirect(peripheral.get_term_target(name)):
|
||||
term.setBackgroundColor(colors.blue)
|
||||
term.setTextColor(colors.white)
|
||||
term.clear()
|
||||
term.setCursorPos(1, 1)
|
||||
print('Redirected to monitor')
|
||||
|
||||
print('Test finished successfully')
|
30
examples/test_redirect_to_window.py
Normal file
30
examples/test_redirect_to_window.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
from contextlib import ExitStack
|
||||
|
||||
from cc import colors, term, window
|
||||
|
||||
|
||||
w, h = term.getSize()
|
||||
with ExitStack() as stack:
|
||||
left = stack.enter_context(window.create(
|
||||
term.get_current_target(),
|
||||
1, 1, w // 2, h, True,
|
||||
))
|
||||
right = stack.enter_context(window.create(
|
||||
term.get_current_target(),
|
||||
w // 2 + 1, 1, w // 2, h, True,
|
||||
))
|
||||
with term.redirect(left.get_term_target()):
|
||||
term.setBackgroundColor(colors.green)
|
||||
term.setTextColor(colors.white)
|
||||
term.clear()
|
||||
term.setCursorPos(1, h // 2)
|
||||
print('Left part')
|
||||
with term.redirect(right.get_term_target()):
|
||||
term.setBackgroundColor(colors.red)
|
||||
term.setTextColor(colors.yellow)
|
||||
term.clear()
|
||||
term.setCursorPos(1, h // 2)
|
||||
print('Right part')
|
||||
print('Default terminal restored')
|
||||
|
||||
print('Test finished successfully')
|
62
examples/test_rednet.py
Normal file
62
examples/test_rednet.py
Normal file
|
@ -0,0 +1,62 @@
|
|||
from cc import LuaException, import_file, os, rednet, parallel
|
||||
|
||||
_lib = import_file('_lib.py', __file__)
|
||||
step, assert_raises = _lib.step, _lib.assert_raises
|
||||
|
||||
|
||||
tbl = _lib.get_object_table('rednet')
|
||||
del tbl['function']['run']
|
||||
assert _lib.get_class_table(rednet) == tbl
|
||||
|
||||
side = 'back'
|
||||
|
||||
step(f'Attach modem to {side} side of computer')
|
||||
|
||||
assert rednet.isOpen(side) is False
|
||||
assert rednet.isOpen() is False
|
||||
|
||||
with assert_raises(LuaException):
|
||||
rednet.close('doesnotexist')
|
||||
|
||||
assert rednet.close(side) is None
|
||||
|
||||
with assert_raises(LuaException):
|
||||
rednet.open('doesnotexist')
|
||||
|
||||
assert rednet.open(side) is None
|
||||
assert rednet.isOpen(side) is True
|
||||
|
||||
with assert_raises(LuaException):
|
||||
# disallowed hostname
|
||||
rednet.host('helloproto', 'localhost')
|
||||
assert rednet.host('helloproto', 'alpha') is None
|
||||
|
||||
cid = os.getComputerID()
|
||||
|
||||
assert rednet.lookup('helloproto', 'localhost') == cid
|
||||
assert rednet.lookup('helloproto') == [cid]
|
||||
assert rednet.lookup('nonexistent', 'localhost') is None
|
||||
assert rednet.lookup('nonexistent') == []
|
||||
|
||||
assert rednet.unhost('helloproto') is None
|
||||
|
||||
assert rednet.send(cid + 100, 'message', 'anyproto') is True
|
||||
assert rednet.broadcast('message', 'anyproto') is None
|
||||
|
||||
assert rednet.receive(timeout=1) is None
|
||||
|
||||
|
||||
def _send():
|
||||
assert rednet.send(cid, 'message') is True
|
||||
|
||||
|
||||
def _recv():
|
||||
assert rednet.receive(timeout=1) == (cid, 'message', None)
|
||||
|
||||
|
||||
parallel.waitForAll(_send, _recv)
|
||||
|
||||
assert rednet.close() is None
|
||||
assert rednet.isOpen(side) is False
|
||||
|
||||
print('Test finished successfully')
|
55
examples/test_redstone.py
Normal file
55
examples/test_redstone.py
Normal file
|
@ -0,0 +1,55 @@
|
|||
from cc import import_file, os, redstone
|
||||
|
||||
_lib = import_file('_lib.py', __file__)
|
||||
|
||||
|
||||
tbl = _lib.get_object_table('redstone')
|
||||
|
||||
# remove British method names to make API lighter
|
||||
del tbl['function']['getAnalogueInput']
|
||||
del tbl['function']['getAnalogueOutput']
|
||||
del tbl['function']['setAnalogueOutput']
|
||||
|
||||
assert _lib.get_class_table(redstone) == tbl
|
||||
|
||||
assert set(redstone.getSides()) == {'top', 'bottom', 'front', 'back', 'left', 'right'}
|
||||
|
||||
_lib.step('Remove all the redstone from sides of computer')
|
||||
|
||||
side = 'top'
|
||||
|
||||
assert redstone.setOutput(side, True) is None
|
||||
assert redstone.getOutput(side) is True
|
||||
assert redstone.getAnalogOutput(side) == 15
|
||||
assert redstone.setOutput(side, False) is None
|
||||
assert redstone.getOutput(side) is False
|
||||
assert redstone.getAnalogOutput(side) == 0
|
||||
|
||||
assert redstone.setAnalogOutput(side, 7) is None
|
||||
assert redstone.getAnalogOutput(side) == 7
|
||||
assert redstone.getOutput(side) is True
|
||||
assert redstone.setAnalogOutput(side, 15) is None
|
||||
assert redstone.getAnalogOutput(side) == 15
|
||||
assert redstone.setAnalogOutput(side, 0) is None
|
||||
assert redstone.getAnalogOutput(side) == 0
|
||||
assert redstone.getOutput(side) is False
|
||||
|
||||
assert redstone.getInput(side) is False
|
||||
assert redstone.getAnalogInput(side) == 0
|
||||
|
||||
_lib.step(f'Put redstone block on {side} side of computer')
|
||||
|
||||
assert redstone.getInput(side) is True
|
||||
assert redstone.getAnalogInput(side) > 0
|
||||
|
||||
_lib.step(f'Remove redstone block\nPut piston on {side} side of computer')
|
||||
|
||||
assert redstone.getInput(side) is False
|
||||
assert redstone.getAnalogInput(side) == 0
|
||||
assert redstone.setOutput(side, True) is None
|
||||
os.sleep(2)
|
||||
assert redstone.setOutput(side, False) is None
|
||||
|
||||
print('Piston must have been activated\nRemove piston')
|
||||
|
||||
print('Test finished successfully')
|
101
examples/test_settings.py
Normal file
101
examples/test_settings.py
Normal file
|
@ -0,0 +1,101 @@
|
|||
from cc import LuaException, import_file, fs, settings
|
||||
|
||||
_lib = import_file('_lib.py', __file__)
|
||||
step, assert_raises = _lib.step, _lib.assert_raises
|
||||
|
||||
|
||||
assert _lib.get_class_table(settings) == _lib.get_object_table('settings')
|
||||
|
||||
step('Settings will be cleared')
|
||||
|
||||
assert settings.clear() is None
|
||||
# names are not empty, there are system settings
|
||||
assert isinstance(settings.getNames(), list)
|
||||
|
||||
assert settings.define('test.a') is None
|
||||
assert settings.define('test.b', description='b') is None
|
||||
assert settings.define('test.c', type='string') is None
|
||||
assert settings.define('test.d', default=42) is None
|
||||
|
||||
assert settings.getDetails('test.a') == {
|
||||
'changed': False,
|
||||
}
|
||||
assert settings.getDetails('test.b') == {
|
||||
'changed': False,
|
||||
'description': 'b',
|
||||
}
|
||||
assert settings.getDetails('test.c') == {
|
||||
'changed': False,
|
||||
'type': 'string',
|
||||
}
|
||||
assert settings.getDetails('test.d') == {
|
||||
'changed': False,
|
||||
'default': 42,
|
||||
'value': 42,
|
||||
}
|
||||
|
||||
# redefining
|
||||
assert settings.define('test.a', type='number', default=11) is None
|
||||
|
||||
assert settings.getDetails('test.a') == {
|
||||
'changed': False,
|
||||
'type': 'number',
|
||||
'default': 11,
|
||||
'value': 11,
|
||||
}
|
||||
|
||||
assert settings.get('test.a') == 11
|
||||
assert settings.set('test.a', 12) is None
|
||||
assert settings.get('test.a') == 12
|
||||
with assert_raises(LuaException):
|
||||
settings.set('test.a', 'text')
|
||||
assert settings.get('test.a') == 12
|
||||
assert settings.unset('test.a') is None
|
||||
assert settings.get('test.a') == 11
|
||||
|
||||
assert settings.set('test.c', 'hello') is None
|
||||
|
||||
assert {'test.a', 'test.b', 'test.c', 'test.d'}.issubset(set(settings.getNames()))
|
||||
|
||||
assert settings.undefine('test.a') is None
|
||||
assert settings.undefine('test.b') is None
|
||||
assert settings.undefine('test.c') is None
|
||||
assert settings.undefine('test.d') is None
|
||||
|
||||
assert 'test.c' in settings.getNames()
|
||||
assert settings.get('test.c') == 'hello'
|
||||
assert settings.getDetails('test.c') == {
|
||||
'changed': True,
|
||||
'value': 'hello',
|
||||
}
|
||||
|
||||
assert settings.unset('test.c') is None
|
||||
|
||||
assert settings.get('test.c') is None
|
||||
assert settings.getDetails('test.c') == {
|
||||
'changed': False,
|
||||
}
|
||||
|
||||
assert {'test.a', 'test.b', 'test.c', 'test.d'} & set(settings.getNames()) == set()
|
||||
|
||||
assert settings.set('test.e', [9, 'text', False]) is None
|
||||
assert settings.get('test.e') == [9, 'text', False]
|
||||
assert settings.clear() is None
|
||||
assert settings.get('test.e') is None
|
||||
|
||||
fs.delete('.settings')
|
||||
|
||||
assert settings.load() is False
|
||||
assert settings.save() is True
|
||||
assert settings.load() is True
|
||||
|
||||
fs.delete('.settings')
|
||||
|
||||
assert settings.set('key', 84) is None
|
||||
|
||||
assert settings.save('sfile') is True
|
||||
assert settings.load('sfile') is True
|
||||
|
||||
fs.delete('sfile')
|
||||
|
||||
print('Test finished successfully')
|
61
examples/test_shell.py
Normal file
61
examples/test_shell.py
Normal file
|
@ -0,0 +1,61 @@
|
|||
from cc import import_file, shell
|
||||
|
||||
_lib = import_file('_lib.py', __file__)
|
||||
|
||||
|
||||
tbl = _lib.get_object_table('shell')
|
||||
del tbl['function']['setCompletionFunction']
|
||||
del tbl['function']['getCompletionInfo']
|
||||
assert _lib.get_class_table(shell) == tbl
|
||||
|
||||
assert shell.complete('ls ro') == ['m/', 'm']
|
||||
assert shell.completeProgram('lu') == ['a']
|
||||
|
||||
ps = shell.programs()
|
||||
assert 'shutdown' in ps
|
||||
|
||||
als = shell.aliases()
|
||||
assert 'ls' in als
|
||||
assert als['ls'] == 'list'
|
||||
assert 'xls' not in als
|
||||
assert shell.setAlias('xls', 'list') is None
|
||||
als = shell.aliases()
|
||||
assert 'xls' in als
|
||||
assert als['xls'] == 'list'
|
||||
assert shell.clearAlias('xls') is None
|
||||
als = shell.aliases()
|
||||
assert 'xls' not in als
|
||||
|
||||
assert shell.getRunningProgram() == 'py'
|
||||
|
||||
assert shell.resolveProgram('doesnotexist') is None
|
||||
assert shell.resolveProgram('hello') == 'rom/programs/fun/hello.lua'
|
||||
|
||||
assert shell.dir() == ''
|
||||
assert shell.resolve('doesnotexist') == 'doesnotexist'
|
||||
assert shell.resolve('startup.lua') == 'startup.lua'
|
||||
assert shell.setDir('rom') is None
|
||||
assert shell.dir() == 'rom'
|
||||
assert shell.resolve('startup.lua') == 'rom/startup.lua'
|
||||
assert shell.setDir('') is None
|
||||
|
||||
assert isinstance(shell.path(), str)
|
||||
assert shell.setPath(shell.path()) is None
|
||||
|
||||
assert shell.execute('hello') is True
|
||||
assert shell.run('hello') is True
|
||||
assert shell.execute('doesnotexist') is False
|
||||
assert shell.run('doesnotexist') is False
|
||||
|
||||
tab = shell.openTab('hello')
|
||||
assert isinstance(tab, int)
|
||||
|
||||
_lib.step(f'Program has been launched in tab {tab}')
|
||||
|
||||
assert shell.switchTab(tab) is None
|
||||
|
||||
_lib.step('Computer will shutdown after test due to shell.exit')
|
||||
|
||||
assert shell.exit() is None
|
||||
|
||||
print('Test finished successfully')
|
5
examples/test_shutdown.py
Normal file
5
examples/test_shutdown.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from cc import os
|
||||
|
||||
|
||||
assert os.shutdown() is None
|
||||
print('Test finished successfully')
|
108
examples/test_term.py
Normal file
108
examples/test_term.py
Normal file
|
@ -0,0 +1,108 @@
|
|||
from cc import import_file, colors, os, term
|
||||
from computercraft.subapis.mixins import TermMixin
|
||||
|
||||
_lib = import_file('_lib.py', __file__)
|
||||
|
||||
|
||||
tbl = _lib.get_object_table('term')
|
||||
|
||||
# not defined in TermMixin
|
||||
del tbl['function']['redirect']
|
||||
del tbl['function']['current']
|
||||
del tbl['function']['native']
|
||||
|
||||
# remove British method names to make API lighter
|
||||
del tbl['function']['getBackgroundColour']
|
||||
del tbl['function']['getPaletteColour']
|
||||
del tbl['function']['getTextColour']
|
||||
del tbl['function']['isColour']
|
||||
del tbl['function']['nativePaletteColour']
|
||||
del tbl['function']['setBackgroundColour']
|
||||
del tbl['function']['setPaletteColour']
|
||||
del tbl['function']['setTextColour']
|
||||
|
||||
assert _lib.get_class_table(TermMixin) == tbl
|
||||
|
||||
_lib.step(
|
||||
'Detach all monitors\n'
|
||||
'Use advanced computer for colors\n'
|
||||
'Screen will be cleared'
|
||||
)
|
||||
|
||||
assert term.getSize() == (51, 19)
|
||||
assert term.isColor() is True
|
||||
assert term.clear() is None
|
||||
assert term.setCursorPos(1, 1) is None
|
||||
assert term.getCursorPos() == (1, 1)
|
||||
assert term.write('Alpha') is None
|
||||
assert term.getCursorPos() == (6, 1)
|
||||
assert term.setCursorBlink(False) is None
|
||||
assert term.getCursorBlink() is False
|
||||
assert term.setCursorBlink(True) is None
|
||||
assert term.getCursorBlink() is True
|
||||
os.sleep(2)
|
||||
|
||||
_lib.term_step('You must have seen word Alpha with blinking cursor')
|
||||
|
||||
assert term.clear() is None
|
||||
for offs, (tc, bc) in enumerate((
|
||||
(colors.lime, colors.green),
|
||||
(colors.yellow, colors.brown),
|
||||
(colors.red, colors.orange),
|
||||
), start=1):
|
||||
assert term.setTextColor(tc) is None
|
||||
assert term.getTextColor() == tc
|
||||
assert term.setBackgroundColor(bc) is None
|
||||
assert term.getBackgroundColor() == bc
|
||||
assert term.setCursorPos(offs * 2, offs) is None
|
||||
assert term.getCursorPos() == (offs * 2, offs)
|
||||
assert term.write('text with colors') is None
|
||||
assert term.setBackgroundColor(colors.black) is None
|
||||
os.sleep(1)
|
||||
for i in range(3):
|
||||
assert term.scroll(-2) is None
|
||||
os.sleep(0.5)
|
||||
for i in range(6):
|
||||
assert term.scroll(1) is None
|
||||
os.sleep(0.25)
|
||||
|
||||
_lib.term_step('You must have seen three texts with different colors scrolling')
|
||||
|
||||
assert term.clear() is None
|
||||
for i in range(1, 10):
|
||||
assert term.setCursorPos(1, i) is None
|
||||
assert term.write((str(i) + ' ') * 10) is None
|
||||
os.sleep(2)
|
||||
for i in range(2, 10, 2):
|
||||
assert term.setCursorPos(1, i) is None
|
||||
assert term.clearLine() is None
|
||||
os.sleep(2)
|
||||
|
||||
_lib.term_step('You must have seen some lines disappearing')
|
||||
|
||||
assert term.clear() is None
|
||||
assert term.setCursorPos(1, 1) is None
|
||||
assert term.blit(
|
||||
'rainbowrainbow',
|
||||
'e14d3ba0000000',
|
||||
'fffffffe14d3ba',
|
||||
) is None
|
||||
os.sleep(3)
|
||||
|
||||
_lib.term_step('You must have seen per-letter colored text')
|
||||
|
||||
assert term.setBackgroundColor(colors.white) is None
|
||||
assert term.clear() is None
|
||||
assert term.setCursorPos(1, 1) is None
|
||||
for i, color in enumerate(colors):
|
||||
term.setPaletteColor(color, i / 15, 0, 0)
|
||||
assert term.blit(
|
||||
' redtextappears!',
|
||||
'0123456789abcdef',
|
||||
'0000000000000000',
|
||||
) is None
|
||||
os.sleep(3)
|
||||
|
||||
_lib.term_step('You must have seen different shades of red made using palettes')
|
||||
|
||||
print('Test finished successfully')
|
52
examples/test_textutils.py
Normal file
52
examples/test_textutils.py
Normal file
|
@ -0,0 +1,52 @@
|
|||
from cc import colors, textutils
|
||||
|
||||
|
||||
assert textutils.slowWrite('write ') is None
|
||||
assert textutils.slowWrite('write ', 5) is None
|
||||
assert textutils.slowPrint('print') is None
|
||||
assert textutils.slowPrint('print', 5) is None
|
||||
|
||||
assert textutils.formatTime(0) == '0:00 AM'
|
||||
assert textutils.formatTime(0, True) == '0:00'
|
||||
|
||||
table = [
|
||||
colors.red,
|
||||
['Planet', 'Distance', 'Mass'],
|
||||
colors.gray,
|
||||
['Mercury', '0.387', '0.055'],
|
||||
colors.lightGray,
|
||||
['Venus', '0.723', '0.815'],
|
||||
colors.green,
|
||||
['Earth', '1.000', '1.000'],
|
||||
colors.red,
|
||||
['Mars', '1.524', '0.107'],
|
||||
colors.orange,
|
||||
['Jupiter', '5.203', '318'],
|
||||
colors.yellow,
|
||||
['Saturn', '9.537', '95'],
|
||||
colors.cyan,
|
||||
['Uranus', '19.191', '14.5'],
|
||||
colors.blue,
|
||||
['Neptune', '30.069', '17'],
|
||||
colors.white,
|
||||
]
|
||||
|
||||
assert textutils.tabulate(*table) is None
|
||||
|
||||
lines = textutils.pagedPrint('''
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
Suspendisse feugiat diam et velit aliquam, nec porttitor eros facilisis.
|
||||
Nulla facilisi.
|
||||
Sed eget dui vel tellus aliquam fermentum.
|
||||
Aliquam sed lorem congue, dignissim nulla in, porta diam.
|
||||
Aliquam erat volutpat.
|
||||
'''.strip())
|
||||
assert isinstance(lines, int)
|
||||
assert lines > 0
|
||||
|
||||
assert textutils.pagedTabulate(*table[:-1], *table[2:-1], *table[2:]) is None
|
||||
|
||||
assert textutils.complete('co', ['command', 'row', 'column']) == [
|
||||
'mmand', 'lumn']
|
||||
|
||||
print('Test finished successfully')
|
248
examples/test_turtle.py
Normal file
248
examples/test_turtle.py
Normal file
|
@ -0,0 +1,248 @@
|
|||
from cc import LuaException, import_file, turtle, peripheral
|
||||
|
||||
_lib = import_file('_lib.py', __file__)
|
||||
assert_raises, step = _lib.assert_raises, _lib.step
|
||||
|
||||
|
||||
tbl = _lib.get_object_table('turtle')
|
||||
assert tbl['table'] == {'native': True}
|
||||
del tbl['table']
|
||||
tbl['function'].setdefault('craft', True)
|
||||
assert _lib.get_class_table(turtle) == tbl
|
||||
|
||||
flimit = turtle.getFuelLimit()
|
||||
assert isinstance(flimit, int)
|
||||
assert flimit > 0
|
||||
|
||||
flevel = turtle.getFuelLevel()
|
||||
assert isinstance(flevel, int)
|
||||
assert 0 <= flevel <= flimit
|
||||
|
||||
assert turtle.select(2) is None
|
||||
assert turtle.getSelectedSlot() == 2
|
||||
with assert_raises(LuaException):
|
||||
turtle.select(0)
|
||||
assert turtle.select(1) is None
|
||||
assert turtle.getSelectedSlot() == 1
|
||||
|
||||
step('Put 3 coals into slot 1')
|
||||
|
||||
assert turtle.getItemCount() == 3
|
||||
assert turtle.getItemCount(1) == 3
|
||||
|
||||
assert turtle.getItemDetail() == {
|
||||
'count': 3,
|
||||
'name': 'minecraft:coal',
|
||||
}
|
||||
assert turtle.getItemDetail(1) == {
|
||||
'count': 3,
|
||||
'name': 'minecraft:coal',
|
||||
}
|
||||
|
||||
assert turtle.getItemSpace() == 61
|
||||
assert turtle.getItemSpace(1) == 61
|
||||
|
||||
assert turtle.refuel(1) is None
|
||||
|
||||
assert turtle.getFuelLevel() > flevel
|
||||
flevel = turtle.getFuelLevel()
|
||||
assert turtle.getItemCount() == 2
|
||||
|
||||
assert turtle.refuel() is None
|
||||
|
||||
assert turtle.getFuelLevel() > flevel
|
||||
assert turtle.getItemCount() == 0
|
||||
|
||||
with assert_raises(LuaException):
|
||||
turtle.refuel(1)
|
||||
with assert_raises(LuaException):
|
||||
turtle.refuel()
|
||||
|
||||
step('Remove blocks in front/below/above turtle')
|
||||
|
||||
assert turtle.detect() is False
|
||||
assert turtle.detectUp() is False
|
||||
assert turtle.detectDown() is False
|
||||
|
||||
assert turtle.inspect() is None
|
||||
assert turtle.inspectUp() is None
|
||||
assert turtle.inspectDown() is None
|
||||
|
||||
step('Put cobblestone blocks in front/below/above turtle')
|
||||
|
||||
assert turtle.detect() is True
|
||||
assert turtle.detectUp() is True
|
||||
assert turtle.detectDown() is True
|
||||
|
||||
for c in [
|
||||
turtle.inspect(),
|
||||
turtle.inspectUp(),
|
||||
turtle.inspectDown()
|
||||
]:
|
||||
assert isinstance(c, dict)
|
||||
assert c['name'] == 'minecraft:cobblestone'
|
||||
|
||||
assert turtle.select(1) is None
|
||||
assert turtle.getItemCount() == 0
|
||||
assert turtle.equipLeft() is None
|
||||
|
||||
assert turtle.select(2) is None
|
||||
assert turtle.getItemCount() == 0
|
||||
assert turtle.equipRight() is None
|
||||
|
||||
if (
|
||||
turtle.getItemCount(1) != 0
|
||||
or turtle.getItemCount(2) != 0
|
||||
):
|
||||
step('Remove all items from slots 1 and 2')
|
||||
|
||||
assert turtle.select(1) is None
|
||||
if turtle.getItemDetail(1) != {
|
||||
'count': 1,
|
||||
'name': 'minecraft:diamond_pickaxe',
|
||||
}:
|
||||
step('Put fresh diamond pickaxe at slot 1')
|
||||
|
||||
assert turtle.equipLeft() is None
|
||||
|
||||
assert turtle.dig() is True
|
||||
assert turtle.dig() is False
|
||||
assert turtle.digUp() is True
|
||||
assert turtle.digUp() is False
|
||||
assert turtle.digDown() is True
|
||||
assert turtle.digDown() is False
|
||||
|
||||
assert turtle.getItemCount() == 3
|
||||
|
||||
assert turtle.forward() is True
|
||||
assert turtle.back() is True
|
||||
assert turtle.up() is True
|
||||
assert turtle.down() is True
|
||||
assert turtle.turnLeft() is None
|
||||
assert turtle.turnRight() is None
|
||||
|
||||
assert turtle.place() is True
|
||||
assert turtle.place() is False
|
||||
assert turtle.placeUp() is True
|
||||
assert turtle.placeUp() is False
|
||||
assert turtle.placeDown() is True
|
||||
with assert_raises(LuaException, 'No items to place'):
|
||||
turtle.placeDown()
|
||||
|
||||
step('Put 3 cobblestone blocks to slot 1')
|
||||
|
||||
assert turtle.getItemCount(1) == 3
|
||||
assert turtle.getItemCount(2) == 0
|
||||
|
||||
assert turtle.compare() is True
|
||||
assert turtle.compareUp() is True
|
||||
assert turtle.compareDown() is True
|
||||
|
||||
assert turtle.select(2) is None
|
||||
|
||||
assert turtle.compare() is False
|
||||
assert turtle.compareUp() is False
|
||||
assert turtle.compareDown() is False
|
||||
|
||||
assert turtle.select(1) is None
|
||||
|
||||
assert turtle.transferTo(2, 1) is True
|
||||
assert turtle.getItemCount(1) == 2
|
||||
assert turtle.getItemCount(2) == 1
|
||||
assert turtle.compareTo(2) is True
|
||||
|
||||
assert turtle.transferTo(2) is True
|
||||
assert turtle.getItemCount(1) == 0
|
||||
assert turtle.getItemCount(2) == 3
|
||||
assert turtle.compareTo(2) is False
|
||||
|
||||
assert turtle.select(2) is None
|
||||
assert turtle.transferTo(1) is True
|
||||
assert turtle.select(1) is None
|
||||
assert turtle.dig() is True
|
||||
assert turtle.digUp() is True
|
||||
assert turtle.digDown() is True
|
||||
assert turtle.getItemCount() == 6
|
||||
|
||||
assert turtle.drop(1) is True
|
||||
assert turtle.dropUp(1) is True
|
||||
assert turtle.dropDown(1) is True
|
||||
assert turtle.getItemCount() == 3
|
||||
assert turtle.drop() is True
|
||||
assert turtle.getItemCount() == 0
|
||||
assert turtle.drop() is False
|
||||
|
||||
step(
|
||||
'Collect dropped cobblestone\n'
|
||||
'Drop stack of sticks right in front of the turtle\n'
|
||||
'Its better to build 1-block room then throw sticks there',
|
||||
)
|
||||
|
||||
assert turtle.suck(1) is True
|
||||
assert turtle.getItemCount() == 1
|
||||
assert turtle.suck() is True
|
||||
assert turtle.getItemCount() == 64
|
||||
assert turtle.suck() is False
|
||||
assert turtle.drop() is True
|
||||
assert turtle.getItemCount() == 0
|
||||
|
||||
step(
|
||||
'Collect dropped sticks\n'
|
||||
'Drop stack of sticks right below the turtle\n'
|
||||
'Its better to build 1-block room then throw sticks there',
|
||||
)
|
||||
|
||||
assert turtle.suckDown(1) is True
|
||||
assert turtle.getItemCount() == 1
|
||||
assert turtle.suckDown() is True
|
||||
assert turtle.getItemCount() == 64
|
||||
assert turtle.suckDown() is False
|
||||
assert turtle.dropDown() is True
|
||||
assert turtle.getItemCount() == 0
|
||||
|
||||
step(
|
||||
'Collect dropped sticks\n'
|
||||
'Drop stack of sticks right above the turtle\n'
|
||||
'Its better to build 1-block room then throw sticks there',
|
||||
)
|
||||
|
||||
assert turtle.suckUp(1) is True
|
||||
assert turtle.getItemCount() == 1
|
||||
assert turtle.suckUp() is True
|
||||
assert turtle.getItemCount() == 64
|
||||
assert turtle.suckUp() is False
|
||||
assert turtle.dropUp() is True
|
||||
assert turtle.getItemCount() == 0
|
||||
|
||||
|
||||
def craft1():
|
||||
return turtle.craft()
|
||||
|
||||
|
||||
def craft2():
|
||||
c = peripheral.wrap('right')
|
||||
return await c.craft()
|
||||
|
||||
|
||||
step('Put crafting table into slot 1')
|
||||
assert turtle.select(1) is None
|
||||
assert turtle.equipRight() is None
|
||||
|
||||
for craft_fn in craft1, craft2:
|
||||
step(
|
||||
'Clean inventory of turtle\n'
|
||||
'Put 8 cobblestones into slot 1',
|
||||
)
|
||||
|
||||
assert turtle.select(1) is None
|
||||
assert craft_fn() is False
|
||||
for idx in [2, 3, 5, 7, 9, 10, 11]:
|
||||
assert turtle.transferTo(idx, 1)
|
||||
assert craft_fn() is True
|
||||
assert craft_fn() is False
|
||||
assert turtle.getItemDetail() == {
|
||||
'count': 1,
|
||||
'name': 'minecraft:furnace',
|
||||
}
|
||||
|
||||
print('Test finished successfully')
|
34
examples/test_turtle_attack.py
Normal file
34
examples/test_turtle_attack.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
from cc import import_file, turtle
|
||||
|
||||
_lib = import_file('_lib.py', __file__)
|
||||
|
||||
|
||||
_lib.step(
|
||||
'NOTE: this test is unreliable\n'
|
||||
'Build 1x1x1 stone cage in front of turtle\n'
|
||||
'Spawn here a chicken',
|
||||
)
|
||||
|
||||
assert turtle.attack() is True
|
||||
assert turtle.attack() is True
|
||||
assert turtle.attack() is False
|
||||
|
||||
_lib.step(
|
||||
'Build 1x1x1 stone cage below turtle\n'
|
||||
'Spawn here a chicken',
|
||||
)
|
||||
|
||||
assert turtle.attackDown() is True
|
||||
assert turtle.attackDown() is True
|
||||
assert turtle.attackDown() is False
|
||||
|
||||
_lib.step(
|
||||
'Build 1x1x1 stone cage above turtle\n'
|
||||
'Spawn here a chicken',
|
||||
)
|
||||
|
||||
assert turtle.attackUp() is True
|
||||
assert turtle.attackUp() is True
|
||||
assert turtle.attackUp() is False
|
||||
|
||||
print('Test finished successfully')
|
36
examples/test_window.py
Normal file
36
examples/test_window.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
from cc import colors, term, os, window
|
||||
|
||||
|
||||
with window.create(
|
||||
term.get_current_target(),
|
||||
15, 5, 5, 5, False,
|
||||
) as win:
|
||||
assert win.getPosition() == (15, 5)
|
||||
assert win.getSize() == (5, 5)
|
||||
|
||||
win.setBackgroundColor(colors.red)
|
||||
win.clear()
|
||||
win.setVisible(True)
|
||||
|
||||
os.sleep(1)
|
||||
|
||||
win.setVisible(False)
|
||||
win.setCursorPos(1, 1)
|
||||
win.setTextColor(colors.yellow)
|
||||
win.write('*********')
|
||||
win.setVisible(True)
|
||||
|
||||
os.sleep(1)
|
||||
|
||||
term.clear()
|
||||
|
||||
os.sleep(1)
|
||||
|
||||
win.redraw()
|
||||
assert win.getLine(1) == ('*****', '44444', 'eeeee')
|
||||
|
||||
# draws immediately
|
||||
win.reposition(21, 5)
|
||||
win.reposition(27, 5)
|
||||
|
||||
print('Test finished successfully')
|
|
@ -1 +1,2 @@
|
|||
aiohttp
|
||||
greenlet
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
[flake8]
|
||||
max-line-length = 120
|
||||
ignore = I,C812,N802,N803,N815,W503
|
||||
ignore = I,C812,N802,N803,N815,N816,W503
|
||||
|
|
4
setup.py
4
setup.py
|
@ -19,7 +19,7 @@ if is_register_command(argv[1:]):
|
|||
|
||||
setup(
|
||||
name='computercraft',
|
||||
version='0.2.0',
|
||||
version='0.3.0',
|
||||
description='Pythonization of ComputerCraft Minecraft mod. Write Python instead Lua!',
|
||||
long_description=longdesc,
|
||||
url='https://github.com/neumond/python-computer-craft',
|
||||
|
@ -38,7 +38,7 @@ setup(
|
|||
keywords='computercraft minecraft',
|
||||
packages=['computercraft', 'computercraft.subapis'],
|
||||
package_data={'computercraft': ['back.lua']},
|
||||
install_requires=['aiohttp'],
|
||||
install_requires=['aiohttp', 'greenlet'],
|
||||
entry_points={
|
||||
'console_scripts': ['computercraft = computercraft.server:main'],
|
||||
},
|
||||
|
|
2171
testmod.py
2171
testmod.py
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue