WIP: mypy

This commit is contained in:
Jan Pochyla 2017-06-19 10:50:36 +02:00
parent fe672fbd09
commit ea39efb10e
35 changed files with 364 additions and 359 deletions

View File

@ -47,6 +47,19 @@ test: ## run unit tests
testpy: ## run selected unit tests from python-trezor
cd tests ; ./run_tests_python_trezor.sh
mypy: ## run mypy on appplication sources
mypy --strict \
src/trezor/config.py \
src/trezor/io.py \
src/trezor/log.py \
src/trezor/loop.py \
src/trezor/main.py \
src/trezor/msg.py \
src/trezor/utils.py \
src/trezor/workflow.py \
src/trezor/crypto/*.py \
src/trezor/ui/*.py
pylint: ## run pylint on application sources
pylint -E $(shell find src -name *.py)

View File

@ -23,7 +23,7 @@ typedef struct _mp_obj_Config_t {
mp_obj_base_t base;
} mp_obj_Config_t;
/// def __init__(self):
/// def __init__(self) -> None:
/// '''
/// Initializes the storage.
/// '''

View File

@ -150,6 +150,14 @@ STATIC mp_obj_t mod_trezorcrypto_AES___del__(mp_obj_t self) {
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_AES___del___obj, mod_trezorcrypto_AES___del__);
/// ECB: int
/// CBC: int
/// CFB: int
/// OFB: int
/// CTR: int
/// Encrypt: int
/// Decrypt: int
STATIC const mp_rom_map_elem_t mod_trezorcrypto_AES_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_update), MP_ROM_PTR(&mod_trezorcrypto_AES_update_obj) },
{ MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mod_trezorcrypto_AES___del___obj) },

View File

@ -242,7 +242,7 @@ typedef struct _mp_obj_Bip32_t {
mp_obj_base_t base;
} mp_obj_Bip32_t;
/// def __init__(self):
/// def __init__(self) -> None:
/// '''
/// '''
STATIC mp_obj_t mod_trezorcrypto_Bip32_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {

View File

@ -16,7 +16,7 @@ typedef struct _mp_obj_Bip39_t {
mp_obj_base_t base;
} mp_obj_Bip39_t;
/// def __init__(self):
/// def __init__(self) -> None:
/// '''
/// '''
STATIC mp_obj_t mod_trezorcrypto_Bip39_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {

View File

@ -374,6 +374,10 @@ STATIC mp_obj_t mod_trezorui_Display_save(mp_obj_t self, mp_obj_t filename) {
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorui_Display_save_obj, mod_trezorui_Display_save);
/// FONT_MONO: int # Normal-weight monotype font.
/// FONT_NORMAL: int # Normal-weight proportional font.
/// FONT_BOLD: int # Bold proportional font.
STATIC const mp_rom_map_elem_t mod_trezorui_Display_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&mod_trezorui_Display_clear_obj) },
{ MP_ROM_QSTR(MP_QSTR_refresh), MP_ROM_PTR(&mod_trezorui_Display_refresh_obj) },

View File

@ -6,7 +6,7 @@ class Config:
Persistent key-value storage, with 16-bit keys and bytes values.
'''
def __init__(self):
def __init__(self) -> None:
'''
Initializes the storage.
'''

View File

@ -15,6 +15,13 @@ class AES:
'''
Update AES context with data.
'''
ECB: int
CBC: int
CFB: int
OFB: int
CTR: int
Encrypt: int
Decrypt: int
# extmod/modtrezorcrypto/modtrezorcrypto-bip32.h
class HDNode:
@ -87,7 +94,7 @@ class Bip32:
'''
'''
def __init__(self):
def __init__(self) -> None:
'''
'''
@ -106,7 +113,7 @@ class Bip39:
'''
'''
def __init__(self):
def __init__(self) -> None:
'''
'''

View File

@ -109,3 +109,6 @@ class Display:
'''
Saves current display contents to file filename.
'''
FONT_MONO: int # Normal-weight monotype font.
FONT_NORMAL: int # Normal-weight proportional font.
FONT_BOLD: int # Bold proportional font.

View File

@ -1,5 +1,4 @@
def const(c):
return c
def const(c: int) -> int: ...
def mem_current() -> int: ...
def mem_total() -> int: ...
def mem_peak() -> int: ...

View File

@ -1,7 +1,8 @@
from typing import *
class utimeq:
class utimeq(Sized):
def __init__(self, max_queue_size: int) -> None: ...
def push(self, time: int, callback: Any, value: Any) -> None: ...
def pop(self, entry: List[Any]) -> None: ...
def peektime(self) -> int: ...
def __len__(self) -> int: ...

View File

@ -1,3 +1,7 @@
[mypy]
mypy_path = mocks:mocks/generated:src/lib
warn_no_return = False
[flake8]
ignore =
# E221: multiple spaces before operator
@ -9,4 +13,6 @@ ignore =
# E402: module level import not at top of file
E402,
# E501: line too long
E501
E501,
# ...
F405

View File

@ -1,69 +0,0 @@
__names_get = [
'AbstractSet',
'AsyncIterable',
'AsyncIterator',
'Awaitable',
'ByteString',
'Callable',
'Container',
'DefaultDict',
'Dict',
'Generator',
'Generic',
'ItemsView',
'Iterable',
'Iterator',
'KeysView',
'List',
'Mapping',
'MappingView',
'MutableMapping',
'MutableSequence',
'MutableSet',
'Optional',
'Reversible',
'Sequence',
'Set',
'Tuple',
'Type',
'Union',
'ValuesView',
]
__names_obj = [
'Any',
'AnyStr',
'Hashable',
'Sized',
'SupportsAbs',
'SupportsFloat',
'SupportsInt',
'SupportsRound',
'Text',
]
class __dummy:
def __getitem__(self, *args):
return object
__t = __dummy()
for __n in __names_get:
globals()[__n] = __t
for __n in __names_obj:
globals()[__n] = object
def TypeVar(*args):
return object
def NewType(*args):
return lambda x: x
TYPE_CHECKING = False

View File

@ -7,9 +7,9 @@ def get(app: int, key: int) -> bytes:
return _config.get(app, key)
def set(app: int, key: int, value: bytes):
def set(app: int, key: int, value: bytes) -> None:
return _config.set(app, key, value)
def wipe():
def wipe() -> None:
return _config.wipe()

View File

@ -13,6 +13,9 @@
# This module adds shiny packaging and support for python3.
#
from typing import Callable
# 58 character alphabet used
_alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
@ -64,14 +67,14 @@ def _dsha256_32(data: bytes) -> bytes:
return sha256(sha256(data).digest()).digest()[:4]
def encode_check(data: bytes, digestfunc=_dsha256_32) -> str:
def encode_check(data: bytes, digestfunc: Callable[[bytes], bytes] = _dsha256_32) -> str:
'''
Convert bytes to base58 encoded string, append checksum.
'''
return encode(data + digestfunc(data))
def decode_check(string: str, digestfunc=_dsha256_32) -> bytes:
def decode_check(string: str, digestfunc: Callable[[bytes], bytes] = _dsha256_32) -> bytes:
'''
Convert base58 encoded string to bytes and verify checksum.
'''

View File

@ -1,15 +1,18 @@
from typing import Any, Optional
class Hmac:
def __init__(self, key, msg, digestmod):
self.__digestmod = digestmod
self.__inner = digestmod()
self.digest_size = self.__inner.digest_size
self.block_size = self.__inner.block_size
def __init__(self, key: bytes, msg: Optional[bytes], digestmod: Any) -> None:
self._digestmod = digestmod
self._inner = digestmod()
self.digest_size = self._inner.digest_size
self.block_size = self._inner.block_size
if len(key) > self.block_size:
key = digestmod(key).digest()
self.__key = key + bytes(self.block_size - len(key))
self.__inner.update(bytes((x ^ 0x36) for x in self.__key))
self._key = digestmod(key).digest()
else:
self._key = key + bytes(self.block_size - len(key))
self._inner.update(bytes((x ^ 0x36) for x in self._key))
if msg is not None:
self.update(msg)
@ -17,20 +20,13 @@ class Hmac:
'''
Update the context with data.
'''
self.__inner.update(msg)
self._inner.update(msg)
def digest(self) -> bytes:
'''
Returns the digest of processed data.
'''
outer = self.__digestmod()
outer.update(bytes((x ^ 0x5C) for x in self.__key))
outer.update(self.__inner.digest())
return outer.digest()
def new(key, msg, digestmod) -> Hmac:
'''
Creates a HMAC context object.
'''
return Hmac(key, msg, digestmod)
outer = self._digestmod()
outer.update(bytes((x ^ 0x5C) for x in self._key))
outer.update(self._inner.digest())
return outer.digest() # type: ignore # FIXME: digestmod type

View File

@ -1,3 +1,5 @@
from typing import Any
def int_to_bytes(x: int) -> bytes:
if x == 0:
@ -20,7 +22,7 @@ def encode_length(l: int, is_list: bool) -> bytes:
raise ValueError('Input too long')
def encode(data) -> bytes:
def encode(data: Any) -> bytes:
if isinstance(data, int):
return encode(int_to_bytes(data))
if isinstance(data, bytes):

View File

@ -1,13 +1,15 @@
from micropython import const
from typing import Any
import sys
import utime
NOTSET = const(0)
DEBUG = const(10)
INFO = const(20)
WARNING = const(30)
ERROR = const(40)
CRITICAL = const(50)
NOTSET = const(0) # type: int
DEBUG = const(10) # type: int
INFO = const(20) # type: int
WARNING = const(30) # type: int
ERROR = const(40) # type: int
CRITICAL = const(50) # type: int
_leveldict = {
DEBUG: ('DEBUG', '32'),
@ -17,11 +19,11 @@ _leveldict = {
CRITICAL: ('CRITICAL', '1;31'),
}
level = NOTSET
color = True
level = NOTSET # type: int
color = True # type: bool
def _log(name, mlevel, msg, *args):
def _log(name: str, mlevel: int, msg: str, *args: Any) -> None:
if __debug__ and mlevel >= level:
if color:
fmt = '%d \x1b[35m%s\x1b[0m %s \x1b[' + \
@ -31,26 +33,26 @@ def _log(name, mlevel, msg, *args):
print(fmt % ((utime.ticks_us(), name, _leveldict[mlevel][0]) + args))
def debug(name, msg, *args):
def debug(name: str, msg: str, *args: Any) -> None:
_log(name, DEBUG, msg, *args)
def info(name, msg, *args):
def info(name: str, msg: str, *args: Any) -> None:
_log(name, INFO, msg, *args)
def warning(name, msg, *args):
def warning(name: str, msg: str, *args: Any) -> None:
_log(name, WARNING, msg, *args)
def error(name, msg, *args):
def error(name: str, msg: str, *args: Any) -> None:
_log(name, ERROR, msg, *args)
def exception(name, exc):
def exception(name: str, exc: BaseException) -> None:
_log(name, ERROR, 'exception:')
sys.print_exception(exc)
def critical(name, msg, *args):
def critical(name: str, msg: str, *args: Any) -> None:
_log(name, CRITICAL, msg, *args)

View File

@ -8,6 +8,8 @@ See `schedule_task`, `run_forever`, and syscalls `Sleep`, `Select`, `Signal`
and `Wait`.
'''
from typing import *
import utime
import utimeq
from micropython import const
@ -19,12 +21,13 @@ TOUCH_START = const(1) # event
TOUCH_MOVE = const(2) # event
TOUCH_END = const(4) # event
after_step_hook = None # function, called after each task step
# function, called after each task step
after_step_hook = None # type: Optional[Callable[[], Any]]
_MAX_SELECT_DELAY = const(1000000) # usec delay if queue is empty
_MAX_QUEUE_SIZE = const(64) # maximum number of scheduled tasks
_paused_tasks = {} # {message interface: [task]}
_paused_tasks = {} # type: Dict[int, List[Coroutine]]
_scheduled_tasks = utimeq.utimeq(_MAX_QUEUE_SIZE)
if __debug__:
@ -35,7 +38,7 @@ if __debug__:
log_delay_rb = array.array('i', [0] * log_delay_rb_len)
def schedule_task(task, value=None, deadline=None):
def schedule_task(task: Coroutine, value: Any = None, deadline: int = None) -> None:
'''
Schedule task to be executed with `value` on given `deadline` (in
microseconds). Does not start the event loop itself, see `run_forever`.
@ -45,7 +48,7 @@ def schedule_task(task, value=None, deadline=None):
_scheduled_tasks.push(deadline, task, value)
def unschedule_task(task):
def unschedule_task(task: Coroutine) -> None:
'''
Remove task from the time queue. Cancels previous `schedule_task`.
'''
@ -59,20 +62,20 @@ def unschedule_task(task):
_scheduled_tasks = queue_copy
def _pause_task(task, iface):
def _pause_task(task: Coroutine, iface: int) -> None:
tasks = _paused_tasks.get(iface, None)
if tasks is None:
tasks = _paused_tasks[iface] = []
tasks.append(task)
def _unpause_task(task):
def _unpause_task(task: Coroutine) -> None:
for iface in _paused_tasks:
if task in _paused_tasks[iface]:
_paused_tasks[iface].remove(task)
def run_forever():
def run_forever() -> None:
'''
Loop forever, stepping through scheduled tasks and awaiting I/O events
inbetween. Use `schedule_task` first to add a coroutine to the task queue.
@ -111,10 +114,10 @@ def run_forever():
_step_task(task_entry[1], task_entry[2])
def _step_task(task, value):
def _step_task(task: Coroutine, value: Any) -> None:
try:
if isinstance(value, Exception):
result = task.throw(value)
result = task.throw(value) # type: ignore
else:
result = task.send(value)
except StopIteration as e:
@ -128,20 +131,25 @@ def _step_task(task, value):
schedule_task(task)
else:
log.error(__name__, '%s is unknown syscall', result)
if after_step_hook:
if after_step_hook is not None:
after_step_hook()
class Syscall:
class Syscall(Awaitable):
'''
When tasks want to perform any I/O, or do any sort of communication with the
scheduler, they do so through instances of a class derived from `Syscall`.
'''
def __iter__(self):
# support `yield from` or `await` on syscalls
def handle(self, task: Coroutine) -> None:
pass
def __iter__(self) -> Generator:
'''Support `yield from` or `await` on syscalls.'''
return (yield self)
__await__ = __iter__
class Sleep(Syscall):
'''
@ -154,10 +162,10 @@ class Sleep(Syscall):
print('missed by %d us', utime.ticks_diff(utime.ticks_us(), planned))
'''
def __init__(self, delay_us):
def __init__(self, delay_us: int) -> None:
self.delay_us = delay_us
def handle(self, task):
def handle(self, task: Coroutine) -> None:
deadline = utime.ticks_add(utime.ticks_us(), self.delay_us)
schedule_task(task, deadline, deadline)
@ -173,10 +181,10 @@ class Select(Syscall):
event, x, y = await loop.Select(loop.TOUCH) # await touch event
'''
def __init__(self, msg_iface):
def __init__(self, msg_iface: int) -> None:
self.msg_iface = msg_iface
def handle(self, task):
def handle(self, task: Coroutine) -> None:
_pause_task(task, self.msg_iface)
@ -198,19 +206,19 @@ class Signal(Syscall):
# prints in the next iteration of the event loop
'''
def __init__(self):
def __init__(self) -> None:
self.value = _NO_VALUE
self.task = None
self.task = None # type: Optional[Coroutine]
def handle(self, task):
def handle(self, task: Coroutine) -> None:
self.task = task
self._deliver()
def send(self, value):
def send(self, value: Any) -> None:
self.value = value
self._deliver()
def _deliver(self):
def _deliver(self) -> None:
if self.task is not None and self.value is not _NO_VALUE:
schedule_task(self.task, self.value)
self.task = None
@ -241,29 +249,31 @@ class Wait(Syscall):
`Wait.__iter__` for explanation. Always use `await`.
'''
def __init__(self, children, wait_for=1, exit_others=True):
def __init__(self, children: Iterable[Coroutine], wait_for: int = 1, exit_others: bool = True) -> None:
self.children = children
self.wait_for = wait_for
self.exit_others = exit_others
self.scheduled = None
self.finished = None
self.callback = None
self.scheduled = [] # type: List[Coroutine]
self.finished = [] # type: List[Coroutine]
self.task = None # type: Optional[Coroutine]
def handle(self, task):
self.callback = task
self.finished = []
self.scheduled = [self._wait(c) for c in self.children]
for ct in self.scheduled:
schedule_task(ct)
def handle(self, task: Coroutine) -> None:
self.task = task
self.finished.clear()
self.scheduled.clear()
for child in self.children:
child_task = self._wait(child)
self.scheduled.append(child_task) # type: ignore # FIXME: https://github.com/python/typing/issues/441
schedule_task(child_task) # type: ignore # FIXME: https://github.com/python/typing/issues/441
def exit(self):
def exit(self) -> None:
for task in self.scheduled:
if task not in self.finished:
_unpause_task(task)
unschedule_task(task)
task.close()
async def _wait(self, child):
async def _wait(self, child: Coroutine) -> None:
try:
result = await child
except Exception as e:
@ -271,14 +281,15 @@ class Wait(Syscall):
else:
self._finish(child, result)
def _finish(self, child, result):
def _finish(self, child: Coroutine, result: Any) -> None:
self.finished.append(child)
if self.wait_for == len(self.finished) or isinstance(result, Exception):
if self.exit_others:
self.exit()
schedule_task(self.callback, result)
if self.task is not None:
schedule_task(self.task, result)
def __iter__(self):
def __iter__(self) -> Generator:
try:
return (yield self)
except:

View File

@ -1,42 +1,15 @@
import sys
sys.path.append('lib')
import gc
from typing import *
from trezor import loop
from trezor import workflow
from trezor import log
log.level = log.DEBUG
# log.level = log.INFO
def perf_info_debug():
while True:
queue_len = len(loop._scheduled_tasks)
delay_avg = sum(loop.log_delay_rb) / loop.log_delay_rb_len
delay_last = loop.log_delay_rb[loop.log_delay_pos]
mem_alloc = gc.mem_alloc()
gc.collect()
log.debug(__name__, "mem_alloc: %s/%s, delay_avg: %d, delay_last: %d, queue_len: %d",
mem_alloc, gc.mem_alloc(), delay_avg, delay_last, queue_len)
yield loop.Sleep(1000000)
def perf_info():
while True:
gc.collect()
log.info(__name__, "mem_alloc: %d", gc.mem_alloc())
yield loop.Sleep(1000000)
def run(default_workflow):
# if __debug__:
# loop.schedule_task(perf_info_debug())
# else:
# loop.schedule_task(perf_info())
def run(default_workflow: Callable[[], Coroutine]) -> None:
workflow.start_default(default_workflow)
loop.run_forever()

View File

@ -1,15 +1,16 @@
from typing import List, Union
from trezormsg import Msg, USB, HID, VCP
_msg = Msg()
def init_usb(usb, ifaces):
def init_usb(usb: USB, ifaces: List[Union[HID, VCP]]) -> None:
return _msg.init_usb(usb, ifaces)
def select(timeout_us):
def select(timeout_us: int) -> tuple:
return _msg.select(timeout_us)
def send(iface, msg):
return _msg.send(iface, msg)
def send(iface: int, message: bytes) -> int:
return _msg.send(iface, message)

View File

@ -1,20 +1,22 @@
from typing import *
try:
from .resources import resdata
except ImportError:
resdata = None
resdata = {}
def load(name):
def load(name: str) -> bytes:
'''
Loads resource of a given name as bytes.
'''
if resdata and name in resdata:
if name in resdata:
return resdata[name]
with open(name, 'rb') as f:
with open(name, 'rb') as f: # type: IO[bytes]
return f.read()
def gettext(message):
def gettext(message: str) -> str:
'''
Returns localized string. This function is aliased to _.
'''

View File

@ -1,3 +1,5 @@
from typing import Any, Callable, Generator, Optional, Tuple
from micropython import const
import sys
@ -17,65 +19,62 @@ def rgbcolor(r: int, g: int, b: int) -> int:
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3)
LIGHT_RED = rgbcolor(0xFF, 0x00, 0x00)
# RED E4572E
RED = rgbcolor(0xE4, 0x57, 0x2E)
# ACTIVE DARK RED A64022
ACTIVE_RED = rgbcolor(0xA6, 0x40, 0x22)
PINK = rgbcolor(0xE9, 0x1E, 0x63)
PURPLE = rgbcolor(0x9C, 0x27, 0xB0)
# colors
RED = rgbcolor(0xE4, 0x57, 0x2E) # RED E4572E
ACTIVE_RED = rgbcolor(0xA6, 0x40, 0x22) # ACTIVE DARK RED A64022
LIGHT_RED = rgbcolor(0xFF, 0x00, 0x00)
PINK = rgbcolor(0xE9, 0x1E, 0x63)
PURPLE = rgbcolor(0x9C, 0x27, 0xB0)
DEEP_PURPLE = rgbcolor(0x67, 0x3A, 0xB7)
INDIGO = rgbcolor(0x3F, 0x51, 0xB5)
BLUE = rgbcolor(0x21, 0x96, 0xF3)
LIGHT_BLUE = rgbcolor(0x03, 0xA9, 0xF4)
CYAN = rgbcolor(0x00, 0xBC, 0xD4)
TEAL = rgbcolor(0x00, 0x96, 0x88)
# GREEN 4CC148
GREEN = rgbcolor(0x4C, 0xC1, 0x48)
# ACTIVE DARK GREEN 1A8C14
ACTIVE_GREEN = rgbcolor(0x1A, 0x8C, 0x14)
INDIGO = rgbcolor(0x3F, 0x51, 0xB5)
BLUE = rgbcolor(0x21, 0x96, 0xF3)
LIGHT_BLUE = rgbcolor(0x03, 0xA9, 0xF4)
CYAN = rgbcolor(0x00, 0xBC, 0xD4)
TEAL = rgbcolor(0x00, 0x96, 0x88)
GREEN = rgbcolor(0x4C, 0xC1, 0x48) # GREEN 4CC148
ACTIVE_GREEN = rgbcolor(0x1A, 0x8C, 0x14) # ACTIVE DARK GREEN 1A8C14
LIGHT_GREEN = rgbcolor(0x87, 0xCE, 0x26)
LIME = rgbcolor(0xCD, 0xDC, 0x39)
YELLOW = rgbcolor(0xFF, 0xEB, 0x3B)
AMBER = rgbcolor(0xFF, 0xC1, 0x07)
ORANGE = rgbcolor(0xFF, 0x98, 0x00)
LIME = rgbcolor(0xCD, 0xDC, 0x39)
YELLOW = rgbcolor(0xFF, 0xEB, 0x3B)
AMBER = rgbcolor(0xFF, 0xC1, 0x07)
ORANGE = rgbcolor(0xFF, 0x98, 0x00)
DEEP_ORANGE = rgbcolor(0xFF, 0x57, 0x22)
BROWN = rgbcolor(0x79, 0x55, 0x48)
LIGHT_GREY = rgbcolor(0xDA, 0xDD, 0xD8)
GREY = rgbcolor(0x9E, 0x9E, 0x9E)
DARK_GREY = rgbcolor(0x3E, 0x3E, 0x3E)
BLUE_GRAY = rgbcolor(0x60, 0x7D, 0x8B)
BLACK = rgbcolor(0x00, 0x00, 0x00)
WHITE = rgbcolor(0xFA, 0xFA, 0xFA)
BROWN = rgbcolor(0x79, 0x55, 0x48)
LIGHT_GREY = rgbcolor(0xDA, 0xDD, 0xD8)
GREY = rgbcolor(0x9E, 0x9E, 0x9E)
DARK_GREY = rgbcolor(0x3E, 0x3E, 0x3E)
BLUE_GRAY = rgbcolor(0x60, 0x7D, 0x8B)
BLACK = rgbcolor(0x00, 0x00, 0x00)
WHITE = rgbcolor(0xFA, 0xFA, 0xFA)
BLACKISH = rgbcolor(0x20, 0x20, 0x20)
BLACKISH = rgbcolor(0x20, 0x20, 0x20)
MONO = Display.FONT_MONO
# fonts
MONO = Display.FONT_MONO
NORMAL = Display.FONT_NORMAL
BOLD = Display.FONT_BOLD
BOLD = Display.FONT_BOLD
# radius for buttons and other elements
BTN_RADIUS = const(2)
# display width and height
SCREEN = const(240)
# backlight vlaues
BACKLIGHT_NORMAL = const(60)
BACKLIGHT_DIM = const(5)
BACKLIGHT_NONE = const(2)
BACKLIGHT_MAX = const(255)
# display width and height
SCREEN = const(240)
# icons
ICON_RESET = 'trezor/res/header_icons/reset.toig'
ICON_WIPE = 'trezor/res/header_icons/wipe.toig'
ICON_RESET = 'trezor/res/header_icons/reset.toig'
ICON_WIPE = 'trezor/res/header_icons/wipe.toig'
ICON_RECOVERY = 'trezor/res/header_icons/recovery.toig'
def in_area(pos: tuple, area: tuple) -> bool:
def in_area(pos: Tuple[int, int], area: Tuple[int, int, int, int]) -> bool:
x, y = pos
ax, ay, aw, ah = area
return ax <= x <= ax + aw and ay <= y <= ay + ah
return (ax <= x <= ax + aw) and (ay <= y <= ay + ah)
def lerpi(a: int, b: int, t: float) -> int:
@ -88,66 +87,75 @@ def blend(ca: int, cb: int, t: float) -> int:
lerpi((ca << 3) & 0xF8, (cb << 3) & 0xF8, t))
async def alert(count=3):
def alert(count: int = 3) -> Generator:
sleep_short = loop.sleep(20000)
sleep_long = loop.sleep(80000)
current = display.backlight()
for i in range(count * 2):
if i % 2 == 0:
display.backlight(BACKLIGHT_MAX)
yield loop.Sleep(20000)
yield sleep_short
else:
display.backlight(BACKLIGHT_NORMAL)
yield loop.Sleep(80000)
yield sleep_long
display.backlight(current)
async def backlight_slide(val, speed=20000):
def backlight_slide(target: int, delay: int = 20000) -> Generator:
sleep = loop.sleep(delay)
current = display.backlight()
for i in range(current, val, -1 if current > val else 1):
for i in range(current, target, -1 if current > target else 1):
display.backlight(i)
await loop.Sleep(speed)
yield sleep
def animate_pulse(func, ca, cb, speed=200000, delay=30000):
def animate_pulse(func: Callable[[int], Any],
color_a: int,
color_b: int,
speed: int = 200000,
delay: int = 30000) -> Generator:
sleep = loop.sleep(delay)
while True:
# normalize sin from interval -1:1 to 0:1
y = 0.5 + 0.5 * math.sin(utime.ticks_us() / speed)
c = blend(ca, cb, y)
c = blend(color_a, color_b, y)
func(c)
yield loop.Sleep(delay)
yield sleep
def header(title, icon=ICON_RESET, fg=BLACK, bg=BLACK):
def header(title: str, icon: str = ICON_RESET, fg: int = BLACK, bg: int = BLACK) -> None:
display.bar(0, 0, 240, 32, bg)
if icon is not None:
display.icon(8, 4, res.load(icon), fg, bg)
display.text(8 + 24 + 2, 24, title, BOLD, fg, bg)
def rotate_coords(pos: tuple) -> tuple:
def rotate_coords(pos: Tuple[int, int]) -> Tuple[int, int]:
r = display.orientation()
if r == 0:
return pos
x, y = pos
if r == 90:
return (y, 240 - x)
if r == 180:
elif r == 180:
return (240 - x, 240 - y)
if r == 270:
else: # r == 270:
return (240 - y, x)
class Widget:
def render(self):
def render(self) -> None:
pass
def touch(self, event, pos):
def touch(self, event: int, pos: Tuple[int, int]) -> Optional[int]:
pass
def __iter__(self):
def __iter__(self) -> Generator:
touch = loop.select(loop.TOUCH)
while True:
self.render()
event, *pos = yield loop.Select(loop.TOUCH)
event, *pos = yield touch
result = self.touch(event, pos)
if result is not None:
return result

View File

@ -1,3 +1,4 @@
from typing import *
from micropython import const
from trezor import ui, loop
from . import display, in_area, rotate_coords, Widget
@ -80,11 +81,13 @@ BTN_DISABLED = const(8)
class Button(Widget):
def __init__(self, area, content,
normal_style=None,
active_style=None,
disabled_style=None,
absolute=False):
def __init__(self,
area: Tuple[int, int, int, int],
content: str,
normal_style: Dict = None,
active_style: Dict = None,
disabled_style: Dict = None,
absolute: bool = False) -> None:
self.area = area
self.content = content
self.normal_style = normal_style or DEFAULT_BUTTON
@ -93,17 +96,17 @@ class Button(Widget):
self.absolute = absolute
self.state = BTN_DIRTY
def enable(self):
def enable(self) -> None:
self.state &= ~BTN_DISABLED
self.state |= BTN_DIRTY
def disable(self):
def disable(self) -> None:
self.state |= BTN_DISABLED | BTN_DIRTY
def taint(self):
def taint(self) -> None:
self.state |= BTN_DIRTY
def render(self):
def render(self) -> None:
if not self.state & BTN_DIRTY:
return
state = self.state & ~BTN_DIRTY
@ -138,7 +141,7 @@ class Button(Widget):
self.state = state
def touch(self, event, pos):
def touch(self, event: int, pos: Tuple[int, int]) -> Optional[int]:
if self.state & BTN_DISABLED:
return
if not self.absolute:

View File

@ -1,3 +1,4 @@
from typing import *
from micropython import const
from trezor import loop
from trezor.ui import Widget
@ -12,7 +13,10 @@ CANCELLED = const(2)
class ConfirmDialog(Widget):
def __init__(self, content=None, confirm='Confirm', cancel='Cancel'):
def __init__(self,
content: Widget = None,
confirm: str = 'Confirm',
cancel: str = 'Cancel') -> None:
self.content = content
if cancel is not None:
self.confirm = Button((121, 240 - 48, 119, 48), confirm,
@ -27,12 +31,12 @@ class ConfirmDialog(Widget):
normal_style=CONFIRM_BUTTON,
active_style=CONFIRM_BUTTON_ACTIVE)
def render(self):
def render(self) -> None:
self.confirm.render()
if self.cancel is not None:
self.cancel.render()
def touch(self, event, pos):
def touch(self, event: int, pos: Tuple[int, int]) -> Optional[int]:
if self.confirm.touch(event, pos) == BTN_CLICKED:
return CONFIRMED
@ -40,27 +44,31 @@ class ConfirmDialog(Widget):
if self.cancel.touch(event, pos) == BTN_CLICKED:
return CANCELLED
async def __iter__(self):
async def __iter__(self): # type: ignore
return await loop.Wait((super().__iter__(), self.content))
class HoldToConfirmDialog(Widget):
def __init__(self, content=None, hold='Hold to confirm', *args, **kwargs):
def __init__(self,
content: Widget = None,
hold: str = 'Hold to confirm',
*args: Any,
**kwargs: Any) -> None:
self.button = Button((0, 240 - 48, 240, 48), hold,
normal_style=CONFIRM_BUTTON,
active_style=CONFIRM_BUTTON_ACTIVE)
self.content = content
self.loader = Loader(*args, **kwargs)
def render(self):
def render(self) -> None:
if self.loader.is_active():
self.loader.render()
elif self.content is not None:
self.content.render()
self.button.render()
def touch(self, event, pos):
def touch(self, event: int, pos: Tuple[int, int]) -> Optional[int]:
button = self.button
was_started = button.state & BTN_STARTED
button.touch(event, pos)
@ -75,18 +83,19 @@ class HoldToConfirmDialog(Widget):
if self.content is not None:
return self.content.touch(event, pos)
async def __iter__(self):
return await loop.Wait((self._render_loop(), self._event_loop()))
async def __iter__(self): # type: ignore
return await loop.wait((self._render_loop(), self._event_loop()))
def _render_loop(self):
RENDER_DELAY = const(1000000 // 60)
def _render_loop(self) -> Generator:
sleep = loop.sleep(1000000 // 60)
while True:
self.render()
yield loop.Sleep(RENDER_DELAY)
yield sleep
def _event_loop(self):
def _event_loop(self) -> Generator:
touch = loop.select(loop.TOUCH)
while True:
event, *pos = yield loop.Select(loop.TOUCH)
event, *pos = yield touch
result = self.touch(event, pos)
if result is not None:
return result

View File

@ -1,3 +1,4 @@
from typing import Generator, Iterable, Optional, Tuple
from trezor import ui, res, loop
from trezor.crypto import bip39
from trezor.ui import display
@ -17,7 +18,14 @@ KEY_BUTTON_ACTIVE = {
}
def cell_area(i, n_x=3, n_y=3, start_x=0, start_y=40, end_x=240, end_y=240 - 48, spacing=0):
def cell_area(i: int,
n_x: int = 3,
n_y: int = 3,
start_x: int = 0,
start_y: int = 40,
end_x: int = ui.SCREEN,
end_y: int = ui.SCREEN - 48,
spacing: int = 0) -> Tuple[int, int, int, int]:
w = (end_x - start_x) // n_x
h = (end_y - start_y) // n_y
x = (i % n_x) * w
@ -25,7 +33,7 @@ def cell_area(i, n_x=3, n_y=3, start_x=0, start_y=40, end_x=240, end_y=240 - 48,
return (x + start_x, y + start_y, w - spacing, h - spacing)
def key_buttons():
def key_buttons() -> Iterable[Button]:
keys = ['abc', 'def', 'ghi', 'jkl', 'mno', 'pqr', 'stu', 'vwx', 'yz']
# keys = [' ', 'abc', 'def', 'ghi', 'jkl', 'mno', 'pqrs', 'tuv', 'wxyz']
return [Button(cell_area(i), k,
@ -33,7 +41,7 @@ def key_buttons():
active_style=KEY_BUTTON_ACTIVE) for i, k in enumerate(keys)]
def compute_mask(text):
def compute_mask(text: str) -> int:
mask = 0
for c in text:
shift = ord(c) - 97 # ord('a') == 97
@ -45,11 +53,11 @@ def compute_mask(text):
class KeyboardMultiTap(ui.Widget):
def __init__(self, content=''):
def __init__(self, content: str = '') -> None:
self.content = content
self.sugg_mask = 0xffffffff
self.sugg_word = None
self.pending_button = None
self.sugg_word = None # type: Optional[str]
self.pending_button = None # type: Optional[Button]
self.pending_index = 0
self.key_buttons = key_buttons()
@ -59,7 +67,7 @@ class KeyboardMultiTap(ui.Widget):
normal_style=CLEAR_BUTTON,
active_style=CLEAR_BUTTON_ACTIVE)
def render(self):
def render(self) -> None:
# clear canvas under input line
display.bar(0, 0, 205, 40, ui.BLACK)
@ -90,7 +98,7 @@ class KeyboardMultiTap(ui.Widget):
for btn in self.key_buttons:
btn.render()
def touch(self, event, pos):
def touch(self, event: int, pos: Tuple[int, int]) -> None:
if self.bs_button.touch(event, pos) == BTN_CLICKED:
self.content = self.content[:-1]
self.pending_button = None
@ -120,7 +128,7 @@ class KeyboardMultiTap(ui.Widget):
self.pending_index = 0
return
def _update_suggestion(self):
def _update_suggestion(self) -> None:
if self.content:
self.sugg_word = bip39.find_word(self.content)
self.sugg_mask = bip39.complete_word(self.content)
@ -128,14 +136,14 @@ class KeyboardMultiTap(ui.Widget):
self.sugg_word = None
self.sugg_mask = 0xffffffff
def _update_buttons(self):
def _update_buttons(self) -> None:
for btn in self.key_buttons:
if compute_mask(btn.content) & self.sugg_mask:
btn.enable()
else:
btn.disable()
def __iter__(self):
def __iter__(self) -> Generator:
timeout = loop.Sleep(1000 * 1000 * 1)
touch = loop.Select(loop.TOUCH)
wait = loop.Wait((touch, timeout))
@ -152,7 +160,7 @@ class KeyboardMultiTap(ui.Widget):
self._update_buttons()
def zoom_buttons(keys, upper=False):
def zoom_buttons(keys: str, upper: bool = False) -> Iterable[Button]:
n_x = len(keys)
if upper:
keys = keys + keys.upper()
@ -164,18 +172,18 @@ def zoom_buttons(keys, upper=False):
class KeyboardZooming(ui.Widget):
def __init__(self, content='', uppercase=True):
def __init__(self, content: str = '', uppercase: bool = True) -> None:
self.content = content
self.uppercase = uppercase
self.zoom_buttons = None
self.zoom_buttons = () # type: Iterable[Button]
self.key_buttons = key_buttons()
self.bs_button = Button((240 - 35, 5, 30, 30),
res.load('trezor/res/pin_close.toig'),
normal_style=CLEAR_BUTTON,
active_style=CLEAR_BUTTON_ACTIVE)
def render(self):
def render(self) -> None:
self.render_input()
if self.zoom_buttons:
for btn in self.zoom_buttons:
@ -184,7 +192,7 @@ class KeyboardZooming(ui.Widget):
for btn in self.key_buttons:
btn.render()
def render_input(self):
def render_input(self) -> None:
if self.content:
display.bar(0, 0, 200, 40, ui.BLACK)
else:
@ -193,27 +201,26 @@ class KeyboardZooming(ui.Widget):
if self.content:
self.bs_button.render()
def touch(self, event, pos):
def touch(self, event: int, pos: Tuple[int, int]) -> None:
if self.bs_button.touch(event, pos) == BTN_CLICKED:
self.content = self.content[:-1]
self.bs_button.taint()
return
if self.zoom_buttons:
return self.touch_zoom(event, pos)
elif self.zoom_buttons:
self.touch_zoom(event, pos)
else:
return self.touch_keyboard(event, pos)
self.touch_keyboard(event, pos)
def touch_zoom(self, event, pos):
def touch_zoom(self, event: int, pos: Tuple[int, int]) -> None:
for btn in self.zoom_buttons:
if btn.touch(event, pos) == BTN_CLICKED:
self.content += btn.content
self.zoom_buttons = None
self.zoom_buttons = ()
for btn in self.key_buttons:
btn.taint()
self.bs_button.taint()
break
def touch_keyboard(self, event, pos):
def touch_keyboard(self, event: int, pos: Tuple[int, int]) -> None:
for btn in self.key_buttons:
if btn.touch(event, pos) == BTN_CLICKED:
self.zoom_buttons = zoom_buttons(btn.content, self.uppercase)
@ -222,7 +229,7 @@ class KeyboardZooming(ui.Widget):
self.bs_button.taint()
break
def __iter__(self):
def __iter__(self) -> Generator:
timeout = loop.Sleep(1000 * 1000 * 1)
touch = loop.Select(loop.TOUCH)
wait = loop.Wait((touch, timeout))
@ -233,7 +240,7 @@ class KeyboardZooming(ui.Widget):
event, *pos = result
self.touch(event, pos)
elif self.zoom_buttons:
self.zoom_buttons = None
self.zoom_buttons = ()
for btn in self.key_buttons:
btn.taint()

View File

@ -1,3 +1,4 @@
from typing import Dict, Optional
import utime
from micropython import const
from trezor import ui
@ -21,36 +22,38 @@ _LOADER_MSEC = const(1000)
class Loader(ui.Widget):
def __init__(self, normal_style=None, active_style=None):
self.start_ticks_ms = None
def __init__(self,
normal_style: Dict = None,
active_style: Dict = None) -> None:
self.start_ticks_ms = 0
self.normal_style = normal_style or DEFAULT_LOADER
self.active_style = active_style or DEFAULT_LOADER_ACTIVE
def start(self):
def start(self) -> None:
self.start_ticks_ms = utime.ticks_ms()
ui.display.bar(0, 32, 240, 240 - 80, ui.BLACK)
def stop(self):
def stop(self) -> bool:
ui.display.bar(0, 32, 240, 240 - 80, ui.BLACK)
ticks_diff = utime.ticks_ms() - self.start_ticks_ms
self.start_ticks_ms = None
self.start_ticks_ms = 0
return ticks_diff >= _LOADER_MSEC
def is_active(self):
return self.start_ticks_ms is not None
def is_active(self) -> bool:
return self.start_ticks_ms == 0
def render(self):
def render(self) -> None:
progress = min(utime.ticks_ms() - self.start_ticks_ms, _LOADER_MSEC)
if progress == _LOADER_MSEC:
style = self.active_style
else:
style = self.normal_style
if style['icon'] is None:
ui.display.loader(
ui.display.loader( # type: ignore # suppress the dict lookup failure
progress, -8, style['fg-color'], style['bg-color'])
elif style['icon-fg-color'] is None:
ui.display.loader(
ui.display.loader( # type: ignore # suppress the dict lookup failure
progress, -8, style['fg-color'], style['bg-color'], style['icon'])
else:
ui.display.loader(
ui.display.loader( # type: ignore # suppress the dict lookup failure
progress, -8, style['fg-color'], style['bg-color'], style['icon'], style['icon-fg-color'])

View File

@ -1,3 +1,4 @@
from typing import List, Tuple
from micropython import const
from trezor import ui, res
from trezor.crypto import random
@ -5,7 +6,7 @@ from trezor.ui import display
from trezor.ui.button import Button, BTN_CLICKED, CLEAR_BUTTON, CLEAR_BUTTON_ACTIVE
def digit_area(i):
def digit_area(i: int) -> Tuple[int, int, int, int]:
width = const(80)
height = const(48)
x = (i % 3) * width
@ -15,7 +16,7 @@ def digit_area(i):
return (x, y + 48, width - 1, height - 1)
def generate_digits():
def generate_digits() -> List[int]:
digits = list(range(1, 10)) # 1-9
random.shuffle(digits)
return digits
@ -23,7 +24,7 @@ def generate_digits():
class PinMatrix(ui.Widget):
def __init__(self, label, pin=''):
def __init__(self, label: str, pin: str = '') -> None:
self.label = label
self.pin = pin
self.digits = generate_digits()
@ -39,7 +40,7 @@ class PinMatrix(ui.Widget):
normal_style=CLEAR_BUTTON,
active_style=CLEAR_BUTTON_ACTIVE)
def render(self):
def render(self) -> None:
header = '*' * len(self.pin) if self.pin else self.label
@ -67,7 +68,7 @@ class PinMatrix(ui.Widget):
# display.bar(0, 95, 240, 2, ui.blend(ui.BLACK, ui.WHITE, 0.25))
# display.bar(0, 142, 240, 2, ui.blend(ui.BLACK, ui.WHITE, 0.25))
def touch(self, event, pos):
def touch(self, event: int, pos: Tuple[int, int]) -> None:
if self.clear_button.touch(event, pos) == BTN_CLICKED:
self.pin = ''
for btn in self.pin_buttons:

View File

@ -1,12 +1,16 @@
from typing import *
from trezor import ui
class Qr(ui.Widget):
def __init__(self, data, pos, scale):
def __init__(self,
data: bytes,
pos: Tuple[int, int],
scale: int) -> None:
self.data = data
self.pos = pos
self.scale = scale
def render(self):
def render(self) -> None:
ui.display.qrcode(self.pos[0], self.pos[1], self.data, self.scale)

View File

@ -3,7 +3,7 @@ from trezor import loop, ui
from .swipe import Swipe, SWIPE_UP, SWIPE_DOWN
async def change_page(page, page_count):
async def change_page(page: int, page_count: int) -> int:
while True:
s = await Swipe()
if s == SWIPE_UP and page < page_count - 1:
@ -12,7 +12,7 @@ async def change_page(page, page_count):
return page - 1 # scroll up
async def paginate(render_page, page_count, page=0, *args):
async def paginate(render_page, page_count: int, page: int = 0, *args):
while True:
changer = change_page(page, page_count)
renderer = render_page(page, page_count, *args)
@ -24,17 +24,17 @@ async def paginate(render_page, page_count, page=0, *args):
return result
async def animate_swipe():
async def animate_swipe() -> None:
await ui.animate_pulse(render_swipe_icon, ui.GREY, ui.DARK_GREY, speed=300000, delay=200000)
def render_swipe_icon(fg):
def render_swipe_icon(fg: int):
ui.display.bar_radius(102, 214, 36, 4, fg, ui.BLACK, 2)
ui.display.bar_radius(106, 222, 28, 4, fg, ui.BLACK, 2)
ui.display.bar_radius(110, 230, 20, 4, fg, ui.BLACK, 2)
def render_scrollbar(page, page_count):
def render_scrollbar(page: int, page_count: int) -> None:
screen_height = const(220)
size = const(10)

View File

@ -1,3 +1,4 @@
from typing import *
import utime
from micropython import const
from trezor import loop, ui
@ -15,15 +16,17 @@ SWIPE_RIGHT = const(270)
class Swipe(ui.Widget):
def __init__(self, area=None, absolute=False):
def __init__(self,
area: Tuple[int, int, int, int]=None,
absolute: bool = False) -> None:
self.area = area or (0, 0, ui.SCREEN, ui.SCREEN)
self.absolute = absolute
self.start_pos = None
self.start_time = 0
self.light_origin = None
self.start_pos = None # type: Optional[Tuple[int, int]]
self.start_time = 0 # type: float
self.light_origin = ui.BACKLIGHT_NORMAL
self.light_target = ui.BACKLIGHT_NONE
def touch(self, event, pos):
def touch(self, event: int, pos: Tuple[int, int]) -> Optional[int]:
if not self.absolute:
pos = rotate_coords(pos)

View File

@ -1,3 +1,4 @@
from typing import Union
from micropython import const
from trezor import ui
@ -8,12 +9,15 @@ TEXT_MARGIN_LEFT = const(10)
class Text(ui.Widget):
def __init__(self, header_text, header_icon, *content):
def __init__(self,
header_text: str,
header_icon: str,
*content: Union[str, int]) -> None:
self.header_text = header_text
self.header_icon = header_icon
self.content = content
def render(self):
def render(self) -> None:
offset_x = TEXT_MARGIN_LEFT
offset_y = TEXT_LINE_HEIGHT + TEXT_HEADER_HEIGHT
style = ui.NORMAL

View File

@ -1,18 +1,19 @@
from typing import Callable, Generator, Iterator, List, Union
import sys
import gc
from trezorutils import halt, memcpy
def _gf():
def _gf() -> Generator:
yield
type_gen = type(_gf())
def _unimport_func(func):
def inner(*args, **kwargs):
def _unimport_func(func): # type: ignore
def inner(*args, **kwargs): # type: ignore
mods = set(sys.modules)
try:
ret = func(*args, **kwargs)
@ -25,8 +26,8 @@ def _unimport_func(func):
return inner
def _unimport_gen(genfunc):
async def inner(*args, **kwargs):
def _unimport_gen(genfunc): # type: ignore
async def inner(*args, **kwargs): # type: ignore
mods = set(sys.modules)
try:
ret = await genfunc(*args, **kwargs)
@ -39,18 +40,18 @@ def _unimport_gen(genfunc):
return inner
def unimport(func):
def unimport(func): # type: ignore
if isinstance(func, type_gen):
return _unimport_gen(func)
return _unimport_gen(func) # type: ignore
else:
return _unimport_func(func)
return _unimport_func(func) # type: ignore
def chunks(items, size):
def chunks(items: List, size: int) -> Iterator[List]:
for i in range(0, len(items), size):
yield items[i:i + size]
def ensure(cond):
def ensure(cond: bool) -> None:
if not cond:
raise AssertionError()

View File

@ -1,11 +1,12 @@
from typing import Callable, Coroutine, List, Optional
from trezor import log, loop, ui
_started = []
_default = None
_default_genfunc = None
_started = [] # type: List[Coroutine]
_default = None # type: Optional[Coroutine]
_default_genfunc = None # type: Optional[Callable[[], Coroutine]]
def start_default(genfunc):
def start_default(genfunc: Callable[[], Coroutine]) -> None:
global _default
global _default_genfunc
_default_genfunc = genfunc
@ -15,23 +16,23 @@ def start_default(genfunc):
ui.display.backlight(ui.BACKLIGHT_NORMAL)
def close_default():
def close_default() -> None:
global _default
log.info(__name__, 'close default %s', _default)
_default.close()
_default = None
def start(workflow):
if _default is not None:
close_default()
log.info(__name__, 'close default %s', _default)
_default.close()
_default = None
def start(workflow: Coroutine) -> None:
close_default()
_started.append(workflow)
log.info(__name__, 'start %s', workflow)
loop.schedule_task(_watch(workflow))
loop.schedule_task(_wrap(workflow)) # type: ignore # FIXME: https://github.com/python/typing/issues/441
async def _wrap(workflow: Coroutine):
ui.display.backlight(ui.BACKLIGHT_NORMAL)
async def _watch(workflow):
try:
return await workflow
finally:

View File

@ -25,14 +25,12 @@ def split_to_parts(line, mod_desc=None):
current_method = line[4:].split('(')[0]
yield (current_package, "\n")
if current_class is None:
yield (current_package, '# ' + mod_desc + "\n")
else:
current_indent = 4
line = current_indent * ' ' + line
yield (current_package, line)
@ -107,6 +105,7 @@ def clear_directory(top_dir):
os.rmdir(root)
if __name__ == '__main__':
clear_directory('../mocks/generated')
build_directory('../micropython/extmod', '../mocks/generated')