Merge pull request #7321 from jepler/dotenv-becomes-toml
Dotenv becomes toml
This commit is contained in:
commit
398b7c26ca
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,6 +9,7 @@
|
||||
!atmel-samd/asf/**/*.a
|
||||
*.elf
|
||||
*.bin
|
||||
!*.toml.bin
|
||||
*.map
|
||||
*.hex
|
||||
*.dis
|
||||
|
@ -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));
|
||||
|
@ -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
|
||||
----------------------
|
||||
|
@ -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`.
|
||||
|
||||
|
@ -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 ""
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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::
|
||||
|
@ -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());
|
||||
|
@ -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++) {
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
common_hal_bleio_adapter_set_name(self, (char *)default_ble_name);
|
||||
}
|
||||
|
||||
static void bluetooth_adapter_background(void *data) {
|
||||
|
@ -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.
|
||||
|
@ -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}}
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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 \
|
||||
|
@ -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 \
|
||||
)
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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);
|
@ -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
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
@ -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);
|
||||
|
@ -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
391
shared-module/os/getenv.c
Normal 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, "ed);
|
||||
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, "ed);
|
||||
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), "ed);
|
||||
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;
|
||||
}
|
@ -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);
|
||||
|
||||
|
@ -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")) {
|
||||
|
@ -12,7 +12,7 @@
|
||||
|
||||
<h1><a href="/"><img src="/favicon.ico"/></a> 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>
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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
|
@ -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))}")
|
@ -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
|
79
tests/circuitpython/getenv.py
Normal file
79
tests/circuitpython/getenv.py
Normal 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)
|
17
tests/circuitpython/getenv.py.exp
Normal file
17
tests/circuitpython/getenv.py.exp
Normal 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'
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user