Skip to content

Commit eb08ae6

Browse files
committed
feat: add generate_template() to main
Add core function for generating .env template files from existing .env files. Values are replaced with key names by default. Supports inline directives ::dotenv-template-preserve and ::dotenv-template-exclude. Directives are stripped from output by default, controllable via keep_directives parameter.
1 parent fa4e6a9 commit eb08ae6

File tree

1 file changed

+89
-0
lines changed

1 file changed

+89
-0
lines changed

src/dotenv/main.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import logging
33
import os
44
import pathlib
5+
import re
56
import stat
67
import sys
78
import tempfile
@@ -480,3 +481,91 @@ def _is_file_or_fifo(path: StrPath) -> bool:
480481
return False
481482

482483
return stat.S_ISFIFO(st.st_mode)
484+
485+
486+
_DIRECTIVE_PRESERVE = "::dotenv-template-preserve"
487+
_DIRECTIVE_EXCLUDE = "::dotenv-template-exclude"
488+
489+
# Matches `= <optional whitespace> <value>` where value is single-quoted,
490+
# double-quoted, or unquoted (everything up to a comment or end of line).
491+
_VALUE_RE = re.compile(
492+
r"(=[ \t]*)"
493+
r"(?:'(?:\\'|[^'])*'|\"(?:\\\"|[^\"])*\"|[^\s#\r\n]*)"
494+
)
495+
496+
# Matches a directive token and any surrounding horizontal whitespace.
497+
_DIRECTIVE_RE = re.compile(
498+
r"[ \t]*(?:::dotenv-template-preserve|::dotenv-template-exclude)[ \t]*"
499+
)
500+
501+
502+
def _strip_directives(line: str) -> str:
503+
"""Remove directive tokens from a line and trim any resulting trailing whitespace."""
504+
result = _DIRECTIVE_RE.sub("", line)
505+
# Preserve the original line ending
506+
stripped = result.rstrip("\r\n")
507+
ending = result[len(stripped) :]
508+
return stripped.rstrip() + ending
509+
510+
511+
def generate_template(
512+
dotenv_path: Optional[StrPath] = None,
513+
stream: Optional[IO[str]] = None,
514+
encoding: Optional[str] = "utf-8",
515+
keep_directives: bool = False,
516+
) -> str:
517+
"""
518+
Generate a template from a .env file.
519+
520+
For each key-value binding, the value is replaced with the key name, unless
521+
an inline directive overrides this behavior:
522+
523+
- ``::dotenv-template-preserve`` keeps the line as-is (value included).
524+
- ``::dotenv-template-exclude`` removes the line from the template.
525+
526+
By default, directive tokens are stripped from the output. Set
527+
*keep_directives* to ``True`` to retain them.
528+
529+
Comments and blank lines are preserved.
530+
531+
Parameters:
532+
dotenv_path: Absolute or relative path to the .env file.
533+
stream: ``StringIO`` with .env content, used if *dotenv_path* is ``None``.
534+
encoding: Encoding used to read the file.
535+
keep_directives: If ``True``, directive comments are kept in the output.
536+
537+
Returns:
538+
The generated template as a string.
539+
"""
540+
if dotenv_path is None and stream is None:
541+
dotenv_path = find_dotenv()
542+
543+
dotenv = DotEnv(
544+
dotenv_path=dotenv_path,
545+
stream=stream,
546+
interpolate=False,
547+
encoding=encoding,
548+
)
549+
550+
lines: list[str] = []
551+
with dotenv._get_stream() as s:
552+
for binding in with_warn_for_invalid_lines(parse_stream(s)):
553+
original = binding.original.string
554+
555+
# Comments, blank lines, and parse errors: preserve as-is
556+
if binding.key is None:
557+
lines.append(original)
558+
continue
559+
560+
if _DIRECTIVE_EXCLUDE in original:
561+
continue
562+
563+
if _DIRECTIVE_PRESERVE in original:
564+
line = original if keep_directives else _strip_directives(original)
565+
lines.append(line)
566+
continue
567+
568+
# Replace the value with the key name
569+
lines.append(_VALUE_RE.sub(r"\g<1>" + binding.key, original, count=1))
570+
571+
return "".join(lines)

0 commit comments

Comments
 (0)