env.parser
import env.parser
envlifts part of this module out to its own top level; each name below is shown with the path that reaches it. Anything still spelledenv.parser.*needsimport env.parser.
The scanner that turns the text of an environment file into names and values, and the writer that turns them back into text.
The grammar is small enough to state in full. A source is a sequence of lines. A line is blank, a comment, or an assignment:
assignment := 'export'? NAME '=' value?
NAME := [A-Za-z_][A-Za-z0-9_]*
value := bare | '...' | "..." | `...`
Everything interesting is in what each kind of value means, and that is
documented on parse() where people will look for it.
Functions
is_name()
env.is_name(name: string) -> bool
Returns true when name is a legal environment variable name.
That means a letter or underscore, then any number of letters, digits
and underscores. It is the same rule POSIX shells apply, and the same
one the parser and stringify() enforce.
Parameters
name(string)
Returns bool
parse()
env.parse(source: string|bytes) -> dict
Reads the text of an environment file and returns its names and values.
This is the syntax layer on its own: no $ reference is resolved and
nothing is written to the process environment. load() is what does
both.
import env
var values = env.parse('PORT=8080\nGREETING="hello there"')
echo values.PORT
echo values.GREETING
8080
hello there
The rules
A name is a letter or underscore followed by letters, digits and
underscores. Anything else is a ParseError rather than a line quietly
dropped, because a name a shell could never export is a typo every time.
Blank lines are skipped, and so is everything from a # to the end of
the line. Inside an unquoted value a # only opens a comment when it
starts the value or follows a space, so KEY=db#1 keeps its #.
A leading export is allowed and ignored, so the same file can be fed
to source in a shell.
Values come in four forms:
| Written | Means |
|---|---|
KEY=value | the text up to a comment or the end of the line, trimmed at both ends |
KEY='value' | every character exactly as written, newlines included |
KEY=`value` | the same as single quotes, for values that contain both kinds |
KEY="value" | backslash escapes resolved, $ references left for expansion |
KEY= is an empty value, and an empty value counts as unconfigured
everywhere else in this module.
Inside double quotes, \n, \r, \t, \f, \v, \b, \a, \e and
\0 mean what they do in Zuri, \xHH, \uHHHH and \u{H...} name a
codepoint, a backslash before a newline joins the two lines, and \$ is
a literal $ that expansion will not touch. A backslash before anything
else keeps both characters, so "C:\Users" survives intact.
Line endings are normalised to \n, and a byte-order mark at the head
of the file is discarded.
Duplicates
The last assignment to a name in a source wins, which is what makes commenting out the line above the one you want work.
Parameters
source(string|bytes)
Returns dict
Raises ParseError when the source is not a valid environment file.
stringify()
env.stringify(values: dict) -> string
Turns a dictionary of names and values into the text of an environment file.
The output round-trips: feeding it back to parse() returns the same
names and the same values. Values are written bare where that is
unambiguous and double-quoted where it is not, and a $ inside a value
is escaped so that loading the file back does not expand it.
import env
print(env.stringify({
PORT: 8080,
GREETING: 'hello there',
DEBUG: false,
}))
PORT=8080
GREETING="hello there"
DEBUG=false
Names are written in the order the dictionary holds them. A number, a
bigint, a boolean or a bytes value is converted to its text; any other
type is a TypeError, because guessing what a list should look like in
an environment file is how configuration goes wrong silently.
Parameters
values(dict)
Returns string
Raises ValueError when a key is not a legal variable name.
Raises TypeError when a value is of a type that has no spelling
here.
2026, Richard Ore and Zuri contributors