From 702006488ad5653c652aec83bbf37daf86287306 Mon Sep 17 00:00:00 2001 From: Mike Wang Date: Wed, 8 Oct 2025 04:51:20 +0800 Subject: [PATCH] feat: Add support for MicroPython --- .gitignore | 3 + micropython/Makefile | 119 + micropython/cmodule/_posix/micropython.mk | 3 + micropython/cmodule/_posix/mod_posix.c | 1257 +++++++++++ micropython/deps.mk | 11 + micropython/patch/ports_unix_main_c.patch | 82 + micropython/patch/py_moderrno_c.patch | 19 + micropython/pymodule/LICENSE.cpython | 255 +++ micropython/pymodule/_manifest.py | 13 + micropython/pymodule/argparse.py | 2428 +++++++++++++++++++++ micropython/pymodule/atexit.py | 43 + micropython/pymodule/codecs.py | 38 + micropython/pymodule/errno.py | 93 + micropython/pymodule/genericpath.py | 134 ++ micropython/pymodule/os.py | 163 ++ micropython/pymodule/posixpath.py | 361 +++ micropython/pymodule/re.py | 138 ++ micropython/pymodule/signal.py | 39 + micropython/pymodule/socket.py | 266 +++ micropython/pymodule/struct.py | 57 + micropython/pymodule/subprocess.py | 254 +++ micropython/pymodule/threading.py | 106 + 22 files changed, 5882 insertions(+) create mode 100644 .gitignore create mode 100644 micropython/Makefile create mode 100644 micropython/cmodule/_posix/micropython.mk create mode 100644 micropython/cmodule/_posix/mod_posix.c create mode 100644 micropython/deps.mk create mode 100644 micropython/patch/ports_unix_main_c.patch create mode 100644 micropython/patch/py_moderrno_c.patch create mode 100644 micropython/pymodule/LICENSE.cpython create mode 100644 micropython/pymodule/_manifest.py create mode 100644 micropython/pymodule/argparse.py create mode 100644 micropython/pymodule/atexit.py create mode 100644 micropython/pymodule/codecs.py create mode 100644 micropython/pymodule/errno.py create mode 100644 micropython/pymodule/genericpath.py create mode 100644 micropython/pymodule/os.py create mode 100644 micropython/pymodule/posixpath.py create mode 100644 micropython/pymodule/re.py create mode 100644 micropython/pymodule/signal.py create mode 100644 micropython/pymodule/socket.py create mode 100644 micropython/pymodule/struct.py create mode 100644 micropython/pymodule/subprocess.py create mode 100644 micropython/pymodule/threading.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5e2a510 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/.vscode +/micropython/build +__pycache__ diff --git a/micropython/Makefile b/micropython/Makefile new file mode 100644 index 0000000..e5ac9c7 --- /dev/null +++ b/micropython/Makefile @@ -0,0 +1,119 @@ +include deps.mk + +PROG := natter +BUILD_DIR := $(CURDIR)/build +PATCH_DIR := $(CURDIR)/patch +COMP_DIR := $(BUILD_DIR)/compressed +MP_TAR := $(BUILD_DIR)/$(MICROPYTHON_TARNAME) +MP_SRC := $(BUILD_DIR)/$(MICROPYTHON_DIRNAME) +MP_PACTHED := $(MP_SRC)/.patched +LXP_TAR := $(BUILD_DIR)/$(LXPACK_TARNAME) +LXP_SRC := $(BUILD_DIR)/$(LXPACK_DIRNAME) +PROG_BIN := $(BUILD_DIR)/$(PROG) +COMP_BIN := $(COMP_DIR)/$(PROG) + +MP_MAKE_EXTRA := \ + MICROPY_STANDALONE=1 \ + MICROPY_PY_BTREE=0 \ + MICROPY_PY_FFI=0 \ + MICROPY_PY_SSL=0 \ + MICROPY_PY_TERMIOS=0 \ + MICROPY_SSL_MBEDTLS=0 \ + MICROPY_USE_READLINE=0 \ + MICROPY_VFS_FAT=0 \ + MICROPY_VFS_LFS1=0 \ + MICROPY_VFS_LFS2=0 \ + MICROPY_GIT_TAG='v$(MICROPYTHON_VERSION)-natter' \ + MICROPY_GIT_HASH='' \ + SOURCE_DATE_EPOCH=0 \ + HELP_BUILD_ERROR= + +ifdef CROSS_COMPILE + MP_MAKE_EXTRA := $(MP_MAKE_EXTRA) CROSS_COMPILE='$(CROSS_COMPILE)' +endif +ifdef DEBUG + MP_MAKE_EXTRA := $(MP_MAKE_EXTRA) DEBUG='$(DEBUG)' +endif +ifdef V + MP_MAKE_EXTRA := $(MP_MAKE_EXTRA) V='$(V)' +endif + +MP_CFLAGS_EXTRA := \ + -frandom-seed=natter \ + -Wdate-time \ + -Wno-error=clobbered \ + -DMICROPY_PY_SYS_TRACEBACKLIMIT=1 \ + -DMICROPY_PY_RE_MATCH_GROUPS=1 \ + -DMICROPY_PY_RE_MATCH_SPAN_START_END=1 \ + -DMICROPY_NLR_SETJMP=1 + +ifeq ($(findstring m68k,$(shell $(CROSS_COMPILE)gcc -dumpmachine)),m68k) + MP_CFLAGS_EXTRA := $(MP_CFLAGS_EXTRA) -malign-int +endif + +ifeq ($(STATIC),1) +MP_LDFLAGS_EXTRA := -static +endif + +all: $(PROG_BIN) + +download: $(MP_TAR) $(LXP_TAR) + +compress: $(COMP_BIN) + +clean: + $(RM) -r '$(MP_SRC)' '$(LXP_SRC)' '$(COMP_DIR)' '$(PROG_BIN)' + +distclean: + $(RM) -r '$(BUILD_DIR)' + +$(MP_TAR): + mkdir -p '$(BUILD_DIR)' + curl -Lfo '$@' '$(MICROPYTHON_SRCURL)' + echo '$(MICROPYTHON_SHA256) *$@' | sha256sum -c - + +$(LXP_TAR): + mkdir -p '$(BUILD_DIR)' + curl -Lfo '$@' '$(LXPACK_SRCURL)' + echo '$(LXPACK_SHA256) *$@' | sha256sum -c - + +untar: $(MP_TAR) $(LXP_TAR) + rm -rf '$(MP_SRC)' '$(LXP_SRC)' + cd '$(BUILD_DIR)' && tar xJf '$(MP_TAR)' + cd '$(BUILD_DIR)' && tar xJf '$(LXP_TAR)' + +$(MP_PACTHED): $(wildcard $(PATCH_DIR)/*.patch) + test -d '$(MP_SRC)' || $(MAKE) untar + find '$(MP_SRC)' -type f -name '*.orig' -exec sh -c 'mv "$$1" "$${1%.orig}"' _ {} \; + for p in '$(PATCH_DIR)'/*.patch; do \ + cd '$(MP_SRC)' && patch -bp1 < "$$p"; \ + done + touch '$@' + +patch: $(MP_PACTHED) + +mpy_cross: $(MP_PACTHED) + $(MAKE) -C '$(MP_SRC)/mpy-cross' \ + PROG=mpy-cross \ + CROSS_COMPILE= \ + CFLAGS_EXTRA= \ + LDFLAGS_EXTRA= + +$(PROG_BIN): mpy_cross + $(MAKE) -C '$(MP_SRC)/ports/unix' install \ + VARIANT=standard \ + BINDIR='$(BUILD_DIR)' \ + PROG='$(PROG)' \ + FROZEN_MANIFEST='$(CURDIR)/pymodule/_manifest.py' \ + USER_C_MODULES='$(CURDIR)/cmodule' \ + CFLAGS_EXTRA='$(MP_CFLAGS_EXTRA)' \ + LDFLAGS_EXTRA='$(MP_LDFLAGS_EXTRA)' \ + $(MP_MAKE_EXTRA) + +$(COMP_BIN): $(PROG_BIN) + test -d '$(LXP_SRC)' || $(MAKE) untar + $(MAKE) -C '$(LXP_SRC)' install \ + BINDIR='$(COMP_DIR)' \ + PAYLOAD='$(PROG_BIN)' + +.PHONY: all download untar patch mpy_cross compress clean diff --git a/micropython/cmodule/_posix/micropython.mk b/micropython/cmodule/_posix/micropython.mk new file mode 100644 index 0000000..9199bdd --- /dev/null +++ b/micropython/cmodule/_posix/micropython.mk @@ -0,0 +1,3 @@ +UNIXAPI_MOD_DIR := $(USERMOD_DIR) +SRC_USERMOD += $(UNIXAPI_MOD_DIR)/mod_posix.c +CFLAGS_USERMOD += -I$(UNIXAPI_MOD_DIR) diff --git a/micropython/cmodule/_posix/mod_posix.c b/micropython/cmodule/_posix/mod_posix.c new file mode 100644 index 0000000..a223f7e --- /dev/null +++ b/micropython/cmodule/_posix/mod_posix.c @@ -0,0 +1,1257 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "py/runtime.h" +#include "py/smallint.h" + + +/* + Module "_posix". + This module provides the most common POSIX functions and system calls for + MicroPython. +*/ + + +#define MP_OBJ_NEW_INT_LL(value) \ + ((MP_SMALL_INT_FITS(value)) ? (mp_obj_new_int(value)) : \ + (mp_obj_new_int_from_ll(value))) + +/* + extern char **environ; +*/ + +static mp_obj_t posixmod__environ(void) { + extern char **environ; + + mp_obj_t environ_list_obj; + char **ptr; + + environ_list_obj = mp_obj_new_list(0, NULL); + for (ptr = environ; *ptr; ptr++) { + mp_obj_list_append(environ_list_obj, + mp_obj_new_str_from_cstr(*ptr)); + } + + return environ_list_obj; +} +static MP_DEFINE_CONST_FUN_OBJ_0(posixmod__environ_obj, posixmod__environ); + + +/* + +*/ + +static mp_obj_t posixmod_fcntl(mp_obj_t fd_obj, mp_obj_t cmd_obj, + mp_obj_t flags_obj) { + int res, fd, cmd, flags; + + fd = mp_obj_get_int(fd_obj); + cmd = mp_obj_get_int(cmd_obj); + + switch (cmd) + { + case F_DUPFD: + case F_GETFL: + case F_GETFD: + case F_GETOWN: + res = fcntl(fd, cmd); + if (res == -1) { + mp_raise_OSError(errno); + } + return mp_obj_new_int(res); + + case F_SETFL: + case F_SETFD: + case F_SETOWN: + flags = mp_obj_get_int(flags_obj); + res = fcntl(fd, cmd, flags); + if (res == -1) { + mp_raise_OSError(errno); + } + return mp_obj_new_int(res); + + default: + mp_raise_NotImplementedError(MP_ERROR_TEXT("fcntl() for specified cmd " + "is not implemented")); + return mp_const_none; + } +} +static MP_DEFINE_CONST_FUN_OBJ_3(posixmod_fcntl_obj, posixmod_fcntl); + + +static mp_obj_t posixmod_open(mp_obj_t path_obj, mp_obj_t flags_obj, + mp_obj_t mode_obj) { + int fd, flags; + long long mode_ll; + mode_t mode; + const char *path; + + path = mp_obj_str_get_str(path_obj); + flags = mp_obj_get_int(flags_obj); + mode_ll = mp_obj_get_ll(mode_obj); + mode = (mode_t) mode_ll; + if (mode_ll != mode /* overflow */) { + mp_raise_msg(&mp_type_OverflowError, + MP_ERROR_TEXT("overflow converting integer to mode_t")); + } + + fd = open(path, flags, mode); + if (fd < 0) { + mp_raise_OSError(errno); + } + + return mp_obj_new_int(fd); +} +static MP_DEFINE_CONST_FUN_OBJ_3(posixmod_open_obj, posixmod_open); + + +/* + +*/ + +static mp_obj_t posixmod_gai_strerror(mp_obj_t errnum_obj) { + char buf[64]; + int errnum; + const char *errstr; + + errnum = mp_obj_get_int(errnum_obj); + errno = 0; + errstr = gai_strerror(errnum); + if (errno || !errstr) { + snprintf(buf, sizeof(buf), "Unknown error %d", errnum); + errstr = buf; + } + + return mp_obj_new_str_from_cstr(errstr); +} +static MP_DEFINE_CONST_FUN_OBJ_1(posixmod_gai_strerror_obj, + posixmod_gai_strerror); + + +static mp_obj_t posixmod_getaddrinfo(size_t n_args, const mp_obj_t *args) { + int res, family, type, proto, flags; + const char *host, *port; + struct addrinfo hints, *pai, *p; + mp_obj_t ai_items[5], tuple_obj, ret_obj; + + if (n_args != 6) { + mp_raise_ValueError(NULL); + } + + ret_obj = mp_obj_new_list(0, NULL); + + host = mp_obj_str_get_str(args[0]); + port = mp_obj_str_get_str(args[1]); + family = mp_obj_get_int(args[2]); + type = mp_obj_get_int(args[3]); + proto = mp_obj_get_int(args[4]); + flags = mp_obj_get_int(args[5]); + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = family; + hints.ai_socktype = type; + hints.ai_protocol = proto; + hints.ai_flags = flags; + + res = getaddrinfo(host, port, &hints, &pai); + if (res) { + mp_raise_OSError(res); + } + + for (p = pai; p; p = p->ai_next) { + ai_items[0] = mp_obj_new_int(pai->ai_family); + ai_items[1] = mp_obj_new_int(pai->ai_socktype); + ai_items[2] = mp_obj_new_int(pai->ai_protocol); + if (pai->ai_canonname) { + ai_items[3] = mp_obj_new_str_from_cstr(pai->ai_canonname); + } else { + ai_items[3] = mp_obj_new_str(NULL, 0); + } + ai_items[4] = mp_obj_new_bytearray(pai->ai_addrlen, pai->ai_addr); + + tuple_obj = mp_obj_new_tuple(5, ai_items); + mp_obj_list_append(ret_obj, tuple_obj); + } + + freeaddrinfo(pai); + + return ret_obj; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(posixmod_getaddrinfo_obj, 6, 6, + posixmod_getaddrinfo); + + +static mp_obj_t posixmod_getnameinfo(mp_obj_t sockaddr_obj, + mp_obj_t flags_obj) { + int res, flags; + mp_buffer_info_t bufinfo; + char host[NI_MAXHOST] = {0}, service[NI_MAXSERV] = {0}; + mp_obj_t ret_items[2]; + + mp_get_buffer_raise(sockaddr_obj, &bufinfo, MP_BUFFER_READ); + flags = mp_obj_get_int(flags_obj); + + res = getnameinfo((struct sockaddr *) bufinfo.buf, bufinfo.len, host, + sizeof(host), service, sizeof(service), flags); + if (res) { + mp_raise_OSError(res); + } + + ret_items[0] = mp_obj_new_str_from_cstr(host); + ret_items[1] = mp_obj_new_str_from_cstr(service); + + return mp_obj_new_tuple(2, ret_items); +} +static MP_DEFINE_CONST_FUN_OBJ_2(posixmod_getnameinfo_obj, + posixmod_getnameinfo); + + +/* + +*/ + +static mp_obj_t sigdict_obj = NULL; + +static void sighandler(int signalnum) { + if (!sigdict_obj) { + return; + } + mp_obj_t signalnum_obj = mp_obj_new_int(signalnum); + mp_map_elem_t *elem = mp_map_lookup(&((mp_obj_dict_t *) sigdict_obj)->map, + signalnum_obj, MP_MAP_LOOKUP); + if (!elem || !elem->value || !mp_obj_is_callable(elem->value)) { + return; + } + mp_call_function_2(elem->value, signalnum_obj, mp_const_none); +} + +static mp_obj_t posixmod_signal(mp_obj_t signalnum_obj, mp_obj_t handler_obj) { + int signalnum; + long long handler_ll; + sighandler_t handler; + mp_map_elem_t *elem; + mp_obj_t old_handler_obj; + + if (!sigdict_obj) { + sigdict_obj = mp_obj_new_dict(0); + } + + if (!mp_obj_is_int(signalnum_obj)) { + mp_raise_TypeError(MP_ERROR_TEXT("signalnum must be an interger")); + } + signalnum = mp_obj_get_int(signalnum_obj); + + elem = mp_map_lookup(&((mp_obj_dict_t *) sigdict_obj)->map, signalnum_obj, + MP_MAP_LOOKUP); + if (elem && elem->value) { + old_handler_obj = elem->value; + } else { + old_handler_obj = MP_OBJ_NEW_INT_LL((intptr_t) SIG_DFL); + } + + if (mp_obj_is_callable(handler_obj)) { + handler = sighandler; + } else { + handler_ll = mp_obj_get_ll(handler_obj); + handler = (sighandler_t) (intptr_t) handler_ll; + if ((handler_ll != (intptr_t) handler /* overflow */) || + (handler != SIG_IGN && handler != SIG_DFL)) { + mp_raise_TypeError(MP_ERROR_TEXT("signal handler must be SIG_IGN, " + "SIG_DFL, or a callable object")); + } + } + + if (signal(signalnum, handler) == SIG_ERR) { + mp_raise_OSError(errno); + } + + mp_obj_dict_store(sigdict_obj, signalnum_obj, handler_obj); + + return old_handler_obj; +} +static MP_DEFINE_CONST_FUN_OBJ_2(posixmod_signal_obj, posixmod_signal); + + +static mp_obj_t posixmod_kill(mp_obj_t pid_obj, mp_obj_t sig_obj) { + int res, sig; + long long pid_ll; + pid_t pid; + + pid_ll = mp_obj_get_ll(pid_obj); + pid = (pid_t) pid_ll; + if (pid != pid_ll /* overflow */) { + mp_raise_msg(&mp_type_OverflowError, + MP_ERROR_TEXT("overflow converting integer to pid_t")); + } + sig = mp_obj_get_int(sig_obj); + + res = kill(pid, sig); + if (res) { + mp_raise_OSError(errno); + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(posixmod_kill_obj, posixmod_kill); + + +/* + +*/ + +static mp_obj_t posixmod_strerror(mp_obj_t errnum_obj) { + char buf[64]; + int errnum; + const char *errstr; + + errnum = mp_obj_get_int(errnum_obj); + errno = 0; + errstr = strerror(errnum); + if (errno || !errstr) { + snprintf(buf, sizeof(buf), "Unknown error %d", errnum); + errstr = buf; + } + + return mp_obj_new_str_from_cstr(errstr); +} +static MP_DEFINE_CONST_FUN_OBJ_1(posixmod_strerror_obj, posixmod_strerror); + + +/* + +*/ + +static mp_obj_t posixmod_getpeername(mp_obj_t fd_obj) { + int res, fd; + struct sockaddr_storage addr; + socklen_t addr_len; + + fd = mp_obj_get_int(fd_obj); + addr_len = sizeof(addr); + + res = getpeername(fd, (struct sockaddr *) &addr, &addr_len); + if (res) { + mp_raise_OSError(errno); + } + + return mp_obj_new_bytearray(addr_len, &addr); +} +static MP_DEFINE_CONST_FUN_OBJ_1(posixmod_getpeername_obj, + posixmod_getpeername); + + +static mp_obj_t posixmod_getsockname(mp_obj_t fd_obj) { + int res, fd; + struct sockaddr_storage addr; + socklen_t addr_len; + + fd = mp_obj_get_int(fd_obj); + addr_len = sizeof(addr); + + res = getsockname(fd, (struct sockaddr *) &addr, &addr_len); + if (res) { + mp_raise_OSError(errno); + } + + return mp_obj_new_bytearray(addr_len, &addr); +} +static MP_DEFINE_CONST_FUN_OBJ_1(posixmod_getsockname_obj, + posixmod_getsockname); + + +static mp_obj_t posixmod_shutdown(mp_obj_t fd_obj, mp_obj_t how_obj) { + int res, fd, how; + + fd = mp_obj_get_int(fd_obj); + how = mp_obj_get_int(how_obj); + + res = shutdown(fd, how); + if (res) { + mp_raise_OSError(errno); + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(posixmod_shutdown_obj, posixmod_shutdown); + + +/* + +*/ + +static mp_obj_t posixmod_lstat(mp_obj_t path_obj) { + int res; + const char *path; + struct stat statbuf; + mp_obj_t ret[10]; + + path = mp_obj_str_get_str(path_obj); + + res = lstat(path, &statbuf); + if (res) { + mp_raise_OSError(errno); + } + + ret[0] = MP_OBJ_NEW_INT_LL(statbuf.st_mode); + ret[1] = MP_OBJ_NEW_INT_LL(statbuf.st_ino); + ret[2] = MP_OBJ_NEW_INT_LL(statbuf.st_dev); + ret[3] = MP_OBJ_NEW_INT_LL(statbuf.st_nlink); + ret[4] = MP_OBJ_NEW_INT_LL(statbuf.st_uid); + ret[5] = MP_OBJ_NEW_INT_LL(statbuf.st_gid); + ret[6] = MP_OBJ_NEW_INT_LL(statbuf.st_size); + ret[7] = MP_OBJ_NEW_INT_LL(statbuf.st_atime); + ret[8] = MP_OBJ_NEW_INT_LL(statbuf.st_mtime); + ret[9] = MP_OBJ_NEW_INT_LL(statbuf.st_ctime); + + return mp_obj_new_tuple(10, ret); +} +static MP_DEFINE_CONST_FUN_OBJ_1(posixmod_lstat_obj, posixmod_lstat); + + +static mp_obj_t posixmod_stat(mp_obj_t path_obj) { + int res; + const char *path; + struct stat statbuf; + mp_obj_t ret[10]; + + path = mp_obj_str_get_str(path_obj); + + res = stat(path, &statbuf); + if (res) { + mp_raise_OSError(errno); + } + + ret[0] = MP_OBJ_NEW_INT_LL(statbuf.st_mode); + ret[1] = MP_OBJ_NEW_INT_LL(statbuf.st_ino); + ret[2] = MP_OBJ_NEW_INT_LL(statbuf.st_dev); + ret[3] = MP_OBJ_NEW_INT_LL(statbuf.st_nlink); + ret[4] = MP_OBJ_NEW_INT_LL(statbuf.st_uid); + ret[5] = MP_OBJ_NEW_INT_LL(statbuf.st_gid); + ret[6] = MP_OBJ_NEW_INT_LL(statbuf.st_size); + ret[7] = MP_OBJ_NEW_INT_LL(statbuf.st_atime); + ret[8] = MP_OBJ_NEW_INT_LL(statbuf.st_mtime); + ret[9] = MP_OBJ_NEW_INT_LL(statbuf.st_ctime); + + return mp_obj_new_tuple(10, ret); +} +static MP_DEFINE_CONST_FUN_OBJ_1(posixmod_stat_obj, posixmod_stat); + + +/* + +*/ + +static mp_obj_t posixmod_uname(void) { + int res; + struct utsname name; + mp_obj_t ret[5]; + + res = uname(&name); + if (res < 0) { + mp_raise_OSError(errno); + } + + ret[0] = mp_obj_new_str_from_cstr(name.sysname); + ret[1] = mp_obj_new_str_from_cstr(name.nodename); + ret[2] = mp_obj_new_str_from_cstr(name.release); + ret[3] = mp_obj_new_str_from_cstr(name.version); + ret[4] = mp_obj_new_str_from_cstr(name.machine); + + return mp_obj_new_tuple(5, ret); +} +static MP_DEFINE_CONST_FUN_OBJ_0(posixmod_uname_obj, posixmod_uname); + + +/* + +*/ + +static mp_obj_t posixmod_waitpid(mp_obj_t pid_obj, mp_obj_t options_obj) { + int status, options; + long long pid_ll; + pid_t pid, pid_ret; + mp_obj_t ret[2]; + + pid_ll = mp_obj_get_ll(pid_obj); + pid = (pid_t) pid_ll; + if (pid != pid_ll /* overflow */) { + mp_raise_msg(&mp_type_OverflowError, + MP_ERROR_TEXT("overflow converting integer to pid_t")); + } + options = mp_obj_get_int(options_obj); + + pid_ret = waitpid(pid, &status, options); + if (pid_ret < 0) { + mp_raise_OSError(errno); + } + + ret[0] = MP_OBJ_NEW_INT_LL(pid_ret); + ret[1] = mp_obj_new_int(status); + + return mp_obj_new_tuple(2, ret); +} +static MP_DEFINE_CONST_FUN_OBJ_2(posixmod_waitpid_obj, posixmod_waitpid); + + +/* + +*/ + +static mp_obj_t posixmod__exit(mp_obj_t status_obj) { + int status; + + status = mp_obj_get_int(status_obj); + _exit(status); + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(posixmod__exit_obj, posixmod__exit); + + +static mp_obj_t posixmod_access(mp_obj_t path_obj, mp_obj_t mode_obj) { + int res, mode; + const char *path; + + path = mp_obj_str_get_str(path_obj); + mode = mp_obj_get_int(mode_obj); + + res = access(path, mode); + + return mp_obj_new_bool(!res); +} +static MP_DEFINE_CONST_FUN_OBJ_2(posixmod_access_obj, posixmod_access); + + +static mp_obj_t posixmod_close(mp_obj_t fd_obj) { + int res, fd; + + fd = mp_obj_get_int(fd_obj); + + res = close(fd); + if (res) { + mp_raise_OSError(errno); + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(posixmod_close_obj, posixmod_close); + + +static mp_obj_t posixmod_dup(mp_obj_t fd_obj) { + int fd, fd2; + + fd = mp_obj_get_int(fd_obj); + + fd2 = dup(fd); + if (fd2 < 0) { + mp_raise_OSError(errno); + } + + return mp_obj_new_int(fd2); +} +static MP_DEFINE_CONST_FUN_OBJ_1(posixmod_dup_obj, posixmod_dup); + + +static mp_obj_t posixmod_dup2(mp_obj_t fd_obj, mp_obj_t fd2_obj) { + int fd, fd2; + + fd = mp_obj_get_int(fd_obj); + fd2 = mp_obj_get_int(fd2_obj); + + fd2 = dup2(fd, fd2); + if (fd2 < 0) { + mp_raise_OSError(errno); + } + + return mp_obj_new_int(fd2); +} +static MP_DEFINE_CONST_FUN_OBJ_2(posixmod_dup2_obj, posixmod_dup2); + + +static mp_obj_t posixmod_execv(mp_obj_t path_obj, mp_obj_t args_obj) { + int err; + const char *path, **argv; + size_t len_args, i; + mp_obj_t len_args_obj, iter_args, item; + mp_obj_iter_buf_t iter_buf_args; + + path = mp_obj_str_get_str(path_obj); + + len_args_obj = mp_obj_len(args_obj); + len_args = mp_obj_get_int(len_args_obj); + + argv = m_new(const char *, len_args + 1); + + i = 0; + iter_args = mp_getiter(args_obj, &iter_buf_args); + while (i < len_args && + ((item = mp_iternext(iter_args)) != MP_OBJ_STOP_ITERATION)) { + argv[i++] = mp_obj_str_get_str(item); + } + argv[i] = NULL; + + execv(path, (char * const *) argv); + + err = errno; + m_del(const char *, argv, len_args + 1); + mp_raise_OSError(err); + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(posixmod_execv_obj, posixmod_execv); + + +static mp_obj_t posixmod_execve(mp_obj_t path_obj, mp_obj_t args_obj, + mp_obj_t env_obj) { + int err; + const char *path, **argv, **envp; + size_t len_args, len_env, i; + mp_obj_t len_args_obj, len_env_obj, iter_args, iter_env, item; + mp_obj_iter_buf_t iter_buf_args, iter_buf_env; + + path = mp_obj_str_get_str(path_obj); + + len_args_obj = mp_obj_len(args_obj); + len_args = mp_obj_get_int(len_args_obj); + + len_env_obj = mp_obj_len(env_obj); + len_env = mp_obj_get_int(len_env_obj); + + argv = m_new(const char *, len_args + 1); + envp = m_new(const char *, len_env + 1); + + i = 0; + iter_args = mp_getiter(args_obj, &iter_buf_args); + while (i < len_args && + ((item = mp_iternext(iter_args)) != MP_OBJ_STOP_ITERATION)) { + argv[i++] = mp_obj_str_get_str(item); + } + argv[i] = NULL; + + i = 0; + iter_env = mp_getiter(env_obj, &iter_buf_env); + while (i < len_env && + ((item = mp_iternext(iter_env)) != MP_OBJ_STOP_ITERATION)) { + envp[i++] = mp_obj_str_get_str(item); + } + envp[i] = NULL; + + execve(path, (char * const *) argv, + (char * const *) envp); + + err = errno; + + m_del(const char *, argv, len_args + 1); + m_del(const char *, envp, len_env + 1); + mp_raise_OSError(err); + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_3(posixmod_execve_obj, posixmod_execve); + + +static mp_obj_t posixmod_execvp(mp_obj_t path_obj, mp_obj_t args_obj) { + int err; + const char *path, **argv; + size_t len_args, i; + mp_obj_t len_args_obj, iter_args, item; + mp_obj_iter_buf_t iter_buf_args; + + path = mp_obj_str_get_str(path_obj); + + len_args_obj = mp_obj_len(args_obj); + len_args = mp_obj_get_int(len_args_obj); + + argv = m_new(const char *, len_args + 1); + + i = 0; + iter_args = mp_getiter(args_obj, &iter_buf_args); + while (i < len_args && + ((item = mp_iternext(iter_args)) != MP_OBJ_STOP_ITERATION)) { + argv[i++] = mp_obj_str_get_str(item); + } + argv[i] = NULL; + + execvp(path, (char * const *) argv); + + err = errno; + m_del(const char *, argv, len_args + 1); + mp_raise_OSError(err); + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(posixmod_execvp_obj, posixmod_execvp); + + +static mp_obj_t posixmod_execvpe(mp_obj_t path_obj, mp_obj_t args_obj, + mp_obj_t env_obj) { + int err; + const char *path, **argv, **envp; + size_t len_args, len_env, i; + mp_obj_t len_args_obj, len_env_obj, iter_args, iter_env, item; + mp_obj_iter_buf_t iter_buf_args, iter_buf_env; + + path = mp_obj_str_get_str(path_obj); + + len_args_obj = mp_obj_len(args_obj); + len_args = mp_obj_get_int(len_args_obj); + + len_env_obj = mp_obj_len(env_obj); + len_env = mp_obj_get_int(len_env_obj); + + argv = m_new(const char *, len_args + 1); + envp = m_new(const char *, len_env + 1); + + i = 0; + iter_args = mp_getiter(args_obj, &iter_buf_args); + while (i < len_args && + ((item = mp_iternext(iter_args)) != MP_OBJ_STOP_ITERATION)) { + argv[i++] = mp_obj_str_get_str(item); + } + argv[i] = NULL; + + i = 0; + iter_env = mp_getiter(env_obj, &iter_buf_env); + while (i < len_env && + ((item = mp_iternext(iter_env)) != MP_OBJ_STOP_ITERATION)) { + envp[i++] = mp_obj_str_get_str(item); + } + envp[i] = NULL; + + execvpe(path, (char * const *) argv, + (char * const *) envp); + + err = errno; + m_del(const char *, argv, len_args + 1); + m_del(const char *, envp, len_env + 1); + mp_raise_OSError(err); + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_3(posixmod_execvpe_obj, posixmod_execvpe); + + +static mp_obj_t posixmod_fork(void) { + pid_t child; + + child = fork(); + if (child < 0) { + mp_raise_OSError(errno); + } + + return mp_obj_new_int(child); +} +static MP_DEFINE_CONST_FUN_OBJ_0(posixmod_fork_obj, posixmod_fork); + + +static mp_obj_t posixmod_getuid(void) { + long long uid; + + uid = getuid(); + return mp_obj_new_int(uid); +} +static MP_DEFINE_CONST_FUN_OBJ_0(posixmod_getuid_obj, posixmod_getuid); + + +static mp_obj_t posixmod_isatty(mp_obj_t fd_obj) { + int fd; + + fd = mp_obj_get_int(fd_obj); + + return mp_obj_new_bool(isatty(fd)); +} +static MP_DEFINE_CONST_FUN_OBJ_1(posixmod_isatty_obj, posixmod_isatty); + + +static mp_obj_t posixmod_pipe(void) { + int res, pipefd[2]; + mp_obj_t pipefd_obj[2]; + + res = pipe(pipefd); + if (res) { + mp_raise_OSError(errno); + } + pipefd_obj[0] = mp_obj_new_int(pipefd[0]); + pipefd_obj[1] = mp_obj_new_int(pipefd[1]); + + return mp_obj_new_tuple(2, pipefd_obj); +} +static MP_DEFINE_CONST_FUN_OBJ_0(posixmod_pipe_obj, posixmod_pipe); + + +static mp_obj_t posixmod_read(mp_obj_t fd_obj, mp_obj_t nbyte_obj) { + int fd, err; + long long nbyte_ll; + size_t nbyte; + ssize_t nread; + byte *buf; + mp_obj_t ret; + + fd = mp_obj_get_int(fd_obj); + nbyte_ll = mp_obj_get_ll(nbyte_obj); + nbyte = (size_t) nbyte_ll; + if (nbyte_ll != (long long) nbyte /* overflow */) { + mp_raise_msg(&mp_type_OverflowError, + MP_ERROR_TEXT("overflow converting integer to size_t")); + } + + buf = m_new(byte, nbyte); + nread = read(fd, buf, nbyte); + if (nread < 0) { + err = errno; + m_del(byte, buf, nbyte); + mp_raise_OSError(err); + } + + ret = mp_obj_new_bytes(buf, nread); + m_del(byte, buf, nbyte); + + return ret; +} +static MP_DEFINE_CONST_FUN_OBJ_2(posixmod_read_obj, posixmod_read); + + +static mp_obj_t posixmod_readlink(mp_obj_t path_obj) { + const char *path; + char buf[PATH_MAX + 1]; + ssize_t len; + + path = mp_obj_str_get_str(path_obj); + + len = readlink(path, buf, sizeof(buf)); + if (len < 0) { + mp_raise_OSError(errno); + } else if (len > PATH_MAX) { + mp_raise_OSError(ENAMETOOLONG); + } + buf[len] = 0; + + return mp_obj_new_str_from_cstr(buf); +} +static MP_DEFINE_CONST_FUN_OBJ_1(posixmod_readlink_obj, posixmod_readlink); + + +static mp_obj_t posixmod_write(mp_obj_t fd_obj, mp_obj_t str_obj) { + int fd; + ssize_t written; + mp_buffer_info_t bufinfo; + + fd = mp_obj_get_int(fd_obj); + mp_get_buffer_raise(str_obj, &bufinfo, MP_BUFFER_READ); + + written = write(fd, bufinfo.buf, bufinfo.len); + if (written < 0) { + mp_raise_OSError(errno); + } + + return MP_OBJ_NEW_INT_LL(written); +} +static MP_DEFINE_CONST_FUN_OBJ_2(posixmod_write_obj, posixmod_write); + + +#define DEFINE_FUN_FOR_MACRO(name, type) \ + static mp_obj_t posixmod_##name(mp_obj_t in) { \ + return mp_obj_new_##type(name(mp_obj_get_int(in))); \ + } \ + static MP_DEFINE_CONST_FUN_OBJ_1(posixmod_##name##_obj, posixmod_##name) + +/* + +*/ +DEFINE_FUN_FOR_MACRO(S_ISBLK, bool); +DEFINE_FUN_FOR_MACRO(S_ISCHR, bool); +DEFINE_FUN_FOR_MACRO(S_ISDIR, bool); +DEFINE_FUN_FOR_MACRO(S_ISFIFO, bool); +DEFINE_FUN_FOR_MACRO(S_ISREG, bool); +DEFINE_FUN_FOR_MACRO(S_ISLNK, bool); +DEFINE_FUN_FOR_MACRO(S_ISSOCK, bool); + +/* + +*/ +DEFINE_FUN_FOR_MACRO(WIFEXITED, bool); +DEFINE_FUN_FOR_MACRO(WEXITSTATUS, int); +DEFINE_FUN_FOR_MACRO(WIFSIGNALED, bool); +DEFINE_FUN_FOR_MACRO(WTERMSIG, int); +DEFINE_FUN_FOR_MACRO(WIFSTOPPED, bool); +DEFINE_FUN_FOR_MACRO(WSTOPSIG, int); +DEFINE_FUN_FOR_MACRO(WIFCONTINUED, bool); + +#undef DEFINE_FUN_FOR_MACRO + +static const mp_rom_map_elem_t posixmod_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_posix) }, +#define F(name) { MP_ROM_QSTR(MP_QSTR_##name), \ + MP_ROM_PTR(&posixmod_##name##_obj) } + /* extern char **environ; */ + F(_environ), + + /* */ + F(fcntl), + F(open), + + /* */ + F(gai_strerror), + F(getaddrinfo), + F(getnameinfo), + + /* */ + F(signal), + F(kill), + + /* */ + F(strerror), + + /* */ + F(getpeername), + F(getsockname), + F(shutdown), + + /* */ + F(lstat), + F(stat), + + /* */ + F(uname), + + /* */ + F(waitpid), + + /* */ + F(_exit), + F(access), + F(close), + F(dup), + F(dup2), + F(execv), + F(execve), + F(execvp), + F(execvpe), + F(fork), + F(getuid), + F(isatty), + F(pipe), + F(read), + F(readlink), + F(write), + + /* function-like macros */ + F(S_ISBLK), + F(S_ISCHR), + F(S_ISDIR), + F(S_ISFIFO), + F(S_ISREG), + F(S_ISLNK), + F(S_ISSOCK), + + /* function-like macros */ + F(WIFEXITED), + F(WEXITSTATUS), + F(WIFSIGNALED), + F(WTERMSIG), + F(WIFSTOPPED), + F(WSTOPSIG), + F(WIFCONTINUED), +#undef F + +#define C(name) { MP_ROM_QSTR(MP_QSTR_##name), MP_ROM_INT(name) } + /* */ + C(E2BIG), + C(EACCES), + C(EADDRINUSE), + C(EADDRNOTAVAIL), + C(EAFNOSUPPORT), + C(EAGAIN), + C(EALREADY), + C(EBADF), + C(EBADMSG), + C(EBUSY), + C(ECANCELED), + C(ECHILD), + C(ECONNABORTED), + C(ECONNREFUSED), + C(ECONNRESET), + C(EDEADLK), + C(EDESTADDRREQ), + C(EDOM), + C(EDQUOT), + C(EEXIST), + C(EFAULT), + C(EFBIG), + C(EHOSTUNREACH), + C(EIDRM), + C(EILSEQ), + C(EINPROGRESS), + C(EINTR), + C(EINVAL), + C(EIO), + C(EISCONN), + C(EISDIR), + C(ELOOP), + C(EMFILE), + C(EMLINK), + C(EMSGSIZE), + C(EMULTIHOP), + C(ENAMETOOLONG), + C(ENETDOWN), + C(ENETRESET), + C(ENETUNREACH), + C(ENFILE), + C(ENOBUFS), + C(ENODATA), + C(ENODEV), + C(ENOENT), + C(ENOEXEC), + C(ENOLCK), + C(ENOLINK), + C(ENOMEM), + C(ENOMSG), + C(ENOPROTOOPT), + C(ENOSPC), + C(ENOSR), + C(ENOSTR), + C(ENOSYS), + C(ENOTCONN), + C(ENOTDIR), + C(ENOTEMPTY), + C(ENOTRECOVERABLE), + C(ENOTSOCK), + C(ENOTSUP), + C(ENOTTY), + C(ENXIO), + C(EOPNOTSUPP), + C(EOVERFLOW), + C(EOWNERDEAD), + C(EPERM), + C(EPIPE), + C(EPROTO), + C(EPROTONOSUPPORT), + C(EPROTOTYPE), + C(ERANGE), + C(EROFS), + C(ESPIPE), + C(ESRCH), + C(ESTALE), + C(ETIME), + C(ETIMEDOUT), + C(ETXTBSY), + C(EWOULDBLOCK), + C(EXDEV), + + /* */ + C(F_DUPFD), + C(F_GETFD), + C(F_SETFD), + C(F_GETFL), + C(F_SETFL), + C(F_GETOWN), + C(F_SETOWN), + C(FD_CLOEXEC), + C(O_CREAT), + C(O_EXCL), + C(O_NOCTTY), + C(O_TRUNC), + C(O_APPEND), + C(O_DSYNC), + C(O_NONBLOCK), + C(O_RSYNC), + C(O_SYNC), + C(O_ACCMODE), + C(O_RDONLY), + C(O_RDWR), + C(O_WRONLY), + + /* */ + C(AI_PASSIVE), + C(AI_CANONNAME), + C(AI_NUMERICHOST), + C(AI_NUMERICSERV), + C(AI_V4MAPPED), + C(AI_ALL), + C(AI_ADDRCONFIG), + C(NI_NOFQDN), + C(NI_NUMERICHOST), + C(NI_NAMEREQD), + C(NI_NUMERICSERV), +#ifdef NI_NUMERICSCOPE /* IEEE Std 1003.1-2001/Cor 1-2002 */ + C(NI_NUMERICSCOPE), +#endif + C(NI_DGRAM), + C(EAI_AGAIN), + C(EAI_BADFLAGS), + C(EAI_FAIL), + C(EAI_FAMILY), + C(EAI_MEMORY), + C(EAI_NONAME), + C(EAI_SERVICE), + C(EAI_SOCKTYPE), + C(EAI_SYSTEM), + C(EAI_OVERFLOW), + + /* */ + C(SIG_IGN), + C(SIG_DFL), + C(SIGABRT), + C(SIGALRM), + C(SIGBUS), + C(SIGCHLD), + C(SIGCONT), + C(SIGFPE), + C(SIGHUP), + C(SIGILL), + C(SIGINT), + C(SIGKILL), + C(SIGPIPE), + C(SIGQUIT), + C(SIGSEGV), + C(SIGSTOP), + C(SIGTERM), + C(SIGTSTP), + C(SIGTTIN), + C(SIGTTOU), + C(SIGUSR1), + C(SIGUSR2), + C(SIGPOLL), + C(SIGPROF), + C(SIGSYS), + C(SIGTRAP), + C(SIGURG), + C(SIGVTALRM), + C(SIGXCPU), + C(SIGXFSZ), + + /* */ + C(BUFSIZ), + + /* */ + C(AF_INET), + C(AF_INET6), + C(AF_UNIX), + C(AF_UNSPEC), + C(SHUT_RD), + C(SHUT_RDWR), + C(SHUT_WR), + C(SOCK_DGRAM), + C(SOCK_RAW), + C(SOCK_SEQPACKET), + C(SOCK_STREAM), + C(SOL_SOCKET), + C(SO_ACCEPTCONN), +#ifdef SO_BINDTODEVICE /* non-POSIX, Linux >= 2.0.30 */ + C(SO_BINDTODEVICE), +#endif + C(SO_BROADCAST), + C(SO_DEBUG), + C(SO_DONTROUTE), + C(SO_ERROR), + C(SO_KEEPALIVE), + C(SO_LINGER), + C(SO_OOBINLINE), + C(SO_RCVBUF), + C(SO_RCVLOWAT), + C(SO_RCVTIMEO), + C(SO_REUSEADDR), +#ifdef SO_REUSEPORT /* non-POSIX, Linux >= 3.9 */ + C(SO_REUSEPORT), +#endif + C(SO_SNDBUF), + C(SO_SNDLOWAT), + C(SO_SNDTIMEO), + C(SO_TYPE), + C(SOMAXCONN), + C(MSG_CTRUNC), + C(MSG_DONTROUTE), + C(MSG_EOR), + C(MSG_OOB), + C(MSG_PEEK), + C(MSG_TRUNC), + C(MSG_WAITALL), + + /* */ + C(S_IFMT), + C(S_IFBLK), + C(S_IFCHR), + C(S_IFIFO), + C(S_IFREG), + C(S_IFDIR), + C(S_IFLNK), + C(S_IFSOCK), + C(S_IRWXU), + C(S_IRUSR), + C(S_IWUSR), + C(S_IXUSR), + C(S_IRWXG), + C(S_IRGRP), + C(S_IWGRP), + C(S_IXGRP), + C(S_IRWXO), + C(S_IROTH), + C(S_IWOTH), + C(S_IXOTH), + C(S_ISUID), + C(S_ISGID), + C(S_ISVTX), + + /* */ + C(WNOHANG), + C(WUNTRACED), + C(WEXITED), + C(WSTOPPED), + C(WCONTINUED), + C(WNOWAIT), + + /* */ + C(IPPROTO_IP), + C(IPPROTO_IPV6), + C(IPPROTO_ICMP), + C(IPPROTO_RAW), + C(IPPROTO_TCP), + C(IPPROTO_UDP), + C(INADDR_ANY), + C(INADDR_BROADCAST), + C(INET_ADDRSTRLEN), + C(INET6_ADDRSTRLEN), + C(IPV6_JOIN_GROUP), + C(IPV6_LEAVE_GROUP), + C(IPV6_MULTICAST_HOPS), + C(IPV6_MULTICAST_IF), + C(IPV6_MULTICAST_LOOP), + C(IPV6_UNICAST_HOPS), + C(IPV6_V6ONLY), + + /* */ + C(TCP_NODELAY), + + /* */ + C(STDIN_FILENO), + C(STDOUT_FILENO), + C(STDERR_FILENO), + C(F_OK), + C(R_OK), + C(W_OK), + C(X_OK), +#undef C +}; +static MP_DEFINE_CONST_DICT(posixmod_module_globals, + posixmod_module_globals_table); + + +const mp_obj_module_t posixmod_user_cmodule = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *) &posixmod_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR__posix, posixmod_user_cmodule); diff --git a/micropython/deps.mk b/micropython/deps.mk new file mode 100644 index 0000000..32421fc --- /dev/null +++ b/micropython/deps.mk @@ -0,0 +1,11 @@ +MICROPYTHON_VERSION = 1.26.1 +MICROPYTHON_DIRNAME = micropython-$(MICROPYTHON_VERSION) +MICROPYTHON_TARNAME = $(MICROPYTHON_DIRNAME).tar.xz +MICROPYTHON_SRCURL = https://github.com/micropython/micropython/releases/download/v$(MICROPYTHON_VERSION)/$(MICROPYTHON_TARNAME) +MICROPYTHON_SHA256 = 12be6514df6272c0fcb328122b534af6b12abdd52435c19f40ee1707cc43ac98 + +LXPACK_VERSION = 1.0.0 +LXPACK_DIRNAME = lxpack-$(LXPACK_VERSION) +LXPACK_TARNAME = $(LXPACK_DIRNAME).tar.xz +LXPACK_SRCURL = https://github.com/MikeWang000000/lxpack/releases/download/$(LXPACK_VERSION)/$(LXPACK_TARNAME) +LXPACK_SHA256 = 639414c7525b40dff12e4791e53e2d8d58d76acb9dc2ea660c371aae8eefe775 diff --git a/micropython/patch/ports_unix_main_c.patch b/micropython/patch/ports_unix_main_c.patch new file mode 100644 index 0000000..0e67feb --- /dev/null +++ b/micropython/patch/ports_unix_main_c.patch @@ -0,0 +1,82 @@ +diff --git a/ports/unix/main.c b/ports/unix/main.c +index 530e20a..c926e54 100644 +--- a/ports/unix/main.c ++++ b/ports/unix/main.c +@@ -37,6 +37,7 @@ + #include + #include + #include ++#include + + #include "py/compile.h" + #include "py/runtime.h" +@@ -509,7 +510,25 @@ MP_NOINLINE int main_(int argc, char **argv) { + signal(SIGPIPE, SIG_IGN); + #endif + +- pre_process_options(argc, argv); ++ int disable_natter = 0; ++ ++ size_t argv0_len = strlen(argv[0]); ++ char *basebuf = malloc(argv0_len + 1); ++ if (!basebuf) { ++ perror(argv[0]); ++ return 1; ++ } ++ memcpy(basebuf, argv[0], argv0_len + 1); ++ char *progname = basename(basebuf); ++ if (strncmp(progname, "micropython", strlen("micropython")) == 0 || ++ strncmp(progname, "python", strlen("python")) == 0) { ++ disable_natter = 1; ++ } ++ free(basebuf); ++ ++ if (disable_natter) { ++ pre_process_options(argc, argv); ++ } + + #if MICROPY_ENABLE_GC + #if !MICROPY_GC_SPLIT_HEAP +@@ -643,7 +662,41 @@ MP_NOINLINE int main_(int argc, char **argv) { + const int NOTHING_EXECUTED = -2; + int ret = NOTHING_EXECUTED; + bool inspect = false; +- for (int a = 1; a < argc; a++) { ++ ++ if (!disable_natter) { ++ // Run Natter from frozen module 'natter'. ++ mp_sys_path = mp_obj_new_list(0, NULL); ++ #ifdef MICROPY_MODULE_FROZEN ++ mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR__dot_frozen)); ++ #endif ++ ++ mp_obj_t import_args[4]; ++ import_args[0] = MP_OBJ_NEW_QSTR(MP_QSTR_natter); ++ import_args[1] = mp_const_none; ++ import_args[2] = mp_const_none; ++ import_args[3] = mp_const_false; ++ set_sys_argv(argv, argc, 0); ++ ++ #if MICROPY_GC_ALLOC_THRESHOLD ++ MP_STATE_MEM(gc_alloc_threshold) = 8 * 1024 * 1024 / ++ MICROPY_BYTES_PER_GC_BLOCK; ++ #endif ++ ++ mp_hal_set_interrupt_char(CHAR_CTRL_C); ++ nlr_buf_t nlr; ++ if (nlr_push(&nlr) == 0) { ++ mp_builtin___import__(MP_ARRAY_SIZE(import_args), import_args); ++ mp_hal_set_interrupt_char(-1); ++ mp_handle_pending(true); ++ nlr_pop(); ++ ret = 0; ++ } else { ++ // uncaught exception ++ mp_hal_set_interrupt_char(-1); ++ mp_handle_pending(false); ++ ret = handle_uncaught_exception(nlr.ret_val) & 0xff; ++ } ++ } else for (volatile int a = 1; a < argc; a++) { + if (argv[a][0] == '-') { + if (strcmp(argv[a], "-i") == 0) { + inspect = true; diff --git a/micropython/patch/py_moderrno_c.patch b/micropython/patch/py_moderrno_c.patch new file mode 100644 index 0000000..8ffdcd6 --- /dev/null +++ b/micropython/patch/py_moderrno_c.patch @@ -0,0 +1,19 @@ +diff --git a/py/moderrno.c b/py/moderrno.c +index 58a141c1..995086db 100644 +--- a/py/moderrno.c ++++ b/py/moderrno.c +@@ -102,6 +102,14 @@ const mp_obj_module_t mp_module_errno = { + MP_REGISTER_EXTENSIBLE_MODULE(MP_QSTR_errno, mp_module_errno); + + qstr mp_errno_to_str(mp_obj_t errno_val) { ++ // Query strerror() first to get a human-readable message ++ mp_int_t errno_int; ++ if (mp_obj_get_int_maybe(errno_val, &errno_int)) { ++ char *msg = strerror(errno_int); ++ if (msg) { ++ return qstr_from_str(msg); ++ } ++ } + #if MICROPY_PY_ERRNO_ERRORCODE + // We have the errorcode dict so can do a lookup using the hash map + mp_map_elem_t *elem = mp_map_lookup((mp_map_t *)&errorcode_dict.map, errno_val, MP_MAP_LOOKUP); diff --git a/micropython/pymodule/LICENSE.cpython b/micropython/pymodule/LICENSE.cpython new file mode 100644 index 0000000..e99408b --- /dev/null +++ b/micropython/pymodule/LICENSE.cpython @@ -0,0 +1,255 @@ +A. HISTORY OF THE SOFTWARE +========================== + +Python was created in the early 1990s by Guido van Rossum at Stichting +Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands +as a successor of a language called ABC. Guido remains Python's +principal author, although it includes many contributions from others. + +In 1995, Guido continued his work on Python at the Corporation for +National Research Initiatives (CNRI, see http://www.cnri.reston.va.us) +in Reston, Virginia where he released several versions of the +software. + +In May 2000, Guido and the Python core development team moved to +BeOpen.com to form the BeOpen PythonLabs team. In October of the same +year, the PythonLabs team moved to Digital Creations (now Zope +Corporation, see http://www.zope.com). In 2001, the Python Software +Foundation (PSF, see http://www.python.org/psf/) was formed, a +non-profit organization created specifically to own Python-related +Intellectual Property. Zope Corporation is a sponsoring member of +the PSF. + +All Python releases are Open Source (see http://www.opensource.org for +the Open Source Definition). Historically, most, but not all, Python +releases have also been GPL-compatible; the table below summarizes +the various releases. + + Release Derived Year Owner GPL- + from compatible? (1) + + 0.9.0 thru 1.2 1991-1995 CWI yes + 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes + 1.6 1.5.2 2000 CNRI no + 2.0 1.6 2000 BeOpen.com no + 1.6.1 1.6 2001 CNRI yes (2) + 2.1 2.0+1.6.1 2001 PSF no + 2.0.1 2.0+1.6.1 2001 PSF yes + 2.1.1 2.1+2.0.1 2001 PSF yes + 2.1.2 2.1.1 2002 PSF yes + 2.1.3 2.1.2 2002 PSF yes + 2.2 and above 2.1.1 2001-now PSF yes + +Footnotes: + +(1) GPL-compatible doesn't mean that we're distributing Python under + the GPL. All Python licenses, unlike the GPL, let you distribute + a modified version without making your changes open source. The + GPL-compatible licenses make it possible to combine Python with + other software that is released under the GPL; the others don't. + +(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, + because its license has a choice of law clause. According to + CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 + is "not incompatible" with the GPL. + +Thanks to the many outside volunteers who have worked under Guido's +direction to make these releases possible. + + +B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON +=============================================================== + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, +2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 Python Software +Foundation; All Rights Reserved" are retained in Python alone or in any +derivative version prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 +------------------------------------------- + +BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 + +1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an +office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the +Individual or Organization ("Licensee") accessing and otherwise using +this software in source or binary form and its associated +documentation ("the Software"). + +2. Subject to the terms and conditions of this BeOpen Python License +Agreement, BeOpen hereby grants Licensee a non-exclusive, +royalty-free, world-wide license to reproduce, analyze, test, perform +and/or display publicly, prepare derivative works, distribute, and +otherwise use the Software alone or in any derivative version, +provided, however, that the BeOpen Python License is retained in the +Software, alone or in any derivative version prepared by Licensee. + +3. BeOpen is making the Software available to Licensee on an "AS IS" +basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE +SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS +AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY +DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +5. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +6. This License Agreement shall be governed by and interpreted in all +respects by the law of the State of California, excluding conflict of +law provisions. Nothing in this License Agreement shall be deemed to +create any relationship of agency, partnership, or joint venture +between BeOpen and Licensee. This License Agreement does not grant +permission to use BeOpen trademarks or trade names in a trademark +sense to endorse or promote products or services of Licensee, or any +third party. As an exception, the "BeOpen Python" logos available at +http://www.pythonlabs.com/logos.html may be used according to the +permissions granted on that web page. + +7. By copying, installing or otherwise using the software, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 +--------------------------------------- + +1. This LICENSE AGREEMENT is between the Corporation for National +Research Initiatives, having an office at 1895 Preston White Drive, +Reston, VA 20191 ("CNRI"), and the Individual or Organization +("Licensee") accessing and otherwise using Python 1.6.1 software in +source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, CNRI +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python 1.6.1 +alone or in any derivative version, provided, however, that CNRI's +License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) +1995-2001 Corporation for National Research Initiatives; All Rights +Reserved" are retained in Python 1.6.1 alone or in any derivative +version prepared by Licensee. Alternately, in lieu of CNRI's License +Agreement, Licensee may substitute the following text (omitting the +quotes): "Python 1.6.1 is made available subject to the terms and +conditions in CNRI's License Agreement. This Agreement together with +Python 1.6.1 may be located on the Internet using the following +unique, persistent identifier (known as a handle): 1895.22/1013. This +Agreement may also be obtained from a proxy server on the Internet +using the following URL: http://hdl.handle.net/1895.22/1013". + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python 1.6.1 or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python 1.6.1. + +4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" +basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. This License Agreement shall be governed by the federal +intellectual property law of the United States, including without +limitation the federal copyright law, and, to the extent such +U.S. federal law does not apply, by the law of the Commonwealth of +Virginia, excluding Virginia's conflict of law provisions. +Notwithstanding the foregoing, with regard to derivative works based +on Python 1.6.1 that incorporate non-separable material that was +previously distributed under the GNU General Public License (GPL), the +law of the Commonwealth of Virginia shall govern this License +Agreement only as to issues arising under or with respect to +Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this +License Agreement shall be deemed to create any relationship of +agency, partnership, or joint venture between CNRI and Licensee. This +License Agreement does not grant permission to use CNRI trademarks or +trade name in a trademark sense to endorse or promote products or +services of Licensee, or any third party. + +8. By clicking on the "ACCEPT" button where indicated, or by copying, +installing or otherwise using Python 1.6.1, Licensee agrees to be +bound by the terms and conditions of this License Agreement. + + ACCEPT + + +CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 +-------------------------------------------------- + +Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, +The Netherlands. All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/micropython/pymodule/_manifest.py b/micropython/pymodule/_manifest.py new file mode 100644 index 0000000..3e42363 --- /dev/null +++ b/micropython/pymodule/_manifest.py @@ -0,0 +1,13 @@ +def __manifest_add_modules(): + import os + for name in os.listdir('.'): + if name.endswith('.py') and \ + not name.startswith('.') and \ + not name.startswith('_'): + module(name) + + +module('natter.py', base_path='../..') +__manifest_add_modules() + +del __manifest_add_modules diff --git a/micropython/pymodule/argparse.py b/micropython/pymodule/argparse.py new file mode 100644 index 0000000..03cfe21 --- /dev/null +++ b/micropython/pymodule/argparse.py @@ -0,0 +1,2428 @@ +# Modified from CPython 3.4.10 "argparse" module for MicroPython +# Original Author: Steven J. Bethard . + +"""Command-line parsing library + +This module is an optparse-inspired command-line parsing library that: + + - handles both optional and positional arguments + - produces highly informative usage messages + - supports parsers that dispatch to sub-parsers + +The following is a simple usage example that sums integers from the +command-line and writes the result to a file:: + + parser = argparse.ArgumentParser( + description='sum the integers at the command line') + parser.add_argument( + 'integers', metavar='int', nargs='+', type=int, + help='an integer to be summed') + parser.add_argument( + '--log', default=sys.stdout, type=argparse.FileType('w'), + help='the file where the sum should be written') + args = parser.parse_args() + args.log.write('%s' % sum(args.integers)) + args.log.close() + +The module contains the following public classes: + + - ArgumentParser -- The main entry point for command-line parsing. As the + example above shows, the add_argument() method is used to populate + the parser with actions for optional and positional arguments. Then + the parse_args() method is invoked to convert the args at the + command-line into an object with attributes. + + - ArgumentError -- The exception raised by ArgumentParser objects when + there are errors with the parser's actions. Errors raised while + parsing the command-line are caught by ArgumentParser and emitted + as command-line messages. + + - FileType -- A factory for defining types of files to be created. As the + example above shows, instances of FileType are typically passed as + the type= argument of add_argument() calls. + + - Action -- The base class for parser actions. Typically actions are + selected by passing strings like 'store_true' or 'append_const' to + the action= argument of add_argument(). However, for greater + customization of ArgumentParser actions, subclasses of Action may + be defined and passed as the action= argument. + + - HelpFormatter, RawDescriptionHelpFormatter, RawTextHelpFormatter, + ArgumentDefaultsHelpFormatter -- Formatter classes which + may be passed as the formatter_class= argument to the + ArgumentParser constructor. HelpFormatter is the default, + RawDescriptionHelpFormatter and RawTextHelpFormatter tell the parser + not to change the formatting for help text, and + ArgumentDefaultsHelpFormatter adds information about argument defaults + to the help. + +All other classes in this module are considered implementation details. +(Also note that HelpFormatter and RawDescriptionHelpFormatter are only +considered public as object names -- the API of the formatter objects is +still considered an implementation detail.) +""" + +__version__ = '1.1' +__all__ = [ + 'ArgumentParser', + 'ArgumentError', + 'ArgumentTypeError', + 'FileType', + 'HelpFormatter', + 'ArgumentDefaultsHelpFormatter', + 'RawDescriptionHelpFormatter', + 'RawTextHelpFormatter', + 'MetavarTypeHelpFormatter', + 'Namespace', + 'Action', + 'ONE_OR_MORE', + 'OPTIONAL', + 'PARSER', + 'REMAINDER', + 'SUPPRESS', + 'ZERO_OR_MORE', +] + +import sys as _usys +_path = _usys.path +_usys.path = () +try: + import collections as _ucollections + import os as _uos + import re as _ure +finally: + _usys.path = _path + del _path + + +SUPPRESS = '==SUPPRESS==' + +OPTIONAL = '?' +ZERO_OR_MORE = '*' +ONE_OR_MORE = '+' +PARSER = 'A...' +REMAINDER = '...' +_UNRECOGNIZED_ARGS_ATTR = '_unrecognized_args' + +# ============================= +# Utility functions and classes +# ============================= + +class _AttributeHolder(object): + """Abstract base class that provides __repr__. + + The __repr__ method returns a string in the format:: + ClassName(attr=name, attr=name, ...) + The attributes are determined either by a class-level attribute, + '_kwarg_names', or by inspecting the instance __dict__. + """ + + def __repr__(self): + type_name = type(self).__name__ + arg_strings = [] + for arg in self._get_args(): + arg_strings.append(repr(arg)) + for name, value in self._get_kwargs(): + arg_strings.append('%s=%r' % (name, value)) + return '%s(%s)' % (type_name, ', '.join(arg_strings)) + + def _get_kwargs(self): + return sorted(self.__dict__.items()) + + def _get_args(self): + return [] + + +def ngettext(a, b, l): + if l == 1: + return a + return b + + +def _(a): + return a + + +def _ensure_value(namespace, name, value): + if getattr(namespace, name, None) is None: + setattr(namespace, name, value) + return getattr(namespace, name) + + +# =============== +# Formatting Help +# =============== + +class HelpFormatter(object): + """Formatter for generating usage messages and argument help strings. + + Only the name of this class is considered a public API. All the methods + provided by the class are considered an implementation detail. + """ + + def __init__(self, + prog, + indent_increment=2, + max_help_position=24, + width=None): + + # default setting for width + if width is None: + try: + width = int(_uos.getenv('COLUMNS', '80')) + except (KeyError, ValueError): + width = 80 + width -= 2 + + self._prog = prog + self._indent_increment = indent_increment + self._max_help_position = max_help_position + self._max_help_position = min(max_help_position, + max(width - 20, indent_increment * 2)) + self._width = width + + self._current_indent = 0 + self._level = 0 + self._action_max_length = 0 + + self._root_section = self._Section(self, None) + self._current_section = self._root_section + + self._whitespace_matcher = _ure.compile('\\s+') + self._long_break_matcher = _ure.compile('\n\n\n+') + + # =============================== + # Section and indentation methods + # =============================== + def _indent(self): + self._current_indent += self._indent_increment + self._level += 1 + + def _dedent(self): + self._current_indent -= self._indent_increment + assert self._current_indent >= 0, 'Indent decreased below 0.' + self._level -= 1 + + class _Section(object): + + def __init__(self, formatter, parent, heading=None): + self.formatter = formatter + self.parent = parent + self.heading = heading + self.items = [] + + def format_help(self): + # format the indented section + if self.parent is not None: + self.formatter._indent() + join = self.formatter._join_parts + for func, args in self.items: + func(*args) + item_help = join([func(*args) for func, args in self.items]) + if self.parent is not None: + self.formatter._dedent() + + # return nothing if the section was empty + if not item_help: + return '' + + # add the heading if the section was non-empty + if self.heading is not SUPPRESS and self.heading is not None: + current_indent = self.formatter._current_indent + heading = '%*s%s:\n' % (current_indent, '', self.heading) + else: + heading = '' + + # join the section-initial newline, the heading and the help + return join(['\n', heading, item_help, '\n']) + + def _add_item(self, func, args): + self._current_section.items.append((func, args)) + + # ======================== + # Message building methods + # ======================== + def start_section(self, heading): + self._indent() + section = self._Section(self, self._current_section, heading) + self._add_item(section.format_help, []) + self._current_section = section + + def end_section(self): + self._current_section = self._current_section.parent + self._dedent() + + def add_text(self, text): + if text is not SUPPRESS and text is not None: + self._add_item(self._format_text, [text]) + + def add_usage(self, usage, actions, groups, prefix=None): + if usage is not SUPPRESS: + args = usage, actions, groups, prefix + self._add_item(self._format_usage, args) + + def add_argument(self, action): + if action.help is not SUPPRESS: + + # find all invocations + get_invocation = self._format_action_invocation + invocations = [get_invocation(action)] + for subaction in self._iter_indented_subactions(action): + invocations.append(get_invocation(subaction)) + + # update the maximum item length + invocation_length = max([len(s) for s in invocations]) + action_length = invocation_length + self._current_indent + self._action_max_length = max(self._action_max_length, + action_length) + + # add the item to the list + self._add_item(self._format_action, [action]) + + def add_arguments(self, actions): + for action in actions: + self.add_argument(action) + + # ======================= + # Help-formatting methods + # ======================= + def format_help(self): + help = self._root_section.format_help() + if help: + help = self._long_break_matcher.sub('\n\n', help) + help = help.strip('\n') + '\n' + return help + + def _findall(self, pattern, string): + if hasattr(_ure, 'findall'): + return _ure.findall(pattern, string) + groups = [] + def cb(m): + match_str = m.group(0) + groups.append(match_str) + return match_str + _ure.sub(pattern, cb, string) + return groups + + def _join_parts(self, part_strings): + return ''.join([part + for part in part_strings + if part and part is not SUPPRESS]) + + def _format_usage(self, usage, actions, groups, prefix): + if prefix is None: + prefix = _('usage: ') + + # if usage is specified, use that + if usage is not None: + usage = usage % dict(prog=self._prog) + + # if no optionals or positionals are available, usage is just prog + elif usage is None and not actions: + usage = '%(prog)s' % dict(prog=self._prog) + + # if optionals and positionals are available, calculate usage + elif usage is None: + prog = '%(prog)s' % dict(prog=self._prog) + + # split optionals from positionals + optionals = [] + positionals = [] + for action in actions: + if action.option_strings: + optionals.append(action) + else: + positionals.append(action) + + # build full usage string + format = self._format_actions_usage + action_usage = format(optionals + positionals, groups) + usage = ' '.join([s for s in [prog, action_usage] if s]) + + # wrap the usage parts if it's too long + text_width = self._width - self._current_indent + if len(prefix) + len(usage) > text_width: + + # break usage into wrappable parts + part_regexp = r'\(.*?\)+|\[.*?\]+|\S+' + opt_usage = format(optionals, groups) + pos_usage = format(positionals, groups) + opt_parts = self._findall(part_regexp, opt_usage) + pos_parts = self._findall(part_regexp, pos_usage) + assert ' '.join(opt_parts) == opt_usage + assert ' '.join(pos_parts) == pos_usage + + # helper for wrapping lines + def get_lines(parts, indent, prefix=None): + lines = [] + line = [] + if prefix is not None: + line_len = len(prefix) - 1 + else: + line_len = len(indent) - 1 + for part in parts: + if line_len + 1 + len(part) > text_width and line: + lines.append(indent + ' '.join(line)) + line = [] + line_len = len(indent) - 1 + line.append(part) + line_len += len(part) + 1 + if line: + lines.append(indent + ' '.join(line)) + if prefix is not None: + lines[0] = lines[0][len(indent):] + return lines + + # if prog is short, follow it with optionals or positionals + if len(prefix) + len(prog) <= 0.75 * text_width: + indent = ' ' * (len(prefix) + len(prog) + 1) + if opt_parts: + lines = get_lines([prog] + opt_parts, indent, prefix) + lines.extend(get_lines(pos_parts, indent)) + elif pos_parts: + lines = get_lines([prog] + pos_parts, indent, prefix) + else: + lines = [prog] + + # if prog is long, put it on its own line + else: + indent = ' ' * len(prefix) + parts = opt_parts + pos_parts + lines = get_lines(parts, indent) + if len(lines) > 1: + lines = [] + lines.extend(get_lines(opt_parts, indent)) + lines.extend(get_lines(pos_parts, indent)) + lines = [prog] + lines + + # join lines into usage + usage = '\n'.join(lines) + + # prefix with 'usage:' + return '%s%s\n\n' % (prefix, usage) + + def _format_actions_usage(self, actions, groups): + # find group indices and identify actions in groups + group_actions = set() + inserts = {} + for group in groups: + try: + start = actions.index(group._group_actions[0]) + except ValueError: + continue + else: + end = start + len(group._group_actions) + if actions[start:end] == group._group_actions: + for action in group._group_actions: + group_actions.add(action) + if not group.required: + if start in inserts: + inserts[start] += ' [' + else: + inserts[start] = '[' + inserts[end] = ']' + else: + if start in inserts: + inserts[start] += ' (' + else: + inserts[start] = '(' + inserts[end] = ')' + for i in range(start + 1, end): + inserts[i] = '|' + + # collect all actions format strings + parts = [] + for i, action in enumerate(actions): + + # suppressed arguments are marked with None + # remove | separators for suppressed arguments + if action.help is SUPPRESS: + parts.append(None) + if inserts.get(i) == '|': + inserts.pop(i) + elif inserts.get(i + 1) == '|': + inserts.pop(i + 1) + + # produce all arg strings + elif not action.option_strings: + default = self._get_default_metavar_for_positional(action) + part = self._format_args(action, default) + + # if it's in a group, strip the outer [] + if action in group_actions: + if part[0] == '[' and part[-1] == ']': + part = part[1:-1] + + # add the action string to the list + parts.append(part) + + # produce the first way to invoke the option in brackets + else: + option_string = action.option_strings[0] + + # if the Optional doesn't take a value, format is: + # -s or --long + if action.nargs == 0: + part = '%s' % option_string + + # if the Optional takes a value, format is: + # -s ARGS or --long ARGS + else: + default = self._get_default_metavar_for_optional(action) + args_string = self._format_args(action, default) + part = '%s %s' % (option_string, args_string) + + # make it look optional if it's not required or in a group + if not action.required and action not in group_actions: + part = '[%s]' % part + + # add the action string to the list + parts.append(part) + + # insert things at the necessary indices + for i in sorted(inserts, reverse=True): + parts[i:i] = [inserts[i]] + + # join all the action items with spaces + text = ' '.join([item for item in parts if item is not None]) + + # clean up separators for mutually exclusive groups + open = r'[\[(]' + close = r'[\])]' + text = _ure.sub(r'(%s) ' % open, r'\1', text) + text = _ure.sub(r' (%s)' % close, r'\1', text) + text = _ure.sub(r'%s *%s' % (open, close), r'', text) + text = _ure.sub(r'\(([^|]*)\)', r'\1', text) + text = text.strip() + + # return the text + return text + + def _format_text(self, text): + if '%(prog)' in text: + text = text % dict(prog=self._prog) + text_width = max(self._width - self._current_indent, 11) + indent = ' ' * self._current_indent + return self._fill_text(text, text_width, indent) + '\n\n' + + def _format_action(self, action): + # determine the required width and the entry label + help_position = min(self._action_max_length + 2, + self._max_help_position) + help_width = max(self._width - help_position, 11) + action_width = help_position - self._current_indent - 2 + action_header = self._format_action_invocation(action) + + # no help; start on same line and add a final newline + if not action.help: + tup = self._current_indent, '', action_header + action_header = '%*s%s\n' % tup + + # short action name; start on the same line and pad two spaces + elif len(action_header) <= action_width: + tup = self._current_indent, '', action_width, action_header + action_header = '%*s%-*s ' % tup + indent_first = 0 + + # long action name; start on the next line + else: + tup = self._current_indent, '', action_header + action_header = '%*s%s\n' % tup + indent_first = help_position + + # collect the pieces of the action help + parts = [action_header] + + # if there was help for the action, add lines of help text + if action.help: + help_text = self._expand_help(action) + help_lines = self._split_lines(help_text, help_width) + parts.append('%*s%s\n' % (indent_first, '', help_lines[0])) + for line in help_lines[1:]: + parts.append('%*s%s\n' % (help_position, '', line)) + + # or add a newline if the description doesn't end with one + elif not action_header.endswith('\n'): + parts.append('\n') + + # if there are any sub-actions, add their help as well + for subaction in self._iter_indented_subactions(action): + parts.append(self._format_action(subaction)) + + # return a single string + return self._join_parts(parts) + + def _format_action_invocation(self, action): + if not action.option_strings: + default = self._get_default_metavar_for_positional(action) + metavar, = self._metavar_formatter(action, default)(1) + return metavar + + else: + parts = [] + + # if the Optional doesn't take a value, format is: + # -s, --long + if action.nargs == 0: + parts.extend(action.option_strings) + + # if the Optional takes a value, format is: + # -s ARGS, --long ARGS + else: + default = self._get_default_metavar_for_optional(action) + args_string = self._format_args(action, default) + for option_string in action.option_strings: + parts.append('%s %s' % (option_string, args_string)) + + return ', '.join(parts) + + def _metavar_formatter(self, action, default_metavar): + if action.metavar is not None: + result = action.metavar + elif action.choices is not None: + choice_strs = [str(choice) for choice in action.choices] + result = '{%s}' % ','.join(choice_strs) + else: + result = default_metavar + + def format(tuple_size): + if isinstance(result, tuple): + return result + else: + return (result, ) * tuple_size + return format + + def _format_args(self, action, default_metavar): + get_metavar = self._metavar_formatter(action, default_metavar) + if action.nargs is None: + result = '%s' % get_metavar(1) + elif action.nargs == OPTIONAL: + result = '[%s]' % get_metavar(1) + elif action.nargs == ZERO_OR_MORE: + result = '[%s [%s ...]]' % get_metavar(2) + elif action.nargs == ONE_OR_MORE: + result = '%s [%s ...]' % get_metavar(2) + elif action.nargs == REMAINDER: + result = '...' + elif action.nargs == PARSER: + result = '%s ...' % get_metavar(1) + else: + formats = ['%s' for _ in range(action.nargs)] + result = ' '.join(formats) % get_metavar(action.nargs) + return result + + def _expand_help(self, action): + params = dict(action.__dict__, prog=self._prog) + for name in list(params): + if params[name] is SUPPRESS: + del params[name] + for name in list(params): + if hasattr(params[name], '__name__'): + params[name] = params[name].__name__ + if params.get('choices') is not None: + choices_str = ', '.join([str(c) for c in params['choices']]) + params['choices'] = choices_str + return self._get_help_string(action) % params + + def _iter_indented_subactions(self, action): + try: + get_subactions = action._get_subactions + except AttributeError: + pass + else: + self._indent() + yield from get_subactions() + self._dedent() + + def _text_wrap(self, text, width): + lines = [] + while text: + line = text[:width] + pos = line.rfind(' ') + if len(text) > width and pos > 0: + line = line[:pos].rstrip(' ') + lines.append(line) + text = text[pos + 1:] + else: + lines.append(line) + text = text[width:] + return lines + + def _split_lines(self, text, width): + text = self._whitespace_matcher.sub(' ', text).strip() + return self._text_wrap(text, width) + + def _fill_text(self, text, width, indent): + text = self._whitespace_matcher.sub(' ', text).strip() + return '\n'.join(self._text_wrap(text, width)) + + def _get_help_string(self, action): + return action.help + + def _get_default_metavar_for_optional(self, action): + return action.dest.upper() + + def _get_default_metavar_for_positional(self, action): + return action.dest + + +class RawDescriptionHelpFormatter(HelpFormatter): + """Help message formatter which retains any formatting in descriptions. + + Only the name of this class is considered a public API. All the methods + provided by the class are considered an implementation detail. + """ + + def _fill_text(self, text, width, indent): + return ''.join(indent + line for line in text.splitlines(keepends=True)) + + +class RawTextHelpFormatter(RawDescriptionHelpFormatter): + """Help message formatter which retains formatting of all help text. + + Only the name of this class is considered a public API. All the methods + provided by the class are considered an implementation detail. + """ + + def _split_lines(self, text, width): + return text.splitlines() + + +class ArgumentDefaultsHelpFormatter(HelpFormatter): + """Help message formatter which adds default values to argument help. + + Only the name of this class is considered a public API. All the methods + provided by the class are considered an implementation detail. + """ + + def _get_help_string(self, action): + help = action.help + if '%(default)' not in action.help: + if action.default is not SUPPRESS: + defaulting_nargs = [OPTIONAL, ZERO_OR_MORE] + if action.option_strings or action.nargs in defaulting_nargs: + help += ' (default: %(default)s)' + return help + + +class MetavarTypeHelpFormatter(HelpFormatter): + """Help message formatter which uses the argument 'type' as the default + metavar value (instead of the argument 'dest') + + Only the name of this class is considered a public API. All the methods + provided by the class are considered an implementation detail. + """ + + def _get_default_metavar_for_optional(self, action): + return action.type.__name__ + + def _get_default_metavar_for_positional(self, action): + return action.type.__name__ + + + +# ===================== +# Options and Arguments +# ===================== + +def _get_action_name(argument): + if argument is None: + return None + elif argument.option_strings: + return '/'.join(argument.option_strings) + elif argument.metavar not in (None, SUPPRESS): + return argument.metavar + elif argument.dest not in (None, SUPPRESS): + return argument.dest + else: + return None + + +class ArgumentError(Exception): + """An error from creating or using an argument (optional or positional). + + The string value of this exception is the message, augmented with + information about the argument that caused it. + """ + + def __init__(self, argument, message): + self.argument_name = _get_action_name(argument) + self.message = message + + def __str__(self): + if self.argument_name is None: + format = '%(message)s' + else: + format = 'argument %(argument_name)s: %(message)s' + return format % dict(message=self.message, + argument_name=self.argument_name) + + +class ArgumentTypeError(Exception): + """An error from trying to convert a command line string to a type.""" + pass + + +# ============== +# Action classes +# ============== + +class Action(_AttributeHolder): + """Information about how to convert command line strings to Python objects. + + Action objects are used by an ArgumentParser to represent the information + needed to parse a single argument from one or more strings from the + command line. The keyword arguments to the Action constructor are also + all attributes of Action instances. + + Keyword Arguments: + + - option_strings -- A list of command-line option strings which + should be associated with this action. + + - dest -- The name of the attribute to hold the created object(s) + + - nargs -- The number of command-line arguments that should be + consumed. By default, one argument will be consumed and a single + value will be produced. Other values include: + - N (an integer) consumes N arguments (and produces a list) + - '?' consumes zero or one arguments + - '*' consumes zero or more arguments (and produces a list) + - '+' consumes one or more arguments (and produces a list) + Note that the difference between the default and nargs=1 is that + with the default, a single value will be produced, while with + nargs=1, a list containing a single value will be produced. + + - const -- The value to be produced if the option is specified and the + option uses an action that takes no values. + + - default -- The value to be produced if the option is not specified. + + - type -- A callable that accepts a single string argument, and + returns the converted value. The standard Python types str, int, + float, and complex are useful examples of such callables. If None, + str is used. + + - choices -- A container of values that should be allowed. If not None, + after a command-line argument has been converted to the appropriate + type, an exception will be raised if it is not a member of this + collection. + + - required -- True if the action must always be specified at the + command line. This is only meaningful for optional command-line + arguments. + + - help -- The help string describing the argument. + + - metavar -- The name to be used for the option's argument with the + help string. If None, the 'dest' value will be used as the name. + """ + + def __init__(self, + option_strings, + dest, + nargs=None, + const=None, + default=None, + type=None, + choices=None, + required=False, + help=None, + metavar=None): + self.option_strings = option_strings + self.dest = dest + self.nargs = nargs + self.const = const + self.default = default + self.type = type + self.choices = choices + self.required = required + self.help = help + self.metavar = metavar + + def _get_kwargs(self): + names = [ + 'option_strings', + 'dest', + 'nargs', + 'const', + 'default', + 'type', + 'choices', + 'help', + 'metavar', + ] + return [(name, getattr(self, name)) for name in names] + + def __call__(self, parser, namespace, values, option_string=None): + raise NotImplementedError(_('.__call__() not defined')) + + +class _StoreAction(Action): + + def __init__(self, + option_strings, + dest, + nargs=None, + const=None, + default=None, + type=None, + choices=None, + required=False, + help=None, + metavar=None): + if nargs == 0: + raise ValueError('nargs for store actions must be > 0; if you ' + 'have nothing to store, actions such as store ' + 'true or store const may be more appropriate') + if const is not None and nargs != OPTIONAL: + raise ValueError('nargs must be %r to supply const' % OPTIONAL) + super(_StoreAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=nargs, + const=const, + default=default, + type=type, + choices=choices, + required=required, + help=help, + metavar=metavar) + + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, values) + + +class _StoreConstAction(Action): + + def __init__(self, + option_strings, + dest, + const, + default=None, + required=False, + help=None, + metavar=None): + super(_StoreConstAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=0, + const=const, + default=default, + required=required, + help=help) + + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, self.const) + + +class _StoreTrueAction(_StoreConstAction): + + def __init__(self, + option_strings, + dest, + default=False, + required=False, + help=None): + super(_StoreTrueAction, self).__init__( + option_strings=option_strings, + dest=dest, + const=True, + default=default, + required=required, + help=help) + + +class _StoreFalseAction(_StoreConstAction): + + def __init__(self, + option_strings, + dest, + default=True, + required=False, + help=None): + super(_StoreFalseAction, self).__init__( + option_strings=option_strings, + dest=dest, + const=False, + default=default, + required=required, + help=help) + + +class _AppendAction(Action): + + def __init__(self, + option_strings, + dest, + nargs=None, + const=None, + default=None, + type=None, + choices=None, + required=False, + help=None, + metavar=None): + if nargs == 0: + raise ValueError('nargs for append actions must be > 0; if arg ' + 'strings are not supplying the value to append, ' + 'the append const action may be more appropriate') + if const is not None and nargs != OPTIONAL: + raise ValueError('nargs must be %r to supply const' % OPTIONAL) + super(_AppendAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=nargs, + const=const, + default=default, + type=type, + choices=choices, + required=required, + help=help, + metavar=metavar) + + def __call__(self, parser, namespace, values, option_string=None): + items = _ensure_value(namespace, self.dest, []).__copy__() + items.append(values) + setattr(namespace, self.dest, items) + + +class _AppendConstAction(Action): + + def __init__(self, + option_strings, + dest, + const, + default=None, + required=False, + help=None, + metavar=None): + super(_AppendConstAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=0, + const=const, + default=default, + required=required, + help=help, + metavar=metavar) + + def __call__(self, parser, namespace, values, option_string=None): + items = _ensure_value(namespace, self.dest, []).__copy__() + items.append(self.const) + setattr(namespace, self.dest, items) + + +class _CountAction(Action): + + def __init__(self, + option_strings, + dest, + default=None, + required=False, + help=None): + super(_CountAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=0, + default=default, + required=required, + help=help) + + def __call__(self, parser, namespace, values, option_string=None): + new_count = _ensure_value(namespace, self.dest, 0) + 1 + setattr(namespace, self.dest, new_count) + + +class _HelpAction(Action): + + def __init__(self, + option_strings, + dest=SUPPRESS, + default=SUPPRESS, + help=None): + super(_HelpAction, self).__init__( + option_strings=option_strings, + dest=dest, + default=default, + nargs=0, + help=help) + + def __call__(self, parser, namespace, values, option_string=None): + parser.print_help() + parser.exit() + + +class _VersionAction(Action): + + def __init__(self, + option_strings, + version=None, + dest=SUPPRESS, + default=SUPPRESS, + help="show program's version number and exit"): + super(_VersionAction, self).__init__( + option_strings=option_strings, + dest=dest, + default=default, + nargs=0, + help=help) + self.version = version + + def __call__(self, parser, namespace, values, option_string=None): + version = self.version + if version is None: + version = parser.version + formatter = parser._get_formatter() + formatter.add_text(version) + parser._print_message(formatter.format_help(), _usys.stdout) + parser.exit() + + +class _SubParsersAction(Action): + + class _ChoicesPseudoAction(Action): + + def __init__(self, name, aliases, help): + metavar = dest = name + if aliases: + metavar += ' (%s)' % ', '.join(aliases) + sup = super(_SubParsersAction._ChoicesPseudoAction, self) + sup.__init__(option_strings=[], dest=dest, help=help, + metavar=metavar) + + def __init__(self, + option_strings, + prog, + parser_class, + dest=SUPPRESS, + help=None, + metavar=None): + + self._prog_prefix = prog + self._parser_class = parser_class + self._name_parser_map = _ucollections.OrderedDict() + self._choices_actions = [] + + super(_SubParsersAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=PARSER, + choices=self._name_parser_map, + help=help, + metavar=metavar) + + def add_parser(self, name, **kwargs): + # set prog from the existing prefix + if kwargs.get('prog') is None: + kwargs['prog'] = '%s %s' % (self._prog_prefix, name) + + aliases = kwargs.pop('aliases', ()) + + # create a pseudo-action to hold the choice help + if 'help' in kwargs: + help = kwargs.pop('help') + choice_action = self._ChoicesPseudoAction(name, aliases, help) + self._choices_actions.append(choice_action) + + # create the parser and add it to the map + parser = self._parser_class(**kwargs) + self._name_parser_map[name] = parser + + # make parser available under aliases also + for alias in aliases: + self._name_parser_map[alias] = parser + + return parser + + def _get_subactions(self): + return self._choices_actions + + def __call__(self, parser, namespace, values, option_string=None): + parser_name = values[0] + arg_strings = values[1:] + + # set the parser name if requested + if self.dest is not SUPPRESS: + setattr(namespace, self.dest, parser_name) + + # select the parser + try: + parser = self._name_parser_map[parser_name] + except KeyError: + args = {'parser_name': parser_name, + 'choices': ', '.join(self._name_parser_map)} + msg = _('unknown parser %(parser_name)r (choices: %(choices)s)') % args + raise ArgumentError(self, msg) + + # parse all the remaining options into the namespace + # store any unrecognized options on the object, so that the top + # level parser can decide what to do with them + + # In case this subparser defines new defaults, we parse them + # in a new namespace object and then update the original + # namespace for the relevant parts. + subnamespace, arg_strings = parser.parse_known_args(arg_strings, None) + for key, value in subnamespace.__dict__.items(): + setattr(namespace, key, value) + + if arg_strings: + namespace.__dict__.setdefault(_UNRECOGNIZED_ARGS_ATTR, []) + getattr(namespace, _UNRECOGNIZED_ARGS_ATTR).extend(arg_strings) + + +# ============== +# Type classes +# ============== + +class FileType(object): + """Factory for creating file object types + + Instances of FileType are typically passed as type= arguments to the + ArgumentParser add_argument() method. + + Keyword Arguments: + - mode -- A string indicating how the file is to be opened. Accepts the + same values as the builtin open() function. + - bufsize -- The file's desired buffer size. Accepts the same values as + the builtin open() function. + - encoding -- The file's encoding. Accepts the same values as the + builtin open() function. + - errors -- A string indicating how encoding and decoding errors are to + be handled. Accepts the same value as the builtin open() function. + """ + + def __init__(self, mode='r', bufsize=-1, encoding=None, errors=None): + self._mode = mode + self._bufsize = bufsize + self._encoding = encoding + self._errors = errors + + def __call__(self, string): + # the special argument "-" means sys.std{in,out} + if string == '-': + if 'r' in self._mode: + return _usys.stdin + elif 'w' in self._mode: + return _usys.stdout + else: + msg = _('argument "-" with mode %r') % self._mode + raise ValueError(msg) + + # all other arguments are used as file names + try: + return open(string, self._mode, self._bufsize, self._encoding, + self._errors) + except OSError as e: + message = _("can't open '%s': %s") + raise ArgumentTypeError(message % (string, e)) + + def __repr__(self): + args = self._mode, self._bufsize + kwargs = [('encoding', self._encoding), ('errors', self._errors)] + args_str = ', '.join([repr(arg) for arg in args if arg != -1] + + ['%s=%r' % (kw, arg) for kw, arg in kwargs + if arg is not None]) + return '%s(%s)' % (type(self).__name__, args_str) + +# =========================== +# Optional and Positional Parsing +# =========================== + +class Namespace(_AttributeHolder): + """Simple object for storing attributes. + + Implements equality by attribute names and values, and provides a simple + string representation. + """ + + def __init__(self, **kwargs): + for name in kwargs: + setattr(self, name, kwargs[name]) + + def __eq__(self, other): + if not isinstance(other, Namespace): + return NotImplemented + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + if not isinstance(other, Namespace): + return NotImplemented + return not (self == other) + + def __contains__(self, key): + return key in self.__dict__ + + def __copy__(self): + return Namespace(**self.__dict__) + + +class _ActionsContainer(object): + + def __init__(self, + description, + prefix_chars, + argument_default, + conflict_handler): + super(_ActionsContainer, self).__init__() + + self.description = description + self.argument_default = argument_default + self.prefix_chars = prefix_chars + self.conflict_handler = conflict_handler + + # set up registries + self._registries = {} + + # register actions + self.register('action', None, _StoreAction) + self.register('action', 'store', _StoreAction) + self.register('action', 'store_const', _StoreConstAction) + self.register('action', 'store_true', _StoreTrueAction) + self.register('action', 'store_false', _StoreFalseAction) + self.register('action', 'append', _AppendAction) + self.register('action', 'append_const', _AppendConstAction) + self.register('action', 'count', _CountAction) + self.register('action', 'help', _HelpAction) + self.register('action', 'version', _VersionAction) + self.register('action', 'parsers', _SubParsersAction) + + # raise an exception if the conflict handler is invalid + self._get_handler() + + # action storage + self._actions = [] + self._option_string_actions = {} + + # groups + self._action_groups = [] + self._mutually_exclusive_groups = [] + + # defaults storage + self._defaults = {} + + # determines whether an "option" looks like a negative number + self._negative_number_matcher = _ure.compile(r'^-\d+$|^-\d*\.\d+$') + + # whether or not there are any optionals that look like negative + # numbers -- uses a list so it can be shared and edited + self._has_negative_number_optionals = [] + + # ==================== + # Registration methods + # ==================== + def register(self, registry_name, value, object): + registry = self._registries.setdefault(registry_name, {}) + registry[value] = object + + def _registry_get(self, registry_name, value, default=None): + return self._registries[registry_name].get(value, default) + + # ================================== + # Namespace default accessor methods + # ================================== + def set_defaults(self, **kwargs): + self._defaults.update(kwargs) + + # if these defaults match any existing arguments, replace + # the previous default on the object with the new one + for action in self._actions: + if action.dest in kwargs: + action.default = kwargs[action.dest] + + def get_default(self, dest): + for action in self._actions: + if action.dest == dest and action.default is not None: + return action.default + return self._defaults.get(dest, None) + + + # ======================= + # Adding argument actions + # ======================= + def add_argument(self, *args, **kwargs): + """ + add_argument(dest, ..., name=value, ...) + add_argument(option_string, option_string, ..., name=value, ...) + """ + + # if no positional args are supplied or only one is supplied and + # it doesn't look like an option string, parse a positional + # argument + chars = self.prefix_chars + if not args or len(args) == 1 and args[0][0] not in chars: + if args and 'dest' in kwargs: + raise ValueError('dest supplied twice for positional argument') + kwargs = self._get_positional_kwargs(*args, **kwargs) + + # otherwise, we're adding an optional argument + else: + kwargs = self._get_optional_kwargs(*args, **kwargs) + + # if no default was supplied, use the parser-level default + if 'default' not in kwargs: + dest = kwargs['dest'] + if dest in self._defaults: + kwargs['default'] = self._defaults[dest] + elif self.argument_default is not None: + kwargs['default'] = self.argument_default + + # create the action object, and add it to the parser + action_class = self._pop_action_class(kwargs) + if not callable(action_class): + raise ValueError('unknown action "%s"' % (action_class,)) + action = action_class(**kwargs) + + # raise an error if the action type is not callable + type_func = self._registry_get('type', action.type, action.type) + if not callable(type_func): + raise ValueError('%r is not callable' % (type_func,)) + + # raise an error if the metavar does not match the type + if hasattr(self, "_get_formatter"): + try: + self._get_formatter()._format_args(action, None) + except TypeError: + raise ValueError("length of metavar tuple does not match nargs") + + return self._add_action(action) + + def add_argument_group(self, *args, **kwargs): + group = _ArgumentGroup(self, *args, **kwargs) + self._action_groups.append(group) + return group + + def add_mutually_exclusive_group(self, **kwargs): + group = _MutuallyExclusiveGroup(self, **kwargs) + self._mutually_exclusive_groups.append(group) + return group + + def _add_action(self, action): + # resolve any conflicts + self._check_conflict(action) + + # add to actions list + self._actions.append(action) + action.container = self + + # index the action by any option strings it has + for option_string in action.option_strings: + self._option_string_actions[option_string] = action + + # set the flag if any option strings look like negative numbers + for option_string in action.option_strings: + if self._negative_number_matcher.match(option_string): + if not self._has_negative_number_optionals: + self._has_negative_number_optionals.append(True) + + # return the created action + return action + + def _remove_action(self, action): + self._actions.remove(action) + + def _add_container_actions(self, container): + # collect groups by titles + title_group_map = {} + for group in self._action_groups: + if group.title in title_group_map: + msg = _('cannot merge actions - two groups are named %r') + raise ValueError(msg % (group.title)) + title_group_map[group.title] = group + + # map each action to its group + group_map = {} + for group in container._action_groups: + + # if a group with the title exists, use that, otherwise + # create a new group matching the container's group + if group.title not in title_group_map: + title_group_map[group.title] = self.add_argument_group( + title=group.title, + description=group.description, + conflict_handler=group.conflict_handler) + + # map the actions to their new group + for action in group._group_actions: + group_map[action] = title_group_map[group.title] + + # add container's mutually exclusive groups + # NOTE: if add_mutually_exclusive_group ever gains title= and + # description= then this code will need to be expanded as above + for group in container._mutually_exclusive_groups: + mutex_group = self.add_mutually_exclusive_group( + required=group.required) + + # map the actions to their new mutex group + for action in group._group_actions: + group_map[action] = mutex_group + + # add all actions to this container or their group + for action in container._actions: + group_map.get(action, self)._add_action(action) + + def _get_positional_kwargs(self, dest, **kwargs): + # make sure required is not specified + if 'required' in kwargs: + msg = _("'required' is an invalid argument for positionals") + raise TypeError(msg) + + # mark positional arguments as required if at least one is + # always required + if kwargs.get('nargs') not in [OPTIONAL, ZERO_OR_MORE]: + kwargs['required'] = True + if kwargs.get('nargs') == ZERO_OR_MORE and 'default' not in kwargs: + kwargs['required'] = True + + # return the keyword arguments with no option strings + return dict(kwargs, dest=dest, option_strings=[]) + + def _get_optional_kwargs(self, *args, **kwargs): + # determine short and long option strings + option_strings = [] + long_option_strings = [] + for option_string in args: + # error on strings that don't start with an appropriate prefix + if not option_string[0] in self.prefix_chars: + args = {'option': option_string, + 'prefix_chars': self.prefix_chars} + msg = _('invalid option string %(option)r: ' + 'must start with a character %(prefix_chars)r') + raise ValueError(msg % args) + + # strings starting with two prefix characters are long options + option_strings.append(option_string) + if option_string[0] in self.prefix_chars: + if len(option_string) > 1: + if option_string[1] in self.prefix_chars: + long_option_strings.append(option_string) + + # infer destination, '--foo-bar' -> 'foo_bar' and '-x' -> 'x' + dest = kwargs.pop('dest', None) + if dest is None: + if long_option_strings: + dest_option_string = long_option_strings[0] + else: + dest_option_string = option_strings[0] + dest = dest_option_string.lstrip(self.prefix_chars) + if not dest: + msg = _('dest= is required for options like %r') + raise ValueError(msg % option_string) + dest = dest.replace('-', '_') + + # return the updated keyword arguments + return dict(kwargs, dest=dest, option_strings=option_strings) + + def _pop_action_class(self, kwargs, default=None): + action = kwargs.pop('action', default) + return self._registry_get('action', action, action) + + def _get_handler(self): + # determine function from conflict handler string + handler_func_name = '_handle_conflict_%s' % self.conflict_handler + try: + return getattr(self, handler_func_name) + except AttributeError: + msg = _('invalid conflict_resolution value: %r') + raise ValueError(msg % self.conflict_handler) + + def _check_conflict(self, action): + + # find all options that conflict with this option + confl_optionals = [] + for option_string in action.option_strings: + if option_string in self._option_string_actions: + confl_optional = self._option_string_actions[option_string] + confl_optionals.append((option_string, confl_optional)) + + # resolve any conflicts + if confl_optionals: + conflict_handler = self._get_handler() + conflict_handler(action, confl_optionals) + + def _handle_conflict_error(self, action, conflicting_actions): + message = ngettext('conflicting option string: %s', + 'conflicting option strings: %s', + len(conflicting_actions)) + conflict_string = ', '.join([option_string + for option_string, action + in conflicting_actions]) + raise ArgumentError(action, message % conflict_string) + + def _handle_conflict_resolve(self, action, conflicting_actions): + + # remove all conflicting options + for option_string, action in conflicting_actions: + + # remove the conflicting option + action.option_strings.remove(option_string) + self._option_string_actions.pop(option_string, None) + + # if the option now has no option string, remove it from the + # container holding it + if not action.option_strings: + action.container._remove_action(action) + + +class _ArgumentGroup(_ActionsContainer): + + def __init__(self, container, title=None, description=None, **kwargs): + # add any missing keyword arguments by checking the container + update = kwargs.setdefault + update('conflict_handler', container.conflict_handler) + update('prefix_chars', container.prefix_chars) + update('argument_default', container.argument_default) + super_init = super(_ArgumentGroup, self).__init__ + super_init(description=description, **kwargs) + + # group attributes + self.title = title + self._group_actions = [] + + # share most attributes with the container + self._registries = container._registries + self._actions = container._actions + self._option_string_actions = container._option_string_actions + self._defaults = container._defaults + self._has_negative_number_optionals = \ + container._has_negative_number_optionals + self._mutually_exclusive_groups = container._mutually_exclusive_groups + + def _add_action(self, action): + action = super(_ArgumentGroup, self)._add_action(action) + self._group_actions.append(action) + return action + + def _remove_action(self, action): + super(_ArgumentGroup, self)._remove_action(action) + self._group_actions.remove(action) + + +class _MutuallyExclusiveGroup(_ArgumentGroup): + + def __init__(self, container, required=False): + super(_MutuallyExclusiveGroup, self).__init__(container) + self.required = required + self._container = container + + def _add_action(self, action): + if action.required: + msg = _('mutually exclusive arguments must be optional') + raise ValueError(msg) + action = self._container._add_action(action) + self._group_actions.append(action) + return action + + def _remove_action(self, action): + self._container._remove_action(action) + self._group_actions.remove(action) + + +class ArgumentParser(_AttributeHolder, _ActionsContainer): + """Object for parsing command line strings into Python objects. + + Keyword Arguments: + - prog -- The name of the program (default: sys.argv[0]) + - usage -- A usage message (default: auto-generated from arguments) + - description -- A description of what the program does + - epilog -- Text following the argument descriptions + - parents -- Parsers whose arguments should be copied into this one + - formatter_class -- HelpFormatter class for printing help messages + - prefix_chars -- Characters that prefix optional arguments + - fromfile_prefix_chars -- Characters that prefix files containing + additional arguments + - argument_default -- The default value for all arguments + - conflict_handler -- String indicating how to handle conflicts + - add_help -- Add a -h/-help option + """ + + def __init__(self, + prog=None, + usage=None, + description=None, + epilog=None, + parents=[], + formatter_class=HelpFormatter, + prefix_chars='-', + fromfile_prefix_chars=None, + argument_default=None, + conflict_handler='error', + add_help=True): + + superinit = super(ArgumentParser, self).__init__ + superinit(description=description, + prefix_chars=prefix_chars, + argument_default=argument_default, + conflict_handler=conflict_handler) + + # default setting for prog + if prog is None: + prog = _usys.argv[0] + pos = max(prog.rfind('/'), prog.rfind('\\')) + 1 + prog = prog[pos:] + + self.prog = prog + self.usage = usage + self.epilog = epilog + self.formatter_class = formatter_class + self.fromfile_prefix_chars = fromfile_prefix_chars + self.add_help = add_help + + add_group = self.add_argument_group + self._positionals = add_group(_('positional arguments')) + self._optionals = add_group(_('optional arguments')) + self._subparsers = None + + # register types + def identity(string): + return string + self.register('type', None, identity) + + # add help argument if necessary + # (using explicit default to override global argument_default) + default_prefix = '-' if '-' in prefix_chars else prefix_chars[0] + if self.add_help: + self.add_argument( + default_prefix+'h', default_prefix*2+'help', + action='help', default=SUPPRESS, + help=_('show this help message and exit')) + + # add parent arguments and defaults + for parent in parents: + self._add_container_actions(parent) + try: + defaults = parent._defaults + except AttributeError: + pass + else: + self._defaults.update(defaults) + + # ======================= + # Pretty __repr__ methods + # ======================= + def _get_kwargs(self): + names = [ + 'prog', + 'usage', + 'description', + 'formatter_class', + 'conflict_handler', + 'add_help', + ] + return [(name, getattr(self, name)) for name in names] + + # ================================== + # Optional/Positional adding methods + # ================================== + def add_subparsers(self, **kwargs): + if self._subparsers is not None: + self.error(_('cannot have multiple subparser arguments')) + + # add the parser class to the arguments if it's not present + kwargs.setdefault('parser_class', type(self)) + + if 'title' in kwargs or 'description' in kwargs: + title = _(kwargs.pop('title', 'subcommands')) + description = _(kwargs.pop('description', None)) + self._subparsers = self.add_argument_group(title, description) + else: + self._subparsers = self._positionals + + # prog defaults to the usage message of this parser, skipping + # optional arguments and with no "usage:" prefix + if kwargs.get('prog') is None: + formatter = self._get_formatter() + positionals = self._get_positional_actions() + groups = self._mutually_exclusive_groups + formatter.add_usage(self.usage, positionals, groups, '') + kwargs['prog'] = formatter.format_help().strip() + + # create the parsers action and add it to the positionals list + parsers_class = self._pop_action_class(kwargs, 'parsers') + action = parsers_class(option_strings=[], **kwargs) + self._subparsers._add_action(action) + + # return the created parsers action + return action + + def _add_action(self, action): + if action.option_strings: + self._optionals._add_action(action) + else: + self._positionals._add_action(action) + return action + + def _get_optional_actions(self): + return [action + for action in self._actions + if action.option_strings] + + def _get_positional_actions(self): + return [action + for action in self._actions + if not action.option_strings] + + # ===================================== + # Command line argument parsing methods + # ===================================== + def parse_args(self, args=None, namespace=None): + args, argv = self.parse_known_args(args, namespace) + if argv: + msg = _('unrecognized arguments: %s') + self.error(msg % ' '.join(argv)) + return args + + def parse_known_args(self, args=None, namespace=None): + if args is None: + # args default to the system args + args = _usys.argv[1:] + else: + # make sure that args are mutable + args = list(args) + + # default Namespace built from parser defaults + if namespace is None: + namespace = Namespace() + + # add any action defaults that aren't present + for action in self._actions: + if action.dest is not SUPPRESS: + if not hasattr(namespace, action.dest): + if action.default is not SUPPRESS: + setattr(namespace, action.dest, action.default) + + # add any parser defaults that aren't present + for dest in self._defaults: + if not hasattr(namespace, dest): + setattr(namespace, dest, self._defaults[dest]) + + # parse the arguments and exit if there are any errors + try: + namespace, args = self._parse_known_args(args, namespace) + if hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR): + args.extend(getattr(namespace, _UNRECOGNIZED_ARGS_ATTR)) + delattr(namespace, _UNRECOGNIZED_ARGS_ATTR) + return namespace, args + except ArgumentError: + err = _usys.exc_info()[1] + self.error(str(err)) + + def _parse_known_args(self, arg_strings, namespace): + # replace arg strings that are file references + if self.fromfile_prefix_chars is not None: + arg_strings = self._read_args_from_files(arg_strings) + + # map all mutually exclusive arguments to the other arguments + # they can't occur with + action_conflicts = {} + for mutex_group in self._mutually_exclusive_groups: + group_actions = mutex_group._group_actions + for i, mutex_action in enumerate(mutex_group._group_actions): + conflicts = action_conflicts.setdefault(mutex_action, []) + conflicts.extend(group_actions[:i]) + conflicts.extend(group_actions[i + 1:]) + + # find all option indices, and determine the arg_string_pattern + # which has an 'O' if there is an option at an index, + # an 'A' if there is an argument, or a '-' if there is a '--' + option_string_indices = {} + arg_string_pattern_parts = [] + arg_strings_iter = iter(arg_strings) + for i, arg_string in enumerate(arg_strings_iter): + + # all args after -- are non-options + if arg_string == '--': + arg_string_pattern_parts.append('-') + for arg_string in arg_strings_iter: + arg_string_pattern_parts.append('A') + + # otherwise, add the arg to the arg strings + # and note the index if it was an option + else: + option_tuple = self._parse_optional(arg_string) + if option_tuple is None: + pattern = 'A' + else: + option_string_indices[i] = option_tuple + pattern = 'O' + arg_string_pattern_parts.append(pattern) + + # join the pieces together to form the pattern + arg_strings_pattern = ''.join(arg_string_pattern_parts) + + # converts arg strings to the appropriate and then takes the action + seen_actions = set() + seen_non_default_actions = set() + + def take_action(action, argument_strings, option_string=None): + seen_actions.add(action) + argument_values = self._get_values(action, argument_strings) + + # error if this argument is not allowed with other previously + # seen arguments, assuming that actions that use the default + # value don't really count as "present" + if argument_values is not action.default: + seen_non_default_actions.add(action) + for conflict_action in action_conflicts.get(action, []): + if conflict_action in seen_non_default_actions: + msg = _('not allowed with argument %s') + action_name = _get_action_name(conflict_action) + raise ArgumentError(action, msg % action_name) + + # take the action if we didn't receive a SUPPRESS value + # (e.g. from a default) + if argument_values is not SUPPRESS: + action(self, namespace, argument_values, option_string) + + # function to convert arg_strings into an optional action + def consume_optional(start_index): + + # get the optional identified at this index + option_tuple = option_string_indices[start_index] + action, option_string, explicit_arg = option_tuple + + # identify additional optionals in the same arg string + # (e.g. -xyz is the same as -x -y -z if no args are required) + match_argument = self._match_argument + action_tuples = [] + while True: + + # if we found no optional action, skip it + if action is None: + extras.append(arg_strings[start_index]) + return start_index + 1 + + # if there is an explicit argument, try to match the + # optional's string arguments to only this + if explicit_arg is not None: + arg_count = match_argument(action, 'A') + + # if the action is a single-dash option and takes no + # arguments, try to parse more single-dash options out + # of the tail of the option string + chars = self.prefix_chars + if arg_count == 0 and option_string[1] not in chars: + action_tuples.append((action, [], option_string)) + char = option_string[0] + option_string = char + explicit_arg[0] + new_explicit_arg = explicit_arg[1:] or None + optionals_map = self._option_string_actions + if option_string in optionals_map: + action = optionals_map[option_string] + explicit_arg = new_explicit_arg + else: + msg = _('ignored explicit argument %r') + raise ArgumentError(action, msg % explicit_arg) + + # if the action expect exactly one argument, we've + # successfully matched the option; exit the loop + elif arg_count == 1: + stop = start_index + 1 + args = [explicit_arg] + action_tuples.append((action, args, option_string)) + break + + # error if a double-dash option did not use the + # explicit argument + else: + msg = _('ignored explicit argument %r') + raise ArgumentError(action, msg % explicit_arg) + + # if there is no explicit argument, try to match the + # optional's string arguments with the following strings + # if successful, exit the loop + else: + start = start_index + 1 + selected_patterns = arg_strings_pattern[start:] + arg_count = match_argument(action, selected_patterns) + stop = start + arg_count + args = arg_strings[start:stop] + action_tuples.append((action, args, option_string)) + break + + # add the Optional to the list and return the index at which + # the Optional's string args stopped + assert action_tuples + for action, args, option_string in action_tuples: + take_action(action, args, option_string) + return stop + + # the list of Positionals left to be parsed; this is modified + # by consume_positionals() + positionals = self._get_positional_actions() + + # function to convert arg_strings into positional actions + def consume_positionals(start_index): + # match as many Positionals as possible + match_partial = self._match_arguments_partial + selected_pattern = arg_strings_pattern[start_index:] + arg_counts = match_partial(positionals, selected_pattern) + + # slice off the appropriate arg strings for each Positional + # and add the Positional and its args to the list + for action, arg_count in zip(positionals, arg_counts): + args = arg_strings[start_index: start_index + arg_count] + start_index += arg_count + take_action(action, args) + + # slice off the Positionals that we just parsed and return the + # index at which the Positionals' string args stopped + positionals[:] = positionals[len(arg_counts):] + return start_index + + # consume Positionals and Optionals alternately, until we have + # passed the last option string + extras = [] + start_index = 0 + if option_string_indices: + max_option_string_index = max(option_string_indices) + else: + max_option_string_index = -1 + while start_index <= max_option_string_index: + + # consume any Positionals preceding the next option + next_option_string_index = min([ + index + for index in option_string_indices + if index >= start_index]) + if start_index != next_option_string_index: + positionals_end_index = consume_positionals(start_index) + + # only try to parse the next optional if we didn't consume + # the option string during the positionals parsing + if positionals_end_index > start_index: + start_index = positionals_end_index + continue + else: + start_index = positionals_end_index + + # if we consumed all the positionals we could and we're not + # at the index of an option string, there were extra arguments + if start_index not in option_string_indices: + strings = arg_strings[start_index:next_option_string_index] + extras.extend(strings) + start_index = next_option_string_index + + # consume the next optional and any arguments for it + start_index = consume_optional(start_index) + + # consume any positionals following the last Optional + stop_index = consume_positionals(start_index) + + # if we didn't consume all the argument strings, there were extras + extras.extend(arg_strings[stop_index:]) + + # make sure all required actions were present and also convert + # action defaults which were not given as arguments + required_actions = [] + for action in self._actions: + if action not in seen_actions: + if action.required: + required_actions.append(_get_action_name(action)) + else: + # Convert action default now instead of doing it before + # parsing arguments to avoid calling convert functions + # twice (which may fail) if the argument was given, but + # only if it was defined already in the namespace + if (action.default is not None and + isinstance(action.default, str) and + hasattr(namespace, action.dest) and + action.default is getattr(namespace, action.dest)): + setattr(namespace, action.dest, + self._get_value(action, action.default)) + + if required_actions: + self.error(_('the following arguments are required: %s') % + ', '.join(required_actions)) + + # make sure all required groups had one option present + for group in self._mutually_exclusive_groups: + if group.required: + for action in group._group_actions: + if action in seen_non_default_actions: + break + + # if no actions were used, report the error + else: + names = [_get_action_name(action) + for action in group._group_actions + if action.help is not SUPPRESS] + msg = _('one of the arguments %s is required') + self.error(msg % ' '.join(names)) + + # return the updated namespace and the extra arguments + return namespace, extras + + def _read_args_from_files(self, arg_strings): + # expand arguments referencing files + new_arg_strings = [] + for arg_string in arg_strings: + + # for regular arguments, just add them back into the list + if not arg_string or arg_string[0] not in self.fromfile_prefix_chars: + new_arg_strings.append(arg_string) + + # replace arguments referencing files with the file content + else: + try: + with open(arg_string[1:]) as args_file: + arg_strings = [] + for arg_line in args_file.read().splitlines(): + for arg in self.convert_arg_line_to_args(arg_line): + arg_strings.append(arg) + arg_strings = self._read_args_from_files(arg_strings) + new_arg_strings.extend(arg_strings) + except OSError: + err = _usys.exc_info()[1] + self.error(str(err)) + + # return the modified argument list + return new_arg_strings + + def convert_arg_line_to_args(self, arg_line): + return [arg_line] + + def _match_argument(self, action, arg_strings_pattern): + # match the pattern for this action to the arg strings + nargs_pattern = self._get_nargs_pattern(action) + match = _ure.match(nargs_pattern, arg_strings_pattern) + + # raise an exception if we weren't able to find a match + if match is None: + nargs_errors = { + None: _('expected one argument'), + OPTIONAL: _('expected at most one argument'), + ONE_OR_MORE: _('expected at least one argument'), + } + default = ngettext('expected %s argument', + 'expected %s arguments', + action.nargs) % action.nargs + msg = nargs_errors.get(action.nargs, default) + raise ArgumentError(action, msg) + + # return the number of arguments matched + return len(match.group(1)) + + def _match_arguments_partial(self, actions, arg_strings_pattern): + # progressively shorten the actions list by slicing off the + # final actions until we find a match + result = [] + for i in range(len(actions), 0, -1): + actions_slice = actions[:i] + pattern = ''.join([self._get_nargs_pattern(action) + for action in actions_slice]) + match = _ure.match(pattern, arg_strings_pattern) + if match is not None: + result.extend([len(string) for string in match.groups()]) + break + + # return the list of arg string counts + return result + + def _parse_optional(self, arg_string): + # if it's an empty string, it was meant to be a positional + if not arg_string: + return None + + # if it doesn't start with a prefix, it was meant to be positional + if not arg_string[0] in self.prefix_chars: + return None + + # if the option string is present in the parser, return the action + if arg_string in self._option_string_actions: + action = self._option_string_actions[arg_string] + return action, arg_string, None + + # if it's just a single character, it was meant to be positional + if len(arg_string) == 1: + return None + + # if the option string before the "=" is present, return the action + if '=' in arg_string: + option_string, explicit_arg = arg_string.split('=', 1) + if option_string in self._option_string_actions: + action = self._option_string_actions[option_string] + return action, option_string, explicit_arg + + # search through all possible prefixes of the option string + # and all actions in the parser for possible interpretations + option_tuples = self._get_option_tuples(arg_string) + + # if multiple actions match, the option string was ambiguous + if len(option_tuples) > 1: + options = ', '.join([option_string + for action, option_string, explicit_arg in option_tuples]) + args = {'option': arg_string, 'matches': options} + msg = _('ambiguous option: %(option)s could match %(matches)s') + self.error(msg % args) + + # if exactly one action matched, this segmentation is good, + # so return the parsed action + elif len(option_tuples) == 1: + option_tuple, = option_tuples + return option_tuple + + # if it was not found as an option, but it looks like a negative + # number, it was meant to be positional + # unless there are negative-number-like options + if self._negative_number_matcher.match(arg_string): + if not self._has_negative_number_optionals: + return None + + # if it contains a space, it was meant to be a positional + if ' ' in arg_string: + return None + + # it was meant to be an optional but there is no such option + # in this parser (though it might be a valid option in a subparser) + return None, arg_string, None + + def _get_option_tuples(self, option_string): + result = [] + + # option strings starting with two prefix characters are only + # split at the '=' + chars = self.prefix_chars + if option_string[0] in chars and option_string[1] in chars: + if '=' in option_string: + option_prefix, explicit_arg = option_string.split('=', 1) + else: + option_prefix = option_string + explicit_arg = None + for option_string in self._option_string_actions: + if option_string.startswith(option_prefix): + action = self._option_string_actions[option_string] + tup = action, option_string, explicit_arg + result.append(tup) + + # single character options can be concatenated with their arguments + # but multiple character options always have to have their argument + # separate + elif option_string[0] in chars and option_string[1] not in chars: + option_prefix = option_string + explicit_arg = None + short_option_prefix = option_string[:2] + short_explicit_arg = option_string[2:] + + for option_string in self._option_string_actions: + if option_string == short_option_prefix: + action = self._option_string_actions[option_string] + tup = action, option_string, short_explicit_arg + result.append(tup) + elif option_string.startswith(option_prefix): + action = self._option_string_actions[option_string] + tup = action, option_string, explicit_arg + result.append(tup) + + # shouldn't ever get here + else: + self.error(_('unexpected option string: %s') % option_string) + + # return the collected option tuples + return result + + def _get_nargs_pattern(self, action): + # in all examples below, we have to allow for '--' args + # which are represented as '-' in the pattern + nargs = action.nargs + + # the default (None) is assumed to be a single argument + if nargs is None: + nargs_pattern = '(-*A-*)' + + # allow zero or one arguments + elif nargs == OPTIONAL: + nargs_pattern = '(-*A?-*)' + + # allow zero or more arguments + elif nargs == ZERO_OR_MORE: + nargs_pattern = '(-*[A-]*)' + + # allow one or more arguments + elif nargs == ONE_OR_MORE: + nargs_pattern = '(-*A[A-]*)' + + # allow any number of options or arguments + elif nargs == REMAINDER: + nargs_pattern = '([-AO]*)' + + # allow one argument followed by any number of options or arguments + elif nargs == PARSER: + nargs_pattern = '(-*A[-AO]*)' + + # all others should be integers + else: + nargs_pattern = '(-*%s-*)' % '-*'.join('A' * nargs) + + # if this is an optional action, -- is not allowed + if action.option_strings: + nargs_pattern = nargs_pattern.replace('-*', '') + nargs_pattern = nargs_pattern.replace('-', '') + + # return the pattern + return nargs_pattern + + # ======================== + # Value conversion methods + # ======================== + def _get_values(self, action, arg_strings): + # for everything but PARSER, REMAINDER args, strip out first '--' + if action.nargs not in [PARSER, REMAINDER]: + try: + arg_strings.remove('--') + except ValueError: + pass + + # optional argument produces a default when not present + if not arg_strings and action.nargs == OPTIONAL: + if action.option_strings: + value = action.const + else: + value = action.default + if isinstance(value, str): + value = self._get_value(action, value) + self._check_value(action, value) + + # when nargs='*' on a positional, if there were no command-line + # args, use the default if it is anything other than None + elif (not arg_strings and action.nargs == ZERO_OR_MORE and + not action.option_strings): + if action.default is not None: + value = action.default + else: + value = arg_strings + self._check_value(action, value) + + # single argument or optional argument produces a single value + elif len(arg_strings) == 1 and action.nargs in [None, OPTIONAL]: + arg_string, = arg_strings + value = self._get_value(action, arg_string) + self._check_value(action, value) + + # REMAINDER arguments convert all values, checking none + elif action.nargs == REMAINDER: + value = [self._get_value(action, v) for v in arg_strings] + + # PARSER arguments convert all values, but check only the first + elif action.nargs == PARSER: + value = [self._get_value(action, v) for v in arg_strings] + self._check_value(action, value[0]) + + # all other types of nargs produce a list + else: + value = [self._get_value(action, v) for v in arg_strings] + for v in value: + self._check_value(action, v) + + # return the converted value + return value + + def _get_value(self, action, arg_string): + type_func = self._registry_get('type', action.type, action.type) + if not callable(type_func): + msg = _('%r is not callable') + raise ArgumentError(action, msg % type_func) + + # convert the value to the appropriate type + try: + result = type_func(arg_string) + + # ArgumentTypeErrors indicate errors + except ArgumentTypeError: + name = getattr(action.type, '__name__', repr(action.type)) + msg = str(_usys.exc_info()[1]) + raise ArgumentError(action, msg) + + # TypeErrors or ValueErrors also indicate errors + except (TypeError, ValueError): + name = getattr(action.type, '__name__', repr(action.type)) + args = {'type': name, 'value': arg_string} + msg = _('invalid %(type)s value: %(value)r') + raise ArgumentError(action, msg % args) + + # return the converted value + return result + + def _check_value(self, action, value): + # converted value must be one of the choices (if specified) + if action.choices is not None and value not in action.choices: + args = {'value': value, + 'choices': ', '.join(map(repr, action.choices))} + msg = _('invalid choice: %(value)r (choose from %(choices)s)') + raise ArgumentError(action, msg % args) + + # ======================= + # Help-formatting methods + # ======================= + def format_usage(self): + formatter = self._get_formatter() + formatter.add_usage(self.usage, self._actions, + self._mutually_exclusive_groups) + return formatter.format_help() + + def format_help(self): + formatter = self._get_formatter() + + # usage + formatter.add_usage(self.usage, self._actions, + self._mutually_exclusive_groups) + + # description + formatter.add_text(self.description) + + # positionals, optionals and user-defined groups + for action_group in self._action_groups: + formatter.start_section(action_group.title) + formatter.add_text(action_group.description) + formatter.add_arguments(action_group._group_actions) + formatter.end_section() + + # epilog + formatter.add_text(self.epilog) + + # determine help from format above + return formatter.format_help() + + def _get_formatter(self): + return self.formatter_class(prog=self.prog) + + # ===================== + # Help-printing methods + # ===================== + def print_usage(self, file=None): + if file is None: + file = _usys.stdout + self._print_message(self.format_usage(), file) + + def print_help(self, file=None): + if file is None: + file = _usys.stdout + self._print_message(self.format_help(), file) + + def _print_message(self, message, file=None): + if message: + if file is None: + file = _usys.stderr + file.write(message) + + # =============== + # Exiting methods + # =============== + def exit(self, status=0, message=None): + if message: + self._print_message(message, _usys.stderr) + _usys.exit(status) + + def error(self, message): + """error(message: string) + + Prints a usage message incorporating the message to stderr and + exits. + + If you override this in a subclass, it should not return -- it + should either exit or raise an exception. + """ + self.print_usage(_usys.stderr) + args = {'prog': self.prog, 'message': message} + self.exit(2, _('%(prog)s: error: %(message)s\n') % args) + diff --git a/micropython/pymodule/atexit.py b/micropython/pymodule/atexit.py new file mode 100644 index 0000000..1df77a6 --- /dev/null +++ b/micropython/pymodule/atexit.py @@ -0,0 +1,43 @@ +# This module provides partial functionality of the CPython "atexit" module for +# MicroPython. + +import sys as _usys + +__all__ = [ + 'register', 'unregister' +] + + +class _AtExit(object): + def __init__(self): + self.regs = [] + _usys.atexit(self._onexit) + + def register(self, func, *args, **kwargs): + self.regs.append((func, args, kwargs)) + return func + + def unregister(self, func): + regs_new = [] + for reg in self.regs: + if reg[0] != func: + regs_new.append(reg) + self.regs = regs_new + + def _onexit(self): + last_ex = None + for func, args, kwargs in reversed(self.regs): + try: + func(*args, **kwargs) + except Exception as ex: + last_ex = ex + _usys.print_exception(ex, _usys.stderr) + + if last_ex: + raise last_ex from None + + +_atexit = _AtExit() + +register = _atexit.register +unregister = _atexit.unregister diff --git a/micropython/pymodule/codecs.py b/micropython/pymodule/codecs.py new file mode 100644 index 0000000..7655d37 --- /dev/null +++ b/micropython/pymodule/codecs.py @@ -0,0 +1,38 @@ +# This module provides partial functionality of the CPython "codecs" module for +# MicroPython. + +__all__ = [ + 'CodecInfo', 'lookup', 'register', 'ascii_encode', 'ascii_decode' +] + + +class CodecInfo(object): + def __init__(self, *args, **kwargs): + pass + + +_dummy = CodecInfo() + + +def lookup(encoding): + return _dummy + + +def register(search_function): + pass + + +def ascii_encode(string, errors=None): + if errors is not None: + data = string.encode('ascii', errors) + else: + data = string.encode('ascii') + return data, len(data) + + +def ascii_decode(data, errors=None): + if errors is not None: + string = data.decode('ascii', errors) + else: + string = data.decode('ascii') + return string, len(string) diff --git a/micropython/pymodule/errno.py b/micropython/pymodule/errno.py new file mode 100644 index 0000000..b8a7d7b --- /dev/null +++ b/micropython/pymodule/errno.py @@ -0,0 +1,93 @@ +# This module provides partial functionality of the CPython "errno" module for +# MicroPython. + +import _posix + +E2BIG = _posix.E2BIG +EACCES = _posix.EACCES +EADDRINUSE = _posix.EADDRINUSE +EADDRNOTAVAIL = _posix.EADDRNOTAVAIL +EAFNOSUPPORT = _posix.EAFNOSUPPORT +EAGAIN = _posix.EAGAIN +EALREADY = _posix.EALREADY +EBADF = _posix.EBADF +EBADMSG = _posix.EBADMSG +EBUSY = _posix.EBUSY +ECANCELED = _posix.ECANCELED +ECHILD = _posix.ECHILD +ECONNABORTED = _posix.ECONNABORTED +ECONNREFUSED = _posix.ECONNREFUSED +ECONNRESET = _posix.ECONNRESET +EDEADLK = _posix.EDEADLK +EDESTADDRREQ = _posix.EDESTADDRREQ +EDOM = _posix.EDOM +EDQUOT = _posix.EDQUOT +EEXIST = _posix.EEXIST +EFAULT = _posix.EFAULT +EFBIG = _posix.EFBIG +EHOSTUNREACH = _posix.EHOSTUNREACH +EIDRM = _posix.EIDRM +EILSEQ = _posix.EILSEQ +EINPROGRESS = _posix.EINPROGRESS +EINTR = _posix.EINTR +EINVAL = _posix.EINVAL +EIO = _posix.EIO +EISCONN = _posix.EISCONN +EISDIR = _posix.EISDIR +ELOOP = _posix.ELOOP +EMFILE = _posix.EMFILE +EMLINK = _posix.EMLINK +EMSGSIZE = _posix.EMSGSIZE +EMULTIHOP = _posix.EMULTIHOP +ENAMETOOLONG = _posix.ENAMETOOLONG +ENETDOWN = _posix.ENETDOWN +ENETRESET = _posix.ENETRESET +ENETUNREACH = _posix.ENETUNREACH +ENFILE = _posix.ENFILE +ENOBUFS = _posix.ENOBUFS +ENODATA = _posix.ENODATA +ENODEV = _posix.ENODEV +ENOENT = _posix.ENOENT +ENOEXEC = _posix.ENOEXEC +ENOLCK = _posix.ENOLCK +ENOLINK = _posix.ENOLINK +ENOMEM = _posix.ENOMEM +ENOMSG = _posix.ENOMSG +ENOPROTOOPT = _posix.ENOPROTOOPT +ENOSPC = _posix.ENOSPC +ENOSR = _posix.ENOSR +ENOSTR = _posix.ENOSTR +ENOSYS = _posix.ENOSYS +ENOTCONN = _posix.ENOTCONN +ENOTDIR = _posix.ENOTDIR +ENOTEMPTY = _posix.ENOTEMPTY +ENOTRECOVERABLE = _posix.ENOTRECOVERABLE +ENOTSOCK = _posix.ENOTSOCK +ENOTSUP = _posix.ENOTSUP +ENOTTY = _posix.ENOTTY +ENXIO = _posix.ENXIO +EOPNOTSUPP = _posix.EOPNOTSUPP +EOVERFLOW = _posix.EOVERFLOW +EOWNERDEAD = _posix.EOWNERDEAD +EPERM = _posix.EPERM +EPIPE = _posix.EPIPE +EPROTO = _posix.EPROTO +EPROTONOSUPPORT = _posix.EPROTONOSUPPORT +EPROTOTYPE = _posix.EPROTOTYPE +ERANGE = _posix.ERANGE +EROFS = _posix.EROFS +ESPIPE = _posix.ESPIPE +ESRCH = _posix.ESRCH +ESTALE = _posix.ESTALE +ETIME = _posix.ETIME +ETIMEDOUT = _posix.ETIMEDOUT +ETXTBSY = _posix.ETXTBSY +EWOULDBLOCK = _posix.EWOULDBLOCK +EXDEV = _posix.EXDEV + + +errorcode = dict() + +for name, value in locals().items(): + if name.startswith('E') and isinstance(value, int): + errorcode[value] = name diff --git a/micropython/pymodule/genericpath.py b/micropython/pymodule/genericpath.py new file mode 100644 index 0000000..6a788ef --- /dev/null +++ b/micropython/pymodule/genericpath.py @@ -0,0 +1,134 @@ +# Modified from CPython 3.4.10 "genericpath" module for MicroPython + +""" +Path operations common to more than one OS +Do not use directly. The OS specific modules import the appropriate +functions from this module themselves. +""" +import _posix +import os as _zos + +__all__ = ['commonprefix', 'exists', 'getatime', 'getctime', 'getmtime', + 'getsize', 'isdir', 'isfile', 'samefile', 'sameopenfile', + 'samestat'] + + +# Does a path exist? +# This is false for dangling symbolic links on systems that support them. +def exists(path): + """Test whether a path exists. Returns False for broken symbolic links""" + try: + _zos.stat(path) + except OSError: + return False + return True + + +# This follows symbolic links, so both islink() and isdir() can be true +# for the same path on systems that support symlinks +def isfile(path): + """Test whether a path is a regular file""" + try: + st = _zos.stat(path) + except OSError: + return False + return _posix.S_ISREG(st.st_mode) + + +# Is a path a directory? +# This follows symbolic links, so both islink() and isdir() +# can be true for the same path on systems that support symlinks +def isdir(s): + """Return true if the pathname refers to an existing directory.""" + try: + st = _zos.stat(s) + except OSError: + return False + return _posix.S_ISDIR(st.st_mode) + + +def getsize(filename): + """Return the size of a file, reported by os.stat().""" + return _zos.stat(filename).st_size + + +def getmtime(filename): + """Return the last modification time of a file, reported by os.stat().""" + return _zos.stat(filename).st_mtime + + +def getatime(filename): + """Return the last access time of a file, reported by os.stat().""" + return _zos.stat(filename).st_atime + + +def getctime(filename): + """Return the metadata change time of a file, reported by os.stat().""" + return _zos.stat(filename).st_ctime + + +# Return the longest prefix of all list elements. +def commonprefix(m): + "Given a list of pathnames, returns the longest common leading component" + if not m: return '' + s1 = min(m) + s2 = max(m) + for i, c in enumerate(s1): + if c != s2[i]: + return s1[:i] + return s1 + +# Are two stat buffers (obtained from stat, fstat or lstat) +# describing the same file? +def samestat(s1, s2): + """Test whether two stat buffers reference the same file""" + return (s1.st_ino == s2.st_ino and + s1.st_dev == s2.st_dev) + + +# Are two filenames really pointing to the same file? +def samefile(f1, f2): + """Test whether two pathnames reference the same actual file""" + s1 = _zos.stat(f1) + s2 = _zos.stat(f2) + return samestat(s1, s2) + + +# Are two open files really referencing the same file? +# (Not necessarily the same file descriptor!) +def sameopenfile(fp1, fp2): + """Test whether two open file objects reference the same file""" + s1 = _zos.fstat(fp1) + s2 = _zos.fstat(fp2) + return samestat(s1, s2) + + +# Split a path in root and extension. +# The extension is everything starting at the last dot in the last +# pathname component; the root is everything before that. +# It is always true that root + ext == p. + +# Generic implementation of splitext, to be parametrized with +# the separators +def _splitext(p, sep, altsep, extsep): + """Split the extension from a pathname. + + Extension is everything from the last dot to the end, ignoring + leading dots. Returns "(root, ext)"; ext may be empty.""" + # NOTE: This code must work for text and bytes strings. + + sepIndex = p.rfind(sep) + if altsep: + altsepIndex = p.rfind(altsep) + sepIndex = max(sepIndex, altsepIndex) + + dotIndex = p.rfind(extsep) + if dotIndex > sepIndex: + # skip all leading dots + filenameIndex = sepIndex + 1 + while filenameIndex < dotIndex: + if p[filenameIndex:filenameIndex+1] != extsep: + return p[:dotIndex], p[dotIndex:] + filenameIndex += 1 + + return p, p[:0] diff --git a/micropython/pymodule/os.py b/micropython/pymodule/os.py new file mode 100644 index 0000000..4c03b1b --- /dev/null +++ b/micropython/pymodule/os.py @@ -0,0 +1,163 @@ +# This module provides partial functionality of the CPython "os" module for +# MicroPython. + +import sys as _sys +_path = _sys.path +_sys.path = () +try: + import os as _uos +finally: + _sys.path = _path + del _path + +import _posix +import posixpath as path + +__all__ = [ + '_Environ', 'altsep', 'chdir', 'curdir', 'defpath', 'devnull', 'environ', + 'extsep', 'getcwd', 'getcwdb', 'getenv', 'linesep', 'listdir', 'lstat', + 'mkdir', 'mount', 'name', 'pardir', 'path', 'pathsep', 'putenv', 'remove', + 'rename', 'rmdir', 'sep', 'stat', 'stat_result', 'system', 'umount', + 'uname', 'uname_result', 'unlink', 'unsetenv', 'urandom' +] + + +class _Environ(dict): + def __setitem__(self, key, value): + _uos.putenv(key, value) + super().__setitem__(key, value) + + def __delitem__(self, key): + _uos.unsetenv(key) + super().__delitem__(key) + + def __repr__(self): + return 'environ({%s})' % ( + ', '.join( + '%s: %s' % (repr(key), repr(value)) + for key, value in self.items() + ) + ) + + +def _createenviron(): + envdict = dict() + envlist = _posix._environ() + for env in envlist: + kv = env.split('=', 1) + if len(kv) < 2: + envdict[kv[0]] = '' + else: + envdict[kv[0]] = kv[1] + return _Environ(envdict) + + +# unicode environ +environ = _createenviron() + + +del _createenviron + + +class stat_result(tuple): + # MicroPython note: + # tuple subclass data is passed to __init__ in MicroPython, not __new__, + # so attributes are set here. + def __init__(self, t): + super().__init__(t) + self.st_mode = t[0] + self.st_ino = t[1] + self.st_dev = t[2] + self.st_nlink = t[3] + self.st_uid = t[4] + self.st_gid = t[5] + self.st_size = t[6] + self.st_atime = t[7] + self.st_mtime = t[8] + self.st_ctime = t[9] + self.__frozen__ = True + + def __setattr__(self, name, value): + if hasattr(self, '__frozen__') and self.__frozen__: + raise AttributeError("Assignment is not allowed") + + def __repr__(self): + return "stat_result(%s)" % repr(super()) + + +class uname_result(tuple): + # MicroPython note: + # tuple subclass data is passed to __init__ in MicroPython, not __new__, + # so attributes are set here. + def __init__(self, t): + super().__init__(t) + self.sysname = t[0] + self.nodename = t[1] + self.release = t[2] + self.version = t[3] + self.machine = t[4] + self.__frozen__ = True + + def __setattr__(self, name, value): + if hasattr(self, '__frozen__') and self.__frozen__: + raise AttributeError("Assignment is not allowed") + + def __repr__(self): + return "uname_result(%s)" % repr(super()) + + +name = 'posix' +linesep = '\n' +curdir = path.curdir +pardir = path.pardir +extsep = path.extsep +sep = path.sep +pathsep = path.pathsep +defpath = path.defpath +altsep = path.altsep +devnull = path.devnull + +chdir = _uos.chdir +getcwd = _uos.getcwd +listdir = _uos.listdir +mkdir = _uos.mkdir +mount = _uos.mount +putenv = _uos.putenv +remove = _uos.remove +rename = _uos.rename +rmdir = _uos.rmdir +system = _uos.system +umount = _uos.umount +unlink = _uos.unlink +unsetenv = _uos.unsetenv +urandom = _uos.urandom + +isatty = _posix.isatty +getuid = _posix.getuid + + +def getcwdb(): + return _uos.getcwd().encode() + + +def getenv(key, default=None): + return _uos.getenv(key, default) + + +def stat(path): + result = _posix.stat(path) + return stat_result(result) + + +def lstat(path): + result = _posix.lstat(path) + return stat_result(result) + + +def uname(): + result = _posix.uname() + return uname_result(result) + + +def strerror(code): + return _posix.strerror(code) diff --git a/micropython/pymodule/posixpath.py b/micropython/pymodule/posixpath.py new file mode 100644 index 0000000..02d5389 --- /dev/null +++ b/micropython/pymodule/posixpath.py @@ -0,0 +1,361 @@ +# Modified from CPython 3.4.10 "posixpath" module for MicroPython + +"""Common operations on Posix pathnames. + +Instead of importing this module directly, import os and refer to +this module as os.path. The "os.path" name is an alias for this +module on Posix systems; on other systems (e.g. Mac, Windows), +os.path provides the same operations in a manner specific to that +platform, and is an alias to another module (e.g. macpath, ntpath). + +Some of this can actually be useful on non-Posix systems too, e.g. +for manipulation of the pathname component of URLs. +""" + +import sys as _usys +import _posix +import os as _zos +import genericpath as _zgenericpath +from genericpath import * + +__all__ = ["normcase","isabs","join","splitdrive","split","splitext", + "basename","dirname","commonprefix","getsize","getmtime", + "getatime","getctime","islink","exists","lexists","isdir","isfile", + "ismount", "normpath","abspath", "samefile","sameopenfile", + "samestat","curdir","pardir","sep","pathsep","defpath","altsep", + "extsep","devnull","realpath","supports_unicode_filenames", + "relpath"] + +# Strings representing various path-related bits and pieces. +# These are primarily for export; internally, they are hardcoded. +curdir = '.' +pardir = '..' +extsep = '.' +sep = '/' +pathsep = ':' +defpath = ':/bin:/usr/bin' +altsep = None +devnull = '/dev/null' + +def _get_sep(path): + if isinstance(path, bytes): + return b'/' + else: + return '/' + +# Normalize the case of a pathname. Trivial in Posix, string.lower on Mac. +# On MS-DOS this may also turn slashes into backslashes; however, other +# normalizations (such as optimizing '../' away) are not allowed +# (another function should be defined to do that). + +def normcase(s): + """Normalize case of pathname. Has no effect under Posix""" + if not isinstance(s, (bytes, str)): + raise TypeError("normcase() argument must be str or bytes, " + "not '{}'".format(s.__class__.__name__)) + return s + + +# Return whether a path is absolute. +# Trivial in Posix, harder on the Mac or MS-DOS. + +def isabs(s): + """Test whether a path is absolute""" + sep = _get_sep(s) + return s.startswith(sep) + + +# Join pathnames. +# Ignore the previous parts if a part is absolute. +# Insert a '/' unless the first part is empty or already ends in '/'. + +def join(a, *p): + """Join two or more pathname components, inserting '/' as needed. + If any component is an absolute path, all previous path components + will be discarded. An empty last part will result in a path that + ends with a separator.""" + sep = _get_sep(a) + path = a + try: + for b in p: + if b.startswith(sep): + path = b + elif not path or path.endswith(sep): + path += b + else: + path += sep + b + except TypeError: + if all(isinstance(s, (str, bytes)) for s in (a,) + p): + # Must have a mixture of text and binary data + raise TypeError("Can't mix strings and bytes in path " + "components") from None + raise + return path + + +# Split a path in head (everything up to the last '/') and tail (the +# rest). If the path ends in '/', tail will be empty. If there is no +# '/' in the path, head will be empty. +# Trailing '/'es are stripped from head unless it is the root. + +def split(p): + """Split a pathname. Returns tuple "(head, tail)" where "tail" is + everything after the final slash. Either part may be empty.""" + sep = _get_sep(p) + i = p.rfind(sep) + 1 + head, tail = p[:i], p[i:] + if head and head != sep*len(head): + head = head.rstrip(sep) + return head, tail + + +# Split a path in root and extension. +# The extension is everything starting at the last dot in the last +# pathname component; the root is everything before that. +# It is always true that root + ext == p. + +def splitext(p): + if isinstance(p, bytes): + sep = b'/' + extsep = b'.' + else: + sep = '/' + extsep = '.' + return _zgenericpath._splitext(p, sep, None, extsep) + + +# Split a pathname into a drive specification and the rest of the +# path. Useful on DOS/Windows/NT; on Unix, the drive is always empty. + +def splitdrive(p): + """Split a pathname into drive and path. On Posix, drive is always + empty.""" + return p[:0], p + + +# Return the tail (basename) part of a path, same as split(path)[1]. + +def basename(p): + """Returns the final component of a pathname""" + sep = _get_sep(p) + i = p.rfind(sep) + 1 + return p[i:] + + +# Return the head (dirname) part of a path, same as split(path)[0]. + +def dirname(p): + """Returns the directory component of a pathname""" + sep = _get_sep(p) + i = p.rfind(sep) + 1 + head = p[:i] + if head and head != sep*len(head): + head = head.rstrip(sep) + return head + + +# Is a path a symbolic link? +# This will always return false on systems where os.lstat doesn't exist. + +def islink(path): + """Test whether a path is a symbolic link""" + try: + st = _zos.lstat(path) + except (OSError, AttributeError): + return False + return _posix.S_ISLNK(st.st_mode) + +# Being true for dangling symbolic links is also useful. + +def lexists(path): + """Test whether a path exists. Returns True for broken symbolic links""" + try: + _zos.lstat(path) + except OSError: + return False + return True + + +# Is a path a mount point? +# (Does this work for all UNIXes? Is it even guaranteed to work by Posix?) + +def ismount(path): + """Test whether a path is a mount point""" + try: + s1 = _zos.lstat(path) + except OSError: + # It doesn't exist -- so not a mount point. :-) + return False + else: + # A symlink can never be a mount point + if _posix.S_ISLNK(s1.st_mode): + return False + + if isinstance(path, bytes): + parent = join(path, b'..') + else: + parent = join(path, '..') + try: + s2 = _zos.lstat(parent) + except OSError: + return False + + dev1 = s1.st_dev + dev2 = s2.st_dev + if dev1 != dev2: + return True # path/.. on a different device as path + ino1 = s1.st_ino + ino2 = s2.st_ino + if ino1 == ino2: + return True # path/.. is the same i-node as path + return False + + + +# Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A/B. +# It should be understood that this may change the meaning of the path +# if it contains symbolic links! + +def normpath(path): + """Normalize path, eliminating double slashes, etc.""" + if isinstance(path, bytes): + sep = b'/' + empty = b'' + dot = b'.' + dotdot = b'..' + else: + sep = '/' + empty = '' + dot = '.' + dotdot = '..' + if path == empty: + return dot + initial_slashes = path.startswith(sep) + # POSIX allows one or two initial slashes, but treats three or more + # as single slash. + if (initial_slashes and + path.startswith(sep*2) and not path.startswith(sep*3)): + initial_slashes = 2 + comps = path.split(sep) + new_comps = [] + for comp in comps: + if comp in (empty, dot): + continue + if (comp != dotdot or (not initial_slashes and not new_comps) or + (new_comps and new_comps[-1] == dotdot)): + new_comps.append(comp) + elif new_comps: + new_comps.pop() + comps = new_comps + path = sep.join(comps) + if initial_slashes: + path = sep*initial_slashes + path + return path or dot + + +def abspath(path): + """Return an absolute path.""" + if not isabs(path): + if isinstance(path, bytes): + cwd = _zos.getcwdb() + else: + cwd = _zos.getcwd() + path = join(cwd, path) + return normpath(path) + + +# Return a canonical path (i.e. the absolute location of a file on the +# filesystem). + +def realpath(filename): + """Return the canonical path of the specified filename, eliminating any +symbolic links encountered in the path.""" + path, ok = _joinrealpath(filename[:0], filename, {}) + return abspath(path) + +# Join two paths, normalizing ang eliminating any symbolic links +# encountered in the second path. +def _joinrealpath(path, rest, seen): + if isinstance(path, bytes): + sep = b'/' + curdir = b'.' + pardir = b'..' + else: + sep = '/' + curdir = '.' + pardir = '..' + + if isabs(rest): + rest = rest[1:] + path = sep + + while rest: + name, _, rest = rest.partition(sep) + if not name or name == curdir: + # current dir + continue + if name == pardir: + # parent dir + if path: + path, name = split(path) + if name == pardir: + path = join(path, pardir, pardir) + else: + path = pardir + continue + newpath = join(path, name) + if not islink(newpath): + path = newpath + continue + # Resolve the symbolic link + if newpath in seen: + # Already seen this path + path = seen[newpath] + if path is not None: + # use cached value + continue + # The symlink is not resolved, so we must have a symlink loop. + # Return already resolved part + rest of the path unchanged. + return join(newpath, rest), False + seen[newpath] = None # not resolved symlink + linkdst = _posix.readlink(newpath) + if isinstance(path, bytes) and isinstance(linkdst, str): + linkdst = linkdst.encode() + path, ok = _joinrealpath(path, linkdst, seen) + if not ok: + return join(path, rest), False + seen[newpath] = path # resolved symlink + + return path, True + + +supports_unicode_filenames = (_usys.platform == 'darwin') + +def relpath(path, start=None): + """Return a relative version of a path""" + + if not path: + raise ValueError("no path specified") + + if isinstance(path, bytes): + curdir = b'.' + sep = b'/' + pardir = b'..' + else: + curdir = '.' + sep = '/' + pardir = '..' + + if start is None: + start = curdir + + start_list = [x for x in abspath(start).split(sep) if x] + path_list = [x for x in abspath(path).split(sep) if x] + + # Work out how much of the filepath is shared by start and path. + i = len(commonprefix([start_list, path_list])) + + rel_list = [pardir] * (len(start_list)-i) + path_list[i:] + if not rel_list: + return curdir + return join(*rel_list) \ No newline at end of file diff --git a/micropython/pymodule/re.py b/micropython/pymodule/re.py new file mode 100644 index 0000000..1677917 --- /dev/null +++ b/micropython/pymodule/re.py @@ -0,0 +1,138 @@ +# This module provides partial functionality of the CPython "re" module for +# MicroPython. +# Notes: +# This module uses MicroPython's builtin regex engine "re1.5". +# Regular expression syntax supported is a subset of CPython re module. + +import sys as _sys +_path = _sys.path +_sys.path = () +try: + import re as _ure +finally: + _sys.path = _path + del _path + +__all__ = [ + 'Match', 'Pattern', 'compile', 'findall', 'match', 'search', 'split', 'sub' +] + + +_default = object() + + +class Pattern(object): + _Pattern = type(_ure.compile('')) + + def __init__(self, p, pstring, flags): + if not isinstance(p, Pattern._Pattern): + raise TypeError + self._p = p + self.pattern = pstring + self.flags = flags + + def search(self, string, pos=0, endpos=_default): + if endpos is _default: + m = self._p.search(string, pos) + else: + m = self._p.search(string, pos, endpos) + if m: + return Match(m, self, string) + + def match(self, string, pos=0, endpos=_default): + if endpos is _default: + m = self._p.match(string, pos) + else: + m = self._p.match(string, pos, endpos) + if m: + return Match(m, self, string) + + def split(self, string, maxsplit=0): + return self._p.split(string, maxsplit) + + def sub(self, repl, string, count=0): + if callable(repl): + return self._p.sub(lambda m: repl(Match(m, self, string)), string, count) + else: + return self._p.sub(repl, string, count) + + def findall(self, string, pos=0, endpos=_default): + all = [] + def cb(m): + groups = m.groups() + if groups and len(groups) > 2: + all.append(groups) + else: + all.append(m.group(0)) + return type(m.string)() + + if endpos is not _default: + if endpos < pos: + endpos = pos + if pos < 0: + pos = 0 + string = string[pos:endpos] + elif pos > 0: + string = string[pos:] + self.sub(cb, string) + return all + + +class Match(object): + _Match = type(_ure.match('','')) + + def __init__(self, m, re, string): + if not isinstance(m, Match._Match): + raise TypeError + if not isinstance(re, Pattern): + raise TypeError + self._m = m + self.re = re + self.string = string + + def group(self, *args): + if not args: + return self._m.group(0) + elif len(args) == 1: + return self._m.group(args[0]) + glist = [] + for idx in args: + glist.append(self._m.group(idx)) + return tuple(glist) + + def groups(self, default=None): + if default is None: + return self._m.groups() + else: + glist = list(self._m.groups()) + for i, value in enumerate(glist): + if value is None: + glist[i] = default + return tuple(glist) + + +def compile(pattern, flags=0): + if isinstance(pattern, Pattern): + return compile(pattern.pattern, flags) + p = _ure.compile(pattern, flags) + return Pattern(p, pattern, flags) + + +def search(pattern, string, flags=0): + return compile(pattern, flags).search(string) + + +def match(pattern, string, flags=0): + return compile(pattern, flags).match(string) + + +def split(pattern, string, maxsplit=0, flags=0): + return compile(pattern, flags).split(string, maxsplit) + + +def sub(pattern, repl, string, count=0, flags=0): + return compile(pattern, flags).sub(repl, string, count) + + +def findall(pattern, string, flags=0): + return compile(pattern, flags).findall(string) diff --git a/micropython/pymodule/signal.py b/micropython/pymodule/signal.py new file mode 100644 index 0000000..928a5bb --- /dev/null +++ b/micropython/pymodule/signal.py @@ -0,0 +1,39 @@ +# This module provides partial functionality of the CPython "signal" module for +# MicroPython. +import _posix + + +def signal(signalnum, handler): + return _posix.signal(signalnum, handler) + + +SIG_IGN = _posix.SIG_IGN +SIG_DFL = _posix.SIG_DFL +SIGABRT = _posix.SIGABRT +SIGALRM = _posix.SIGALRM +SIGBUS = _posix.SIGBUS +SIGCHLD = _posix.SIGCHLD +SIGCONT = _posix.SIGCONT +SIGFPE = _posix.SIGFPE +SIGHUP = _posix.SIGHUP +SIGILL = _posix.SIGILL +SIGINT = _posix.SIGINT +SIGKILL = _posix.SIGKILL +SIGPIPE = _posix.SIGPIPE +SIGQUIT = _posix.SIGQUIT +SIGSEGV = _posix.SIGSEGV +SIGSTOP = _posix.SIGSTOP +SIGTERM = _posix.SIGTERM +SIGTSTP = _posix.SIGTSTP +SIGTTIN = _posix.SIGTTIN +SIGTTOU = _posix.SIGTTOU +SIGUSR1 = _posix.SIGUSR1 +SIGUSR2 = _posix.SIGUSR2 +SIGPOLL = _posix.SIGPOLL +SIGPROF = _posix.SIGPROF +SIGSYS = _posix.SIGSYS +SIGTRAP = _posix.SIGTRAP +SIGURG = _posix.SIGURG +SIGVTALRM = _posix.SIGVTALRM +SIGXCPU = _posix.SIGXCPU +SIGXFSZ = _posix.SIGXFSZ diff --git a/micropython/pymodule/socket.py b/micropython/pymodule/socket.py new file mode 100644 index 0000000..df78707 --- /dev/null +++ b/micropython/pymodule/socket.py @@ -0,0 +1,266 @@ +# This module provides partial functionality of the CPython "socket" module for +# MicroPython. + +import sys as _sys +_path = _sys.path +_sys.path = () +try: + import socket as _usocket +finally: + _sys.path = _path + del _path + +import _posix + + +class TimeoutError(OSError): + pass + + +class gaierror(OSError): + def __init__(self, *args): + if len(args) == 1 and isinstance(args[0], int): + errno = args[0] + msg = "[Errno %d] %s" %(errno, _posix.gai_strerror(args[0])) + super().__init__(msg) + self.errno = errno + else: + super().__init__(*args) + + +class socket(object): + def __init__(self, family=_posix.AF_INET, type=_posix.SOCK_STREAM, + proto=0, *, _sock=None): + if _sock is not None: + self._s = _sock + else: + self._s = _usocket.socket(family, type, proto) + self.family = family + self.type = type + self.proto = proto + self._closed = False + + def __enter__(self): + return self + + def __exit__(self, *args): + if not self._closed: + self.close() + + def _is_timeouterror(self, oserr): + return oserr.errno in [_posix.ETIMEDOUT, _posix.EAGAIN] + + def _arg_addr(self, addr): + host, port = addr + if host == '': + if self.family == _posix.AF_INET: + host = '0.0.0.0' + elif self.family == _posix.AF_INET6: + host = '::' + if isinstance(port, int): + port = str(port) + return _getaddrinfo( + host, port, self.family, self.type, self.proto, + _posix.AI_NUMERICSERV + )[0][4] + + def _ret_addr(self, addr): + host, serv = _getnameinfo( + addr, _posix.NI_NUMERICHOST | _posix.NI_NUMERICSERV + ) + return host, int(serv) + + def fileno(self): + if self._closed: + return -1 + return self._s.fileno() + + def bind(self, addr): + addr = self._arg_addr(addr) + self._s.bind(addr) + + def listen(self, backlog): + self._s.listen(backlog) + + def accept(self): + s, addr = self._s.accept() + s = socket(self.family, self.type, self.proto, _sock=s) + addr = self._ret_addr(addr) + return s, addr + + def connect(self, addr): + addr = self._arg_addr(addr) + try: + self._s.connect(addr) + except OSError as ex: + if self._is_timeouterror(ex): + raise timeout(ex) from None + raise + + def connect_ex(self, addr): + try: + self.connect(addr) + except OSError as ex: + return ex.errno + else: + return 0 + + def send(self, data): + try: + return self._s.send(data) + except OSError as ex: + if self._is_timeouterror(ex): + raise timeout(ex) from None + raise + + def sendto(self, data, addr): + addr = self._arg_addr(addr) + try: + return self._s.sendto(data, addr) + except OSError as ex: + if self._is_timeouterror(ex): + raise timeout(ex) from None + raise + + def sendall(self, data): + try: + tosend = data + while tosend: + size = self.send(tosend) + tosend = tosend[size:] + except OSError as ex: + if self._is_timeouterror(ex): + raise timeout(ex) from None + raise + + def recv(self, bufsize): + try: + return self._s.recv(bufsize) + except OSError as ex: + if self._is_timeouterror(ex): + raise timeout(ex) from None + raise + + def recvfrom(self, bufsize): + try: + data, addr = self._s.recvfrom(bufsize) + except OSError as ex: + if self._is_timeouterror(ex): + raise timeout(ex) from None + raise + addr = self._ret_addr(addr) + return data, addr + + def shutdown(self, how): + _posix.shutdown(self.fileno(), how) + + def close(self): + if not self._closed: + self._s.close() + self._closed = True + + def getsockname(self): + addr = _posix.getsockname(self.fileno()) + return self._ret_addr(addr) + + def getpeername(self): + addr = _posix.getpeername(self.fileno()) + return self._ret_addr(addr) + + def setsockopt(self, level, optname, value): + return self._s.setsockopt(level, optname, value) + + def settimeout(self, value): + return self._s.settimeout(value) + + def setblocking(self, flag): + return self._s.setblocking(flag) + + +def inet_aton(ip_addr): + return _usocket.inet_pton(_posix.AF_INET, ip_addr) + + +def inet_ntoa(packed_ip): + return _usocket.inet_ntop(_posix.AF_INET, packed_ip) + + +def _getaddrinfo(*args, **kwargs): + try: + return _posix.getaddrinfo(*args, **kwargs) + except OSError as ex: + raise gaierror(ex.errno) from None + + +def _getnameinfo(*args, **kwargs): + try: + return _posix.getnameinfo(*args, **kwargs) + except OSError as ex: + raise gaierror(ex.errno) from None + + +error = OSError +timeout = TimeoutError + +inet_pton = _usocket.inet_pton +inet_ntop = _usocket.inet_ntop + + +SOCK_DGRAM = _posix.SOCK_DGRAM +SOCK_RAW = _posix.SOCK_RAW +SOCK_SEQPACKET = _posix.SOCK_SEQPACKET +SOCK_STREAM = _posix.SOCK_STREAM +SOL_SOCKET = _posix.SOL_SOCKET +SO_ACCEPTCONN = _posix.SO_ACCEPTCONN +if hasattr(_posix, 'SO_BINDTODEVICE'): + SO_BINDTODEVICE = _posix.SO_BINDTODEVICE +SO_BROADCAST = _posix.SO_BROADCAST +SO_DEBUG = _posix.SO_DEBUG +SO_DONTROUTE = _posix.SO_DONTROUTE +SO_ERROR = _posix.SO_ERROR +SO_KEEPALIVE = _posix.SO_KEEPALIVE +SO_LINGER = _posix.SO_LINGER +SO_OOBINLINE = _posix.SO_OOBINLINE +SO_RCVBUF = _posix.SO_RCVBUF +SO_RCVLOWAT = _posix.SO_RCVLOWAT +SO_RCVTIMEO = _posix.SO_RCVTIMEO +SO_REUSEADDR = _posix.SO_REUSEADDR +if hasattr(_posix, 'SO_REUSEPORT'): + SO_REUSEPORT = _posix.SO_REUSEPORT +SO_SNDBUF = _posix.SO_SNDBUF +SO_SNDLOWAT = _posix.SO_SNDLOWAT +SO_SNDTIMEO = _posix.SO_SNDTIMEO +SO_TYPE = _posix.SO_TYPE +SOMAXCONN = _posix.SOMAXCONN +MSG_CTRUNC = _posix.MSG_CTRUNC +MSG_DONTROUTE = _posix.MSG_DONTROUTE +MSG_EOR = _posix.MSG_EOR +MSG_OOB = _posix.MSG_OOB +MSG_PEEK = _posix.MSG_PEEK +MSG_TRUNC = _posix.MSG_TRUNC +MSG_WAITALL = _posix.MSG_WAITALL +AF_INET = _posix.AF_INET +AF_INET6 = _posix.AF_INET6 +AF_UNIX = _posix.AF_UNIX +AF_UNSPEC = _posix.AF_UNSPEC +SHUT_RD = _posix.SHUT_RD +SHUT_RDWR = _posix.SHUT_RDWR +SHUT_WR = _posix.SHUT_WR +IPPROTO_IP = _posix.IPPROTO_IP +IPPROTO_IPV6 = _posix.IPPROTO_IPV6 +IPPROTO_ICMP = _posix.IPPROTO_ICMP +IPPROTO_RAW = _posix.IPPROTO_RAW +IPPROTO_TCP = _posix.IPPROTO_TCP +IPPROTO_UDP = _posix.IPPROTO_UDP +INADDR_ANY = _posix.INADDR_ANY +INADDR_BROADCAST = _posix.INADDR_BROADCAST +INET_ADDRSTRLEN = _posix.INET_ADDRSTRLEN +INET6_ADDRSTRLEN = _posix.INET6_ADDRSTRLEN +IPV6_JOIN_GROUP = _posix.IPV6_JOIN_GROUP +IPV6_LEAVE_GROUP = _posix.IPV6_LEAVE_GROUP +IPV6_MULTICAST_HOPS = _posix.IPV6_MULTICAST_HOPS +IPV6_MULTICAST_IF = _posix.IPV6_MULTICAST_IF +IPV6_MULTICAST_LOOP = _posix.IPV6_MULTICAST_LOOP +IPV6_UNICAST_HOPS = _posix.IPV6_UNICAST_HOPS +IPV6_V6ONLY = _posix.IPV6_V6ONLY +TCP_NODELAY = _posix.TCP_NODELAY diff --git a/micropython/pymodule/struct.py b/micropython/pymodule/struct.py new file mode 100644 index 0000000..a4f129a --- /dev/null +++ b/micropython/pymodule/struct.py @@ -0,0 +1,57 @@ +# This module provides partial functionality of the CPython "struct" module for +# MicroPython. + +import sys as _sys +_path = _sys.path +_sys.path = () +try: + import struct as _ustruct +finally: + _sys.path = _path + del _path + +__all__ = [ + 'calcsize', 'pack', 'pack_into', 'unpack', 'unpack_from' +] + + +class _StructError(Exception): + pass + + +error = _StructError + + +def pack(format, *args): + try: + return _ustruct.pack(format, *args) + except Exception as ex: + raise error(ex) from None + + +def unpack(format, buffer): + try: + return _ustruct.unpack(format, buffer) + except Exception as ex: + raise error(ex) from None + + +def pack_into(format, buffer, offset, *args): + try: + return _ustruct.pack_into(format, buffer, offset, *args) + except Exception as ex: + raise error(ex) from None + + +def unpack_from(format, buffer, offset=0): + try: + return _ustruct.unpack_from(format, buffer, offset) + except Exception as ex: + raise error(ex) from None + + +def calcsize(format): + try: + return _ustruct.calcsize(format) + except Exception as ex: + raise error(ex) from None diff --git a/micropython/pymodule/subprocess.py b/micropython/pymodule/subprocess.py new file mode 100644 index 0000000..176e262 --- /dev/null +++ b/micropython/pymodule/subprocess.py @@ -0,0 +1,254 @@ +# This module provides partial functionality of the CPython "subprocess" module +# for MicroPython. +# Note: Thread safety is not guaranteed. + +import sys as _usys +import _posix + +__all__ = [ + 'CalledProcessError', 'Popen', 'STDOUT', 'SubprocessError', 'check_output' +] + + +STDOUT = -2 + + +class SubprocessError(Exception): + pass + + +class CalledProcessError(SubprocessError): + def __init__(self, returncode, cmd, output=None): + self.returncode = returncode + self.cmd = cmd + self.output = output + + def __str__(self): + return "Command '%s' returned non-zero exit status %d" % ( + self.cmd, self.returncode + ) + + +def _handle_exitstatus(sts): + if _posix.WIFSIGNALED(sts): + return -(_posix.WTERMSIG(sts)) + elif _posix.WIFEXITED(sts): + return _posix.WEXITSTATUS(sts) + else: + # Should never happen + raise SubprocessError("Unknown child exit status!") + + +def _close_fd(fd): + if fd < 0: + return + try: + _posix.close(fd) + fd = -1 + except OSError as ex: + _usys.stderr.write('close(): %s\n' % str(ex)) + return fd + + +def _kill_child(pid, signal): + if pid <= 0: + return + _, sts = _posix.waitpid(pid, _posix.WNOHANG) + if not sts: + try: + _posix.kill(pid, signal) + except OSError as ex: + if ex.errno != _posix.ESRCH: + raise + _posix.waitpid(pid, 0) + + +def _set_fd_cloexec(fd): + flags = _posix.fcntl(fd, _posix.F_GETFD, None) + _posix.fcntl(fd, _posix.F_SETFD, flags | _posix.FD_CLOEXEC) + + +def check_output(args, *, stderr=None): + if stderr is not None and stderr != STDOUT: + raise NotImplementedError("Unsupported stderr redirection") + + executable = args[0] + r, w = _posix.pipe() + er, ew = _posix.pipe() + _set_fd_cloexec(er) + _set_fd_cloexec(ew) + + try: + pid = _posix.fork() + except Exception: + _close_fd(r) + _close_fd(w) + raise + + if pid == 0: + # child + try: + r = _close_fd(r) + er = _close_fd(er) + _posix.dup2(w, _posix.STDOUT_FILENO) + if stderr == STDOUT: + _posix.dup2(w, _posix.STDERR_FILENO) + w = _close_fd(w) + _posix.execvp(executable, args) + except OSError as ex: + _posix.write(ew, str(ex.errno).encode()) + finally: + _close_fd(r) + _close_fd(er) + _close_fd(w) + _close_fd(ew) + _posix._exit(1) + else: + # parent + try: + w = _close_fd(w) + ew = _close_fd(ew) + + output_l = [] + while True: + chunk = _posix.read(r, _posix.BUFSIZ) + if not chunk: + break + output_l.append(chunk) + + errno_l = [] + while True: + chunk = _posix.read(er, _posix.BUFSIZ) + if not chunk: + break + errno_l.append(chunk) + + r = _close_fd(r) + er = _close_fd(er) + + output = b''.join(output_l) + errno_b = b''.join(errno_l) + + _, status = _posix.waitpid(pid, 0) + pid = -1 + + if errno_b: + errno = int(errno_b.decode()) + raise OSError(errno) + + retcode = _handle_exitstatus(status) + if retcode: + raise CalledProcessError(retcode, args, output) + return output + finally: + _close_fd(r) + _close_fd(er) + _close_fd(w) + _close_fd(ew) + _kill_child(pid, _posix.SIGKILL) + + +class Popen(object): + def __init__(self, args): + self.args = args + self.pid = None + self.returncode = None + self._start_child() + + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + self.wait() + + def _start_child(self): + executable = self.args[0] + + er, ew = _posix.pipe() + _set_fd_cloexec(er) + _set_fd_cloexec(ew) + + try: + pid = _posix.fork() + except Exception: + _close_fd(er) + _close_fd(ew) + raise + + if pid == 0: + # child + try: + er = _close_fd(er) + _posix.execvp(executable, self.args) + except OSError as ex: + _posix.write(ew, str(ex.errno).encode()) + finally: + _close_fd(er) + _close_fd(ew) + _posix._exit(1) + else: + # parent + self.pid = pid + try: + ew = _close_fd(ew) + + errno_l = [] + while True: + chunk = _posix.read(er, _posix.BUFSIZ) + if not chunk: + break + errno_l.append(chunk) + + er = _close_fd(er) + + errno_b = b''.join(errno_l) + if errno_b: + errno = int(errno_b.decode()) + raise OSError(errno) + except Exception: + _kill_child(pid, _posix.SIGKILL) + raise + + finally: + _close_fd(er) + _close_fd(ew) + + def poll(self): + if self.returncode is not None: + return self.returncode + try: + pid, sts = _posix.waitpid(self.pid, _posix.WNOHANG) + if pid == self.pid: + self.returncode = _handle_exitstatus(sts) + except OSError as e: + if e.errno == _posix.ECHILD: + self.returncode = 0 + return self.returncode + + def wait(self): + if self.returncode is not None: + return self.returncode + try: + _, sts = _posix.waitpid(self.pid, 0) + self.returncode = _handle_exitstatus(sts) + except OSError as ex: + if ex.errno != _posix.ECHILD: + raise + self.returncode = 0 + return self.returncode + + def send_signal(self, sig): + self.poll() + if self.returncode is not None: + return + try: + _posix.kill(self.pid, sig) + except OSError as ex: + if ex.errno != _posix.ESRCH: + raise + + def terminate(self): + self.send_signal(_posix.SIGTERM) + + def kill(self): + self.send_signal(_posix.SIGKILL) diff --git a/micropython/pymodule/threading.py b/micropython/pymodule/threading.py new file mode 100644 index 0000000..621dd25 --- /dev/null +++ b/micropython/pymodule/threading.py @@ -0,0 +1,106 @@ +# This module provides partial functionality of the CPython "threading" module +# for MicroPython. + +import _thread + +__all__ = [ + 'Thread', 'active_count', 'current_thread', 'main_thread' +] + + +class Thread(object): + _lock = _thread.allocate_lock() + _active = dict() + + def __init__(self, group=None, target=None, name=None, args=(), + kwargs=None): + if group is not None: + raise NotImplementedError("group argument must be None for now") + if kwargs is None: + kwargs = {} + self.name = str(name or "unnamed thread") + self.ident = None + self._target = target + self._args = args + self._kwargs = kwargs + self._start_called = False + self._is_started = False + self._is_stopped = False + self._initialized = True + self._join_lck = _thread.allocate_lock() + + def _pre_run(self): + # this method is internally called inside the child thread + self._join_lck.acquire() + self.ident = _thread.get_ident() + Thread._lock.acquire() + Thread._active[self.ident] = self + Thread._lock.release() + self._is_started = True + + def _post_run(self): + # this method is internally called inside the child thread + self._is_stopped = True + Thread._lock.acquire() + del Thread._active[self.ident] + Thread._lock.release() + self._join_lck.release() + + def run(self): + try: + if self._target: + self._target(*self._args, **self._kwargs) + finally: + del self._target, self._args, self._kwargs + + def start(self): + if not self._initialized: + raise RuntimeError("thread.__init__() not called") + if self._start_called: + raise RuntimeError("threads can only be started once") + def _entry(): + self._pre_run() + try: + self.run() + finally: + self._post_run() + self._start_called = True + _thread.start_new_thread(_entry, ()) + + def is_alive(self): + if not self._initialized: + raise RuntimeError("thread.__init__() not called") + return self._is_started and not self._is_stopped + + def join(self): + if not self._initialized: + raise RuntimeError("thread.__init__() not called") + if not self._is_started: + raise RuntimeError("cannot join thread before it is started") + if self.ident == _thread.get_ident(): + raise RuntimeError("cannot join current thread") + self._join_lck.acquire() + self._join_lck.release() + + +class _MainThread(Thread): + def __init__(self): + super().__init__(name="MainThread") + self._start_called = True + self._pre_run() + + +_main_thread = _MainThread() + + +def active_count(): + return len(Thread._active) + + +def current_thread(): + ident = _thread.get_ident() + return Thread._active[ident] + + +def main_thread(): + return _main_thread