Merge pull request #7321 from jepler/dotenv-becomes-toml

Dotenv becomes toml
This commit is contained in:
Dan Halbert 2022-12-13 19:56:36 -05:00 committed by GitHub
commit 398b7c26ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 708 additions and 723 deletions

1
.gitignore vendored
View File

@ -9,6 +9,7 @@
!atmel-samd/asf/**/*.a
*.elf
*.bin
!*.toml.bin
*.map
*.hex
*.dis

View File

@ -49,8 +49,8 @@
#include "shared-bindings/_bleio/ScanEntry.h"
#include "shared-bindings/time/__init__.h"
#if CIRCUITPY_DOTENV
#include "shared-module/dotenv/__init__.h"
#if CIRCUITPY_OS_GETENV
#include "shared-bindings/os/__init__.h"
#endif
#define MSEC_TO_UNITS(TIME, RESOLUTION) (((TIME) * 1000) / (RESOLUTION))
@ -284,15 +284,15 @@ char default_ble_name[] = { 'C', 'I', 'R', 'C', 'U', 'I', 'T', 'P', 'Y', 0, 0, 0
STATIC void bleio_adapter_hci_init(bleio_adapter_obj_t *self) {
mp_int_t name_len = 0;
#if CIRCUITPY_DOTENV
char ble_name[32];
name_len = dotenv_get_key("/.env", "CIRCUITPY_BLE_NAME", ble_name, sizeof(ble_name) - 1);
if (name_len > 0) {
self->name = mp_obj_new_str(ble_name, (size_t)name_len);
#if CIRCUITPY_OS_GETENV
mp_obj_t name = common_hal_os_getenv("CIRCUITPY_BLE_NAME", mp_const_none);
if (name != mp_const_none) {
mp_arg_validate_type_string(name, MP_QSTR_CIRCUITPY_BLE_NAME);
self->name = name;
}
#endif
if (name_len <= 0) {
if (!self->name) {
name_len = sizeof(default_ble_name);
bt_addr_t addr;
hci_check_error(hci_read_bd_addr(&addr));

View File

@ -6,24 +6,49 @@ variables are commonly used to store "secrets" such as Wi-Fi passwords and API
keys. This method *does not* make them secure. It only separates them from the
code.
CircuitPython supports these by mimicking the `dotenv <https://github.com/theskumar/python-dotenv>`_
CPython library. Other languages such as Javascript, PHP and Ruby also have
dotenv libraries.
CircuitPython uses a file called ``settings.toml`` at the drive root (no
folder) as the environment. User code can access the values from the file
using `os.getenv()`. It is recommended to save any values used repeatedly in a
variable because `os.getenv()` will parse the ``settings.toml`` file contents
on every access.
These libraries store environment variables in a ``.env`` file. Here is a simple
example:
CircuitPython only supports a subset of the full toml specification, see below
for more details. The subset is very "Python-like", which is a key reason we
selected the format.
.. code-block:: bash
Due to technical limitations it probably also accepts some files that are
not valid TOML files; bugs of this nature are subject to change (i.e., be
fixed) without the usual deprecation period for incompatible changes.
KEY1='value1'
# Comment
KEY2='value2
is multiple lines'
File format example:
CircuitPython uses the ``.env`` at the drive root (no folder) as the environment.
User code can access the values from the file using `os.getenv()`. It is
recommended to save any values used repeatedly in a variable because `os.getenv()`
will parse the ``/.env`` on every access.
.. code-block::
str_key="Hello world" # with trailing comment
int_key = 7
unicode_key="œuvre"
unicode_key2="\\u0153uvre" # same as above
unicode_key3="\\U00000153uvre" # same as above
escape_codes="supported, including \\r\\n\\"\\\\"
# comment
[subtable]
subvalue="cannot retrieve this using getenv"
Details of the toml language subset
-----------------------------------
* The content is required to be in UTF-8 encoding
* The supported data types are string and integer
* Only basic strings are supported, not triple-quoted strings
* Only integers supported by strtol. (no 0o, no 0b, no underscores 1_000, 011
is 9, not 11)
* Only bare keys are supported
* Duplicate keys are not diagnosed.
* Comments are supported
* Only values from the "root table" can be retrieved
* due to technical limitations, the content of multi-line
strings can erroneously be parsed as a value.
CircuitPython behavior
----------------------

View File

@ -46,7 +46,7 @@ connection, the central device can discover two default services. One for file t
CircuitPython specifically that includes serial characteristics.
To change the default BLE advertising name without (or before) running user code, the desired name
can be put in the `/.env` file. The key is `CIRCUITPY_BLE_NAME`. It's limited to approximately
can be put in the `settings.toml` file. The key is `CIRCUITPY_BLE_NAME`. It's limited to approximately
30 characters depending on the port's settings and will be truncated if longer.
### File Transfer API
@ -69,21 +69,21 @@ Read-only characteristic that returns the UTF-8 encoded version string.
## Web
The web workflow is depends on adding Wi-Fi credentials into the `/.env` file. The keys are
The web workflow is depends on adding Wi-Fi credentials into the `settings.toml` file. The keys are
`CIRCUITPY_WIFI_SSID` and `CIRCUITPY_WIFI_PASSWORD`. Once these are defined, CircuitPython will
automatically connect to the network and start the webserver used for the workflow. The webserver
is on port 80 unless overridden by `CIRCUITPY_WEB_API_PORT`. It also enables MDNS.
Here is an example `/.env`:
Here is an example `/settings.toml`:
```bash
# To auto-connect to Wi-Fi
CIRCUITPY_WIFI_SSID='scottswifi'
CIRCUITPY_WIFI_PASSWORD='secretpassword'
CIRCUITPY_WIFI_SSID="scottswifi"
CIRCUITPY_WIFI_PASSWORD="secretpassword"
# To enable modifying files from the web. Change this too!
# Leave the User field blank in the browser.
CIRCUITPY_WEB_API_PASSWORD='passw0rd'
CIRCUITPY_WEB_API_PASSWORD="passw0rd"
CIRCUITPY_WEB_API_PORT=80
```
@ -124,7 +124,7 @@ All file system related APIs are protected by HTTP basic authentication. It is *
hopefully prevent some griefing in shared settings. The password is sent unencrypted so do not reuse
a password with something important. The user field is left blank.
The password is taken from `/.env` with the key `CIRCUITPY_WEB_API_PASSWORD`. If this is unset, the
The password is taken from `settings.toml` with the key `CIRCUITPY_WEB_API_PASSWORD`. If this is unset, the
server will respond with `403 Forbidden`. When a password is set, but not provided in a request, it
will respond `401 Unauthorized`.

View File

@ -1002,6 +1002,10 @@ msgstr ""
msgid "File exists"
msgstr ""
#: shared-module/os/getenv.c
msgid "File not found"
msgstr ""
#: ports/atmel-samd/common-hal/canio/Listener.c
#: ports/espressif/common-hal/canio/Listener.c
#: ports/stm/common-hal/canio/Listener.c
@ -1058,6 +1062,8 @@ msgstr ""
#: shared-bindings/displayio/Display.c
#: shared-bindings/displayio/EPaperDisplay.c
#: shared-bindings/framebufferio/FramebufferDisplay.c
#: shared-module/displayio/Display.c
#: shared-module/framebufferio/FramebufferDisplay.c
msgid "Group already used"
msgstr ""
@ -1184,6 +1190,7 @@ msgid "Internal define error"
msgstr ""
#: ports/espressif/common-hal/paralleldisplay/ParallelBus.c
#: shared-module/os/getenv.c
msgid "Internal error"
msgstr ""
@ -1230,6 +1237,11 @@ msgstr ""
msgid "Invalid bits per value"
msgstr ""
#: shared-module/os/getenv.c
#, c-format
msgid "Invalid byte %.*s"
msgstr ""
#: ports/atmel-samd/common-hal/imagecapture/ParallelImageCapture.c
#, c-format
msgid "Invalid data_pins[%d]"
@ -1260,10 +1272,18 @@ msgstr ""
msgid "Invalid state"
msgstr ""
#: shared-module/os/getenv.c
msgid "Invalid unicode escape"
msgstr ""
#: shared-bindings/aesio/aes.c
msgid "Key must be 16, 24, or 32 bytes long"
msgstr ""
#: shared-module/os/getenv.c
msgid "Key not found"
msgstr ""
#: shared-module/is31fl3741/FrameBuffer.c
msgid "LED mappings must match display size"
msgstr ""
@ -2262,7 +2282,7 @@ msgid "Unkown error code %d"
msgstr ""
#: shared-bindings/adafruit_pixelbuf/PixelBuf.c
#: shared-module/adafruit_pixelbuf/PixelMap.c
#: shared-module/_pixelmap/PixelMap.c
#, c-format
msgid "Unmatched number of items on RHS (expected %d, got %d)."
msgstr ""
@ -3143,7 +3163,7 @@ msgstr ""
msgid "index is out of bounds"
msgstr ""
#: shared-bindings/adafruit_pixelbuf/PixelMap.c
#: shared-bindings/_pixelmap/PixelMap.c
msgid "index must be tuple or int"
msgstr ""
@ -3522,7 +3542,7 @@ msgstr ""
msgid "negative shift count"
msgstr ""
#: shared-bindings/adafruit_pixelbuf/PixelMap.c
#: shared-bindings/_pixelmap/PixelMap.c
msgid "nested index must be int"
msgstr ""

View File

@ -24,13 +24,13 @@ ifeq ($(CHIP_FAMILY),samd21)
CIRCUITPY_AESIO ?= 0
CIRCUITPY_ATEXIT ?= 0
CIRCUITPY_AUDIOMIXER ?= 0
CIRCUITPY_AUDIOMP3 ?= 0
CIRCUITPY_BINASCII ?= 0
CIRCUITPY_BITBANGIO ?= 0
CIRCUITPY_BITMAPTOOLS ?= 0
CIRCUITPY_BUSDEVICE ?= 0
CIRCUITPY_AUDIOMP3 ?= 0
CIRCUITPY_BLEIO_HCI = 0
CIRCUITPY_BUILTINS_POW3 ?= 0
CIRCUITPY_BUSDEVICE ?= 0
CIRCUITPY_COMPUTED_GOTO_SAVE_SPACE ?= 1
CIRCUITPY_COUNTIO ?= 0
# Not enough RAM for framebuffers
@ -42,6 +42,7 @@ CIRCUITPY_I2CTARGET ?= 0
CIRCUITPY_JSON ?= 0
CIRCUITPY_KEYPAD ?= 0
CIRCUITPY_MSGPACK ?= 0
CIRCUITPY_OS_GETENV ?= 0
CIRCUITPY_PIXELMAP ?= 0
CIRCUITPY_RE ?= 0
CIRCUITPY_SDCARDIO ?= 0

View File

@ -134,7 +134,8 @@ SRC_COMMON_HAL_EXPANDED = $(addprefix shared-bindings/, $(SRC_COMMON_HAL)) \
$(addprefix common-hal/, $(SRC_COMMON_HAL))
SRC_SHARED_MODULE_EXPANDED = $(addprefix shared-bindings/, $(SRC_SHARED_MODULE)) \
$(addprefix shared-module/, $(SRC_SHARED_MODULE))
$(addprefix shared-module/, $(SRC_SHARED_MODULE)) \
$(addprefix shared-module/, $(SRC_SHARED_MODULE_INTERNAL))
SRC_S = supervisor/cpu.s

View File

@ -63,7 +63,7 @@
//| """
//| Configure and initialize a camera with the given properties
//|
//| This driver requires that the ``CIRCUITPY_RESERVED_PSRAM`` in ``/.env`` be large enough to hold the camera frambuffer(s). Generally, boards with built-in cameras will have a default setting that is large enough. If the constructor raises a MemoryError or an IDFError, this probably indicates the setting is too small and should be increased.
//| This driver requires that the ``CIRCUITPY_RESERVED_PSRAM`` in ``settings.toml`` be large enough to hold the camera frambuffer(s). Generally, boards with built-in cameras will have a default setting that is large enough. If the constructor raises a MemoryError or an IDFError, this probably indicates the setting is too small and should be increased.
//|
//|
//| .. important::

View File

@ -132,7 +132,7 @@ STATIC mp_obj_t espidf_get_total_psram(void) {
MP_DEFINE_CONST_FUN_OBJ_0(espidf_get_total_psram_obj, espidf_get_total_psram);
//| def get_reserved_psram() -> int:
//| """Returns number of bytes of psram reserved for use by esp-idf, either a board-specific default value or the value defined in ``/.env``."""
//| """Returns number of bytes of psram reserved for use by esp-idf, either a board-specific default value or the value defined in ``settings.toml``."""
//|
STATIC mp_obj_t espidf_get_reserved_psram(void) {
return MP_OBJ_NEW_SMALL_INT(common_hal_espidf_get_reserved_psram());

View File

@ -59,8 +59,8 @@
#include "esp_bt.h"
#include "esp_nimble_hci.h"
#if CIRCUITPY_DOTENV
#include "shared-module/dotenv/__init__.h"
#if CIRCUITPY_OS_GETENV
#include "shared-module/os/__init__.h"
#endif
bleio_connection_internal_t bleio_connections[BLEIO_TOTAL_CONNECTION_COUNT];
@ -101,22 +101,16 @@ void common_hal_bleio_adapter_set_enabled(bleio_adapter_obj_t *self, bool enable
ble_hs_cfg.sync_cb = _on_sync;
// ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
#if CIRCUITPY_DOTENV
mp_int_t name_len = 0;
char ble_name[32];
name_len = dotenv_get_key("/.env", "CIRCUITPY_BLE_NAME", ble_name, sizeof(ble_name) - 1);
if (name_len > 0) {
if (name_len > MYNEWT_VAL_BLE_SVC_GAP_DEVICE_NAME_MAX_LENGTH) {
name_len = MYNEWT_VAL_BLE_SVC_GAP_DEVICE_NAME_MAX_LENGTH;
}
ble_name[name_len] = '\0';
#if CIRCUITPY_OS_GETENV
char ble_name[1 + MYNEWT_VAL_BLE_SVC_GAP_DEVICE_NAME_MAX_LENGTH];
os_getenv_err_t result = common_hal_os_getenv_str("CIRCUITPY_BLE_NAME", ble_name, sizeof(ble_name));
if (result == GETENV_OK) {
ble_svc_gap_device_name_set(ble_name);
} else {
} else
#endif
{
ble_svc_gap_device_name_set("CIRCUITPY");
}
#else
ble_svc_gap_device_name_set("CIRCUITPY");
#endif
// Clear all of the internal connection objects.
for (size_t i = 0; i < BLEIO_TOTAL_CONNECTION_COUNT; i++) {

View File

@ -55,7 +55,7 @@
#include "shared-bindings/microcontroller/RunMode.h"
#include "shared-bindings/rtc/__init__.h"
#include "shared-bindings/socketpool/__init__.h"
#include "shared-module/dotenv/__init__.h"
#include "shared-module/os/__init__.h"
#include "peripherals/rmt.h"
#include "peripherals/timer.h"
@ -519,7 +519,7 @@ void port_idle_until_interrupt(void) {
void port_post_boot_py(bool heap_valid) {
if (!heap_valid && filesystem_present()) {
mp_int_t reserved;
if (dotenv_get_key_int("/.env", "CIRCUITPY_RESERVED_PSRAM", &reserved)) {
if (common_hal_os_getenv_int("CIRCUITPY_RESERVED_PSRAM", &reserved) == GETENV_OK) {
common_hal_espidf_set_reserved_psram(reserved);
}
common_hal_espidf_reserve_psram();

View File

@ -52,8 +52,9 @@
#include "shared-bindings/_bleio/ScanEntry.h"
#include "shared-bindings/time/__init__.h"
#if CIRCUITPY_DOTENV
#include "shared-module/dotenv/__init__.h"
#if CIRCUITPY_OS_GETENV
#include "shared-bindings/os/__init__.h"
#include "shared-module/os/__init__.h"
#endif
#define BLE_MIN_CONN_INTERVAL MSEC_TO_UNITS(15, UNIT_0_625_MS)
@ -343,20 +344,17 @@ STATIC void bleio_adapter_reset_name(bleio_adapter_obj_t *self) {
default_ble_name[len - 1] = nibble_to_hex_lower[addr.addr[0] & 0xf];
default_ble_name[len] = '\0'; // for now we add null for compatibility with C ASCIIZ strings
mp_int_t name_len = 0;
#if CIRCUITPY_DOTENV
#if CIRCUITPY_OS_GETENV
char ble_name[32];
name_len = dotenv_get_key("/.env", "CIRCUITPY_BLE_NAME", ble_name, sizeof(ble_name) - 1);
if (name_len > 0) {
ble_name[name_len] = '\0';
common_hal_bleio_adapter_set_name(self, (char *)ble_name);
os_getenv_err_t result = common_hal_os_getenv_str("CIRCUITPY_BLE_NAME", ble_name, sizeof(ble_name));
if (result == GETENV_OK) {
common_hal_bleio_adapter_set_name(self, ble_name);
return;
}
#endif
if (name_len <= 0) {
common_hal_bleio_adapter_set_name(self, (char *)default_ble_name);
}
}
static void bluetooth_adapter_background(void *data) {

View File

@ -422,7 +422,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(rp2pio_statemachine_write_obj, 2, rp2pio_statemachine
//| ) -> None:
//| """Write data to the TX fifo in the background, with optional looping.
//|
//| First, if any previous ``once`` or ``loop`` buffer has not been started, this function blocks until they have.
//| First, if any previous ``once`` or ``loop`` buffer has not been started, this function blocks until they have been started.
//| This means that any ``once`` or ``loop`` buffer will be written at least once.
//| Then the ``once`` and/or ``loop`` buffers are queued. and the function returns.
//| The ``once`` buffer (if specified) will be written just once.

View File

@ -4,5 +4,7 @@
#define CIRCUITPY_DIGITALIO_HAVE_INVALID_PULL (1)
#define CIRCUITPY_DIGITALIO_HAVE_INVALID_DRIVE_MODE (1)
#define MICROPY_HW_LED_STATUS (&pin_CYW0)
#define CIRCUITPY_BOARD_I2C (1)
#define CIRCUITPY_BOARD_I2C_PIN {{.scl = &pin_GPIO5, .sda = &pin_GPIO4}}

View File

@ -206,6 +206,19 @@ void common_hal_wifi_radio_stop_ap(wifi_radio_obj_t *self) {
*/
}
static bool connection_unchanged(wifi_radio_obj_t *self, const uint8_t *ssid, size_t ssid_len) {
if (cyw43_tcpip_link_status(&cyw43_state, CYW43_ITF_STA) != CYW43_LINK_UP) {
return false;
}
if (ssid_len != self->connected_ssid_len) {
return false;
}
if (memcmp(ssid, self->connected_ssid, self->connected_ssid_len)) {
return false;
}
return true;
}
wifi_radio_error_t common_hal_wifi_radio_connect(wifi_radio_obj_t *self, uint8_t *ssid, size_t ssid_len, uint8_t *password, size_t password_len, uint8_t channel, mp_float_t timeout, uint8_t *bssid, size_t bssid_len) {
if (!common_hal_wifi_radio_get_enabled(self)) {
mp_raise_RuntimeError(translate("Wifi is not enabled"));
@ -215,11 +228,18 @@ wifi_radio_error_t common_hal_wifi_radio_connect(wifi_radio_obj_t *self, uint8_t
mp_raise_RuntimeError(translate("Wifi is in access point mode."));
}
if (ssid_len > 32) {
return WIFI_RADIO_ERROR_CONNECTION_FAIL;
}
size_t timeout_ms = timeout <= 0 ? 8000 : (size_t)MICROPY_FLOAT_C_FUN(ceil)(timeout * 1000);
uint64_t start = port_get_raw_ticks(NULL);
uint64_t deadline = start + timeout_ms;
if (connection_unchanged(self, ssid, ssid_len)) {
return WIFI_RADIO_ERROR_NONE;
}
// disconnect
common_hal_wifi_radio_stop_station(self);
@ -237,6 +257,8 @@ wifi_radio_error_t common_hal_wifi_radio_connect(wifi_radio_obj_t *self, uint8_t
switch (result) {
case CYW43_LINK_UP:
memcpy(self->connected_ssid, ssid, ssid_len);
self->connected_ssid_len = ssid_len;
bindings_cyw43_wifi_enforce_pm();
return WIFI_RADIO_ERROR_NONE;
case CYW43_LINK_FAIL:

View File

@ -35,6 +35,8 @@ typedef struct {
mp_obj_base_t base;
char hostname[254]; // hostname max is 253 chars, + 1 for trailing NUL
wifi_scannednetworks_obj_t *current_scan;
uint8_t connected_ssid[32];
uint8_t connected_ssid_len;
bool enabled;
} wifi_radio_obj_t;

View File

@ -127,6 +127,13 @@ safe_mode_t port_init(void) {
(&_ld_dtcm_bss_start)[i] = 0;
}
#if CIRCUITPY_CYW43
never_reset_pin_number(23);
never_reset_pin_number(24);
never_reset_pin_number(25);
never_reset_pin_number(29);
#endif
// Reset everything into a known state before board_init.
reset_port();
@ -140,10 +147,6 @@ safe_mode_t port_init(void) {
// Check brownout.
#if CIRCUITPY_CYW43
never_reset_pin_number(23);
never_reset_pin_number(24);
never_reset_pin_number(25);
never_reset_pin_number(29);
// A small number of samples of pico w need an additional delay before
// initializing the cyw43 chip. Delays inside cyw43_arch_init_with_country
// are intended to meet the power on timing requirements, but apparently

View File

@ -193,7 +193,12 @@ STATIC mp_obj_t mod_os_system(mp_obj_t cmd_in) {
}
MP_DEFINE_CONST_FUN_OBJ_1(mod_os_system_obj, mod_os_system);
mp_obj_t common_hal_os_getenv(const char *key, mp_obj_t default_);
STATIC mp_obj_t mod_os_getenv(mp_obj_t var_in) {
mp_obj_t result = common_hal_os_getenv(mp_obj_str_get_str(var_in), mp_const_none);
if (result != mp_const_none) {
return result;
}
const char *s = getenv(mp_obj_str_get_str(var_in));
if (s == NULL) {
return mp_const_none;

View File

@ -33,7 +33,6 @@ SRC_BITMAP := \
shared-bindings/aesio/__init__.c \
shared-bindings/bitmaptools/__init__.c \
shared-bindings/displayio/Bitmap.c \
shared-bindings/dotenv/__init__.c \
shared-bindings/rainbowio/__init__.c \
shared-bindings/traceback/__init__.c \
shared-bindings/util.c \
@ -45,7 +44,7 @@ SRC_BITMAP := \
shared-module/displayio/Bitmap.c \
shared-module/displayio/ColorConverter.c \
shared-module/displayio/ColorConverter.c \
shared-module/dotenv/__init__.c \
shared-module/os/getenv.c \
shared-module/rainbowio/__init__.c \
shared-module/traceback/__init__.c \
shared-module/zlib/__init__.c \
@ -56,7 +55,7 @@ CFLAGS += \
-DCIRCUITPY_AESIO=1 \
-DCIRCUITPY_BITMAPTOOLS=1 \
-DCIRCUITPY_DISPLAYIO_UNIX=1 \
-DCIRCUITPY_DOTENV=1 \
-DCIRCUITPY_OS_GETENV=1 \
-DCIRCUITPY_GIFIO=1 \
-DCIRCUITPY_RAINBOWIO=1 \
-DCIRCUITPY_TRACEBACK=1 \

View File

@ -182,9 +182,6 @@ endif
ifeq ($(CIRCUITPY_DISPLAYIO),1)
SRC_PATTERNS += displayio/%
endif
ifeq ($(CIRCUITPY_DOTENV),1)
SRC_PATTERNS += dotenv/%
endif
ifeq ($(CIRCUITPY__EVE),1)
SRC_PATTERNS += _eve/%
endif
@ -589,7 +586,6 @@ SRC_SHARED_MODULE_ALL = \
displayio/TileGrid.c \
displayio/area.c \
displayio/__init__.c \
dotenv/__init__.c \
floppyio/__init__.c \
fontio/BuiltinFont.c \
fontio/__init__.c \
@ -718,6 +714,7 @@ endif
SRC_SHARED_MODULE_INTERNAL = \
$(filter $(SRC_PATTERNS), \
displayio/display_core.c \
os/getenv.c \
usb/utf16le.c \
)

View File

@ -208,9 +208,6 @@ CFLAGS += -DCIRCUITPY_BITMAPTOOLS=$(CIRCUITPY_BITMAPTOOLS)
CFLAGS += -DCIRCUITPY_FRAMEBUFFERIO=$(CIRCUITPY_FRAMEBUFFERIO)
CFLAGS += -DCIRCUITPY_VECTORIO=$(CIRCUITPY_VECTORIO)
CIRCUITPY_DOTENV ?= $(CIRCUITPY_FULL_BUILD)
CFLAGS += -DCIRCUITPY_DOTENV=$(CIRCUITPY_DOTENV)
CIRCUITPY_DUALBANK ?= 0
CFLAGS += -DCIRCUITPY_DUALBANK=$(CIRCUITPY_DUALBANK)
@ -218,6 +215,9 @@ CFLAGS += -DCIRCUITPY_DUALBANK=$(CIRCUITPY_DUALBANK)
CIRCUITPY_ENABLE_MPY_NATIVE ?= 0
CFLAGS += -DCIRCUITPY_ENABLE_MPY_NATIVE=$(CIRCUITPY_ENABLE_MPY_NATIVE)
CIRCUITPY_OS_GETENV ?= $(CIRCUITPY_FULL_BUILD)
CFLAGS += -DCIRCUITPY_OS_GETENV=$(CIRCUITPY_OS_GETENV)
CIRCUITPY_ERRNO ?= $(CIRCUITPY_FULL_BUILD)
CFLAGS += -DCIRCUITPY_ERRNO=$(CIRCUITPY_ERRNO)

View File

@ -1,113 +0,0 @@
/*
* This file is part of the Micro Python project, http://micropython.org/
*
* The MIT License (MIT)
*
* SPDX-FileCopyrightText: Copyright (c) 2022 Scott Shawcroft for Adafruit Industries
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <string.h>
#include "extmod/vfs.h"
#include "lib/oofatfs/ff.h"
#include "lib/oofatfs/diskio.h"
#include "py/mpstate.h"
#include "py/obj.h"
#include "py/objstr.h"
#include "py/runtime.h"
#include "shared-bindings/dotenv/__init__.h"
//| """Functions to manage environment variables from a .env file.
//|
//| A subset of the CPython `dotenv library <https://saurabh-kumar.com/python-dotenv/>`_. It does
//| not support variables or double quotes.
//|
//| Keys and values may be put in single quotes.
//| ``\`` and ``'`` are escaped by ``\`` in single quotes. Newlines can occur in quotes for multiline values.
//| Comments start with ``#`` and apply for the rest of the line.
//| A ``#`` immediately following an ``=`` is part of the value, not the start of a comment,
//| and a ``#`` embedded in a value without whitespace will be part of that value.
//| This corresponds to how assignments and comments work in most Unix shells.
//|
//|
//| File format example:
//|
//| .. code-block::
//|
//| key=value
//| key2 = value2
//| 'key3' = 'value with spaces'
//| # comment
//| key4 = value3 # comment 2
//| 'key5'=value4
//| key=value5 # overrides the first one
//| multiline = 'hello
//| world
//| how are you?'
//| # The #'s below will be included in the value. They do not start a comment.
//| key6=#value
//| key7=abc#def
//|
//| """
//|
//| import typing
//|
//| def get_key(dotenv_path: str, key_to_get: str) -> Optional[str]:
//| """Get the value for the given key from the given .env file. If the key occurs multiple
//| times in the file, then the last value will be returned.
//|
//| Returns None if the key isn't found or doesn't have a value."""
//| ...
//|
STATIC mp_obj_t _dotenv_get_key(mp_obj_t path_in, mp_obj_t key_to_get_in) {
return common_hal_dotenv_get_key(mp_obj_str_get_str(path_in),
mp_obj_str_get_str(key_to_get_in));
}
MP_DEFINE_CONST_FUN_OBJ_2(dotenv_get_key_obj, _dotenv_get_key);
//| def load_dotenv() -> None:
//| """Does nothing in CircuitPython because os.getenv will automatically read .env when
//| available.
//|
//| Present in CircuitPython so CPython-compatible code can use it without error."""
//| ...
//|
STATIC mp_obj_t dotenv_load_dotenv(void) {
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_0(dotenv_load_dotenv_obj, dotenv_load_dotenv);
STATIC const mp_rom_map_elem_t dotenv_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_dotenv) },
{ MP_ROM_QSTR(MP_QSTR_get_key), MP_ROM_PTR(&dotenv_get_key_obj) },
{ MP_ROM_QSTR(MP_QSTR_load_dotenv), MP_ROM_PTR(&dotenv_load_dotenv_obj) },
};
STATIC MP_DEFINE_CONST_DICT(dotenv_module_globals, dotenv_module_globals_table);
const mp_obj_module_t dotenv_module = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t *)&dotenv_module_globals,
};
MP_REGISTER_MODULE(MP_QSTR_dotenv, dotenv_module, CIRCUITPY_DOTENV);

View File

@ -1,39 +0,0 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2022 Scott Shawcroft for Adafruit Industries
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef MICROPY_INCLUDED_SHARED_BINDINGS_DOTENV___INIT___H
#define MICROPY_INCLUDED_SHARED_BINDINGS_DOTENV___INIT___H
#include <stdint.h>
#include <stdbool.h>
#include "py/objtuple.h"
#include "shared-module/dotenv/__init__.h"
mp_obj_t common_hal_dotenv_get_key(const char *path, const char *key);
#endif // MICROPY_INCLUDED_SHARED_BINDINGS_DOTENV___INIT___H

View File

@ -92,6 +92,7 @@ MP_DEFINE_CONST_FUN_OBJ_0(os_getcwd_obj, os_getcwd);
//| ...
//|
STATIC mp_obj_t os_getenv(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
#if CIRCUITPY_OS_GETENV
enum { ARG_key, ARG_default };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_key, MP_ARG_REQUIRED | MP_ARG_OBJ },
@ -101,6 +102,9 @@ STATIC mp_obj_t os_getenv(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
return common_hal_os_getenv(mp_obj_str_get_str(args[ARG_key].u_obj), args[ARG_default].u_obj);
#else
return mp_const_none;
#endif
}
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(os_getenv_obj, 1, os_getenv);

View File

@ -38,6 +38,8 @@ mp_obj_t common_hal_os_uname(void);
void common_hal_os_chdir(const char *path);
mp_obj_t common_hal_os_getcwd(void);
mp_obj_t common_hal_os_getenv(const char *key, mp_obj_t default_);
mp_obj_t common_hal_os_getenv_path(const char *path, const char *key, mp_obj_t default_);
mp_obj_t common_hal_os_listdir(const char *path);
void common_hal_os_mkdir(const char *path);
void common_hal_os_remove(const char *path);

View File

@ -1,312 +0,0 @@
/*
* This file is part of the Micro Python project, http://micropython.org/
*
* The MIT License (MIT)
*
* SPDX-FileCopyrightText: Copyright (c) 2022 Scott Shawcroft for Adafruit Industries
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <stdlib.h>
#include <string.h>
#include "shared-bindings/dotenv/__init__.h"
#include "py/misc.h"
#include "py/mpstate.h"
#include "py/objstr.h"
#include "supervisor/filesystem.h"
#if defined(UNIX)
typedef FILE *file_arg;
STATIC bool open_file(const char *name, file_arg *active_file) {
FILE *result = fopen(name, "r");
if (result) {
*active_file = result;
}
return result != NULL;
}
STATIC void close_file(file_arg *active_file) {
fclose(*active_file);
}
STATIC uint8_t get_next_character(file_arg *active_file) {
int value = fgetc(*active_file);
if (value == EOF) {
return 0;
}
return value;
}
STATIC void seek_minus_one(file_arg *active_file) {
fseek(*active_file, -1, SEEK_CUR);
}
#else
#include "extmod/vfs.h"
#include "extmod/vfs_fat.h"
typedef FIL file_arg;
STATIC bool open_file(const char *name, file_arg *active_file) {
FATFS *fs = filesystem_circuitpy();
FRESULT result = f_open(fs, active_file, name, FA_READ);
return result == FR_OK;
}
STATIC void close_file(file_arg *active_file) {
// nothing
}
// Return 0 if there is no next character (EOF).
STATIC uint8_t get_next_character(FIL *active_file) {
uint8_t character = 0;
UINT quantity_read;
// If there's an error or quantity_read is 0, character will remain 0.
f_read(active_file, &character, 1, &quantity_read);
return character;
}
STATIC void seek_minus_one(file_arg *active_file) {
f_lseek(active_file, f_tell(active_file) - 1);
}
#endif
// Discard whitespace, except for newlines, returning the next character after the whitespace.
// Return 0 if there is no next character (EOF).
STATIC uint8_t consume_whitespace(file_arg *active_file) {
uint8_t character;
do {
character = get_next_character(active_file);
} while (character != '\n' && character != 0 && unichar_isspace(character));
return character;
}
// Starting at the start of a new line, determines if the key matches the given
// key. File pointer is set to be just before the = after the key.
STATIC bool key_matches(file_arg *active_file, const char *key) {
uint8_t character;
character = consume_whitespace(active_file);
if (character == 0) {
return false;
}
bool quoted = false;
if (character == '\'') {
// Beginning of single-quoted string.
quoted = true;
character = get_next_character(active_file);
}
size_t key_pos = 0;
bool escaped = false;
bool matches = true;
size_t key_len = strlen(key);
while (character != 0) {
if (character == '\\' && !escaped && quoted) {
escaped = true;
} else if (!escaped && quoted && character == '\'') {
quoted = false;
// End of quoted key. Skip over the ending quote.
character = get_next_character(active_file);
break;
} else if (!quoted && (unichar_isspace(character) || character == '=' || character == '\n' || character == '#' || character == 0)) {
// End of unquoted key.
break;
} else {
// Still on tentative key; see if it matches the next supplied key character,
// but don't run off the end of the supplied key.
if (key_pos < key_len) {
matches = matches && (unsigned char)key[key_pos] == character;
escaped = false;
key_pos++;
} else {
// Key on line is too long.
matches = false;
}
}
character = get_next_character(active_file);
}
if (character == '=' || character == '\n' || character == '#' || character == 0) {
// Rewind one so the value, if any, can be found.
seek_minus_one(active_file);
} else if (!unichar_isspace(character)) {
// We're followed by something else that is invalid syntax.
matches = false;
}
return matches && key_pos == key_len;
}
STATIC bool next_line(file_arg *active_file) {
uint8_t character;
bool quoted = false;
bool escaped = false;
// Track comments because they last until the end of the line.
bool comment = false;
// Consume all characters while quoted or others up to \n.
do {
character = get_next_character(active_file);
if ((!quoted || character == '#') || comment) {
// Comments consume any escaping.
comment = true;
} else if (!escaped) {
if (character == '\'') {
quoted = !quoted;
} else if (character == '\\') {
escaped = true;
}
} else {
escaped = false;
}
} while (character != 0 && (quoted || character != '\n'));
return character != 0;
}
STATIC mp_int_t read_value(file_arg *active_file, char *value, size_t value_len) {
uint8_t character;
// Consume spaces before "=", and get first character of interest.
character = consume_whitespace(active_file);
if (character != '=') {
if (character == '#' || character == '\n') {
// Keys without an = after them are valid with the value None.
return -1;
}
// All other characters are invalid.
return -1;
}
// Consume space after =
if (character != '#') {
// a # immediately after = is part of the value!
character = consume_whitespace(active_file);
}
bool quoted = false;
if (character == '\'') {
quoted = true;
character = get_next_character(active_file);
}
if (character == '"') {
// We don't support double quoted values.
return -1;
}
// Copy the value over.
size_t value_pos = 0;
bool escaped = false;
// Count trailing spaces so we can ignore them at the end of unquoted
// values.
size_t trailing_spaces = 0;
bool first_char = true;
while (character != 0) {
// Consume the first \ if the value is quoted.
if (quoted && character == '\\' && !escaped) {
escaped = true;
// Drop this backslash by short circuiting the rest of the loop.
character = get_next_character(active_file);
continue;
}
if (quoted && !escaped && character == '\'') {
// trailing ' means the value is done.
break;
}
// Unquoted values are ended by a newline or comment.
if (!quoted && (character == '\n' || (character == '#' && !first_char))) {
if (character == '\n') {
// Rewind one so the next_line can find the \n.
seek_minus_one(active_file);
}
break;
}
if (!quoted && unichar_isspace(character)) {
trailing_spaces += 1;
} else {
trailing_spaces = 0;
}
escaped = false;
// Only copy the value over if we have space. Otherwise, we'll just
// count the overall length.
if (value_pos < value_len) {
value[value_pos] = character;
}
value_pos++;
character = get_next_character(active_file);
first_char = false;
}
return value_pos - trailing_spaces;
}
mp_int_t dotenv_get_key(const char *path, const char *key, char *value, mp_int_t value_len) {
file_arg active_file;
if (!open_file(path, &active_file)) {
return -1;
}
mp_int_t actual_value_len = -1;
bool read_ok = true;
while (read_ok) {
if (key_matches(&active_file, key)) {
actual_value_len = read_value(&active_file, value, value_len);
}
read_ok = next_line(&active_file);
}
close_file(&active_file);
return actual_value_len;
}
mp_obj_t common_hal_dotenv_get_key(const char *path, const char *key) {
// Use the stack for short values. Longer values will require a heap allocation after we know
// the length.
char value[64];
mp_int_t actual_len = dotenv_get_key(path, key, value, sizeof(value));
if (actual_len < 0) {
return mp_const_none;
}
if ((size_t)actual_len >= sizeof(value)) {
byte *buf = m_new(byte, actual_len + 1);
dotenv_get_key(path, key, (char *)buf, actual_len);
buf[actual_len] = 0;
mp_obj_str_t *o = m_new_obj(mp_obj_str_t);
o->base.type = &mp_type_str;
o->len = actual_len;
o->data = buf;
o->hash = qstr_compute_hash(buf, actual_len);
return MP_OBJ_FROM_PTR(o);
}
return mp_obj_new_str(value, actual_len);
}
bool dotenv_get_key_terminated(const char *path, const char *key, char *value, mp_int_t value_len) {
mp_int_t actual_len = dotenv_get_key(path, key, value, value_len - 1);
if (actual_len >= value_len) {
return false;
}
value[actual_len] = '\0'; // terminate string
return true;
}
bool dotenv_get_key_int(const char *path, const char *key, mp_int_t *value) {
char buf[16];
if (!dotenv_get_key_terminated(path, key, buf, (mp_int_t)sizeof(buf))) {
return false;
}
char *end;
long result = strtol(buf, &end, 0);
if (end == buf || *end) { // If the whole buffer was not consumed it's an error
return false;
}
*value = (mp_int_t)result;
return true;
}

View File

@ -36,10 +36,6 @@
#include "py/runtime.h"
#include "shared-bindings/os/__init__.h"
#if CIRCUITPY_DOTENV
#include "shared-bindings/dotenv/__init__.h"
#endif
// This provides all VFS related OS functions so that ports can share the code
// as needed. It does not provide uname.
@ -111,16 +107,6 @@ mp_obj_t common_hal_os_getcwd(void) {
return mp_vfs_getcwd();
}
mp_obj_t common_hal_os_getenv(const char *key, mp_obj_t default_) {
#if CIRCUITPY_DOTENV
mp_obj_t env_obj = common_hal_dotenv_get_key("/.env", key);
if (env_obj != mp_const_none) {
return env_obj;
}
#endif
return default_;
}
mp_obj_t common_hal_os_listdir(const char *path) {
mp_obj_t path_out;
mp_vfs_mount_t *vfs = lookup_dir_path(path, &path_out);

View File

@ -24,13 +24,23 @@
* THE SOFTWARE.
*/
#pragma once
typedef enum {
GETENV_OK = 0,
GETENV_ERR_OPEN,
GETENV_ERR_UNICODE,
GETENV_ERR_LENGTH,
GETENV_ERR_NOT_FOUND,
GETENV_ERR_UNEXPECTED = 0xff00, // logical or'd with the byte value
} os_getenv_err_t;
// Allocation free version that returns the full length of the value.
mp_int_t dotenv_get_key(const char *path, const char *key, char *value, mp_int_t value_len);
// If it fits, the return value is 0-terminated. The passed in buffer
// may be modified even if an error is returned. Allocation free.
os_getenv_err_t common_hal_os_getenv_str(const char *key, char *value, size_t value_len);
// Returns true and sets value to a '\0'-terminated string if key is present
// and the value (including the terminating '\0') fits strictly within
// value_len bytes.
bool dotenv_get_key_terminated(const char *path, const char *key, char *value, mp_int_t value_len);
// Returns true and sets value to the read value. Returns false if the value was not numeric.
bool dotenv_get_key_int(const char *path, const char *key, mp_int_t *value);
// Returns GETENV_OK and sets value to the read value. Returns
// GETENV_ERR_... if the value was not numeric. allocation-free.
// If any error code is returned, value is guaranteed not modified
os_getenv_err_t common_hal_os_getenv_int(const char *key, mp_int_t *value);

391
shared-module/os/getenv.c Normal file
View File

@ -0,0 +1,391 @@
/*
* This file is part of the Micro Python project, http://micropython.org/
*
* The MIT License (MIT)
*
* SPDX-FileCopyrightText: Copyright (c) 2022 Scott Shawcroft for Adafruit Industries
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
// These functions are separate from __init__.c so that os.getenv() can be
// tested in the unix "coverage" build, without bringing in "our" os module
#include <stdlib.h>
#include <string.h>
#include "shared-bindings/os/__init__.h"
#include "shared-module/os/__init__.h"
#include "py/gc.h"
#include "py/misc.h"
#include "py/mpstate.h"
#include "py/objstr.h"
#include "py/parsenum.h"
#include "py/runtime.h"
#include "supervisor/filesystem.h"
#include "supervisor/memory.h"
#define GETENV_PATH "/settings.toml"
#include "extmod/vfs.h"
#include "extmod/vfs_fat.h"
typedef FIL file_arg;
STATIC bool open_file(const char *name, file_arg *active_file) {
#if defined(UNIX)
nlr_buf_t nlr;
if (nlr_push(&nlr) == 0) {
mp_obj_t file_obj = mp_call_function_2(MP_OBJ_FROM_PTR(&mp_builtin_open_obj), mp_obj_new_str(name, strlen(name)), MP_ROM_QSTR(MP_QSTR_rb));
mp_arg_validate_type(file_obj, &mp_type_vfs_fat_fileio, MP_QSTR_file);
pyb_file_obj_t *file = MP_OBJ_TO_PTR(file_obj);
*active_file = file->fp;
nlr_pop();
return true;
} else {
return false;
}
#else
FATFS *fs = filesystem_circuitpy();
FRESULT result = f_open(fs, active_file, name, FA_READ);
return result == FR_OK;
#endif
}
STATIC void close_file(file_arg *active_file) {
// nothing
}
STATIC bool is_eof(file_arg *active_file) {
return f_eof(active_file);
}
// Return 0 if there is no next character (EOF).
STATIC uint8_t get_next_byte(FIL *active_file) {
uint8_t character = 0;
UINT quantity_read;
// If there's an error or quantity_read is 0, character will remain 0.
f_read(active_file, &character, 1, &quantity_read);
return character;
}
STATIC void seek_eof(file_arg *active_file) {
f_lseek(active_file, f_size(active_file));
}
// For a fixed buffer, record the required size rather than throwing
STATIC void vstr_add_byte_nonstd(vstr_t *vstr, byte b) {
if (!vstr->fixed_buf || vstr->alloc > vstr->len) {
vstr_add_byte(vstr, b);
} else {
vstr->len++;
}
}
// For a fixed buffer, record the required size rather than throwing
STATIC void vstr_add_char_nonstd(vstr_t *vstr, unichar c) {
size_t ulen =
(c < 0x80) ? 1 :
(c < 0x800) ? 2 :
(c < 0x10000) ? 3 : 4;
if (!vstr->fixed_buf || vstr->alloc > vstr->len + ulen) {
vstr_add_char(vstr, c);
} else {
vstr->len += ulen;
}
}
STATIC void next_line(file_arg *active_file) {
uint8_t character;
do {
character = get_next_byte(active_file);
} while (character != 0 && character != '\n');
}
// Discard whitespace, except for newlines, returning the next character after the whitespace.
// Return 0 if there is no next character (EOF).
STATIC uint8_t consume_whitespace(file_arg *active_file) {
uint8_t character;
do {
character = get_next_byte(active_file);
} while (character != '\n' && character != 0 && unichar_isspace(character));
return character;
}
// Starting at the start of a new line, determines if the key matches the given
// key.
//
// If result is true, the key matches and file pointer is pointing just after the "=".
// If the result is false, the key does NOT match and the file pointer is
// pointing at the start of the next line, if any
STATIC bool key_matches(file_arg *active_file, const char *key) {
uint8_t character;
character = consume_whitespace(active_file);
if (character == '[' || character == 0) {
seek_eof(active_file);
return false;
}
while (*key) {
if (character != *key++) {
// A character didn't match the key, so it's not a match
// If the non-matching char was not the end of the line,
// then consume the rest of the line
if (character != '\n') {
next_line(active_file);
}
return false;
}
character = get_next_byte(active_file);
}
// the next character could be whitespace; consume if necessary
if (unichar_isspace(character)) {
character = consume_whitespace(active_file);
}
// If we're not looking at the "=" then the key didn't match
if (character != '=') {
// A character didn't match the key, so it's not a match
// If the non-matching char was not the end of the line,
// then consume the rest of the line
if (character != '\n') {
next_line(active_file);
}
return false;
}
return true;
}
STATIC os_getenv_err_t read_unicode_escape(file_arg *active_file, int sz, vstr_t *buf) {
char hex_buf[sz + 1];
for (int i = 0; i < sz; i++) {
hex_buf[i] = get_next_byte(active_file);
}
hex_buf[sz] = 0;
char *end;
unsigned long c = strtoul(hex_buf, &end, 16);
if (end != &hex_buf[sz]) {
return GETENV_ERR_UNEXPECTED | *end;
}
if (c >= 0x110000) {
return GETENV_ERR_UNICODE;
}
vstr_add_char_nonstd(buf, c);
return GETENV_OK;
}
// Read a quoted string
STATIC os_getenv_err_t read_string_value(file_arg *active_file, vstr_t *buf) {
while (true) {
int character = get_next_byte(active_file);
switch (character) {
case 0:
case '\n':
return GETENV_ERR_UNEXPECTED | character;
case '"':
character = consume_whitespace(active_file);
switch (character) {
case '#':
next_line(active_file);
MP_FALLTHROUGH;
case '\n':
return GETENV_OK;
default:
return GETENV_ERR_UNEXPECTED | character;
}
case '\\':
character = get_next_byte(active_file);
switch (character) {
case 0:
case '\n':
return GETENV_ERR_UNEXPECTED | character;
case 'b':
character = '\b';
break;
case 'r':
character = '\r';
break;
case 'n':
character = '\n';
break;
case 't':
character = '\t';
break;
case 'v':
character = '\v';
break;
case 'f':
character = '\f';
break;
case 'U':
case 'u': {
int sz = (character == 'u') ? 4 : 8;
os_getenv_err_t res;
res = read_unicode_escape(active_file, sz, buf);
if (res != GETENV_OK) {
return res;
}
continue;
}
// default falls through, other escaped characters
// represent themselves
}
MP_FALLTHROUGH;
default:
vstr_add_byte_nonstd(buf, character);
}
}
}
// Read a numeric value (non-quoted value) as a string
STATIC os_getenv_err_t read_bare_value(file_arg *active_file, vstr_t *buf, int first_character) {
int character = first_character;
while (true) {
switch (character) {
case 0:
return GETENV_ERR_UNEXPECTED | character;
case '\n':
return GETENV_OK;
case '#':
next_line(active_file);
return GETENV_OK;
default:
vstr_add_byte_nonstd(buf, character);
}
character = get_next_byte(active_file);
}
}
STATIC mp_int_t read_value(file_arg *active_file, vstr_t *buf, bool *quoted) {
uint8_t character;
character = consume_whitespace(active_file);
*quoted = (character == '"');
if (*quoted) {
return read_string_value(active_file, buf);
} else {
return read_bare_value(active_file, buf, character);
}
}
STATIC os_getenv_err_t os_getenv_vstr(const char *path, const char *key, vstr_t *buf, bool *quoted) {
file_arg active_file;
if (!open_file(path, &active_file)) {
return GETENV_ERR_OPEN;
}
os_getenv_err_t result = GETENV_ERR_NOT_FOUND;
while (!is_eof(&active_file)) {
if (key_matches(&active_file, key)) {
result = read_value(&active_file, buf, quoted);
}
}
close_file(&active_file);
return result;
}
STATIC os_getenv_err_t os_getenv_buf_terminated(const char *key, char *value, size_t value_len, bool *quoted) {
vstr_t buf;
vstr_init_fixed_buf(&buf, value_len, value);
os_getenv_err_t result = os_getenv_vstr(GETENV_PATH, key, &buf, quoted);
if (result == GETENV_OK) {
vstr_add_byte_nonstd(&buf, 0);
memcpy(value, buf.buf, MIN(buf.len, value_len));
if (buf.len > value_len) { // this length includes trailing NUL
result = GETENV_ERR_LENGTH;
}
}
return result;
}
os_getenv_err_t common_hal_os_getenv_str(const char *key, char *value, size_t value_len) {
bool quoted;
os_getenv_err_t result = os_getenv_buf_terminated(key, value, value_len, &quoted);
if (result == GETENV_OK && !quoted) {
result = GETENV_ERR_UNEXPECTED | value[0];
}
return result;
}
STATIC void throw_getenv_error(os_getenv_err_t error) {
if (error == GETENV_OK) {
return;
}
if (error & GETENV_ERR_UNEXPECTED) {
byte character = (error & 0xff);
mp_print_t print;
vstr_t vstr;
vstr_init_print(&vstr, 8 + 4 + 1, &print);
if (character) {
mp_str_print_quoted(&print, &character, 1, true);
} else {
mp_str_print_quoted(&print, (byte *)"EOF", 3, true);
}
mp_raise_ValueError_varg(translate("Invalid byte %.*s"),
vstr.len, vstr.buf);
}
switch (error) {
case GETENV_ERR_OPEN:
mp_raise_ValueError(translate("File not found"));
case GETENV_ERR_UNICODE:
mp_raise_ValueError(translate("Invalid unicode escape"));
case GETENV_ERR_NOT_FOUND:
mp_raise_ValueError(translate("Key not found"));
default:
mp_raise_RuntimeError(translate("Internal error"));
}
}
mp_obj_t common_hal_os_getenv_path(const char *path, const char *key, mp_obj_t default_) {
vstr_t buf;
bool quoted;
vstr_init(&buf, 64);
os_getenv_err_t result = os_getenv_vstr(path, key, &buf, &quoted);
if (result == GETENV_ERR_NOT_FOUND || result == GETENV_ERR_OPEN) {
return default_;
}
throw_getenv_error(result);
if (quoted) {
return mp_obj_new_str_from_vstr(&mp_type_str, &buf);
} else {
return mp_parse_num_integer(buf.buf, buf.len, 0, NULL);
}
}
mp_obj_t common_hal_os_getenv(const char *key, mp_obj_t default_) {
return common_hal_os_getenv_path(GETENV_PATH, key, default_);
}
os_getenv_err_t common_hal_os_getenv_int(const char *key, mp_int_t *value) {
char buf[16];
bool quoted;
os_getenv_err_t result = os_getenv_buf_terminated(key, buf, sizeof(buf), &quoted);
if (result != GETENV_OK) {
return result;
}
if (quoted) {
return GETENV_ERR_UNEXPECTED | '"';
}
char *end;
long num = strtol(buf, &end, 0);
if (end == buf || *end) { // If the whole buffer was not consumed it's an error
return GETENV_ERR_UNEXPECTED | *end;
}
*value = (mp_int_t)num;
return GETENV_OK;
}

View File

@ -129,6 +129,9 @@ bool filesystem_init(bool create_allowed, bool force_create) {
make_empty_file(&vfs_fat->fatfs, "/.metadata_never_index");
make_empty_file(&vfs_fat->fatfs, "/.Trashes");
make_empty_file(&vfs_fat->fatfs, "/.fseventsd/no_log");
#if CIRCUITPY_OS_GETENV
make_empty_file(&vfs_fat->fatfs, "/settings.toml");
#endif
// make a sample code.py file
make_sample_code_file(&vfs_fat->fatfs);

View File

@ -95,7 +95,7 @@ async function refresh_list() {
if (f.directory) {
icon = "📁";
} else if(f.name.endsWith(".txt") ||
f.name.endsWith(".env") ||
f.name.endsWith(".toml") ||
f.name.endsWith(".py") ||
f.name.endsWith(".js") ||
f.name.endsWith(".json")) {

View File

@ -12,7 +12,7 @@
<h1><a href="/"><img src="/favicon.ico"/></a>&nbsp;Welcome!</h1>
<p>Welcome to CircuitPython's Web API. Go to the <a href="/fs/">file browser</a> to work with files in the CIRCUITPY drive. Go to the <a href="/code/">full code editor</a> for a more enriching experience which requires an active internet connection. Go to the <a href="/cp/serial/">serial terminal</a> to see code output and interact with the REPL. Make sure you've set <code>CIRCUITPY_WEB_API_PASSWORD='somepassword'</code> in <code>/.env</code>. Provide the password when the browser prompts for it. <strong>Leave the username blank.</strong></p>
<p>Welcome to CircuitPython's Web API. Go to the <a href="/fs/">file browser</a> to work with files in the CIRCUITPY drive. Go to the <a href="/code/">full code editor</a> for a more enriching experience which requires an active internet connection. Go to the <a href="/cp/serial/">serial terminal</a> to see code output and interact with the REPL. Make sure you've set <code>CIRCUITPY_WEB_API_PASSWORD='somepassword'</code> in <code>/settings.toml</code>. Provide the password when the browser prompts for it. <strong>Leave the username blank.</strong></p>
<h2>Device Info:</h2>

View File

@ -64,8 +64,8 @@
#include "shared-bindings/wifi/__init__.h"
#endif
#if CIRCUITPY_DOTENV
#include "shared-module/dotenv/__init__.h"
#if CIRCUITPY_OS_GETENV
#include "shared-module/os/__init__.h"
#endif
enum request_state {
@ -115,7 +115,7 @@ static wifi_radio_error_t _last_wifi_status = WIFI_RADIO_ERROR_NONE;
static mdns_server_obj_t mdns;
#endif
static uint32_t web_api_port = 80;
static mp_int_t web_api_port = 80;
static socketpool_socketpool_obj_t pool;
static socketpool_socket_obj_t listening;
@ -244,22 +244,21 @@ void supervisor_web_workflow_status(void) {
#endif
void supervisor_start_web_workflow(void) {
#if CIRCUITPY_WEB_WORKFLOW && CIRCUITPY_WIFI
#if CIRCUITPY_WEB_WORKFLOW && CIRCUITPY_WIFI && CIRCUITPY_OS_GETENV
char ssid[33];
char password[64];
mp_int_t ssid_len = 0;
mp_int_t password_len = 0;
#if CIRCUITPY_DOTENV
ssid_len = dotenv_get_key("/.env", "CIRCUITPY_WIFI_SSID", ssid, sizeof(ssid) - 1);
password_len = dotenv_get_key("/.env", "CIRCUITPY_WIFI_PASSWORD", password, sizeof(password) - 1);
#endif
if (ssid_len <= 0 || (size_t)ssid_len >= sizeof(ssid) ||
password_len <= 0 || (size_t)password_len >= sizeof(password)) {
os_getenv_err_t result = common_hal_os_getenv_str("CIRCUITPY_WIFI_SSID", ssid, sizeof(ssid));
if (result != GETENV_OK) {
return;
}
result = common_hal_os_getenv_str("CIRCUITPY_WIFI_PASSWORD", password, sizeof(password));
if (result != GETENV_OK) {
return;
}
if (!common_hal_wifi_radio_get_enabled(&common_hal_wifi_radio_obj)) {
common_hal_wifi_init(false);
common_hal_wifi_radio_set_enabled(&common_hal_wifi_radio_obj, true);
@ -268,14 +267,11 @@ void supervisor_start_web_workflow(void) {
// TODO: Do our own scan so that we can find the channel we want before calling connect.
// Otherwise, connect will do a full slow scan to pick the best AP.
// NUL terminate the strings because dotenv doesn't.
ssid[ssid_len] = '\0';
password[password_len] = '\0';
// We can all connect again because it will return early if we're already connected to the
// network. If we are connected to a different network, then it will disconnect before
// attempting to connect to the given network.
_wifi_status = common_hal_wifi_radio_connect(
&common_hal_wifi_radio_obj, (uint8_t *)ssid, ssid_len, (uint8_t *)password, password_len,
&common_hal_wifi_radio_obj, (uint8_t *)ssid, strlen(ssid), (uint8_t *)password, strlen(password),
0, 8, NULL, 0);
if (_wifi_status != WIFI_RADIO_ERROR_NONE) {
@ -283,21 +279,15 @@ void supervisor_start_web_workflow(void) {
return;
}
char port_encoded[6];
size_t port_len = 0;
size_t new_port = web_api_port;
#if CIRCUITPY_DOTENV
port_len = dotenv_get_key("/.env", "CIRCUITPY_WEB_API_PORT", port_encoded, sizeof(port_encoded) - 1);
#endif
if (0 < port_len && port_len < sizeof(port_encoded)) {
port_encoded[port_len] = '\0';
new_port = strtoul(port_encoded, NULL, 10);
}
mp_int_t new_port = web_api_port;
// (leaves new_port unchanged on any failure)
(void)common_hal_os_getenv_int("CIRCUITPY_WEB_API_PORT", &new_port);
bool first_start = pool.base.type != &socketpool_socketpool_type;
bool port_changed = new_port != web_api_port;
if (first_start) {
port_changed = false;
#if CIRCUITPY_MDNS
mdns_server_construct(&mdns, true);
mdns.base.type = &mdns_server_type;
@ -326,11 +316,12 @@ void supervisor_start_web_workflow(void) {
common_hal_socketpool_socket_listen(&listening, 1);
}
mp_int_t api_password_len = dotenv_get_key("/.env", "CIRCUITPY_WEB_API_PASSWORD", _api_password + 1, sizeof(_api_password) - 2);
if (api_password_len > 0) {
const size_t api_password_len = sizeof(_api_password) - 1;
result = common_hal_os_getenv_str("CIRCUITPY_WEB_API_PASSWORD", _api_password + 1, api_password_len);
if (result == GETENV_OK) {
_api_password[0] = ':';
_api_password[api_password_len + 1] = '\0';
_base64_in_place(_api_password, api_password_len + 1, sizeof(_api_password));
_base64_in_place(_api_password, strlen(_api_password), sizeof(_api_password) - 1);
}
#endif
}
@ -360,7 +351,7 @@ static void _send_str(socketpool_socket_obj_t *socket, const char *str) {
}
// The last argument must be NULL! Otherwise, it won't stop.
static void _send_strs(socketpool_socket_obj_t *socket, ...) {
static __attribute__((sentinel)) void _send_strs(socketpool_socket_obj_t *socket, ...) {
va_list ap;
va_start(ap, socket);
@ -691,16 +682,16 @@ static void _reply_with_file(socketpool_socket_obj_t *socket, _request *request,
mp_print_t _socket_print = {socket, _print_raw};
mp_printf(&_socket_print, "Content-Length: %d\r\n", total_length);
// TODO: Make this a table to save space.
if (_endswith(filename, ".txt") || _endswith(filename, ".py")) {
_send_strs(socket, "Content-Type: text/plain", ";charset=UTF-8\r\n", NULL);
if (_endswith(filename, ".txt") || _endswith(filename, ".py") || _endswith(filename, ".toml")) {
_send_strs(socket, "Content-Type:", "text/plain", ";charset=UTF-8\r\n", NULL);
} else if (_endswith(filename, ".js")) {
_send_strs(socket, "Content-Type: text/javascript", ";charset=UTF-8\r\n", NULL);
_send_strs(socket, "Content-Type:", "text/javascript", ";charset=UTF-8\r\n", NULL);
} else if (_endswith(filename, ".html")) {
_send_strs(socket, "Content-Type: text/html", ";charset=UTF-8\r\n", NULL);
_send_strs(socket, "Content-Type:", "text/html", ";charset=UTF-8\r\n", NULL);
} else if (_endswith(filename, ".json")) {
_send_strs(socket, "Content-Type: application/json", ";charset=UTF-8\r\n", NULL);
_send_strs(socket, "Content-Type:", "application/json", ";charset=UTF-8\r\n", NULL);
} else {
_send_str(socket, "Content-Type: application/octet-stream\r\n");
_send_strs(socket, "Content-Type:", "application/octet-stream\r\n", NULL);
}
_cors_header(socket, request);
_send_str(socket, "\r\n");

View File

@ -1,37 +0,0 @@
# Key "notpresent" is not present
# comment preceded by spaces
plain_value=value
value_with_comment=value # value followed by a comment
quoted_value='value'
quoted_value_with_comment='value' # quoted value followed by a comment
should_be_none
should_be_empty_string=
should_be_hash=#
quoted_should_be_empty_string=''
duplicate_key=wrong
duplicate_key=right
value_with_hash=value#value
quoted_value_with_hash='value#value'
multi_line_value='multi
line'
space_before_key=value
space_before_value= value
space_before_hash_value= #value
space_after_key =value
space_after_key_before_value = value
quoted_then_comment='value'#comment
hash_with_spaces=#value value
aa🐍bb=key with emoji
value_with_emoji=aa🐍bb
sz0=x
sz1=xx
sz2=xxxx
sz3=xxxxxxxx
sz4=xxxxxxxxxxxxxxxx
sz5=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
sz6=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
sz7=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
sz8=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
sz9=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
sz10=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
sz11=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

View File

@ -1,31 +0,0 @@
import dotenv
FILE = __file__.rsplit(".", 1)[0] + ".env"
print(f"notpresent={dotenv.get_key(FILE, 'notpresent')}")
print(f"plain_value={dotenv.get_key(FILE, 'plain_value')}")
print(f"value_with_comment={dotenv.get_key(FILE, 'value_with_comment')}")
print(f"quoted_value={dotenv.get_key(FILE, 'quoted_value')}")
print(f"quoted_value_with_comment={dotenv.get_key(FILE, 'quoted_value_with_comment')}")
print(f"should_be_none={dotenv.get_key(FILE, 'should_be_none')}")
print(f"should_be_empty_string={dotenv.get_key(FILE, 'should_be_empty_string')}")
print(f"should_be_hash={dotenv.get_key(FILE, 'should_be_hash')}")
print(f"quoted_should_be_empty_string={dotenv.get_key(FILE, 'quoted_should_be_empty_string')}")
print(f"duplicate_key={dotenv.get_key(FILE, 'duplicate_key')}")
### This is the a difference from CPython dotenv. The trailing #value is taken as a comment.
print(f"value_with_hash={dotenv.get_key(FILE, 'value_with_hash')}")
print(f"quoted_value_with_hash={dotenv.get_key(FILE, 'quoted_value_with_hash')}")
print(f"multi_line_value={dotenv.get_key(FILE, 'multi_line_value')}")
print(f"space_before_key={dotenv.get_key(FILE, 'space_before_key')}")
print(f"space_before_value={dotenv.get_key(FILE, 'space_before_value')}")
print(f"space_before_hash_value={dotenv.get_key(FILE, 'space_before_hash_value')}")
print(f"space_after_key={dotenv.get_key(FILE, 'space_after_key')}")
print(f"space_after_key_before_value={dotenv.get_key(FILE, 'space_after_key_before_value')}")
print(f"quoted_then_comment={dotenv.get_key(FILE, 'quoted_then_comment')}")
print(f"hash_with_spaces={dotenv.get_key(FILE, 'hash_with_spaces')}")
print(f"aa🐍bb={dotenv.get_key(FILE, 'aa🐍bb')}")
print(f"value_with_emoji={dotenv.get_key(FILE, 'value_with_emoji')}")
for i in range(12):
key = f"sz{i}"
print(f"len({key})={len(dotenv.get_key(FILE, key))}")

View File

@ -1,35 +0,0 @@
notpresent=None
plain_value=value
value_with_comment=value
quoted_value=value
quoted_value_with_comment=value
should_be_none=None
should_be_empty_string=
should_be_hash=#
quoted_should_be_empty_string=
duplicate_key=right
value_with_hash=value
quoted_value_with_hash=value#value
multi_line_value=multi
line
space_before_key=value
space_before_value=value
space_before_hash_value=#value
space_after_key=value
space_after_key_before_value=value
quoted_then_comment=value
hash_with_spaces=#value value
aa🐍bb=key with emoji
value_with_emoji=aa🐍bb
len(sz0)=1
len(sz1)=2
len(sz2)=4
len(sz3)=8
len(sz4)=16
len(sz5)=32
len(sz6)=64
len(sz7)=128
len(sz8)=256
len(sz9)=512
len(sz10)=1024
len(sz11)=2048

View File

@ -0,0 +1,79 @@
import uos
uos.umount("/")
class RAMBlockDevice:
ERASE_BLOCK_SIZE = 512
def __init__(self, blocks):
self.data = bytearray(blocks * self.ERASE_BLOCK_SIZE)
def readblocks(self, block, buf, off=0):
addr = block * self.ERASE_BLOCK_SIZE + off
for i in range(len(buf)):
buf[i] = self.data[addr + i]
def writeblocks(self, block, buf, off=None):
if off is None:
# erase, then write
off = 0
addr = block * self.ERASE_BLOCK_SIZE + off
for i in range(len(buf)):
self.data[addr + i] = buf[i]
def ioctl(self, op, arg):
if op == 4: # block count
return len(self.data) // self.ERASE_BLOCK_SIZE
if op == 5: # block size
return self.ERASE_BLOCK_SIZE
if op == 6: # erase block
return 0
bdev = RAMBlockDevice(64)
uos.VfsFat.mkfs(bdev)
uos.mount(uos.VfsFat(bdev), "/")
content_good = """
# comment
key0 = "hello world"
key1 = 7
key2= "\n"
key3 ="\u00c1x"
key4 = "\U000000c1x"
key5 = "\f\"\\"
key6 = "\t\r\b"
key7 = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
key8 = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
key9 = "hello comment" # comment
key10 = 0x7f # comment
key11 = 0
[section]
subvalue = "hi"
"""
content_bad = [
'key = "\n',
'key = """\n',
"key =\n",
'key="',
]
def run_test(key, content):
with open("/settings.toml", "w") as f:
f.write(content)
try:
v = uos.getenv(key)
print(key, repr(v))
except Exception as e:
print(key, str(e))
for i in range(13):
run_test(f"key{i}", content_good)
for content in content_bad:
run_test("key", content)

View File

@ -0,0 +1,17 @@
key0 'hello world'
key1 7
key2 Invalid byte '\n'
key3 'Áx'
key4 'Áx'
key5 Invalid byte '\\'
key6 '\t\r\x08'
key7 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
key8 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
key9 'hello comment'
key10 127
key11 0
key12 None
key Invalid byte '\n'
key Invalid byte '"'
key invalid syntax for integer with base 10: ''
key Invalid byte 'EOF'

View File

@ -32,19 +32,18 @@ mport
builtins micropython _asyncio _thread
_uasyncio aesio array binascii
bitmaptools btree cexample cmath
collections cppexample displayio dotenv
errno ffi framebuf gc
gifio hashlib json math
qrio rainbowio re sys
termios traceback ubinascii uctypes
uerrno uheapq uio ujson
ulab ulab.numpy ulab.numpy.fft
ulab.numpy.linalg ulab.scipy
ulab.scipy.linalg ulab.scipy.optimize
ulab.scipy.signal ulab.scipy.special
ulab.utils uos urandom ure
uselect ustruct utime utimeq
uzlib zlib
collections cppexample displayio errno
ffi framebuf gc gifio
hashlib json math qrio
rainbowio re sys termios
traceback ubinascii uctypes uerrno
uheapq uio ujson ulab
ulab.numpy ulab.numpy.fft ulab.numpy.linalg
ulab.scipy ulab.scipy.linalg
ulab.scipy.optimize ulab.scipy.signal
ulab.scipy.special ulab.utils uos
urandom ure uselect ustruct
utime utimeq uzlib zlib
ime
utime utimeq