fix some dotenv parsing
This commit is contained in:
parent
abd02287e6
commit
6dc03ae3ce
@ -40,9 +40,13 @@
|
|||||||
//| A subset of the CPython `dotenv library <https://saurabh-kumar.com/python-dotenv/>`_. It does
|
//| A subset of the CPython `dotenv library <https://saurabh-kumar.com/python-dotenv/>`_. It does
|
||||||
//| not support variables or double quotes.
|
//| not support variables or double quotes.
|
||||||
//|
|
//|
|
||||||
//| The simplest way to define keys and values is to put them in single quotes. \ and ' are
|
//| Keys and values may be put in single quotes.
|
||||||
//| escaped by \ in single quotes. Newlines can occur in quotes for multiline values. Comments
|
//| ``\`` and ``'`` are escaped by ``\`` in single quotes. Newlines can occur in quotes for multiline values.
|
||||||
//| start with # and apply for the rest of the line.
|
//| 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:
|
//| File format example:
|
||||||
//|
|
//|
|
||||||
@ -58,6 +62,9 @@
|
|||||||
//| multiline = 'hello
|
//| multiline = 'hello
|
||||||
//| world
|
//| world
|
||||||
//| how are you?'
|
//| how are you?'
|
||||||
|
//| # The #'s below will be included in the value. They do not start a comment.
|
||||||
|
//| key6=#value
|
||||||
|
//| key7=abc#def
|
||||||
//|
|
//|
|
||||||
//| """
|
//| """
|
||||||
//|
|
//|
|
||||||
|
@ -35,72 +35,90 @@
|
|||||||
#include "py/objstr.h"
|
#include "py/objstr.h"
|
||||||
#include "supervisor/filesystem.h"
|
#include "supervisor/filesystem.h"
|
||||||
|
|
||||||
STATIC uint8_t consume_spaces(FIL *active_file) {
|
// Return 0 if there is no next character (EOF).
|
||||||
uint8_t character = ' ';
|
STATIC uint8_t get_next_character(FIL *active_file) {
|
||||||
UINT quantity_read = 1;
|
uint8_t character = 0;
|
||||||
while (unichar_isspace(character) && quantity_read > 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);
|
f_read(active_file, &character, 1, &quantity_read);
|
||||||
}
|
return character;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(FIL *active_file) {
|
||||||
|
uint8_t character;
|
||||||
|
do {
|
||||||
|
character = get_next_character(active_file);
|
||||||
|
} while (character != '\n' && character != 0 && unichar_isspace(character));
|
||||||
return character;
|
return character;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Starting at the start of a new line, determines if the key matches the given
|
// Starting at the start of a new line, determines if the key matches the given
|
||||||
// key. File pointer is left after the = after the key.
|
// key. File pointer is set to be just before the = after the key.
|
||||||
STATIC bool key_matches(FIL *active_file, const char *key) {
|
STATIC bool key_matches(FIL *active_file, const char *key) {
|
||||||
uint8_t character = ' ';
|
uint8_t character;
|
||||||
UINT quantity_read = 1;
|
character = consume_whitespace(active_file);
|
||||||
character = consume_spaces(active_file);
|
if (character == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
bool quoted = false;
|
bool quoted = false;
|
||||||
if (character == '\'') {
|
if (character == '\'') {
|
||||||
|
// Beginning of single-quoted string.
|
||||||
quoted = true;
|
quoted = true;
|
||||||
f_read(active_file, &character, 1, &quantity_read);
|
character = get_next_character(active_file);
|
||||||
}
|
}
|
||||||
size_t key_pos = 0;
|
size_t key_pos = 0;
|
||||||
bool escaped = false;
|
bool escaped = false;
|
||||||
bool matches = true;
|
bool matches = true;
|
||||||
size_t key_len = strlen(key);
|
size_t key_len = strlen(key);
|
||||||
while (quantity_read > 0) {
|
while (character != 0) {
|
||||||
if (character == '\\' && !escaped && quoted) {
|
if (character == '\\' && !escaped && quoted) {
|
||||||
escaped = true;
|
escaped = true;
|
||||||
} else if (!escaped && quoted && character == '\'') {
|
} else if (!escaped && quoted && character == '\'') {
|
||||||
quoted = false;
|
quoted = false;
|
||||||
// Move past the quoted before breaking so we can check the validity of data past it.
|
// End of quoted key. Skip over the ending quote.
|
||||||
f_read(active_file, &character, 1, &quantity_read);
|
character = get_next_character(active_file);
|
||||||
break;
|
break;
|
||||||
} else if (!quoted && (unichar_isspace(character) || character == '=' || character == '\n' || character == '#')) {
|
} else if (!quoted && (unichar_isspace(character) || character == '=' || character == '\n' || character == '#' || character == 0)) {
|
||||||
|
// End of unquoted key.
|
||||||
break;
|
break;
|
||||||
} else {
|
} 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 && key[key_pos] == character;
|
matches = matches && key[key_pos] == character;
|
||||||
escaped = false;
|
escaped = false;
|
||||||
key_pos++;
|
key_pos++;
|
||||||
|
} else {
|
||||||
|
// Key on line is too long.
|
||||||
|
matches = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
f_read(active_file, &character, 1, &quantity_read);
|
|
||||||
}
|
}
|
||||||
if (unichar_isspace(character)) {
|
character = get_next_character(active_file);
|
||||||
character = consume_spaces(active_file);
|
|
||||||
}
|
}
|
||||||
if (character == '=' || character == '\n' || character == '#') {
|
if (character == '=' || character == '\n' || character == '#' || character == 0) {
|
||||||
// Rewind one so the value can find it.
|
// Rewind one so the value, if any, can be found.
|
||||||
f_lseek(active_file, f_tell(active_file) - 1);
|
f_lseek(active_file, f_tell(active_file) - 1);
|
||||||
} else {
|
} else {
|
||||||
// We're followed by something else that is invalid syntax.
|
// We're followed by something else that is invalid syntax.
|
||||||
matches = false;
|
matches = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return matches && key_pos == key_len;
|
return matches && key_pos == key_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
STATIC bool next_line(FIL *active_file) {
|
STATIC bool next_line(FIL *active_file) {
|
||||||
uint8_t character = ' ';
|
uint8_t character;
|
||||||
UINT quantity_read = 1;
|
|
||||||
bool quoted = false;
|
bool quoted = false;
|
||||||
bool escaped = false;
|
bool escaped = false;
|
||||||
// Track comments because they last until the end of the line.
|
// Track comments because they last until the end of the line.
|
||||||
bool comment = false;
|
bool comment = false;
|
||||||
FRESULT result = FR_OK;
|
|
||||||
// Consume all characters while quoted or others up to \n.
|
// Consume all characters while quoted or others up to \n.
|
||||||
while (result == FR_OK && quantity_read > 0 && (quoted || character != '\n')) {
|
do {
|
||||||
if (character == '#' || comment) {
|
character = get_next_character(active_file);
|
||||||
|
|
||||||
|
if ((!quoted || character == '#') || comment) {
|
||||||
// Comments consume any escaping.
|
// Comments consume any escaping.
|
||||||
comment = true;
|
comment = true;
|
||||||
} else if (!escaped) {
|
} else if (!escaped) {
|
||||||
@ -112,33 +130,32 @@ STATIC bool next_line(FIL *active_file) {
|
|||||||
} else {
|
} else {
|
||||||
escaped = false;
|
escaped = false;
|
||||||
}
|
}
|
||||||
result = f_read(active_file, &character, 1, &quantity_read);
|
} while (character != 0 && (quoted || character != '\n'));
|
||||||
}
|
|
||||||
return result == FR_OK && quantity_read > 0;
|
return character != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
STATIC mp_int_t read_value(FIL *active_file, char *value, size_t value_len) {
|
STATIC mp_int_t read_value(FIL *active_file, char *value, size_t value_len) {
|
||||||
uint8_t character = ' ';
|
uint8_t character;
|
||||||
UINT quantity_read = 1;
|
// Consume spaces before "=", and get first character of interest.
|
||||||
// Consume spaces before =
|
character = consume_whitespace(active_file);
|
||||||
character = consume_spaces(active_file);
|
|
||||||
if (character != '=') {
|
if (character != '=') {
|
||||||
if (character == '#' || character == '\n') {
|
if (character == '#' || character == '\n') {
|
||||||
// Keys without an = after them are valid with the value None.
|
// Keys without an = after them are valid with the value None.
|
||||||
return 0;
|
return -1;
|
||||||
}
|
}
|
||||||
// All other characters are invalid.
|
// All other characters are invalid.
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
character = ' ';
|
|
||||||
// Consume space after =
|
// Consume space after =
|
||||||
while (unichar_isspace(character) && quantity_read > 0) {
|
if (character != '#') {
|
||||||
f_read(active_file, &character, 1, &quantity_read);
|
// a # immediately after = is part of the value!
|
||||||
|
character = consume_whitespace(active_file);
|
||||||
}
|
}
|
||||||
bool quoted = false;
|
bool quoted = false;
|
||||||
if (character == '\'') {
|
if (character == '\'') {
|
||||||
quoted = true;
|
quoted = true;
|
||||||
f_read(active_file, &character, 1, &quantity_read);
|
character = get_next_character(active_file);
|
||||||
}
|
}
|
||||||
if (character == '"') {
|
if (character == '"') {
|
||||||
// We don't support double quoted values.
|
// We don't support double quoted values.
|
||||||
@ -150,12 +167,13 @@ STATIC mp_int_t read_value(FIL *active_file, char *value, size_t value_len) {
|
|||||||
// Count trailing spaces so we can ignore them at the end of unquoted
|
// Count trailing spaces so we can ignore them at the end of unquoted
|
||||||
// values.
|
// values.
|
||||||
size_t trailing_spaces = 0;
|
size_t trailing_spaces = 0;
|
||||||
while (quantity_read > 0) {
|
bool first_char = true;
|
||||||
|
while (character != 0) {
|
||||||
// Consume the first \ if the value is quoted.
|
// Consume the first \ if the value is quoted.
|
||||||
if (quoted && character == '\\' && !escaped) {
|
if (quoted && character == '\\' && !escaped) {
|
||||||
escaped = true;
|
escaped = true;
|
||||||
// Drop this slash by short circuiting the rest of the loop.
|
// Drop this backslash by short circuiting the rest of the loop.
|
||||||
f_read(active_file, &character, 1, &quantity_read);
|
character = get_next_character(active_file);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (quoted && !escaped && character == '\'') {
|
if (quoted && !escaped && character == '\'') {
|
||||||
@ -163,7 +181,7 @@ STATIC mp_int_t read_value(FIL *active_file, char *value, size_t value_len) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// Unquoted values are ended by a newline or comment.
|
// Unquoted values are ended by a newline or comment.
|
||||||
if (!quoted && (character == '\n' || character == '#')) {
|
if (!quoted && (character == '\n' || (character == '#' && !first_char))) {
|
||||||
if (character == '\n') {
|
if (character == '\n') {
|
||||||
// Rewind one so the next_line can find the \n.
|
// Rewind one so the next_line can find the \n.
|
||||||
f_lseek(active_file, f_tell(active_file) - 1);
|
f_lseek(active_file, f_tell(active_file) - 1);
|
||||||
@ -182,7 +200,8 @@ STATIC mp_int_t read_value(FIL *active_file, char *value, size_t value_len) {
|
|||||||
value[value_pos] = character;
|
value[value_pos] = character;
|
||||||
}
|
}
|
||||||
value_pos++;
|
value_pos++;
|
||||||
f_read(active_file, &character, 1, &quantity_read);
|
character = get_next_character(active_file);
|
||||||
|
first_char = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return value_pos - trailing_spaces;
|
return value_pos - trailing_spaces;
|
||||||
@ -214,7 +233,7 @@ mp_obj_t common_hal_dotenv_get_key(const char *path, const char *key) {
|
|||||||
// the length.
|
// the length.
|
||||||
char value[64];
|
char value[64];
|
||||||
mp_int_t actual_len = dotenv_get_key(path, key, value, sizeof(value));
|
mp_int_t actual_len = dotenv_get_key(path, key, value, sizeof(value));
|
||||||
if (actual_len <= 0) {
|
if (actual_len < 0) {
|
||||||
return mp_const_none;
|
return mp_const_none;
|
||||||
}
|
}
|
||||||
if ((size_t)actual_len >= sizeof(value)) {
|
if ((size_t)actual_len >= sizeof(value)) {
|
||||||
|
32
tests/circuitpython-manual/dotenv_test.env
Normal file
32
tests/circuitpython-manual/dotenv_test.env
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# No e0 value
|
||||||
|
# comment preceded by spaces
|
||||||
|
e1=e1value
|
||||||
|
e2=e2value # value followed by a comment
|
||||||
|
e3='e3value'
|
||||||
|
e4='e4value' # quoted value followed by a comment
|
||||||
|
# e5 should be None
|
||||||
|
e5
|
||||||
|
# e6 should be the empty string
|
||||||
|
e6=
|
||||||
|
# e7 should be '#' (bash-like syntax processing)
|
||||||
|
e7=#
|
||||||
|
# e8 should be the empty string
|
||||||
|
e8=''
|
||||||
|
# e9 should be the empty string
|
||||||
|
e9= #
|
||||||
|
e10=e10_first
|
||||||
|
e10=e10_last
|
||||||
|
e11='abc#def'
|
||||||
|
# e12 should be 'abc#def'
|
||||||
|
e12=abc#def
|
||||||
|
e12='multi
|
||||||
|
line'
|
||||||
|
e13=e13value
|
||||||
|
e14 #comment
|
||||||
|
e15 = e15value
|
||||||
|
# e16 should be '#'
|
||||||
|
e16=# #
|
||||||
|
# e17 should be 'def#hi'
|
||||||
|
e17='def'#hi
|
||||||
|
# e18 should be '#has a hash'
|
||||||
|
e18=#has a hash
|
23
tests/circuitpython-manual/dotenv_test.py
Normal file
23
tests/circuitpython-manual/dotenv_test.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import dotenv
|
||||||
|
|
||||||
|
FILE = "dotenv_test.env"
|
||||||
|
|
||||||
|
print("e0", dotenv.get_key(FILE, "e0"))
|
||||||
|
print("e1", dotenv.get_key(FILE, "e1"))
|
||||||
|
print("e2", dotenv.get_key(FILE, "e2"))
|
||||||
|
print("e3", dotenv.get_key(FILE, "e3"))
|
||||||
|
print("e4", dotenv.get_key(FILE, "e4"))
|
||||||
|
print("e5", dotenv.get_key(FILE, "e5"))
|
||||||
|
print("e6", dotenv.get_key(FILE, "e6"))
|
||||||
|
print("e7", dotenv.get_key(FILE, "e7"))
|
||||||
|
print("e8", dotenv.get_key(FILE, "e8"))
|
||||||
|
print("e9", dotenv.get_key(FILE, "e9"))
|
||||||
|
print("e10", dotenv.get_key(FILE, "e10"))
|
||||||
|
print("e11", dotenv.get_key(FILE, "e11"))
|
||||||
|
print("e12", dotenv.get_key(FILE, "e12"))
|
||||||
|
print("e13", dotenv.get_key(FILE, "e13"))
|
||||||
|
print("e14", dotenv.get_key(FILE, "e14"))
|
||||||
|
print("e15", dotenv.get_key(FILE, "e15"))
|
||||||
|
print("e16", dotenv.get_key(FILE, "e16"))
|
||||||
|
print("e17", dotenv.get_key(FILE, "e17"))
|
||||||
|
print("e18", dotenv.get_key(FILE, "e18"))
|
Loading…
x
Reference in New Issue
Block a user