Rewrite to greenlets (remove awaits from user programs)

This commit is contained in:
neumond 2020-07-13 02:47:27 +03:00
parent a948091980
commit c4c6523c55
78 changed files with 4150 additions and 3246 deletions

120
README.md
View file

@ -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.

View file

@ -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

View file

@ -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)

View file

@ -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
View 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)

View 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'))

View file

@ -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),
))

View file

@ -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

View file

@ -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))

View file

@ -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))

View file

@ -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))

View file

@ -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))

View file

@ -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))

View file

@ -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))

View file

@ -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):

View file

@ -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'))

View file

@ -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'))

View file

@ -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))

View 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()

View file

@ -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),
))

View file

@ -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'))

View file

@ -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)

View file

@ -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))

View file

@ -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'))

View file

@ -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))

View file

@ -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

View file

@ -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()')

View file

@ -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

View file

@ -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))

View file

@ -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)

View file

@ -1,4 +0,0 @@
from .hello import program as hello
__all__ = (hello, )

135
examples/_lib.py Normal file
View 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')

View file

@ -1,2 +1 @@
async def program(api):
await api.print('Hello world!')
print('Hello world!')

5
examples/id.py Normal file
View 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
View 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
View 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
View file

@ -0,0 +1,2 @@
line = input()
print(f'Entered line: {line}')

38
examples/test_colors.py Normal file
View 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
View 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
View 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
View 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')

View 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')

View 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
View 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
View 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')

View 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
View 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')

View 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
View 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')

View 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')

View 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')

View file

@ -0,0 +1,5 @@
from cc import import_file
_lib = import_file('_lib.py', __file__)
_lib._computer_peri('another computer', 'computer')

View 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')

View 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')

View 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')

View 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')

View 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')

View 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')

View 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
View 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
View file

@ -0,0 +1,5 @@
from cc import os
assert os.reboot() is None
print('Test finished successfully')

View 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')

View 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')

View 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
View 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
View 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
View 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
View 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')

View 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
View 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')

View 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
View 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')

View 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
View 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')

View file

@ -1 +1,2 @@
aiohttp
greenlet

View file

@ -1,3 +1,3 @@
[flake8]
max-line-length = 120
ignore = I,C812,N802,N803,N815,W503
ignore = I,C812,N802,N803,N815,N816,W503

View file

@ -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

File diff suppressed because it is too large Load diff