dotenv becomes settings.toml

This commit is contained in:
Jeff Epler 2022-12-01 16:43:08 -06:00
parent 748cb92ff3
commit ef2bfdb5db
No known key found for this signature in database
GPG Key ID: D5BF15AB975AB4DE
31 changed files with 642 additions and 566 deletions

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_ENVIRON
#include "shared-bindings/_environ/__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_ENVIRON
mp_obj_t name = common_hal__environ_get_key("CIRCUITPY_BLE_NAME");
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,36 @@ 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 supports these by parsing a subset of the `toml <https://toml.io/>`_ file format internally.
These libraries store environment variables in a ``.env`` file. Here is a simple
example:
Here is a simple example:
.. code-block:: bash
KEY1='value1'
KEY1="value1"
# Comment
KEY2='value2
is multiple lines'
KEY2="value2\ncontains a newline"
CircuitPython uses the ``.env`` at the drive root (no folder) as the environment.
[SECTION] # Only values in the "root table" are parsed
SECTION_VALUE = ... # so this value cannot be seen by getenv
CircuitPython uses the ``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 ``/.env`` on every access.
will parse the ``settings.toml`` file contents on every access.
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
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/dotenv/__init__.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
@ -1184,6 +1188,7 @@ msgid "Internal define error"
msgstr ""
#: ports/espressif/common-hal/paralleldisplay/ParallelBus.c
#: shared-module/dotenv/__init__.c
msgid "Internal error"
msgstr ""
@ -1230,6 +1235,11 @@ msgstr ""
msgid "Invalid bits per value"
msgstr ""
#: shared-module/dotenv/__init__.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 +1270,18 @@ msgstr ""
msgid "Invalid state"
msgstr ""
#: shared-module/dotenv/__init__.c
msgid "Invalid unicode escape"
msgstr ""
#: shared-bindings/aesio/aes.c
msgid "Key must be 16, 24, or 32 bytes long"
msgstr ""
#: shared-module/dotenv/__init__.c
msgid "Key not found"
msgstr ""
#: shared-module/is31fl3741/FrameBuffer.c
msgid "LED mappings must match display size"
msgstr ""

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_ENVIRON
#include "shared-module/_environ/__init__.h"
#endif
bleio_connection_internal_t bleio_connections[BLEIO_TOTAL_CONNECTION_COUNT];
@ -101,28 +101,24 @@ 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_ENVIRON
char ble_name[1 + MYNEWT_VAL_BLE_SVC_GAP_DEVICE_NAME_MAX_LENGTH];
_environ_err_t result = _environ_get_key_str("CIRCUITPY_BLE_NAME", ble_name, sizeof(ble_name));
if (result == ENVIRON_OK) {
ble_svc_gap_device_name_set(ble_name);
} else {
} else
#else
{
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++) {
bleio_connection_internal_t *connection = &bleio_connections[i];
// Reset connection.
connection->conn_handle = BLEIO_HANDLE_INVALID;
{// Clear all of the internal connection objects.
for (size_t i = 0; i < BLEIO_TOTAL_CONNECTION_COUNT; i++) {
bleio_connection_internal_t *connection = &bleio_connections[i];
// Reset connection.
connection->conn_handle = BLEIO_HANDLE_INVALID;
}
}
cp_task = xTaskGetCurrentTaskHandle();

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/_environ/__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 (_environ_get_key_int("CIRCUITPY_RESERVED_PSRAM", &reserved) == ENVIRON_OK) {
common_hal_espidf_set_reserved_psram(reserved);
}
common_hal_espidf_reserve_psram();

View File

@ -52,8 +52,8 @@
#include "shared-bindings/_bleio/ScanEntry.h"
#include "shared-bindings/time/__init__.h"
#if CIRCUITPY_DOTENV
#include "shared-module/dotenv/__init__.h"
#if CIRCUITPY_ENVIRON
#include "shared-bindings/_environ/__init__.h"
#endif
#define BLE_MIN_CONN_INTERVAL MSEC_TO_UNITS(15, UNIT_0_625_MS)
@ -345,12 +345,11 @@ STATIC void bleio_adapter_reset_name(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) {
ble_name[name_len] = '\0';
common_hal_bleio_adapter_set_name(self, (char *)ble_name);
#if CIRCUITPY_ENVIRON
mp_obj_t ble_name = common_hal__environ_get_key("CIRCUITPY_BLE_NAME");
if (ble_name != mp_const_none) {
common_hal_bleio_adapter_set_name(self, mp_obj_str_get_str(ble_name));
return;
}
#endif

View File

@ -33,7 +33,7 @@ SRC_BITMAP := \
shared-bindings/aesio/__init__.c \
shared-bindings/bitmaptools/__init__.c \
shared-bindings/displayio/Bitmap.c \
shared-bindings/dotenv/__init__.c \
shared-bindings/_environ/__init__.c \
shared-bindings/rainbowio/__init__.c \
shared-bindings/traceback/__init__.c \
shared-bindings/util.c \
@ -45,7 +45,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/_environ/__init__.c \
shared-module/rainbowio/__init__.c \
shared-module/traceback/__init__.c \
shared-module/zlib/__init__.c \
@ -56,7 +56,7 @@ CFLAGS += \
-DCIRCUITPY_AESIO=1 \
-DCIRCUITPY_BITMAPTOOLS=1 \
-DCIRCUITPY_DISPLAYIO_UNIX=1 \
-DCIRCUITPY_DOTENV=1 \
-DCIRCUITPY_ENVIRON=1 \
-DCIRCUITPY_GIFIO=1 \
-DCIRCUITPY_RAINBOWIO=1 \
-DCIRCUITPY_TRACEBACK=1 \

View File

@ -182,8 +182,8 @@ endif
ifeq ($(CIRCUITPY_DISPLAYIO),1)
SRC_PATTERNS += displayio/%
endif
ifeq ($(CIRCUITPY_DOTENV),1)
SRC_PATTERNS += dotenv/%
ifeq ($(CIRCUITPY_ENVIRON),1)
SRC_PATTERNS += _environ/%
endif
ifeq ($(CIRCUITPY__EVE),1)
SRC_PATTERNS += _eve/%
@ -589,7 +589,7 @@ SRC_SHARED_MODULE_ALL = \
displayio/TileGrid.c \
displayio/area.c \
displayio/__init__.c \
dotenv/__init__.c \
_environ/__init__.c \
floppyio/__init__.c \
fontio/BuiltinFont.c \
fontio/__init__.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_ENVIRON ?= $(CIRCUITPY_FULL_BUILD)
CFLAGS += -DCIRCUITPY_ENVIRON=$(CIRCUITPY_ENVIRON)
CIRCUITPY_ERRNO ?= $(CIRCUITPY_FULL_BUILD)
CFLAGS += -DCIRCUITPY_ERRNO=$(CIRCUITPY_ERRNO)

View File

@ -33,11 +33,11 @@
#include "py/obj.h"
#include "py/objstr.h"
#include "py/runtime.h"
#include "shared-bindings/dotenv/__init__.h"
#include "shared-bindings/_environ/__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
//| A subset of the CPython `_environ library <https://saurabh-kumar.com/python-_environ/>`_. It does
//| not support variables or double quotes.
//|
//| Keys and values may be put in single quotes.
@ -71,43 +71,30 @@
//| import typing
//|
//| def get_key(dotenv_path: str, key_to_get: str) -> Optional[str]:
//| def get_key(_environ_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),
STATIC mp_obj_t __environ_get_key(mp_obj_t path_in, mp_obj_t key_to_get_in) {
return common_hal__environ_get_key_path(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);
MP_DEFINE_CONST_FUN_OBJ_2(_environ_get_key_obj, __environ_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 _environ_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR__environ) },
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) },
{ MP_ROM_QSTR(MP_QSTR_get_key), MP_ROM_PTR(&_environ_get_key_obj) },
};
STATIC MP_DEFINE_CONST_DICT(dotenv_module_globals, dotenv_module_globals_table);
STATIC MP_DEFINE_CONST_DICT(_environ_module_globals, _environ_module_globals_table);
const mp_obj_module_t dotenv_module = {
const mp_obj_module_t _environ_module = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t *)&dotenv_module_globals,
.globals = (mp_obj_dict_t *)&_environ_module_globals,
};
MP_REGISTER_MODULE(MP_QSTR_dotenv, dotenv_module, CIRCUITPY_DOTENV);
MP_REGISTER_MODULE(MP_QSTR__environ, _environ_module, CIRCUITPY_ENVIRON);

View File

@ -24,16 +24,14 @@
* THE SOFTWARE.
*/
#ifndef MICROPY_INCLUDED_SHARED_BINDINGS_DOTENV___INIT___H
#define MICROPY_INCLUDED_SHARED_BINDINGS_DOTENV___INIT___H
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "py/objtuple.h"
#include "shared-module/dotenv/__init__.h"
#include "shared-module/_environ/__init__.h"
mp_obj_t common_hal_dotenv_get_key(const char *path, const char *key);
#endif // MICROPY_INCLUDED_SHARED_BINDINGS_DOTENV___INIT___H
mp_obj_t common_hal__environ_get_key_path(const char *path, const char *key);
mp_obj_t common_hal__environ_get_key(const char *key);

View File

@ -0,0 +1,402 @@
/*
* 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/_environ/__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 ENVIRON_PATH "settings.toml"
#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 bool is_eof(file_arg *active_file) {
return feof(*active_file);
}
STATIC uint8_t get_next_byte(file_arg *active_file) {
int value = fgetc(*active_file);
if (value == EOF) {
return 0;
}
return value;
}
__attribute__((unused))
STATIC void seek_eof(file_arg *active_file) {
fseek(*active_file, 0, SEEK_END);
(void)fgetc(*active_file);
}
#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
}
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));
}
#endif
// 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 _environ_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 ENVIRON_ERR_UNEXPECTED | *end;
}
if (c >= 0x110000) {
return ENVIRON_ERR_UNICODE;
}
vstr_add_char_nonstd(buf, c);
return ENVIRON_OK;
}
// Read a quoted string
STATIC _environ_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 ENVIRON_ERR_UNEXPECTED | character;
case '"':
character = consume_whitespace(active_file);
switch (character) {
case '#':
next_line(active_file);
MP_FALLTHROUGH;
case '\n':
return ENVIRON_OK;
default:
return ENVIRON_ERR_UNEXPECTED | character;
}
case '\\':
character = get_next_byte(active_file);
switch (character) {
case 0:
case '\n':
return ENVIRON_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;
_environ_err_t res;
res = read_unicode_escape(active_file, sz, buf);
if (res != ENVIRON_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 _environ_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 ENVIRON_ERR_UNEXPECTED | character;
case '\n':
return ENVIRON_OK;
case '#':
next_line(active_file);
return ENVIRON_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 _environ_err_t _environ_get_key_vstr(const char *path, const char *key, vstr_t *buf, bool *quoted) {
file_arg active_file;
if (!open_file(path, &active_file)) {
return ENVIRON_ERR_OPEN;
}
_environ_err_t result = ENVIRON_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 _environ_err_t _environ_get_key_buf_terminated(const char *key, char *value, size_t value_len, bool *quoted) {
vstr_t buf;
vstr_init_fixed_buf(&buf, value_len, value);
_environ_err_t result = _environ_get_key_vstr(ENVIRON_PATH, key, &buf, quoted);
if (result == ENVIRON_OK) {
vstr_add_byte_nonstd(&buf, 0);
memcpy(value, buf.buf, MIN(buf.len, value_len));
if (buf.len > value_len) {
result = ENVIRON_ERR_LENGTH;
}
}
return result;
}
_environ_err_t _environ_get_key_str(const char *key, char *value, size_t value_len) {
bool quoted;
_environ_err_t result = _environ_get_key_buf_terminated(key, value, value_len, &quoted);
if (result == ENVIRON_OK && !quoted) {
result = ENVIRON_ERR_UNEXPECTED | value[0];
}
return result;
}
STATIC void throw__environ_error(_environ_err_t error) {
if (error == ENVIRON_OK) {
return;
}
if (error & ENVIRON_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 ENVIRON_ERR_OPEN:
mp_raise_ValueError(translate("File not found"));
case ENVIRON_ERR_UNICODE:
mp_raise_ValueError(translate("Invalid unicode escape"));
case ENVIRON_ERR_NOT_FOUND:
mp_raise_ValueError(translate("Key not found"));
default:
mp_raise_RuntimeError(translate("Internal error"));
}
}
mp_obj_t common_hal__environ_get_key_path(const char *path, const char *key) {
vstr_t buf;
bool quoted;
vstr_init(&buf, 64);
_environ_err_t result = _environ_get_key_vstr(path, key, &buf, &quoted);
if (result == ENVIRON_ERR_NOT_FOUND) {
return mp_const_none;
}
throw__environ_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__environ_get_key(const char *key) {
return common_hal__environ_get_key_path(ENVIRON_PATH, key);
}
_environ_err_t _environ_get_key_int(const char *key, mp_int_t *value) {
char buf[16];
bool quoted;
_environ_err_t result = _environ_get_key_buf_terminated(key, buf, sizeof(buf), &quoted);
if (result != ENVIRON_OK) {
return result;
}
if (quoted) {
return ENVIRON_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 ENVIRON_ERR_UNEXPECTED | *end;
}
*value = (mp_int_t)num;
return ENVIRON_OK;
}

View File

@ -24,13 +24,22 @@
* THE SOFTWARE.
*/
#pragma once
typedef enum {
ENVIRON_OK = 0,
ENVIRON_ERR_OPEN,
ENVIRON_ERR_UNICODE,
ENVIRON_ERR_LENGTH,
ENVIRON_ERR_NOT_FOUND,
ENVIRON_ERR_UNEXPECTED = 0xff00, // logical or'd with the byte value
} _environ_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. If the value doesn't fit,
// *value_len may be an over-estimate but never an under-estimate.
_environ_err_t _environ_get_key_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 ENVIRON_ERR_OK and sets value to the read value. Returns
// ENVIRON_ERR_... if the value was not numeric. allocation-free.
_environ_err_t _environ_get_key_int(const char *key, mp_int_t *value);

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,8 +36,8 @@
#include "py/runtime.h"
#include "shared-bindings/os/__init__.h"
#if CIRCUITPY_DOTENV
#include "shared-bindings/dotenv/__init__.h"
#if CIRCUITPY_ENVIRON
#include "shared-bindings/_environ/__init__.h"
#endif
// This provides all VFS related OS functions so that ports can share the code
@ -112,8 +112,9 @@ mp_obj_t common_hal_os_getcwd(void) {
}
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 CIRCUITPY_ENVIRON
mp_obj_t env_obj = common_hal__environ_get_key(key);
// TODO must be a str object
if (env_obj != mp_const_none) {
return env_obj;
}

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_ENVIRON
#include "shared-module/_environ/__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,23 @@ 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_ENVIRON
char ssid[33];
char password[64];
mp_int_t ssid_len = 0;
mp_int_t password_len = 0;
size_t ssid_len = 0;
size_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)) {
_environ_err_t result = _environ_get_key_str("CIRCUITPY_WIFI_SSID", ssid, sizeof(ssid));
if (result != ENVIRON_OK) {
return;
}
result = _environ_get_key_str("CIRCUITPY_WIFI_PASSWORD", password, sizeof(password));
if (result != ENVIRON_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,9 +269,6 @@ 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.
@ -283,21 +281,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)_environ_get_key_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 +318,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 = _environ_get_key_str("CIRCUITPY_WEB_API_PASSWORD", _api_password + 1, api_password_len);
if (result == ENVIRON_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
}
@ -691,16 +684,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");
}
_cors_header(socket, request);
_send_str(socket, "\r\n");

View File

@ -0,0 +1 @@
string = "

View File

@ -0,0 +1 @@
string = """

View File

@ -0,0 +1 @@
string =

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,42 @@
import os
try:
from _environ import get_key
except:
# Because run-tests.py suppresses site-packages, this test can't be run
# on the host interpreter. However, it can be run manually to
# generate/update the expected file.
#
# After 3.11 becomes standard, change this to use tomllib.
import tomlkit
def get_key(filename, key):
with open(filename) as f:
s = tomlkit.load(f)
return s.get(key, None)
def run_test(f, k=None):
try:
v = get_key(f"{BASE}/{f}.toml", k or f)
print(f, k, repr(v))
except Exception as e:
print(f, k, "err")
if "/" in __file__:
BASE = __file__.rsplit("/", 1)[0]
else:
BASE = "."
run_test("good", "notpresent")
run_test("good", "string")
run_test("good", "number")
run_test("good", "cstring")
run_test("good", "cnumber")
run_test("good", "subvalue")
for i in range(8):
run_test("good", f"string{i}")
run_test("bad1", "string")
run_test("bad2", "string")
run_test("bad3", "string")

View File

@ -0,0 +1,17 @@
good notpresent None
good string 'hello world'
good number 7
good cstring 'hello comment'
good cnumber 127
good subvalue None
good string0 None
good string1 '\n'
good string2 'Áx'
good string3 'Áx'
good string4 '\x0c"\\'
good string5 '\t\r\x08'
good string6 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
good string7 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
bad1 string err
bad2 string err
bad3 string err

View File

@ -0,0 +1,14 @@
# comment
string = "hello world"
number = 7
cstring = "hello comment" # comment
cnumber = 0x7f # comment
string1= "\n"
string2 ="\u00c1x"
string3 = "\U000000c1x"
string4 = "\f\"\\"
string5 = "\t\r\b"
string6 = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
string7 = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
[section]
subvalue = "hi"

View File

@ -29,10 +29,10 @@ RuntimeError:
ame__
mport
builtins micropython _asyncio _thread
_uasyncio aesio array binascii
bitmaptools btree cexample cmath
collections cppexample displayio dotenv
builtins micropython _asyncio _environ
_thread _uasyncio aesio array
binascii bitmaptools btree cexample
cmath collections cppexample displayio
errno ffi framebuf gc
gifio hashlib json math
qrio rainbowio re sys