Skip to content
File

Blob: scripts/config.py

python48 lines
1"""Read single-line dotenv values without evaluating shell expressions."""
2import re
3 
4ENV_ASSIGNMENT = re.compile(r"^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$")
5 
6 
7def read_env(path):
8 values = {}
9 if path.exists():
10 for number, line in enumerate(path.read_text().splitlines(), 1):
11 if not line.strip() or line.lstrip().startswith("#"):
12 continue
13 match = ENV_ASSIGNMENT.fullmatch(line)
14 if not match:
15 raise SystemExit(f"{path.name}:{number}: expected NAME=value")
16 key, raw = match.groups()
17 if key in values:
18 raise SystemExit(f"{path.name}:{number}: duplicate {key}")
19 if raw.startswith(("'", '"', "`")):
20 quote = raw[0]
21 end = raw.find(quote, 1)
22 if end < 0 or (raw[end + 1:].strip() and not raw[end + 1:].lstrip().startswith("#")):
23 raise SystemExit(
24 f"{path.name}:{number}: wrap {key} in a quote style absent from its value"
25 )
26 value = raw[1:end]
27 if quote == '"':
28 value = value.replace("\\n", "\n").replace("\\r", "\r")
29 else:
30 value = raw.split("#", 1)[0].strip()
31 if any(character in value for character in ("\n", "\r", "\0")):
32 raise SystemExit(f"{path.name}:{number}: {key} must be a single-line value")
33 values[key] = value
34 return values
35 
36 
37def env_value(key, value):
38 """Quote a literal value identically for this reader, Node and Wrangler."""
39 if any(character in value for character in ("\n", "\r", "\0")):
40 raise SystemExit(f"{key} must be a single-line value")
41 for quote in ("'", '"', "`"):
42 if quote in value:
43 continue
44 if quote == '"' and ("\\n" in value or "\\r" in value):
45 continue
46 return f"{quote}{value}{quote}"
47 raise SystemExit(f"{key} needs a value that can be wrapped in one dotenv quote style")