Bug
dumps() does not quote string values that contain #, {, }, \n, or \r. Each of these is a special character in the lexer:
# begins a comment — everything after it on the line is discarded
{ / } are block delimiters
\n / \r terminate a bare term
Values containing these characters are emitted unquoted, and the parser cannot recover the original value.
Reproducer
import structprop
# '#' truncates the value to a comment
out = structprop.dumps({"k": "foo#bar"})
print(repr(out)) # 'k = foo#bar\n'
back = structprop.loads(out)
print(back) # {'k': 'foo'} — 'bar' silently lost
# '{' / '}' break the block structure
out = structprop.dumps({"k": "v{x}"})
print(repr(out)) # 'k = v{x}\n'
back = structprop.loads(out) # ParserError or wrong structure
Root cause
_ESCAPE_CHARACTERS is defined as ' \t' — only space and tab. The characters #, {, }, \n, and \r are all special in the lexer but are absent from the escape set, so _escape() never wraps values containing them in double quotes.
Fix
Extend _ESCAPE_CHARACTERS to cover all lexer-special characters:
_ESCAPE_CHARACTERS = ' \t\n\r#{}='
This is consistent with what the lexer already recognises as terminators/specials in both whitespace and term states.
Bug
dumps()does not quote string values that contain#,{,},\n, or\r. Each of these is a special character in the lexer:#begins a comment — everything after it on the line is discarded{/}are block delimiters\n/\rterminate a bare termValues containing these characters are emitted unquoted, and the parser cannot recover the original value.
Reproducer
Root cause
_ESCAPE_CHARACTERSis defined as' \t'— only space and tab. The characters#,{,},\n, and\rare all special in the lexer but are absent from the escape set, so_escape()never wraps values containing them in double quotes.Fix
Extend
_ESCAPE_CHARACTERSto cover all lexer-special characters:This is consistent with what the lexer already recognises as terminators/specials in both
whitespaceandtermstates.