Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/rstjinja.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@


def render_with_jinja(docname, source):
if re.search("^\s*.. jinja$", source[0], re.M):
if re.search(r"^\s*.. jinja$", source[0], re.M):
return True
return False

Expand Down
2 changes: 2 additions & 0 deletions docs/shared_bindings_matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ def get_settings_from_makefile(port_dir, board_name):
contents = subprocess.run(
[
"make",
# Don't let make run in parallel; it can mix up the output from the various "print-" targets.
"-j1",
"-C",
port_dir,
"-f",
Expand Down
11 changes: 7 additions & 4 deletions shared-bindings/supervisor/__init__.c
Original file line number Diff line number Diff line change
Expand Up @@ -364,13 +364,13 @@ static mp_obj_t supervisor_set_usb_identification(size_t n_args, const mp_obj_t
}
MP_DEFINE_CONST_FUN_OBJ_KW(supervisor_set_usb_identification_obj, 0, supervisor_set_usb_identification);

//| def get_setting(key: str, default: object=None) -> int | str | bool:
//| def get_setting(key: str, default: object=None) -> int | float | str | bool:
//| """
//| Get and parse the value for the given ``key`` from the ``/settings.toml`` file.
//| If ``key`` is not found or ``settings.toml`` is not present, return the ``default`` value.
//|
//| :param str key: The setting key to retrieve
//| :return: The setting value as an ``int``, ``str``, or ``bool`` depending on the value in the file
//| :return: The setting value as an ``int``, ``float``, ``str``, or ``bool`` depending on the value in the file
//|
//| :raises ValueError: If the value cannot be parsed as a valid TOML value.
//|
Expand All @@ -379,21 +379,24 @@ MP_DEFINE_CONST_FUN_OBJ_KW(supervisor_set_usb_identification_obj, 0, supervisor_
//| - ``str``: Double-quoted string.
//| The string may include Unicode characters, and ``\\u`` Unicode escapes. Backslash-escaped characters
//| ``\\b``, ``\\r``, ``\\n``, ``\\t``, ``\\v``, ``\\v`` are also allowed.
//| - ``int``: signed or unsigned integer
//| - lower-case boolean words ``true`` and ``false``.
//| - ``int``: signed or unsigned integer.
//| - ``bool``: lower-case ``true`` or ``false``.
//| The values are returned as Python ``True`` or ``False`` values.
//| - ``float``: signed or unsigned decimal number with a ``.`` or exponent, or ``inf``, ``inf``, ``nan``.
//|
//| Example::
//|
//| # settings.toml:
//| WIDTH = 42
//| color = "red"
//| DEBUG = true
//| RATIO = 1.5
//|
//| import supervisor
//| print(supervisor.get_setting("WIDTH")) # prints 42
//| print(supervisor.get_setting("color")) # prints 'red'
//| print(supervisor.get_setting("DEBUG")) # prints True
//| print(supervisor.get_setting("RATIO")) # prints 1.5
//| """
//| ...
//|
Expand Down
62 changes: 62 additions & 0 deletions supervisor/shared/settings.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
//
// SPDX-License-Identifier: MIT

#include <math.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
Expand Down Expand Up @@ -419,6 +420,57 @@ settings_err_t settings_get_bool(const char *key, bool *value) {
return result;
}

#if MICROPY_PY_BUILTINS_FLOAT
static settings_err_t get_float(const char *key, mp_float_t *value) {
char buf[48];
bool quoted;
settings_err_t result = settings_get_buf_terminated(key, buf, sizeof(buf), &quoted);
if (result != SETTINGS_OK) {
return result;
}
if (quoted) {
return SETTINGS_ERR_BAD_VALUE;
}

const char *str = buf;
const char *end = buf + strlen(buf);
bool neg = false;

if (str < end && (*str == '+' || *str == '-')) {
neg = (*str == '-');
str++;
}

mp_float_t val;
if (end - str >= 3 && str[0] == 'i' && str[1] == 'n' && str[2] == 'f') {
str += 3;
val = (mp_float_t)INFINITY;
} else if (end - str >= 3 && str[0] == 'n' && str[1] == 'a' && str[2] == 'n') {
str += 3;
val = MICROPY_FLOAT_C_FUN(nan)("");
} else {
const char *num_start = str;
str = mp_parse_float_internal(str, end - str, &val);
if (str == NULL || str == num_start) {
return SETTINGS_ERR_BAD_VALUE;
}
}

if (str != end) {
return SETTINGS_ERR_BAD_VALUE;
}

*value = neg ? -val : val;
return SETTINGS_OK;
}

settings_err_t settings_get_float(const char *key, mp_float_t *value) {
settings_err_t result = get_float(key, value);
print_error(key, result);
return result;
}
#endif

// Get the raw value as a vstr, whether quoted or bare. Value may be an invalid TOML value.
settings_err_t settings_get_raw_vstr(const char *key, vstr_t *vstr) {
bool quoted;
Expand Down Expand Up @@ -457,6 +509,16 @@ settings_err_t settings_get_obj(const char *key, mp_obj_t *value) {
return SETTINGS_OK;
}

// Not an integer, try float
#if MICROPY_PY_BUILTINS_FLOAT
mp_float_t float_val;
result = get_float(key, &float_val);
if (result == SETTINGS_OK) {
*value = mp_obj_new_float(float_val);
return SETTINGS_OK;
}
#endif

return SETTINGS_ERR_BAD_VALUE;
}

Expand Down
15 changes: 13 additions & 2 deletions supervisor/shared/settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

typedef enum {
SETTINGS_OK = 0,
// The settings file could not be opened.
SETTINGS_ERR_OPEN,
SETTINGS_ERR_UNICODE,
SETTINGS_ERR_LENGTH,
Expand All @@ -20,7 +21,7 @@ typedef enum {
// Read a string value from the settings file.
// If it fits, the return value is 0-terminated. The passed-in buffer
// may be modified even if an error is returned. Allocation free.
// An error that is not 'open' or 'not found' is printed on the repl.
// An error that is not SETTINGS_ERR_OPEN or SETTINGS_ERR_NOT_FOUND is printed on the repl.
// Returns an error if the value is not a quoted string.
settings_err_t settings_get_str(const char *key, char *value, size_t value_len);

Expand All @@ -38,13 +39,23 @@ settings_err_t settings_get_int(const char *key, mp_int_t *value);
// An error that is not 'open' or 'not found' is printed on the repl.
settings_err_t settings_get_bool(const char *key, bool *value);

#if MICROPY_PY_BUILTINS_FLOAT
// Read a float value from the settings file.
// Returns SETTINGS_OK and sets value to the read value. Returns
// SETTINGS_ERR_... if the value was not a float. allocation-free.
// If any error code is returned, value is guaranteed not modified.
// An error that is not SETTINGS_ERR_OPEN or SETTINGS_ERR_NOT_FOUND is printed on the repl.
settings_err_t settings_get_float(const char *key, mp_float_t *value);
#endif

// Read a value from the settings file and return as parsed Python object.
// Returns SETTINGS_OK and sets value to a parsed Python object from the RHS value:
// - Quoted strings return as str
// - Bare "true" or "false" return as bool
// - Valid integers return as int
// - Valid floats (containing '.' or 'e'/'E') return as float
// Returns SETTINGS_ERR_... if the value is not parseable as one of these types.
// An error that is not 'open' or 'not found' is printed on the repl.
// An error that is not SETTINGS_ERR_OPEN or SETTINGS_ERR_NOT_FOUND is printed on the repl.
settings_err_t settings_get_obj(const char *key, mp_obj_t *value);

// Read the raw value as a string, whether quoted or bare.
Expand Down
66 changes: 66 additions & 0 deletions tests/circuitpython-manual/settings/code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Test supervisor.get_setting() parsing of settings.toml values.
# Copy settings.toml to the root of the board, then run this script.
# All lines should print PASS.

import math
import supervisor


def check(key, expected):
got = supervisor.get_setting(key)
if type(got) is not type(expected):
print(
f"FAIL {key}: expected type {type(expected).__name__}, got {type(got).__name__} ({got!r})"
)
return
if isinstance(expected, float) and math.isnan(expected):
ok = math.isnan(got)
else:
ok = got == expected
if ok:
print(f"PASS {key}")
else:
print(f"FAIL {key}: expected {expected!r}, got {got!r}")


check("str_val", "hello")
check("int_val", 42)
check("neg_int_val", -7)
check("hex_int_val", 31)
check("bool_true", True)
check("bool_false", False)
check("float_val", 3.14)
check("neg_float_val", -1.5)
check("sci_float_val", 6.626e-34)
# math.nan and math.inf are not always available
check("pos_inf", float("inf"))
check("neg_inf", -float("inf"))
check("pos_nan", float("nan"))


def check_bad(key):
try:
got = supervisor.get_setting(key)
print(f"FAIL {key}: expected ValueError, got {got!r}")
except ValueError:
print(f"PASS {key} raises ValueError")


check_bad("bad_word") # unquoted non-boolean word
check_bad("bad_float_junk") # float with trailing garbage
check_bad("bad_inf_junk") # inf with trailing characters
check_bad("bad_lone_sign") # bare + with nothing after it

# Missing key returns default
got = supervisor.get_setting("no_such_key", "default")
if got == "default":
print("PASS missing key default")
else:
print(f"FAIL missing key default: got {got!r}")

# Invalid value raises ValueError
try:
supervisor.get_setting("str_val") # quoted string is valid
print("PASS str via get_setting")
except ValueError:
print("FAIL str via get_setting raised ValueError unexpectedly")
16 changes: 16 additions & 0 deletions tests/circuitpython-manual/settings/settings.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
str_val = "hello"
bad_word = hello
bad_float_junk = 1.2abc
bad_inf_junk = infoo
bad_lone_sign = +
int_val = 42
neg_int_val = -7
hex_int_val = 0x1F
bool_true = true
bool_false = false
float_val = 3.14
neg_float_val = -1.5
sci_float_val = 6.626e-34
pos_inf = inf
neg_inf = -inf
pos_nan = nan
Loading