diff --git a/.gitmodules b/.gitmodules index f52ca8e7fa756..3c6aac61cadc3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -173,7 +173,7 @@ [submodule "ports/raspberrypi/sdk"] path = ports/raspberrypi/sdk url = https://github.com/adafruit/pico-sdk.git - branch = force_inline_critical_section + branch = force_inline_critical_section_2.1.1 [submodule "data/nvm.toml"] path = data/nvm.toml url = https://github.com/adafruit/nvm.toml.git diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index 687cf5f071524..6cf22b730b26c 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -3024,6 +3024,10 @@ msgstr "" msgid "expected ':' after format specifier" msgstr "" +#: shared-module/pathlib/PosixPath.c +msgid "expected str or PosixPath" +msgstr "" + #: py/obj.c msgid "expected tuple/list" msgstr "" diff --git a/main.c b/main.c index 0ea389635f40f..db5add2ebfa25 100644 --- a/main.c +++ b/main.c @@ -457,14 +457,19 @@ static bool __attribute__((noinline)) run_code_py(safe_mode_t safe_mode, bool *s usb_setup_with_vm(); #endif + // Always return to root before trying to run files. + common_hal_os_chdir("/"); // Check if a different run file has been allocated if (next_code_configuration != NULL) { next_code_configuration->options &= ~SUPERVISOR_NEXT_CODE_OPT_NEWLY_SET; next_code_options = next_code_configuration->options; if (next_code_configuration->filename[0] != '\0') { + if (next_code_configuration->working_directory != NULL) { + common_hal_os_chdir(next_code_configuration->working_directory); + } // This is where the user's python code is actually executed: const char *const filenames[] = { next_code_configuration->filename }; - found_main = maybe_run_list(filenames, MP_ARRAY_SIZE(filenames)); + found_main = maybe_run_list(filenames, 1); if (!found_main) { serial_write(next_code_configuration->filename); serial_write_compressed(MP_ERROR_TEXT(" not found.\n")); @@ -1109,9 +1114,6 @@ int __attribute__((used)) main(void) { } simulate_reset = false; - // Always return to root before trying to run files. - common_hal_os_chdir("/"); - if (pyexec_mode_kind == PYEXEC_MODE_FRIENDLY_REPL) { // If code.py did a fake deep sleep, pretend that we // are running code.py for the first time after a hard diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index 7c895a821c832..f18f0afb7f582 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -303,6 +303,9 @@ endif ifeq ($(CIRCUITPY_OS),1) SRC_PATTERNS += os/% endif +ifeq ($(CIRCUITPY_PATHLIB),1) +SRC_PATTERNS += pathlib/% +endif ifeq ($(CIRCUITPY_DUALBANK),1) SRC_PATTERNS += dualbank/% endif @@ -714,6 +717,8 @@ SRC_SHARED_MODULE_ALL = \ onewireio/__init__.c \ onewireio/OneWire.c \ os/__init__.c \ + pathlib/__init__.c \ + pathlib/PosixPath.c \ paralleldisplaybus/ParallelBus.c \ qrio/__init__.c \ qrio/QRDecoder.c \ diff --git a/py/circuitpy_mpconfig.mk b/py/circuitpy_mpconfig.mk index 52520215b24d7..2b9b4edd7f018 100644 --- a/py/circuitpy_mpconfig.mk +++ b/py/circuitpy_mpconfig.mk @@ -403,6 +403,9 @@ CFLAGS += -DCIRCUITPY_OPT_MAP_LOOKUP_CACHE=$(CIRCUITPY_OPT_MAP_LOOKUP_CACHE) CIRCUITPY_OS ?= 1 CFLAGS += -DCIRCUITPY_OS=$(CIRCUITPY_OS) +CIRCUITPY_PATHLIB ?= $(CIRCUITPY_FULL_BUILD) +CFLAGS += -DCIRCUITPY_PATHLIB=$(CIRCUITPY_PATHLIB) + CIRCUITPY_PEW ?= 0 CFLAGS += -DCIRCUITPY_PEW=$(CIRCUITPY_PEW) diff --git a/shared-bindings/pathlib/PosixPath.c b/shared-bindings/pathlib/PosixPath.c new file mode 100644 index 0000000000000..5931ddf1e3b22 --- /dev/null +++ b/shared-bindings/pathlib/PosixPath.c @@ -0,0 +1,236 @@ +#include + +#include "py/obj.h" +#include "py/objproperty.h" +#include "py/runtime.h" +#include "py/mperrno.h" +#include "py/mphal.h" +#include "py/gc.h" +#include "py/objstr.h" +#include "py/objtuple.h" +#include "py/objtype.h" + +#include "shared-bindings/pathlib/PosixPath.h" +#include "shared-module/pathlib/PosixPath.h" + +//| class PosixPath: +//| """Object representing a path on Posix systems""" +//| +//| def __init__(self, path: Union[str, "PosixPath"]) -> None: +//| """Construct a PosixPath object from a string or another PosixPath object. +//| +//| :param path: A string or PosixPath object representing a path +//| """ +//| ... +//| + +static mp_obj_t pathlib_posixpath_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, 1, false); + return common_hal_pathlib_posixpath_new(args[0]); +} + +//| def joinpath(self, *args: Union[str, "PosixPath"]) -> "PosixPath": +//| """Join path components. +//| +//| :param args: Path components to join +//| :return: A new PosixPath object +//| """ +//| ... +//| +//| def __truediv__(self, other: Union[str, "PosixPath"]) -> "PosixPath": +//| """Implement path / other for joining paths. +//| +//| :param other: Path component to join +//| :return: A new PosixPath object +//| """ +//| ... +//| + +static mp_obj_t pathlib_posixpath_joinpath(mp_obj_t self_in, mp_obj_t arg) { + pathlib_posixpath_obj_t *self = MP_OBJ_TO_PTR(self_in); + return common_hal_pathlib_posixpath_joinpath(self, arg); +} +MP_DEFINE_CONST_FUN_OBJ_2(pathlib_posixpath_joinpath_obj, pathlib_posixpath_joinpath); + +// Binary operator for implementing the / operator +static mp_obj_t pathlib_posixpath_binary_op(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs) { + switch (op) { + case MP_BINARY_OP_TRUE_DIVIDE: { + // Implement path / other + pathlib_posixpath_obj_t *self = MP_OBJ_TO_PTR(lhs); + return common_hal_pathlib_posixpath_joinpath(self, rhs); + } + default: + return MP_OBJ_NULL; // op not supported + } +} + +//| @property +//| def parent(self) -> "PosixPath": +//| """The logical parent of the path.""" +//| ... +//| + +static mp_obj_t pathlib_posixpath_parent(mp_obj_t self_in) { + pathlib_posixpath_obj_t *self = MP_OBJ_TO_PTR(self_in); + return common_hal_pathlib_posixpath_parent(self); +} +MP_DEFINE_CONST_FUN_OBJ_1(pathlib_posixpath_parent_obj, pathlib_posixpath_parent); + +MP_PROPERTY_GETTER(pathlib_posixpath_parent_property_obj, + (mp_obj_t)&pathlib_posixpath_parent_obj); + +//| @property +//| def name(self) -> str: +//| """The final path component, excluding the drive and root, if any.""" +//| ... +//| + +static mp_obj_t pathlib_posixpath_name(mp_obj_t self_in) { + pathlib_posixpath_obj_t *self = MP_OBJ_TO_PTR(self_in); + return common_hal_pathlib_posixpath_name(self); +} +MP_DEFINE_CONST_FUN_OBJ_1(pathlib_posixpath_name_obj, pathlib_posixpath_name); + +MP_PROPERTY_GETTER(pathlib_posixpath_name_property_obj, + (mp_obj_t)&pathlib_posixpath_name_obj); + +//| @property +//| def stem(self) -> str: +//| """The final path component, without its suffix.""" +//| ... +//| + +static mp_obj_t pathlib_posixpath_stem(mp_obj_t self_in) { + pathlib_posixpath_obj_t *self = MP_OBJ_TO_PTR(self_in); + return common_hal_pathlib_posixpath_stem(self); +} +MP_DEFINE_CONST_FUN_OBJ_1(pathlib_posixpath_stem_obj, pathlib_posixpath_stem); + +MP_PROPERTY_GETTER(pathlib_posixpath_stem_property_obj, + (mp_obj_t)&pathlib_posixpath_stem_obj); + +//| @property +//| def suffix(self) -> str: +//| """The final component's extension.""" +//| ... +//| + +static mp_obj_t pathlib_posixpath_suffix(mp_obj_t self_in) { + pathlib_posixpath_obj_t *self = MP_OBJ_TO_PTR(self_in); + return common_hal_pathlib_posixpath_suffix(self); +} +MP_DEFINE_CONST_FUN_OBJ_1(pathlib_posixpath_suffix_obj, pathlib_posixpath_suffix); + +MP_PROPERTY_GETTER(pathlib_posixpath_suffix_property_obj, + (mp_obj_t)&pathlib_posixpath_suffix_obj); + +//| def exists(self) -> bool: +//| """Check whether the path exists.""" +//| ... +//| + +static mp_obj_t pathlib_posixpath_exists(mp_obj_t self_in) { + pathlib_posixpath_obj_t *self = MP_OBJ_TO_PTR(self_in); + return common_hal_pathlib_posixpath_exists(self); +} +MP_DEFINE_CONST_FUN_OBJ_1(pathlib_posixpath_exists_obj, pathlib_posixpath_exists); + +//| def is_dir(self) -> bool: +//| """Check whether the path is a directory.""" +//| ... +//| + +static mp_obj_t pathlib_posixpath_is_dir(mp_obj_t self_in) { + pathlib_posixpath_obj_t *self = MP_OBJ_TO_PTR(self_in); + return common_hal_pathlib_posixpath_is_dir(self); +} +MP_DEFINE_CONST_FUN_OBJ_1(pathlib_posixpath_is_dir_obj, pathlib_posixpath_is_dir); + +//| def is_file(self) -> bool: +//| """Check whether the path is a regular file.""" +//| ... +//| + +static mp_obj_t pathlib_posixpath_is_file(mp_obj_t self_in) { + pathlib_posixpath_obj_t *self = MP_OBJ_TO_PTR(self_in); + return common_hal_pathlib_posixpath_is_file(self); +} +MP_DEFINE_CONST_FUN_OBJ_1(pathlib_posixpath_is_file_obj, pathlib_posixpath_is_file); + +//| def absolute(self) -> "PosixPath": +//| """Return an absolute version of this path.""" +//| ... +//| + +static mp_obj_t pathlib_posixpath_absolute(mp_obj_t self_in) { + pathlib_posixpath_obj_t *self = MP_OBJ_TO_PTR(self_in); + return common_hal_pathlib_posixpath_absolute(self); +} +MP_DEFINE_CONST_FUN_OBJ_1(pathlib_posixpath_absolute_obj, pathlib_posixpath_absolute); + +//| def resolve(self) -> "PosixPath": +//| """Make the path absolute, resolving any symlinks.""" +//| ... +//| + +static mp_obj_t pathlib_posixpath_resolve(mp_obj_t self_in) { + pathlib_posixpath_obj_t *self = MP_OBJ_TO_PTR(self_in); + return common_hal_pathlib_posixpath_resolve(self); +} +MP_DEFINE_CONST_FUN_OBJ_1(pathlib_posixpath_resolve_obj, pathlib_posixpath_resolve); + +//| def iterdir(self) -> Iterator["PosixPath"]: +//| """Iterate over the files in this directory. +//| Does not include the special paths '.' and '..'. +//| +//| :return: An iterator yielding path objects +//| +//| Example:: +//| +//| for path in Path('.').iterdir(): +//| print(path) +//| """ +//| ... +//| + +static mp_obj_t pathlib_posixpath_iterdir(mp_obj_t self_in) { + pathlib_posixpath_obj_t *self = MP_OBJ_TO_PTR(self_in); + return common_hal_pathlib_posixpath_iterdir(self); +} +MP_DEFINE_CONST_FUN_OBJ_1(pathlib_posixpath_iterdir_obj, pathlib_posixpath_iterdir); + +static const mp_rom_map_elem_t pathlib_posixpath_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_joinpath), MP_ROM_PTR(&pathlib_posixpath_joinpath_obj) }, + { MP_ROM_QSTR(MP_QSTR_exists), MP_ROM_PTR(&pathlib_posixpath_exists_obj) }, + { MP_ROM_QSTR(MP_QSTR_is_dir), MP_ROM_PTR(&pathlib_posixpath_is_dir_obj) }, + { MP_ROM_QSTR(MP_QSTR_is_file), MP_ROM_PTR(&pathlib_posixpath_is_file_obj) }, + { MP_ROM_QSTR(MP_QSTR_absolute), MP_ROM_PTR(&pathlib_posixpath_absolute_obj) }, + { MP_ROM_QSTR(MP_QSTR_resolve), MP_ROM_PTR(&pathlib_posixpath_resolve_obj) }, + { MP_ROM_QSTR(MP_QSTR_iterdir), MP_ROM_PTR(&pathlib_posixpath_iterdir_obj) }, + + // Properties + { MP_ROM_QSTR(MP_QSTR_parent), MP_ROM_PTR(&pathlib_posixpath_parent_property_obj) }, + { MP_ROM_QSTR(MP_QSTR_name), MP_ROM_PTR(&pathlib_posixpath_name_property_obj) }, + { MP_ROM_QSTR(MP_QSTR_stem), MP_ROM_PTR(&pathlib_posixpath_stem_property_obj) }, + { MP_ROM_QSTR(MP_QSTR_suffix), MP_ROM_PTR(&pathlib_posixpath_suffix_property_obj) }, +}; + +static MP_DEFINE_CONST_DICT(pathlib_posixpath_locals_dict, pathlib_posixpath_locals_dict_table); + +// String representation for PosixPath objects +static void pathlib_posixpath_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + pathlib_posixpath_obj_t *self = MP_OBJ_TO_PTR(self_in); + // Just print the path string directly + mp_obj_print_helper(print, self->path_str, kind); +} + +MP_DEFINE_CONST_OBJ_TYPE( + pathlib_posixpath_type, + MP_QSTR_PosixPath, + MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS, + make_new, pathlib_posixpath_make_new, + print, pathlib_posixpath_print, + binary_op, pathlib_posixpath_binary_op, + locals_dict, &pathlib_posixpath_locals_dict + ); diff --git a/shared-bindings/pathlib/PosixPath.h b/shared-bindings/pathlib/PosixPath.h new file mode 100644 index 0000000000000..67fcbadca6787 --- /dev/null +++ b/shared-bindings/pathlib/PosixPath.h @@ -0,0 +1,21 @@ +#pragma once + +#include "py/obj.h" + +extern const mp_obj_type_t pathlib_posixpath_type; + +// Include the struct definition from shared-module +#include "shared-module/pathlib/PosixPath.h" + +mp_obj_t common_hal_pathlib_posixpath_new(mp_obj_t path); +mp_obj_t common_hal_pathlib_posixpath_joinpath(pathlib_posixpath_obj_t *self, mp_obj_t arg); +mp_obj_t common_hal_pathlib_posixpath_parent(pathlib_posixpath_obj_t *self); +mp_obj_t common_hal_pathlib_posixpath_name(pathlib_posixpath_obj_t *self); +mp_obj_t common_hal_pathlib_posixpath_stem(pathlib_posixpath_obj_t *self); +mp_obj_t common_hal_pathlib_posixpath_suffix(pathlib_posixpath_obj_t *self); +mp_obj_t common_hal_pathlib_posixpath_exists(pathlib_posixpath_obj_t *self); +mp_obj_t common_hal_pathlib_posixpath_is_dir(pathlib_posixpath_obj_t *self); +mp_obj_t common_hal_pathlib_posixpath_is_file(pathlib_posixpath_obj_t *self); +mp_obj_t common_hal_pathlib_posixpath_absolute(pathlib_posixpath_obj_t *self); +mp_obj_t common_hal_pathlib_posixpath_resolve(pathlib_posixpath_obj_t *self); +mp_obj_t common_hal_pathlib_posixpath_iterdir(pathlib_posixpath_obj_t *self); diff --git a/shared-bindings/pathlib/__init__.c b/shared-bindings/pathlib/__init__.c new file mode 100644 index 0000000000000..7b45c14c8b266 --- /dev/null +++ b/shared-bindings/pathlib/__init__.c @@ -0,0 +1,40 @@ +#include + +#include "py/obj.h" +#include "py/runtime.h" +#include "py/objtype.h" + +#include "shared-bindings/pathlib/__init__.h" +#include "shared-bindings/pathlib/PosixPath.h" + +//| """Filesystem path operations""" +//| + +//| class Path: +//| """Factory function that returns a new PosixPath.""" +//| +//| def __new__(cls, *args) -> PosixPath: +//| """Create a new Path object. +//| +//| :param args: Path components +//| :return: A new PosixPath object +//| """ +//| ... +//| + +/* Path is just an alias for PosixPath in CircuitPython */ + +static const mp_rom_map_elem_t pathlib_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_pathlib) }, + { MP_ROM_QSTR(MP_QSTR_Path), MP_ROM_PTR(&pathlib_posixpath_type) }, + { MP_ROM_QSTR(MP_QSTR_PosixPath), MP_ROM_PTR(&pathlib_posixpath_type) }, +}; + +static MP_DEFINE_CONST_DICT(pathlib_module_globals, pathlib_module_globals_table); + +const mp_obj_module_t pathlib_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&pathlib_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_pathlib, pathlib_module); diff --git a/shared-bindings/pathlib/__init__.h b/shared-bindings/pathlib/__init__.h new file mode 100644 index 0000000000000..50c64c54067bb --- /dev/null +++ b/shared-bindings/pathlib/__init__.h @@ -0,0 +1,5 @@ +#pragma once + +#include "py/obj.h" + +mp_obj_t common_hal_pathlib_path_new(size_t n_args, const mp_obj_t *args); diff --git a/shared-bindings/supervisor/__init__.c b/shared-bindings/supervisor/__init__.c index 7b1eac7c2a712..852e2cba13224 100644 --- a/shared-bindings/supervisor/__init__.c +++ b/shared-bindings/supervisor/__init__.c @@ -3,6 +3,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2016-2017 Scott Shawcroft for Adafruit Industries // // SPDX-License-Identifier: MIT +#include #include #include "py/obj.h" @@ -57,6 +58,7 @@ MP_DEFINE_CONST_FUN_OBJ_0(supervisor_reload_obj, supervisor_reload); //| def set_next_code_file( //| filename: Optional[str], //| *, +//| working_directory: Optional[str] = None, //| reload_on_success: bool = False, //| reload_on_error: bool = False, //| sticky_on_success: bool = False, @@ -99,6 +101,7 @@ MP_DEFINE_CONST_FUN_OBJ_0(supervisor_reload_obj, supervisor_reload); static mp_obj_t supervisor_set_next_code_file(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { static const mp_arg_t allowed_args[] = { { MP_QSTR_filename, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none} }, + { MP_QSTR_working_directory, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = mp_const_none} }, { MP_QSTR_reload_on_success, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, { MP_QSTR_reload_on_error, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, { MP_QSTR_sticky_on_success, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, @@ -107,6 +110,7 @@ static mp_obj_t supervisor_set_next_code_file(size_t n_args, const mp_obj_t *pos }; struct { mp_arg_val_t filename; + mp_arg_val_t working_directory; mp_arg_val_t reload_on_success; mp_arg_val_t reload_on_error; mp_arg_val_t sticky_on_success; @@ -118,6 +122,11 @@ static mp_obj_t supervisor_set_next_code_file(size_t n_args, const mp_obj_t *pos if (!mp_obj_is_str_or_bytes(filename_obj) && filename_obj != mp_const_none) { mp_raise_TypeError_varg(MP_ERROR_TEXT("%q must be of type %q or %q, not %q"), MP_QSTR_filename, MP_QSTR_str, MP_QSTR_None, mp_obj_get_type(filename_obj)->name); } + + mp_obj_t working_directory_obj = args.working_directory.u_obj; + if (!mp_obj_is_str_or_bytes(working_directory_obj) && working_directory_obj != mp_const_none) { + mp_raise_TypeError_varg(MP_ERROR_TEXT("%q must be of type %q or %q, not %q"), MP_QSTR_working_directory, MP_QSTR_str, MP_QSTR_None, mp_obj_get_type(working_directory_obj)->name); + } if (filename_obj == mp_const_none) { filename_obj = mp_const_empty_bytes; } @@ -144,13 +153,37 @@ static mp_obj_t supervisor_set_next_code_file(size_t n_args, const mp_obj_t *pos next_code_configuration = NULL; } if (options != 0 || len != 0) { - next_code_configuration = port_malloc(sizeof(supervisor_next_code_info_t) + len + 1, false); + size_t working_directory_len = 0; + if (working_directory_obj != mp_const_none) { + mp_obj_str_get_data(working_directory_obj, &working_directory_len); + } + + size_t next_code_size = sizeof(supervisor_next_code_info_t) + len + 1; + if (working_directory_len > 0) { + next_code_size += working_directory_len + 1; + } + next_code_configuration = port_malloc(next_code_size, false); if (next_code_configuration == NULL) { - m_malloc_fail(sizeof(supervisor_next_code_info_t) + len + 1); + m_malloc_fail(next_code_size); } next_code_configuration->options = options | SUPERVISOR_NEXT_CODE_OPT_NEWLY_SET; - memcpy(&next_code_configuration->filename, filename, len); - next_code_configuration->filename[len] = '\0'; + + char *filename_ptr = (char *)next_code_configuration + sizeof(supervisor_next_code_info_t); + next_code_configuration->filename = filename_ptr; + next_code_configuration->working_directory = NULL; + + // Copy filename + memcpy(filename_ptr, filename, len); + filename_ptr[len] = '\0'; + + // Copy working directory after filename if present + if (working_directory_len > 0) { + char *working_directory_ptr = filename_ptr + len + 1; + next_code_configuration->working_directory = working_directory_ptr; + const char *working_directory = mp_obj_str_get_data(working_directory_obj, &working_directory_len); + memcpy(working_directory_ptr, working_directory, working_directory_len); + working_directory_ptr[working_directory_len] = '\0'; + } } return mp_const_none; } diff --git a/shared-bindings/supervisor/__init__.h b/shared-bindings/supervisor/__init__.h index c442534072ae7..4be9667ef563a 100644 --- a/shared-bindings/supervisor/__init__.h +++ b/shared-bindings/supervisor/__init__.h @@ -6,6 +6,8 @@ #pragma once +#include +#include // #include "py/mpconfig.h" #include "py/obj.h" @@ -18,7 +20,8 @@ typedef struct { uint8_t options; - char filename[]; + const char *working_directory; + const char *filename; } supervisor_next_code_info_t; extern const super_runtime_obj_t common_hal_supervisor_runtime_obj; diff --git a/shared-module/pathlib/PosixPath.c b/shared-module/pathlib/PosixPath.c new file mode 100644 index 0000000000000..3bedda2933468 --- /dev/null +++ b/shared-module/pathlib/PosixPath.c @@ -0,0 +1,334 @@ +#include + +#include "py/obj.h" +#include "py/runtime.h" +#include "py/mperrno.h" +#include "py/mphal.h" +#include "py/objstr.h" +#include "py/gc.h" + +#include "shared-bindings/os/__init__.h" +#include "shared-module/pathlib/PosixPath.h" + +#include "supervisor/shared/serial.h" + +// Path directory iterator type +typedef struct _mp_obj_path_dir_iter_t { + mp_obj_base_t base; + mp_fun_1_t iternext; // Iterator function pointer + mp_obj_t path; // Parent path + mp_obj_t iter; // Iterator for the filenames +} mp_obj_path_dir_iter_t; + +mp_obj_t pathlib_posixpath_from_str(mp_obj_t path_str) { + pathlib_posixpath_obj_t *self = m_new_obj(pathlib_posixpath_obj_t); + self->base.type = &pathlib_posixpath_type; + self->path_str = path_str; + return MP_OBJ_FROM_PTR(self); +} + +mp_obj_t common_hal_pathlib_posixpath_new(mp_obj_t path) { + if (mp_obj_is_str(path)) { + return pathlib_posixpath_from_str(path); + } else if (mp_obj_is_type(path, &pathlib_posixpath_type)) { + // Return a copy to ensure immutability + pathlib_posixpath_obj_t *p = MP_OBJ_TO_PTR(path); + return pathlib_posixpath_from_str(p->path_str); + } + + mp_raise_TypeError(MP_ERROR_TEXT("expected str or PosixPath")); + return mp_const_none; +} + +mp_obj_t common_hal_pathlib_posixpath_joinpath(pathlib_posixpath_obj_t *self, mp_obj_t arg) { + + mp_obj_t path_part; + if (mp_obj_is_str(arg)) { + path_part = arg; + } else if (mp_obj_is_type(arg, &pathlib_posixpath_type)) { + pathlib_posixpath_obj_t *p = MP_OBJ_TO_PTR(arg); + path_part = p->path_str; + } else { + mp_raise_TypeError(MP_ERROR_TEXT("expected str or PosixPath")); + return mp_const_none; + } + + // Get string values + GET_STR_DATA_LEN(self->path_str, path_str, path_len); + GET_STR_DATA_LEN(path_part, part_str, part_len); + + // If either part is empty, return the other + if (path_len == 0) { + return pathlib_posixpath_from_str(path_part); + } + if (part_len == 0) { + return pathlib_posixpath_from_str(self->path_str); + } + + // Calculate the required buffer size (path + '/' + part + '\0') + size_t need_slash = (path_str[path_len - 1] != '/') ? 1 : 0; + size_t new_len = path_len + need_slash + part_len; + + // Allocate new string + byte *new_str = m_new(byte, new_len + 1); + + // Copy first part + memcpy(new_str, path_str, path_len); + + // Add slash if needed + if (need_slash) { + new_str[path_len] = '/'; + } + + // Copy second part + memcpy(new_str + path_len + need_slash, part_str, part_len); + new_str[new_len] = '\0'; + + // Create a new string object + mp_obj_t result_str = mp_obj_new_str((const char *)new_str, new_len); + + // Free the temporary buffer + m_del(byte, new_str, new_len + 1); + + return pathlib_posixpath_from_str(result_str); +} + +mp_obj_t common_hal_pathlib_posixpath_parent(pathlib_posixpath_obj_t *self) { + GET_STR_DATA_LEN(self->path_str, path_str, path_len); + + // Handle root directory and empty path + if (path_len == 0 || (path_len == 1 && path_str[0] == '/')) { + return MP_OBJ_FROM_PTR(self); // Root is its own parent + } + + // Find the last slash + int last_slash = -1; + for (int i = path_len - 1; i >= 0; i--) { + if (path_str[i] == '/') { + last_slash = i; + break; + } + } + + // No slash found, return "." + if (last_slash == -1) { + return pathlib_posixpath_from_str(MP_OBJ_NEW_QSTR(qstr_from_str("."))); + } + + // Root directory + if (last_slash == 0) { + return pathlib_posixpath_from_str(MP_OBJ_NEW_QSTR(qstr_from_str("/"))); + } + + // Create parent path + mp_obj_t parent = mp_obj_new_str((const char *)path_str, last_slash); + return pathlib_posixpath_from_str(parent); +} + +mp_obj_t common_hal_pathlib_posixpath_name(pathlib_posixpath_obj_t *self) { + GET_STR_DATA_LEN(self->path_str, path_str, path_len); + console_uart_printf("common_hal_pathlib_posixpath_name\n"); + + // Empty path + if (path_len == 0) { + return MP_OBJ_NEW_QSTR(MP_QSTR_); + } + + // Find the last slash + int last_slash = -1; + for (int i = path_len - 1; i >= 0; i--) { + if (path_str[i] == '/') { + last_slash = i; + break; + } + } + + // No slash found, return the whole path + if (last_slash == -1) { + return self->path_str; + } + + // Slash at the end + if (last_slash == (int)(path_len - 1)) { + return MP_OBJ_NEW_QSTR(MP_QSTR_); + } + + // Return the component after the last slash + return mp_obj_new_str((const char *)(path_str + last_slash + 1), path_len - last_slash - 1); +} + +mp_obj_t common_hal_pathlib_posixpath_stem(pathlib_posixpath_obj_t *self) { + mp_obj_t name = common_hal_pathlib_posixpath_name(self); + GET_STR_DATA_LEN(name, name_str, name_len); + + // Empty name + if (name_len == 0) { + return name; + } + + // Find the last dot + int last_dot = -1; + for (int i = name_len - 1; i >= 0; i--) { + if (name_str[i] == '.') { + last_dot = i; + break; + } + } + + // No dot found or dot at the start, return the whole name + if (last_dot <= 0) { + return name; + } + + // Return the part before the last dot + return mp_obj_new_str((const char *)name_str, last_dot); +} + +mp_obj_t common_hal_pathlib_posixpath_suffix(pathlib_posixpath_obj_t *self) { + mp_obj_t name = common_hal_pathlib_posixpath_name(self); + GET_STR_DATA_LEN(name, name_str, name_len); + + // Empty name + if (name_len == 0) { + return MP_OBJ_NEW_QSTR(MP_QSTR_); + } + + // Find the last dot + int last_dot = -1; + for (int i = name_len - 1; i >= 0; i--) { + if (name_str[i] == '.') { + last_dot = i; + break; + } + } + + // No dot found or dot at the start, return empty string + if (last_dot <= 0) { + return MP_OBJ_NEW_QSTR(MP_QSTR_); + } + + // Return the part after the last dot (including the dot) + return mp_obj_new_str((const char *)(name_str + last_dot), name_len - last_dot); +} + +mp_obj_t common_hal_pathlib_posixpath_exists(pathlib_posixpath_obj_t *self) { + + // Use common_hal_os_stat to check if path exists + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + const char *path = mp_obj_str_get_str(self->path_str); + common_hal_os_stat(path); + nlr_pop(); + return mp_const_true; + } else { + // Path doesn't exist + return mp_const_false; + } +} + +mp_obj_t common_hal_pathlib_posixpath_is_dir(pathlib_posixpath_obj_t *self) { + + // Use common_hal_os_stat to check if path is directory + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + const char *path = mp_obj_str_get_str(self->path_str); + mp_obj_t stat_result = common_hal_os_stat(path); + mp_obj_tuple_t *stat_result_tpl = MP_OBJ_TO_PTR(stat_result); + + // Check if it's a directory (S_IFDIR flag in st_mode) + mp_obj_t mode_obj = stat_result_tpl->items[0]; + mp_int_t mode_val = mp_obj_get_int(mode_obj); + nlr_pop(); + + // 0x4000 is S_IFDIR (directory) + return mp_obj_new_bool((mode_val & 0x4000) != 0); + } else { + // Path doesn't exist + return mp_const_false; + } +} + +mp_obj_t common_hal_pathlib_posixpath_is_file(pathlib_posixpath_obj_t *self) { + + // Use common_hal_os_stat to check if path is a regular file + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + const char *path = mp_obj_str_get_str(self->path_str); + mp_obj_t stat_result = common_hal_os_stat(path); + mp_obj_tuple_t *stat_result_tpl = MP_OBJ_TO_PTR(stat_result); + + // Check if it's a regular file (S_IFREG flag in st_mode) + mp_obj_t mode_obj = stat_result_tpl->items[0]; + mp_int_t mode_val = mp_obj_get_int(mode_obj); + nlr_pop(); + + // 0x8000 is S_IFREG (regular file) + return mp_obj_new_bool((mode_val & 0x8000) != 0); + } else { + // Path doesn't exist + return mp_const_false; + } +} + +mp_obj_t common_hal_pathlib_posixpath_absolute(pathlib_posixpath_obj_t *self) { + GET_STR_DATA_LEN(self->path_str, path_str, path_len); + + // Already absolute + if (path_len > 0 && path_str[0] == '/') { + return MP_OBJ_FROM_PTR(self); + } + + // Get current directory using common_hal_os_getcwd() + mp_obj_t cwd = common_hal_os_getcwd(); + + // Join with current path + mp_obj_t abs_path = common_hal_pathlib_posixpath_joinpath( + MP_OBJ_TO_PTR(pathlib_posixpath_from_str(cwd)), + MP_OBJ_FROM_PTR(self) + ); + + return abs_path; +} + +mp_obj_t common_hal_pathlib_posixpath_resolve(pathlib_posixpath_obj_t *self) { + // For now, just call absolute() since CircuitPython doesn't support symlinks + return common_hal_pathlib_posixpath_absolute(self); +} + +// Iterator iternext function - called for each iteration +static mp_obj_t path_dir_iter_iternext(mp_obj_t self_in) { + mp_obj_path_dir_iter_t *self = MP_OBJ_TO_PTR(self_in); + + // Get the next filename from the iterator + mp_obj_t next = mp_iternext(self->iter); + if (next == MP_OBJ_STOP_ITERATION) { + return MP_OBJ_STOP_ITERATION; // End of iteration + } + + // Create a new path by joining the parent path with the filename + return common_hal_pathlib_posixpath_joinpath(self->path, next); +} + +// Create a new path directory iterator +mp_obj_t mp_obj_new_path_dir_iter(mp_obj_t path, mp_obj_t iter) { + mp_obj_path_dir_iter_t *o = m_new_obj(mp_obj_path_dir_iter_t); + o->base.type = &mp_type_polymorph_iter; + o->iternext = path_dir_iter_iternext; + o->path = path; + o->iter = iter; + return MP_OBJ_FROM_PTR(o); +} + +mp_obj_t common_hal_pathlib_posixpath_iterdir(pathlib_posixpath_obj_t *self) { + // Get the path as a C string + const char *path = mp_obj_str_get_str(self->path_str); + + // Use os.listdir to get the directory contents + mp_obj_t dir_list = common_hal_os_listdir(path); + + // Get an iterator for the list of filenames + mp_obj_t iter = mp_getiter(dir_list, NULL); + + // Create and return a path directory iterator + return mp_obj_new_path_dir_iter(MP_OBJ_FROM_PTR(self), iter); +} diff --git a/shared-module/pathlib/PosixPath.h b/shared-module/pathlib/PosixPath.h new file mode 100644 index 0000000000000..b6f7802905640 --- /dev/null +++ b/shared-module/pathlib/PosixPath.h @@ -0,0 +1,30 @@ +#pragma once + +#include "py/obj.h" +#include "py/objstr.h" + +typedef struct { + mp_obj_base_t base; + mp_obj_t path_str; +} pathlib_posixpath_obj_t; + +// Forward declaration +extern const mp_obj_type_t pathlib_posixpath_type; + +// Helper functions +mp_obj_t pathlib_posixpath_from_str(mp_obj_t path_str); +mp_obj_t mp_obj_new_path_dir_iter(mp_obj_t path, mp_obj_t iter); + +// Common HAL functions +mp_obj_t common_hal_pathlib_posixpath_new(mp_obj_t path); +mp_obj_t common_hal_pathlib_posixpath_joinpath(pathlib_posixpath_obj_t *self, mp_obj_t arg); +mp_obj_t common_hal_pathlib_posixpath_parent(pathlib_posixpath_obj_t *self); +mp_obj_t common_hal_pathlib_posixpath_name(pathlib_posixpath_obj_t *self); +mp_obj_t common_hal_pathlib_posixpath_stem(pathlib_posixpath_obj_t *self); +mp_obj_t common_hal_pathlib_posixpath_suffix(pathlib_posixpath_obj_t *self); +mp_obj_t common_hal_pathlib_posixpath_exists(pathlib_posixpath_obj_t *self); +mp_obj_t common_hal_pathlib_posixpath_is_dir(pathlib_posixpath_obj_t *self); +mp_obj_t common_hal_pathlib_posixpath_is_file(pathlib_posixpath_obj_t *self); +mp_obj_t common_hal_pathlib_posixpath_absolute(pathlib_posixpath_obj_t *self); +mp_obj_t common_hal_pathlib_posixpath_resolve(pathlib_posixpath_obj_t *self); +mp_obj_t common_hal_pathlib_posixpath_iterdir(pathlib_posixpath_obj_t *self); diff --git a/shared-module/pathlib/__init__.c b/shared-module/pathlib/__init__.c new file mode 100644 index 0000000000000..3ae428e50f8bb --- /dev/null +++ b/shared-module/pathlib/__init__.c @@ -0,0 +1,28 @@ +#include + +#include "py/obj.h" +#include "py/runtime.h" + +#include "shared-module/pathlib/__init__.h" +#include "shared-module/pathlib/PosixPath.h" + +mp_obj_t common_hal_pathlib_path_new(size_t n_args, const mp_obj_t *args) { + if (n_args == 0) { + return pathlib_posixpath_from_str(MP_OBJ_NEW_QSTR(MP_QSTR_)); + } + + if (n_args == 1) { + return common_hal_pathlib_posixpath_new(args[0]); + } + + // Join multiple path components + mp_obj_t path = args[0]; + for (size_t i = 1; i < n_args; i++) { + path = common_hal_pathlib_posixpath_joinpath( + common_hal_pathlib_posixpath_new(path), + args[i] + ); + } + + return path; +} diff --git a/shared-module/pathlib/__init__.h b/shared-module/pathlib/__init__.h new file mode 100644 index 0000000000000..64861f867af02 --- /dev/null +++ b/shared-module/pathlib/__init__.h @@ -0,0 +1,6 @@ +#pragma once + +#include "py/obj.h" + +// Forward declaration to Path constructor +mp_obj_t common_hal_pathlib_path_new(size_t n_args, const mp_obj_t *args);