dotenv becomes settings.toml
This commit is contained in:
parent
748cb92ff3
commit
ef2bfdb5db
@ -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));
|
||||
|
@ -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
|
||||
----------------------
|
||||
|
@ -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/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 ""
|
||||
|
@ -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_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();
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 \
|
||||
|
@ -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 \
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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);
|
@ -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);
|
402
shared-module/_environ/__init__.c
Normal file
402
shared-module/_environ/__init__.c
Normal 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, "ed);
|
||||
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, "ed);
|
||||
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), "ed);
|
||||
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;
|
||||
}
|
@ -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);
|
@ -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,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;
|
||||
}
|
||||
|
@ -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_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");
|
||||
|
1
tests/circuitpython/bad1.toml
Normal file
1
tests/circuitpython/bad1.toml
Normal file
@ -0,0 +1 @@
|
||||
string = "
|
1
tests/circuitpython/bad2.toml
Normal file
1
tests/circuitpython/bad2.toml
Normal file
@ -0,0 +1 @@
|
||||
string = """
|
1
tests/circuitpython/bad3.toml
Normal file
1
tests/circuitpython/bad3.toml
Normal file
@ -0,0 +1 @@
|
||||
string =
|
@ -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
|
42
tests/circuitpython/environ_test.py
Normal file
42
tests/circuitpython/environ_test.py
Normal 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")
|
17
tests/circuitpython/environ_test.py.exp
Normal file
17
tests/circuitpython/environ_test.py.exp
Normal 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
|
14
tests/circuitpython/good.toml
Normal file
14
tests/circuitpython/good.toml
Normal 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"
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user