Initial commit
This commit is contained in:
commit
84913f272e
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
__pycache__/
|
||||
*.pyc
|
5
README.md
Normal file
5
README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Pythonized ComputerCraft API
|
||||
|
||||
TODO:
|
||||
- add host and port cmdline parameters for server
|
||||
- use current dir for programs
|
0
computercraft/__init__.py
Normal file
0
computercraft/__init__.py
Normal file
134
computercraft/back.lua
Normal file
134
computercraft/back.lua
Normal file
|
@ -0,0 +1,134 @@
|
|||
local genv = getfenv()
|
||||
local temp = {}
|
||||
genv.temp = temp
|
||||
local url = 'http://127.0.0.1:4343/'
|
||||
local args = {...}
|
||||
local tasks = {}
|
||||
|
||||
-- https://www.lua.org/pil/11.4.html
|
||||
local Queue = {}
|
||||
function Queue.new()
|
||||
return {head = 0, tail = 0}
|
||||
end
|
||||
function Queue.push(q, value)
|
||||
q[q.head] = value
|
||||
q.head = q.head + 1
|
||||
end
|
||||
function Queue.pop(q)
|
||||
if q.tail >= q.head then return nil end
|
||||
local value = q[q.tail]
|
||||
q[q.tail] = nil
|
||||
q.tail = q.tail + 1
|
||||
return value
|
||||
end
|
||||
function Queue.length(q)
|
||||
return q.head - q.tail
|
||||
end
|
||||
local output = Queue.new()
|
||||
|
||||
local function is_special_task(task_id)
|
||||
return (task_id == '_fetch' or task_id == '_send')
|
||||
end
|
||||
|
||||
local function string_split(text)
|
||||
local pos = string.find(text, ';')
|
||||
return string.sub(text, 1, pos - 1), string.sub(text, pos + 1)
|
||||
end
|
||||
|
||||
local function inner_resume(task_id, ...)
|
||||
local r = {coroutine.resume(tasks[task_id].co, ...)}
|
||||
if coroutine.status(tasks[task_id].co) == 'dead' then
|
||||
if not is_special_task(task_id) then
|
||||
Queue.push(output, {task_id=task_id, result=r})
|
||||
end
|
||||
tasks[task_id] = nil
|
||||
return true
|
||||
end
|
||||
if r[1] then tasks[task_id].exp = r[2] end
|
||||
return false
|
||||
end
|
||||
|
||||
local function make_task(task_id, f, ...)
|
||||
tasks[task_id] = {co=coroutine.create(f)}
|
||||
return inner_resume(task_id, ...)
|
||||
end
|
||||
|
||||
local function resume_task(task_id, event, ...)
|
||||
local exp = tasks[task_id].exp
|
||||
if exp ~= nil and event ~= exp then return false end
|
||||
return inner_resume(task_id, event, ...)
|
||||
end
|
||||
|
||||
local function event_queue(task_id, event)
|
||||
while true do
|
||||
local estruct = {os.pullEvent(event)}
|
||||
Queue.push(output, {task_id=task_id, result=estruct})
|
||||
end
|
||||
end
|
||||
|
||||
local function fetch_fn()
|
||||
local r = http.post(url..'start/'..os.getComputerID()..'/'..args[1]..'/')
|
||||
if (r == nil or r.getResponseCode() ~= 200) then
|
||||
print('Failed to start program '..args[1])
|
||||
return
|
||||
end
|
||||
while true do
|
||||
r = http.post(url..'gettask/'..os.getComputerID()..'/', answer)
|
||||
if (r == nil or r.getResponseCode() ~= 200) then
|
||||
print('Connection broken')
|
||||
return
|
||||
end
|
||||
local cmd = r.readAll()
|
||||
if cmd == 'END' then
|
||||
break
|
||||
elseif cmd ~= 'NOOP' then
|
||||
local cmd, text = string_split(cmd)
|
||||
if cmd == 'TASK' then
|
||||
local task_id, text = string_split(text)
|
||||
local f = loadstring(text)
|
||||
setfenv(f, genv)
|
||||
make_task(task_id, f)
|
||||
elseif cmd == 'STARTQUEUE' then
|
||||
local task_id, text = string_split(text)
|
||||
make_task(task_id, event_queue, task_id, text)
|
||||
elseif cmd == 'STOPQUEUE' then
|
||||
tasks[text] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function send_fn()
|
||||
while true do
|
||||
local r = Queue.pop(output)
|
||||
if r == nil then break end
|
||||
local answer = textutils.serializeJSON(r.result)
|
||||
http.post(url..'taskresult/'..os.getComputerID()..'/'..r.task_id..'/', answer)
|
||||
end
|
||||
end
|
||||
|
||||
if make_task('_fetch', fetch_fn) then return end
|
||||
|
||||
while true do
|
||||
local event, p1, p2, p3, p4, p5 = os.pullEvent()
|
||||
if resume_task('_fetch', event, p1, p2, p3, p4, p5) then break end
|
||||
|
||||
-- Use of http API in user code is explicitly disallowed
|
||||
if event ~= 'http_success' and event ~= 'http_failure' then
|
||||
local local_task_ids = {}
|
||||
for task_id in pairs(tasks) do
|
||||
local_task_ids[task_id] = true
|
||||
end
|
||||
for task_id in pairs(local_task_ids) do
|
||||
if not is_special_task(task_id) then
|
||||
resume_task(task_id, event, p1, p2, p3, p4, p5)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if tasks['_send'] ~= nil then
|
||||
resume_task('_send', event, p1, p2, p3, p4, p5)
|
||||
else
|
||||
if Queue.length(output) > 0 then make_task('_send', send_fn) end
|
||||
end
|
||||
end
|
10
computercraft/errors.py
Normal file
10
computercraft/errors.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
class TurtleException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class LuaException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ApiException(Exception):
|
||||
pass
|
249
computercraft/server.py
Normal file
249
computercraft/server.py
Normal file
|
@ -0,0 +1,249 @@
|
|||
import asyncio
|
||||
import json
|
||||
import string
|
||||
from aiohttp import web
|
||||
from traceback import print_exc
|
||||
from os.path import getmtime, join, dirname, abspath
|
||||
from os import listdir
|
||||
import importlib
|
||||
from subapis.root import RootAPIMixin
|
||||
from common import LuaException
|
||||
|
||||
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.peripheral import PeripheralAPI
|
||||
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
|
||||
|
||||
|
||||
THIS_DIR = dirname(abspath(__file__))
|
||||
LUA_FILE = join(THIS_DIR, 'back.lua')
|
||||
|
||||
|
||||
exchange = {}
|
||||
module_map = {}
|
||||
|
||||
|
||||
DIGITS = string.digits + string.ascii_lowercase
|
||||
|
||||
|
||||
def base36(n):
|
||||
r = ''
|
||||
while n:
|
||||
r += DIGITS[n % 36]
|
||||
n //= 36
|
||||
return r[::-1]
|
||||
|
||||
|
||||
async def lua_json(request):
|
||||
body = await request.text()
|
||||
body = body.replace('\\\n', '\\n')
|
||||
return json.loads(body)
|
||||
|
||||
|
||||
def program_filenames():
|
||||
return [f[:-3] for f in listdir(join(THIS_DIR, 'programs')) if f.endswith('.py') and f != '__init__.py']
|
||||
|
||||
|
||||
def m_filename(m):
|
||||
return join(THIS_DIR, 'programs', '{}.py'.format(m))
|
||||
|
||||
|
||||
async def reload_all_modules(module_map):
|
||||
prev = set(module_map.keys())
|
||||
nxt = set(program_filenames())
|
||||
|
||||
# unloading old modules
|
||||
for m in prev - nxt:
|
||||
del module_map[m]
|
||||
|
||||
# loading new modules
|
||||
for m in nxt - prev:
|
||||
module_map[m] = importlib.import_module('programs.{}'.format(m))
|
||||
module_map[m]._mtime_mark = getmtime(m_filename(m))
|
||||
print('Loaded {}'.format(m))
|
||||
|
||||
# reloading modified modules
|
||||
for m in nxt & prev:
|
||||
mtime = getmtime(m_filename(m))
|
||||
if module_map[m]._mtime_mark < mtime:
|
||||
importlib.reload(module_map[m])
|
||||
module_map[m]._mtime_mark = mtime
|
||||
print('Reloaded {}'.format(m))
|
||||
|
||||
|
||||
async def module_reloader():
|
||||
while True:
|
||||
fut = asyncio.ensure_future(reload_all_modules(module_map))
|
||||
await asyncio.wait([fut])
|
||||
await asyncio.sleep(5)
|
||||
|
||||
|
||||
class CCAPI(RootAPIMixin):
|
||||
def __init__(self, nid, program):
|
||||
self._id = nid
|
||||
self._task_autoid = 1
|
||||
self._cmd = asyncio.Queue(maxsize=1)
|
||||
self._result_locks = {}
|
||||
self._result_values = {}
|
||||
self._result_queues = {}
|
||||
|
||||
self.colors = ColorsAPI
|
||||
self.commands = CommandsAPI(self)
|
||||
self.disk = DiskAPI(self)
|
||||
self.fs = FSAPI(self)
|
||||
self.gps = GpsAPI(self) # TODO: test
|
||||
self.help = HelpAPI(self)
|
||||
self.keys = KeysAPI(self)
|
||||
self.multishell = MultishellAPI(self)
|
||||
self.os = OSAPI(self)
|
||||
self.peripheral = PeripheralAPI(self)
|
||||
self.rednet = RednetAPI(self)
|
||||
self.redstone = RedstoneAPI(self)
|
||||
self.settings = SettingsAPI(self)
|
||||
self.shell = ShellAPI(self) # TODO: autocomplete functions
|
||||
self.term = TermAPI(self) # TODO: window redirections
|
||||
self.textutils = TextutilsAPI(self)
|
||||
self.turtle = TurtleAPI(self)
|
||||
self.window = WindowAPI(self) # TODO: unimplemented
|
||||
|
||||
async def prog_wrap():
|
||||
cancel = False
|
||||
try:
|
||||
await program(self)
|
||||
except asyncio.CancelledError:
|
||||
print('program {} cancelled'.format(self._id))
|
||||
print_exc()
|
||||
cancel = True
|
||||
except Exception as e:
|
||||
print('program {} crashed: {} {}'.format(self._id, type(e), e))
|
||||
print_exc()
|
||||
else:
|
||||
print('program {} finished'.format(self._id))
|
||||
finally:
|
||||
if not cancel:
|
||||
await self._cmd.put('END')
|
||||
del exchange[self._id]
|
||||
|
||||
self._task = asyncio.ensure_future(prog_wrap())
|
||||
|
||||
def _new_task_id(self):
|
||||
task_id = base36(self._task_autoid)
|
||||
self._task_autoid += 1
|
||||
return task_id
|
||||
|
||||
async def _send_cmd(self, lua):
|
||||
task_id = self._new_task_id()
|
||||
self._result_locks[task_id] = asyncio.Event()
|
||||
await self._cmd.put('TASK;' + task_id + ';' + lua)
|
||||
await self._result_locks[task_id].wait()
|
||||
del self._result_locks[task_id]
|
||||
result = self._result_values.pop(task_id)
|
||||
print('{} → {}'.format(lua, result))
|
||||
if not result[0]:
|
||||
raise LuaException(*result[1:])
|
||||
return result[1:]
|
||||
|
||||
async def _start_queue(self, event):
|
||||
task_id = self._new_task_id()
|
||||
self._result_queues[task_id] = asyncio.Queue()
|
||||
await self._cmd.put('STARTQUEUE;' + task_id + ';' + event)
|
||||
return self._result_queues[task_id], task_id
|
||||
|
||||
async def _stop_queue(self, task_id):
|
||||
await self._cmd.put('STOPQUEUE;' + task_id)
|
||||
del self._result_queues[task_id]
|
||||
|
||||
|
||||
async def start(request):
|
||||
tid = int(request.match_info['turtle'])
|
||||
if tid in exchange:
|
||||
# terminate old program
|
||||
exchange[tid]._task.cancel()
|
||||
exchange[tid] = CCAPI(tid, module_map[request.match_info['program']].program)
|
||||
return web.Response(text='')
|
||||
|
||||
|
||||
async def gettask(request):
|
||||
api = exchange.get(int(request.match_info['turtle']))
|
||||
if api is None:
|
||||
return web.Response(text='END')
|
||||
return web.Response(text=await api._cmd.get())
|
||||
|
||||
|
||||
async def taskresult(request):
|
||||
api = exchange.get(int(request.match_info['turtle']))
|
||||
if api is not None:
|
||||
tid = request.match_info['task_id']
|
||||
if tid in api._result_locks:
|
||||
# it's a TASK
|
||||
api._result_values[tid] = await lua_json(request)
|
||||
api._result_locks[tid].set()
|
||||
elif tid in api._result_queues:
|
||||
# it's a QUEUE
|
||||
await api._result_queues[tid].put(await lua_json(request))
|
||||
# otherwise just ignore
|
||||
return web.Response(text='')
|
||||
|
||||
|
||||
def backdoor(request):
|
||||
with open(LUA_FILE, 'r') as f:
|
||||
fcont = f.read()
|
||||
return web.Response(text=fcont)
|
||||
|
||||
|
||||
logging_config = '''
|
||||
version: 1
|
||||
disable_existing_loggers: false
|
||||
root:
|
||||
level: ERROR
|
||||
handlers:
|
||||
- console
|
||||
handlers:
|
||||
console:
|
||||
level: INFO
|
||||
class: logging.StreamHandler
|
||||
formatter: verbose
|
||||
loggers:
|
||||
aiohttp.access:
|
||||
level: INFO
|
||||
handlers: []
|
||||
propagate: true
|
||||
formatters:
|
||||
verbose:
|
||||
format: '%(message)s'
|
||||
'''
|
||||
|
||||
|
||||
def enable_request_logging():
|
||||
import logging.config
|
||||
import yaml
|
||||
logging.config.dictConfig(yaml.load(logging_config))
|
||||
|
||||
|
||||
def main():
|
||||
# enable_request_logging()
|
||||
asyncio.ensure_future(module_reloader())
|
||||
app = web.Application()
|
||||
app.router.add_get('/', backdoor)
|
||||
app.router.add_post('/start/{turtle}/{program}/', start)
|
||||
app.router.add_post('/gettask/{turtle}/', gettask)
|
||||
app.router.add_post('/taskresult/{turtle}/{task_id}/', taskresult)
|
||||
web.run_app(app, port=4343)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
0
computercraft/subapis/__init__.py
Normal file
0
computercraft/subapis/__init__.py
Normal file
131
computercraft/subapis/base.py
Normal file
131
computercraft/subapis/base.py
Normal file
|
@ -0,0 +1,131 @@
|
|||
from common import TurtleException, ApiException
|
||||
from typing import Union
|
||||
|
||||
|
||||
LuaTable = Union[list, dict]
|
||||
LuaNum = Union[int, float]
|
||||
|
||||
|
||||
def lua_string(v):
|
||||
return '"{}"'.format(
|
||||
v.replace('\\', '\\\\')
|
||||
.replace('\a', '\\a')
|
||||
.replace('\b', '\\b')
|
||||
.replace('\f', '\\f')
|
||||
.replace('\n', '\\n')
|
||||
.replace('\r', '\\r')
|
||||
.replace('\t', '\\t')
|
||||
.replace('\v', '\\v')
|
||||
.replace('"', '\\"')
|
||||
.replace("'", "\\'")
|
||||
.replace('[', '\\[')
|
||||
.replace(']', '\\]')
|
||||
)
|
||||
|
||||
|
||||
def lua_list(v):
|
||||
return '{' + ', '.join(lua_value(x) for x in v) + '}'
|
||||
|
||||
|
||||
def lua_dict(v):
|
||||
return '{' + ', '.join(
|
||||
'[{}]={}'.format(lua_value(k), lua_value(v)) for k, v in v.items()
|
||||
) + '}'
|
||||
|
||||
|
||||
def lua_value(v):
|
||||
if v is None:
|
||||
return 'nil'
|
||||
if v is False:
|
||||
return 'false'
|
||||
if v is True:
|
||||
return 'true'
|
||||
if isinstance(v, str):
|
||||
return lua_string(v)
|
||||
if isinstance(v, (int, float)):
|
||||
return str(v)
|
||||
if isinstance(v, list):
|
||||
return lua_list(v)
|
||||
if isinstance(v, dict):
|
||||
return lua_dict(v)
|
||||
raise ValueError('Can\'t convert a value to lua {}'.format(v))
|
||||
|
||||
|
||||
def lua_args(*params, omit_nulls=True):
|
||||
ps, can_add = [], True
|
||||
for p in params:
|
||||
if omit_nulls and p is None:
|
||||
can_add = False
|
||||
continue
|
||||
if not can_add:
|
||||
raise ApiException('Optional parameter order error')
|
||||
ps.append(lua_value(p))
|
||||
return ', '.join(ps)
|
||||
|
||||
|
||||
def bool_success(v):
|
||||
if v == [True]:
|
||||
return
|
||||
if len(v) == 2 and v[0] is False:
|
||||
assert isinstance(v[1], str)
|
||||
raise TurtleException(v[1])
|
||||
raise ApiException('Bad return value: {}'.format(v))
|
||||
|
||||
|
||||
def single_return(v):
|
||||
assert len(v) == 1
|
||||
return v[0]
|
||||
|
||||
|
||||
def make_optional(fn):
|
||||
def op_fn(v):
|
||||
if v == []:
|
||||
return None
|
||||
return fn(v)
|
||||
return op_fn
|
||||
|
||||
|
||||
def make_single_value_return(*types):
|
||||
if len(types) > 1:
|
||||
types = tuple(types)
|
||||
else:
|
||||
types = types[0]
|
||||
|
||||
def fn(v):
|
||||
assert len(v) == 1
|
||||
assert isinstance(v[0], types)
|
||||
return v[0]
|
||||
return fn
|
||||
|
||||
|
||||
bool_return = make_single_value_return(bool)
|
||||
int_return = make_single_value_return(int)
|
||||
number_return = make_single_value_return(int, float)
|
||||
str_return = make_single_value_return(str)
|
||||
list_return = make_single_value_return(list)
|
||||
dict_return = make_single_value_return(dict)
|
||||
|
||||
|
||||
@make_optional
|
||||
def nil_return(v):
|
||||
assert False
|
||||
|
||||
|
||||
opt_bool_return = make_optional(bool_return)
|
||||
opt_int_return = make_optional(int_return)
|
||||
opt_number_return = make_optional(number_return)
|
||||
opt_str_return = make_optional(str_return)
|
||||
opt_list_return = make_optional(list_return)
|
||||
opt_dict_return = make_optional(dict_return)
|
||||
|
||||
|
||||
class BaseSubAPI:
|
||||
_API = NotImplemented
|
||||
|
||||
def __init__(self, cc):
|
||||
self._cc = cc
|
||||
|
||||
async def _send(self, method, *params, omit_nulls=True):
|
||||
return await self._cc._send_cmd('return {}.{}({})'.format(
|
||||
self._API, method, lua_args(*params, omit_nulls=omit_nulls)
|
||||
))
|
36
computercraft/subapis/colors.py
Normal file
36
computercraft/subapis/colors.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
class ColorsAPI:
|
||||
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
|
||||
|
||||
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,
|
||||
}
|
28
computercraft/subapis/commands.py
Normal file
28
computercraft/subapis/commands.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
from typing import Tuple, List
|
||||
from .base import BaseSubAPI, int_return, list_return, dict_return
|
||||
|
||||
|
||||
class CommandsAPI(BaseSubAPI):
|
||||
_API = 'commands'
|
||||
|
||||
async def exec(self, command: str):
|
||||
r = await self._send('exec', command)
|
||||
assert len(r) == 2
|
||||
assert isinstance(r[0], bool)
|
||||
assert isinstance(r[1], list)
|
||||
return r[0], r[1]
|
||||
|
||||
async def execAsync(self, command: str) -> int:
|
||||
return int_return(await self._send('execAsync', command))
|
||||
|
||||
async def list(self) -> List[str]:
|
||||
return list_return(await self._send('list'))
|
||||
|
||||
async def getBlockPosition(self) -> Tuple[int, int, int]:
|
||||
return tuple(await self._send('getBlockPosition'))
|
||||
|
||||
async def getBlockInfo(self, x: int, y: int, z: int) -> dict:
|
||||
return dict_return(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 list_return(await self._send('getBlockInfos', x1, y1, z1, x2, y2, z2))
|
39
computercraft/subapis/disk.py
Normal file
39
computercraft/subapis/disk.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
from typing import Optional
|
||||
from .base import BaseSubAPI, nil_return, bool_return, opt_str_return, opt_int_return
|
||||
|
||||
|
||||
class DiskAPI(BaseSubAPI):
|
||||
_API = 'disk'
|
||||
|
||||
async def isPresent(self, side: str) -> bool:
|
||||
return bool_return(await self._send('isPresent', side))
|
||||
|
||||
async def hasData(self, side: str) -> bool:
|
||||
return bool_return(await self._send('hasData', side))
|
||||
|
||||
async def getMountPath(self, side: str) -> Optional[str]:
|
||||
return opt_str_return(await self._send('getMountPath', side))
|
||||
|
||||
async def setLabel(self, side: str, label: str):
|
||||
return nil_return(await self._send('setLabel', side, label))
|
||||
|
||||
async def getLabel(self, side: str) -> Optional[str]:
|
||||
return opt_str_return(await self._send('getLabel', side))
|
||||
|
||||
async def getID(self, side: str) -> Optional[int]:
|
||||
return opt_int_return(await self._send('getID', side))
|
||||
|
||||
async def hasAudio(self, side: str) -> bool:
|
||||
return bool_return(await self._send('hasAudio', side))
|
||||
|
||||
async def getAudioTitle(self, side: str) -> Optional[str]:
|
||||
return opt_str_return(await self._send('getAudioTitle', side))
|
||||
|
||||
async def playAudio(self, side: str):
|
||||
return nil_return(await self._send('playAudio', side))
|
||||
|
||||
async def stopAudio(self, side: str):
|
||||
return nil_return(await self._send('stopAudio', side))
|
||||
|
||||
async def eject(self, side: str):
|
||||
return nil_return(await self._send('eject', side))
|
118
computercraft/subapis/fs.py
Normal file
118
computercraft/subapis/fs.py
Normal file
|
@ -0,0 +1,118 @@
|
|||
from typing import Optional, Union, List
|
||||
from .base import (
|
||||
BaseSubAPI, lua_string,
|
||||
nil_return, opt_str_return, str_return, bool_return, int_return, list_return, opt_int_return,
|
||||
)
|
||||
from uuid import uuid4
|
||||
|
||||
|
||||
class CCFile(BaseSubAPI):
|
||||
def __init__(self, cc, path, mode):
|
||||
super().__init__(cc)
|
||||
self._path = path
|
||||
self._mode = mode
|
||||
|
||||
async def __aenter__(self):
|
||||
self._id = str(uuid4())
|
||||
self._API = 'temp[{}]'.format(lua_string(self._id))
|
||||
await self._cc._send_cmd('{} = fs.open({}, {})'.format(self._API, *map(lua_string, [
|
||||
self._path, self._mode
|
||||
])))
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc_type, exc, tb):
|
||||
await self._cc._send_cmd('{}.close(); {} = nil'.format(self._API, self._API))
|
||||
|
||||
async def read(self) -> Optional[int]:
|
||||
return opt_int_return(await self._send('read'))
|
||||
|
||||
async def readLine(self) -> Optional[str]:
|
||||
return opt_str_return(await self._send('readLine'))
|
||||
|
||||
async def readAll(self) -> str:
|
||||
return str_return(await self._send('readAll'))
|
||||
|
||||
async def write(self, data: Union[str, int]):
|
||||
return nil_return(await self._send('write', data))
|
||||
|
||||
async def writeLine(self, data: str):
|
||||
return nil_return(await self._send('writeLine', data))
|
||||
|
||||
async def flush(self):
|
||||
return nil_return(await self._send('flush'))
|
||||
|
||||
def __aiter__(self):
|
||||
return self
|
||||
|
||||
async def __anext__(self):
|
||||
line = await self.readLine()
|
||||
if line is None:
|
||||
raise StopAsyncIteration
|
||||
return line
|
||||
|
||||
|
||||
class FSAPI(BaseSubAPI):
|
||||
_API = 'fs'
|
||||
|
||||
async def list(self, path: str) -> List[str]:
|
||||
return list_return(await self._send('list', path))
|
||||
|
||||
async def exists(self, path: str) -> bool:
|
||||
return bool_return(await self._send('exists', path))
|
||||
|
||||
async def isDir(self, path: str) -> bool:
|
||||
return bool_return(await self._send('isDir', path))
|
||||
|
||||
async def isReadOnly(self, path: str) -> bool:
|
||||
return bool_return(await self._send('isReadOnly', path))
|
||||
|
||||
async def getName(self, path: str) -> str:
|
||||
return str_return(await self._send('getName', path))
|
||||
|
||||
async def getDrive(self, path: str) -> Optional[str]:
|
||||
return opt_str_return(await self._send('getDrive', path))
|
||||
|
||||
async def getSize(self, path: str) -> int:
|
||||
return int_return(await self._send('getSize', path))
|
||||
|
||||
async def getFreeSpace(self, path: str) -> int:
|
||||
return int_return(await self._send('getFreeSpace', path))
|
||||
|
||||
async def makeDir(self, path: str):
|
||||
return nil_return(await self._send('makeDir', path))
|
||||
|
||||
async def move(self, fromPath: str, toPath: str):
|
||||
return nil_return(await self._send('move', fromPath, toPath))
|
||||
|
||||
async def copy(self, fromPath: str, toPath: str):
|
||||
return nil_return(await self._send('copy', fromPath, toPath))
|
||||
|
||||
async def delete(self, path: str):
|
||||
return nil_return(await self._send('delete', path))
|
||||
|
||||
async def combine(self, basePath: str, localPath: str) -> str:
|
||||
return str_return(await self._send('combine', basePath, localPath))
|
||||
|
||||
def open(self, path: str, mode: str) -> CCFile:
|
||||
'''
|
||||
Usage:
|
||||
|
||||
async with api.fs.open('filename', 'w') as f:
|
||||
await f.writeLine('textline')
|
||||
|
||||
async with api.fs.open('filename', 'r') as f:
|
||||
async for line in f:
|
||||
...
|
||||
'''
|
||||
return CCFile(self._cc, path, mode)
|
||||
|
||||
async def find(self, wildcard: str) -> List[str]:
|
||||
return list_return(await self._send('find', wildcard))
|
||||
|
||||
async def getDir(self, path: str) -> str:
|
||||
return str_return(await self._send('getDir', path))
|
||||
|
||||
async def complete(
|
||||
self, partialName: str, path: str, includeFiles: bool=None, includeSlashes: bool=None
|
||||
) -> List[str]:
|
||||
return list_return(await self._send('complete', partialName, path, includeFiles, includeSlashes))
|
12
computercraft/subapis/gps.py
Normal file
12
computercraft/subapis/gps.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
from typing import Tuple, Optional
|
||||
from .base import BaseSubAPI, LuaNum
|
||||
|
||||
|
||||
class GpsAPI(BaseSubAPI):
|
||||
_API = 'gps'
|
||||
|
||||
async def locate(self, timeout: LuaNum=None, debug: bool=None) -> Optional[Tuple[int, int, int]]:
|
||||
r = await self._send('locate', timeout, debug)
|
||||
if r == [None]:
|
||||
return None
|
||||
return tuple(r)
|
21
computercraft/subapis/help.py
Normal file
21
computercraft/subapis/help.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
from typing import Optional, List
|
||||
from .base import BaseSubAPI, nil_return, opt_str_return, str_return, list_return
|
||||
|
||||
|
||||
class HelpAPI(BaseSubAPI):
|
||||
_API = 'help'
|
||||
|
||||
async def path(self) -> str:
|
||||
return str_return(await self._send('path'))
|
||||
|
||||
async def setPath(self, path: str):
|
||||
return nil_return(await self._send('setPath', path))
|
||||
|
||||
async def lookup(self, topic: str) -> Optional[str]:
|
||||
return opt_str_return(await self._send('lookup', topic))
|
||||
|
||||
async def topics(self) -> List[str]:
|
||||
return list_return(await self._send('topics'))
|
||||
|
||||
async def completeTopic(self, topicPrefix: str) -> List[str]:
|
||||
return list_return(await self._send('completeTopic', topicPrefix))
|
8
computercraft/subapis/keys.py
Normal file
8
computercraft/subapis/keys.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
from .base import BaseSubAPI, str_return
|
||||
|
||||
|
||||
class KeysAPI(BaseSubAPI):
|
||||
_API = 'keys'
|
||||
|
||||
async def getName(self, code: int) -> str:
|
||||
return str_return(await self._send('getName', code))
|
46
computercraft/subapis/mixins.py
Normal file
46
computercraft/subapis/mixins.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
from typing import Tuple
|
||||
from .base import bool_return, nil_return, int_return
|
||||
|
||||
|
||||
class TermMixin:
|
||||
async def write(self, text: str):
|
||||
return nil_return(await self._send('write', text))
|
||||
|
||||
async def blit(self, text: str, textColors: str, backgroundColors: str):
|
||||
return nil_return(await self._send('blit', text, textColors, backgroundColors))
|
||||
|
||||
async def clear(self):
|
||||
return nil_return(await self._send('clear'))
|
||||
|
||||
async def clearLine(self):
|
||||
return nil_return(await self._send('clearLine'))
|
||||
|
||||
async def getCursorPos(self) -> Tuple[int, int]:
|
||||
return tuple(await self._send('getCursorPos'))
|
||||
|
||||
async def setCursorPos(self, x: int, y: int):
|
||||
return nil_return(await self._send('setCursorPos', x, y))
|
||||
|
||||
async def setCursorBlink(self, value: bool):
|
||||
return nil_return(await self._send('setCursorBlink', value))
|
||||
|
||||
async def isColor(self) -> bool:
|
||||
return bool_return(await self._send('isColor'))
|
||||
|
||||
async def getSize(self) -> Tuple[int, int]:
|
||||
return tuple(await self._send('getSize'))
|
||||
|
||||
async def scroll(self, n: int):
|
||||
return nil_return(await self._send('scroll', n))
|
||||
|
||||
async def setTextColor(self, color: int):
|
||||
return nil_return(await self._send('setTextColor', color))
|
||||
|
||||
async def getTextColor(self) -> int:
|
||||
return int_return(await self._send('getTextColor'))
|
||||
|
||||
async def setBackgroundColor(self, color: int):
|
||||
return nil_return(await self._send('setBackgroundColor', color))
|
||||
|
||||
async def getBackgroundColor(self) -> int:
|
||||
return int_return(await self._send('getBackgroundColor'))
|
27
computercraft/subapis/multishell.py
Normal file
27
computercraft/subapis/multishell.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
from typing import List
|
||||
from .base import BaseSubAPI, str_return, int_return, nil_return, bool_return
|
||||
|
||||
|
||||
class MultishellAPI(BaseSubAPI):
|
||||
_API = 'multishell'
|
||||
|
||||
async def getCurrent(self) -> int:
|
||||
return int_return(await self._send('getCurrent'))
|
||||
|
||||
async def getCount(self) -> int:
|
||||
return int_return(await self._send('getCount'))
|
||||
|
||||
async def launch(self, environment: dict, programPath: str, *args: List[str]) -> int:
|
||||
return int_return(await self._send('launch', environment, programPath, *args))
|
||||
|
||||
async def setFocus(self, tabID: int):
|
||||
return bool_return(await self._send('setFocus', tabID))
|
||||
|
||||
async def setTitle(self, tabID: int, title: str):
|
||||
return nil_return(await self._send('setTitle', tabID, title))
|
||||
|
||||
async def getTitle(self, tabID: int) -> str:
|
||||
return str_return(await self._send('getTitle', tabID))
|
||||
|
||||
async def getFocus(self) -> int:
|
||||
return int_return(await self._send('getFocus'))
|
107
computercraft/subapis/os.py
Normal file
107
computercraft/subapis/os.py
Normal file
|
@ -0,0 +1,107 @@
|
|||
from typing import Optional, List
|
||||
from .base import (
|
||||
BaseSubAPI, LuaTable, LuaNum,
|
||||
nil_return, str_return, opt_str_return, number_return, int_return, bool_return
|
||||
)
|
||||
|
||||
|
||||
class CCEventQueue(BaseSubAPI):
|
||||
def __init__(self, cc, targetEvent):
|
||||
super().__init__(cc)
|
||||
self._targetEvent = targetEvent
|
||||
|
||||
async def __aenter__(self):
|
||||
self._q, self._tid = await self._cc._start_queue(self._targetEvent)
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc_type, exc, tb):
|
||||
await self._cc._stop_queue(self._tid)
|
||||
|
||||
def __aiter__(self):
|
||||
return self
|
||||
|
||||
async def __anext__(self):
|
||||
return await self._q.get()
|
||||
|
||||
|
||||
class OSAPI(BaseSubAPI):
|
||||
_API = 'os'
|
||||
|
||||
async def version(self) -> str:
|
||||
return str_return(await self._send('version'))
|
||||
|
||||
async def getComputerID(self) -> int:
|
||||
return int_return(await self._send('getComputerID'))
|
||||
|
||||
async def getComputerLabel(self) -> Optional[str]:
|
||||
return opt_str_return(await self._send('getComputerLabel'))
|
||||
|
||||
async def setComputerLabel(self, label: Optional[str]):
|
||||
return nil_return(await self._send('setComputerLabel', label, omit_nulls=False))
|
||||
|
||||
async def run(self, environment: LuaTable, programPath: str, *args: List[str]):
|
||||
return bool_return(await self._send('run', environment, programPath, *args))
|
||||
|
||||
async def loadAPI(self, path: str):
|
||||
return bool_return(await self._send('loadAPI', path))
|
||||
|
||||
async def unloadAPI(self, name: str):
|
||||
return nil_return(await self._send('unloadAPI', name))
|
||||
|
||||
def registerEventQueue(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.
|
||||
|
||||
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.
|
||||
|
||||
registerEventQueue gives you reliable way of receiving events without losses.
|
||||
Register queue before firing a timer, start a timer, listen for messages in queue.
|
||||
|
||||
# it's significant here: start queue before starting a timer
|
||||
async with api.os.registerEventQueue('timer') as timer_queue:
|
||||
myTimer = await api.os.startTimer(3)
|
||||
async for e in timer_queue:
|
||||
if e[1] == myTimer:
|
||||
await api.print('Timer reached')
|
||||
break
|
||||
'''
|
||||
return CCEventQueue(self._cc, targetEvent)
|
||||
|
||||
async def queueEvent(self, event: str, *params):
|
||||
return nil_return(await self._send('queueEvent', event, *params))
|
||||
|
||||
async def clock(self) -> LuaNum:
|
||||
return number_return(await self._send('clock'))
|
||||
|
||||
async def startTimer(self, timeout: LuaNum) -> int:
|
||||
return int_return(await self._send('startTimer', timeout))
|
||||
|
||||
async def cancelTimer(self, timerID: int):
|
||||
return nil_return(await self._send('cancelTimer', timerID))
|
||||
|
||||
async def time(self) -> LuaNum:
|
||||
return number_return(await self._send('time'))
|
||||
|
||||
async def sleep(self, seconds: LuaNum):
|
||||
return nil_return(await self._send('sleep', seconds))
|
||||
|
||||
async def day(self) -> int:
|
||||
return int_return(await self._send('day'))
|
||||
|
||||
async def setAlarm(self, time: LuaNum) -> int:
|
||||
return int_return(await self._send('setAlarm', time))
|
||||
|
||||
async def cancelAlarm(self, alarmID: int):
|
||||
return nil_return(await self._send('cancelAlarm', alarmID))
|
||||
|
||||
async def shutdown(self):
|
||||
return nil_return(await self._send('shutdown'))
|
||||
|
||||
async def reboot(self):
|
||||
return nil_return(await self._send('reboot'))
|
177
computercraft/subapis/peripheral.py
Normal file
177
computercraft/subapis/peripheral.py
Normal file
|
@ -0,0 +1,177 @@
|
|||
from typing import Optional, List, Tuple, Any
|
||||
from .base import (
|
||||
BaseSubAPI, bool_return, opt_str_return, list_return,
|
||||
nil_return, opt_int_return, int_return, str_return, bool_success,
|
||||
)
|
||||
from .mixins import TermMixin
|
||||
|
||||
|
||||
class CCPeripheral(BaseSubAPI):
|
||||
def __init__(self, cc, call_fn):
|
||||
super().__init__(cc)
|
||||
self._send = call_fn
|
||||
|
||||
|
||||
class CCDrive(CCPeripheral):
|
||||
async def isDiskPresent(self) -> bool:
|
||||
return bool_return(await self._send('isDiskPresent'))
|
||||
|
||||
async def getDiskLabel(self) -> Optional[str]:
|
||||
return opt_str_return(await self._send('getDiskLabel'))
|
||||
|
||||
async def setDiskLabel(self, label: str):
|
||||
return nil_return(await self._send('setDiskLabel', label))
|
||||
|
||||
async def hasData(self) -> bool:
|
||||
return bool_return(await self._send('hasData'))
|
||||
|
||||
async def getMountPath(self) -> Optional[str]:
|
||||
return opt_str_return(await self._send('getMountPath'))
|
||||
|
||||
async def hasAudio(self) -> bool:
|
||||
return bool_return(await self._send('hasAudio'))
|
||||
|
||||
async def getAudioTitle(self) -> Optional[str]:
|
||||
return opt_str_return(await self._send('getAudioTitle'))
|
||||
|
||||
async def playAudio(self):
|
||||
return nil_return(await self._send('playAudio'))
|
||||
|
||||
async def stopAudio(self):
|
||||
return nil_return(await self._send('stopAudio'))
|
||||
|
||||
async def ejectDisk(self):
|
||||
return nil_return(await self._send('ejectDisk'))
|
||||
|
||||
async def getDiskID(self) -> Optional[int]:
|
||||
return opt_int_return(await self._send('getDiskID'))
|
||||
|
||||
|
||||
class CCMonitor(CCPeripheral, TermMixin):
|
||||
async def setTextScale(self, scale: int):
|
||||
return nil_return(await self._send('setTextScale', scale))
|
||||
|
||||
|
||||
class CCComputer(CCPeripheral):
|
||||
async def turnOn(self):
|
||||
return nil_return(await self._send('turnOn'))
|
||||
|
||||
async def shutdown(self):
|
||||
return nil_return(await self._send('shutdown'))
|
||||
|
||||
async def reboot(self):
|
||||
return nil_return(await self._send('reboot'))
|
||||
|
||||
async def getID(self) -> int:
|
||||
return int_return(await self._send('getID'))
|
||||
|
||||
async def isOn(self) -> bool:
|
||||
return bool_return(await self._send('isOn'))
|
||||
|
||||
|
||||
class CCModem(CCPeripheral):
|
||||
async def isOpen(self, channel: int) -> bool:
|
||||
return bool_return(await self._send('isOpen', channel))
|
||||
|
||||
async def open(self, channel: int):
|
||||
return nil_return(await self._send('open', channel))
|
||||
|
||||
async def close(self, channel: int):
|
||||
return nil_return(await self._send('close', channel))
|
||||
|
||||
async def closeAll(self):
|
||||
return nil_return(await self._send('closeAll'))
|
||||
|
||||
async def transmit(self, channel: int, replyChannel: int, message: Any):
|
||||
return nil_return(await self._send('transmit', channel, replyChannel, message))
|
||||
|
||||
async def isWireless(self) -> bool:
|
||||
return bool_return(await self._send('isWireless'))
|
||||
|
||||
# wired only functions below
|
||||
|
||||
async def getNamesRemote(self) -> List[str]:
|
||||
return list_return(await self._send('getNamesRemote'))
|
||||
|
||||
async def getTypeRemote(self, peripheralName: str) -> str:
|
||||
return str_return(await self._send('getTypeRemote', peripheralName))
|
||||
|
||||
async def isPresentRemote(self, peripheralName: str) -> bool:
|
||||
return bool_return(await self._send('isPresentRemote', peripheralName))
|
||||
|
||||
async def wrapRemote(self, peripheralName: str) -> CCPeripheral:
|
||||
# use instead getMethodsRemote and callRemote
|
||||
async def call_fn(method, *args):
|
||||
return await self._send('callRemote', peripheralName, method, *args)
|
||||
|
||||
return TYPE_MAP[await self.getTypeRemote(peripheralName)](self._cc, call_fn)
|
||||
|
||||
|
||||
class CCPrinter(CCPeripheral):
|
||||
async def newPage(self) -> bool:
|
||||
return bool_return(await self._send('newPage'))
|
||||
|
||||
async def endPage(self) -> bool:
|
||||
return bool_return(await self._send('endPage'))
|
||||
|
||||
async def write(self, text: str):
|
||||
return nil_return(await self._send('write', text))
|
||||
|
||||
async def setCursorPos(self, x: int, y: int):
|
||||
return nil_return(await self._send('setCursorPos', x, y))
|
||||
|
||||
async def getCursorPos(self) -> Tuple[int, int]:
|
||||
return tuple(await self._send('getCursorPos'))
|
||||
|
||||
async def getPageSize(self) -> Tuple[int, int]:
|
||||
return tuple(await self._send('getPageSize'))
|
||||
|
||||
async def setPageTitle(self, title: str):
|
||||
return nil_return(await self._send('setPageTitle', title))
|
||||
|
||||
async def getPaperLevel(self) -> int:
|
||||
return int_return(await self._send('getPaperLevel'))
|
||||
|
||||
async def getInkLevel(self) -> int:
|
||||
return int_return(await self._send('getInkLevel'))
|
||||
|
||||
|
||||
class CCCommandBlock(CCPeripheral):
|
||||
async def getCommand(self) -> str:
|
||||
return str_return(await self._send('getCommand'))
|
||||
|
||||
async def setCommand(self, command: str):
|
||||
return nil_return(await self._send('setCommand', command))
|
||||
|
||||
async def runCommand(self):
|
||||
return bool_success(await self._send('runCommand'))
|
||||
|
||||
|
||||
TYPE_MAP = {
|
||||
'drive': CCDrive,
|
||||
'monitor': CCMonitor,
|
||||
'computer': CCComputer,
|
||||
'modem': CCModem,
|
||||
'printer': CCPrinter,
|
||||
'command': CCCommandBlock,
|
||||
}
|
||||
|
||||
|
||||
class PeripheralAPI(BaseSubAPI):
|
||||
_API = 'peripheral'
|
||||
|
||||
async def isPresent(self, side: str) -> bool:
|
||||
return bool_return(await self._send('isPresent', side))
|
||||
|
||||
async def getType(self, side: str) -> Optional[str]:
|
||||
return opt_str_return(await self._send('getType', side))
|
||||
|
||||
async def getNames(self) -> List[str]:
|
||||
return list_return(await self._send('getNames'))
|
||||
|
||||
async def wrap(self, side: str) -> CCPeripheral:
|
||||
# use instead getMethods and call
|
||||
async def call_fn(method, *args):
|
||||
return await self._send('call', side, method, *args)
|
||||
|
||||
return TYPE_MAP[await self.getType(side)](self._cc, call_fn)
|
33
computercraft/subapis/rednet.py
Normal file
33
computercraft/subapis/rednet.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
from typing import Any, List
|
||||
from .base import BaseSubAPI, LuaNum, list_return, nil_return, int_return, bool_return
|
||||
|
||||
|
||||
class RednetAPI(BaseSubAPI):
|
||||
_API = 'rednet'
|
||||
|
||||
async def open(self, side: str):
|
||||
return nil_return(await self._send('open', side))
|
||||
|
||||
async def close(self, side: str):
|
||||
return nil_return(await self._send('close', side))
|
||||
|
||||
async def send(self, receiverID: int, message: Any, protocol: str=None):
|
||||
return nil_return(await self._send('send', receiverID, message, protocol))
|
||||
|
||||
async def broadcast(self, message: Any, protocol: str=None):
|
||||
return nil_return(await self._send('broadcast', message, protocol))
|
||||
|
||||
async def receive(self, protocolFilter: str=None, timeout: LuaNum=None) -> int:
|
||||
return int_return(await self._send('receive', protocolFilter, timeout))
|
||||
|
||||
async def isOpen(self, side: str) -> bool:
|
||||
return bool_return(await self._send('isOpen', side))
|
||||
|
||||
async def host(self, protocol: str, hostname: str):
|
||||
return nil_return(await self._send('host', protocol, hostname))
|
||||
|
||||
async def unhost(self, protocol: str, hostname: str):
|
||||
return nil_return(await self._send('unhost', protocol, hostname))
|
||||
|
||||
async def lookup(self, protocol: str, hostname: str) -> List[int]:
|
||||
return list_return(await self._send('lookup', protocol, hostname))
|
39
computercraft/subapis/redstone.py
Normal file
39
computercraft/subapis/redstone.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
from typing import List
|
||||
from .base import BaseSubAPI, list_return, bool_return, nil_return, int_return
|
||||
|
||||
|
||||
class RedstoneAPI(BaseSubAPI):
|
||||
_API = 'redstone'
|
||||
|
||||
async def getSides(self) -> List[str]:
|
||||
return list_return(await self._send('getSides'))
|
||||
|
||||
async def getInput(self, side: str) -> bool:
|
||||
return bool_return(await self._send('getInput', side))
|
||||
|
||||
async def setOutput(self, side: str, value: bool):
|
||||
return nil_return(await self._send('setOutput', side, value))
|
||||
|
||||
async def getOutput(self, side: str) -> bool:
|
||||
return bool_return(await self._send('getOutput', side))
|
||||
|
||||
async def getAnalogInput(self, side: str) -> int:
|
||||
return int_return(await self._send('getAnalogInput', side))
|
||||
|
||||
async def setAnalogOutput(self, side: str, strength: int):
|
||||
return nil_return(await self._send('setAnalogOutput', side, strength))
|
||||
|
||||
async def getAnalogOutput(self, side: str) -> int:
|
||||
return int_return(await self._send('getAnalogOutput', side))
|
||||
|
||||
async def getBundledInput(self, side: str) -> int:
|
||||
return int_return(await self._send('getBundledInput', side))
|
||||
|
||||
async def setBundledOutput(self, side: str, colors: int):
|
||||
return nil_return(await self._send('setBundledOutput', side, colors))
|
||||
|
||||
async def getBundledOutput(self, side: str) -> int:
|
||||
return int_return(await self._send('getBundledOutput', side))
|
||||
|
||||
async def testBundledInput(self, side: str, color: int) -> bool:
|
||||
return bool_return(await self._send('testBundledInput', side, color))
|
21
computercraft/subapis/root.py
Normal file
21
computercraft/subapis/root.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
from subapis.base import nil_return, bool_return, lua_args, str_return
|
||||
|
||||
|
||||
class RootAPIMixin:
|
||||
async def print(self, *args):
|
||||
return nil_return(await self._send_cmd('print({})'.format(lua_args(*args))))
|
||||
|
||||
async def read_line(self) -> str:
|
||||
return str_return(await self._send_cmd('return io.read()'))
|
||||
|
||||
async def has_commands_api(self) -> bool:
|
||||
return bool_return(await self._send_cmd('return commands ~= nil'))
|
||||
|
||||
async def has_multishell_api(self) -> bool:
|
||||
return bool_return(await self._send_cmd('return multishell ~= nil'))
|
||||
|
||||
async def has_turtle_api(self) -> bool:
|
||||
return bool_return(await self._send_cmd('return turtle ~= nil'))
|
||||
|
||||
async def is_pocket(self) -> bool:
|
||||
return bool_return(await self._send_cmd('return pocket ~= nil'))
|
27
computercraft/subapis/settings.py
Normal file
27
computercraft/subapis/settings.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
from typing import Any, List
|
||||
from .base import BaseSubAPI, list_return, nil_return, single_return, bool_return
|
||||
|
||||
|
||||
class SettingsAPI(BaseSubAPI):
|
||||
_API = 'settings'
|
||||
|
||||
async def set(self, name: str, value):
|
||||
return nil_return(await self._send('set', name, value))
|
||||
|
||||
async def get(self, name: str, value=None) -> Any:
|
||||
return single_return(await self._send('get', name, value))
|
||||
|
||||
async def unset(self, name: str):
|
||||
return nil_return(await self._send('unset', name))
|
||||
|
||||
async def clear(self):
|
||||
return nil_return(await self._send('clear'))
|
||||
|
||||
async def getNames(self) -> List[str]:
|
||||
return list_return(await self._send('getNames'))
|
||||
|
||||
async def load(self, path: str) -> bool:
|
||||
return bool_return(await self._send('load', path))
|
||||
|
||||
async def save(self, path: str) -> bool:
|
||||
return bool_return(await self._send('save', path))
|
61
computercraft/subapis/shell.py
Normal file
61
computercraft/subapis/shell.py
Normal file
|
@ -0,0 +1,61 @@
|
|||
from typing import List, Dict
|
||||
from .base import BaseSubAPI, nil_return, str_return, bool_return, int_return, dict_return, list_return
|
||||
|
||||
|
||||
class ShellAPI(BaseSubAPI):
|
||||
_API = 'shell'
|
||||
|
||||
async def exit(self):
|
||||
return nil_return(await self._send('exit'))
|
||||
|
||||
async def dir(self) -> str:
|
||||
return str_return(await self._send('dir'))
|
||||
|
||||
async def setDir(self, path: str):
|
||||
return nil_return(await self._send('setDir', path))
|
||||
|
||||
async def path(self) -> str:
|
||||
return str_return(await self._send('path'))
|
||||
|
||||
async def setPath(self, path: str):
|
||||
return nil_return(await self._send('setPath', path))
|
||||
|
||||
async def resolve(self, localPath: str) -> str:
|
||||
return str_return(await self._send('resolve', localPath))
|
||||
|
||||
async def resolveProgram(self, name: str) -> str:
|
||||
return str_return(await self._send('resolveProgram', name))
|
||||
|
||||
async def aliases(self) -> Dict[str, str]:
|
||||
return dict_return(await self._send('aliases'))
|
||||
|
||||
async def setAlias(self, alias: str, program: str):
|
||||
return nil_return(await self._send('setAlias', alias, program))
|
||||
|
||||
async def clearAlias(self, alias: str):
|
||||
return nil_return(await self._send('clearAlias', alias))
|
||||
|
||||
async def programs(self, showHidden: bool=None) -> List[str]:
|
||||
return list_return(await self._send('programs', showHidden))
|
||||
|
||||
async def getRunningProgram(self) -> str:
|
||||
return str_return(await self._send('getRunningProgram'))
|
||||
|
||||
async def run(self, command: str, *args: List[str]):
|
||||
return bool_return(await self._send('run', command, *args))
|
||||
|
||||
async def openTab(self, command: str, *args: List[str]) -> int:
|
||||
return int_return(await self._send('openTab', command, *args))
|
||||
|
||||
async def switchTab(self, tabID: int):
|
||||
return nil_return(await self._send('switchTab', tabID))
|
||||
|
||||
async def complete(self, prefix: str) -> List[str]:
|
||||
return list_return(await self._send('complete', prefix))
|
||||
|
||||
async def completeProgram(self, prefix: str) -> List[str]:
|
||||
return list_return(await self._send('completeProgram', prefix))
|
||||
|
||||
# TODO: autocomplete functions
|
||||
# async def setCompletionFunction(self, path: str)
|
||||
# async def getCompletionInfo(self) -> LuaTable
|
35
computercraft/subapis/term.py
Normal file
35
computercraft/subapis/term.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
from typing import Tuple
|
||||
from .base import BaseSubAPI, list_return, nil_return
|
||||
from .mixins import TermMixin
|
||||
|
||||
|
||||
class CCWindow(BaseSubAPI, TermMixin):
|
||||
# TODO
|
||||
def __init__(self, cc):
|
||||
super().__init__(cc)
|
||||
|
||||
async def setVisible(self, visibility: bool):
|
||||
return nil_return(await self._send('setVisible', visibility))
|
||||
|
||||
async def redraw(self):
|
||||
return nil_return(await self._send('redraw'))
|
||||
|
||||
async def restoreCursor(self):
|
||||
return nil_return(await self._send('restoreCursor'))
|
||||
|
||||
async def getPosition(self) -> Tuple[int, int]:
|
||||
return tuple(await self._send('getPosition'))
|
||||
|
||||
async def reposition(self, x: int, y: int, width: int=None, height: int=None):
|
||||
return nil_return(await self._send('reposition', x, y, width, height))
|
||||
|
||||
|
||||
class TermAPI(BaseSubAPI, TermMixin):
|
||||
_API = 'term'
|
||||
|
||||
# TODO
|
||||
# async def redirect
|
||||
|
||||
# term.redirect(target) table previous terminal object
|
||||
# term.current() table terminal object
|
||||
# term.native() table terminal object
|
35
computercraft/subapis/textutils.py
Normal file
35
computercraft/subapis/textutils.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
from typing import List, Union
|
||||
from .base import BaseSubAPI, LuaNum, str_return, nil_return, int_return, list_return
|
||||
|
||||
|
||||
class TextutilsAPI(BaseSubAPI):
|
||||
_API = 'textutils'
|
||||
|
||||
async def slowWrite(self, text: str, rate: LuaNum):
|
||||
return nil_return(await self._send('slowWrite', text, rate))
|
||||
|
||||
async def slowPrint(self, text: str, rate: LuaNum):
|
||||
return nil_return(await self._send('slowPrint', text, rate))
|
||||
|
||||
async def formatTime(self, time: LuaNum, twentyFourHour: bool) -> str:
|
||||
return str_return(await self._send('formatTime', time, twentyFourHour))
|
||||
|
||||
async def tabulate(self, *rows_and_colors: Union[list, int]):
|
||||
return nil_return(await self._send('tabulate', *rows_and_colors))
|
||||
|
||||
async def pagedTabulate(self, *rows_and_colors: Union[list, int]):
|
||||
return nil_return(await self._send('pagedTabulate', *rows_and_colors))
|
||||
|
||||
async def pagedPrint(self, text: str, freeLines: int=None) -> int:
|
||||
return int_return(await self._send('pagedPrint', text, freeLines))
|
||||
|
||||
async def complete(self, partialName: str, environment: dict=None) -> List[str]:
|
||||
return list_return(await self._send('complete', partialName, environment))
|
||||
|
||||
# Questionable to implement
|
||||
# serialize
|
||||
# unserialize
|
||||
|
||||
# Will not implement, use pythonic equivalents
|
||||
# serializeJSON
|
||||
# urlEncode
|
144
computercraft/subapis/turtle.py
Normal file
144
computercraft/subapis/turtle.py
Normal file
|
@ -0,0 +1,144 @@
|
|||
from typing import Optional
|
||||
from .base import BaseSubAPI, bool_success, number_return, LuaNum, int_return, bool_return, opt_dict_return
|
||||
from common import TurtleException
|
||||
|
||||
|
||||
class TurtleAPI(BaseSubAPI):
|
||||
_API = 'turtle'
|
||||
|
||||
async def craft(self, quantity: int):
|
||||
return bool_success(await self._send('craft', quantity))
|
||||
|
||||
async def forward(self):
|
||||
return bool_success(await self._send('forward'))
|
||||
|
||||
async def back(self):
|
||||
return bool_success(await self._send('back'))
|
||||
|
||||
async def up(self):
|
||||
return bool_success(await self._send('up'))
|
||||
|
||||
async def down(self):
|
||||
return bool_success(await self._send('down'))
|
||||
|
||||
async def turnLeft(self):
|
||||
return bool_success(await self._send('turnLeft'))
|
||||
|
||||
async def turnRight(self):
|
||||
return bool_success(await self._send('turnRight'))
|
||||
|
||||
async def select(self, slotNum: int):
|
||||
return bool_success(await self._send('select', slotNum))
|
||||
|
||||
async def getSelectedSlot(self) -> int:
|
||||
return int_return(await self._send('getSelectedSlot'))
|
||||
|
||||
async def getItemCount(self, slotNum: int=None) -> int:
|
||||
return int_return(await self._send('getItemCount', slotNum))
|
||||
|
||||
async def getItemSpace(self, slotNum: int=None) -> int:
|
||||
return int_return(await self._send('getItemSpace', slotNum))
|
||||
|
||||
async def getItemDetail(self, slotNum: int=None) -> dict:
|
||||
return opt_dict_return(await self._send('getItemDetail', slotNum))
|
||||
|
||||
async def equipLeft(self):
|
||||
return bool_success(await self._send('equipLeft'))
|
||||
|
||||
async def equipRight(self):
|
||||
return bool_success(await self._send('equipRight'))
|
||||
|
||||
async def attack(self):
|
||||
return bool_success(await self._send('attack'))
|
||||
|
||||
async def attackUp(self):
|
||||
return bool_success(await self._send('attackUp'))
|
||||
|
||||
async def attackDown(self):
|
||||
return bool_success(await self._send('attackDown'))
|
||||
|
||||
async def dig(self):
|
||||
return bool_success(await self._send('dig'))
|
||||
|
||||
async def digUp(self):
|
||||
return bool_success(await self._send('digUp'))
|
||||
|
||||
async def digDown(self):
|
||||
return bool_success(await self._send('digDown'))
|
||||
|
||||
async def place(self, signText: str=None):
|
||||
return bool_success(await self._send('place', signText))
|
||||
|
||||
async def placeUp(self):
|
||||
return bool_success(await self._send('placeUp'))
|
||||
|
||||
async def placeDown(self):
|
||||
return bool_success(await self._send('placeDown'))
|
||||
|
||||
async def detect(self) -> bool:
|
||||
return bool_return(await self._send('detect'))
|
||||
|
||||
async def detectUp(self) -> bool:
|
||||
return bool_return(await self._send('detectUp'))
|
||||
|
||||
async def detectDown(self) -> bool:
|
||||
return bool_return(await self._send('detectDown'))
|
||||
|
||||
async def _inspect_result(self, v):
|
||||
r, data = v
|
||||
if not r and data == 'No block to inspect':
|
||||
return None
|
||||
if not r:
|
||||
raise TurtleException(data)
|
||||
return data
|
||||
|
||||
async def inspect(self) -> Optional[dict]:
|
||||
return self._inspect_result(await self._send('inspect'))
|
||||
|
||||
async def inspectUp(self) -> Optional[dict]:
|
||||
return self._inspect_result(await self._send('inspectUp'))
|
||||
|
||||
async def inspectDown(self) -> Optional[dict]:
|
||||
return self._inspect_result(await self._send('inspectDown'))
|
||||
|
||||
async def compare(self) -> bool:
|
||||
return bool_return(await self._send('compare'))
|
||||
|
||||
async def compareUp(self) -> bool:
|
||||
return bool_return(await self._send('compareUp'))
|
||||
|
||||
async def compareDown(self) -> bool:
|
||||
return bool_return(await self._send('compareDown'))
|
||||
|
||||
async def compareTo(self, slot: int) -> bool:
|
||||
return bool_return(await self._send('compareTo', slot))
|
||||
|
||||
async def drop(self, count: LuaNum=None):
|
||||
return bool_success(await self._send('drop', count))
|
||||
|
||||
async def dropUp(self, count: LuaNum=None):
|
||||
return bool_success(await self._send('dropUp', count))
|
||||
|
||||
async def dropDown(self, count: LuaNum=None):
|
||||
return bool_success(await self._send('dropDown', count))
|
||||
|
||||
async def suck(self, amount: LuaNum=None):
|
||||
return bool_success(await self._send('suck', amount))
|
||||
|
||||
async def suckUp(self, amount: LuaNum=None):
|
||||
return bool_success(await self._send('suckUp', amount))
|
||||
|
||||
async def suckDown(self, amount: LuaNum=None):
|
||||
return bool_success(await self._send('suckDown', amount))
|
||||
|
||||
async def refuel(self, quantity: LuaNum=None):
|
||||
return bool_success(await self._send('refuel', quantity))
|
||||
|
||||
async def getFuelLevel(self) -> LuaNum:
|
||||
return number_return(await self._send('getFuelLevel'))
|
||||
|
||||
async def getFuelLimit(self) -> LuaNum:
|
||||
return number_return(await self._send('getFuelLimit'))
|
||||
|
||||
async def transferTo(self, slot: int, quantity: int=None):
|
||||
return bool_success(await self._send('transferTo', slot, quantity))
|
11
computercraft/subapis/window.py
Normal file
11
computercraft/subapis/window.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
from .base import BaseSubAPI, LuaTable
|
||||
|
||||
|
||||
class WindowAPI(BaseSubAPI):
|
||||
_API = 'window'
|
||||
|
||||
async def create(
|
||||
self, parentTerm: LuaTable, x: int, y: int, width: int, height: int, visible: bool=None
|
||||
) -> LuaTable:
|
||||
# TODO
|
||||
return await self._send('create', parentTerm, x, y, width, height, visible)
|
Loading…
Reference in a new issue