Skip to content

Commit 5977aba

Browse files
committed
feat: add template CLI command
Add `dotenv template` subcommand to generate .env template files. Supports optional output path argument and --keep-directives flag. Fix _strip_directives to clean up bare comment markers left after stripping.
1 parent eb08ae6 commit 5977aba

File tree

2 files changed

+36
-3
lines changed

2 files changed

+36
-3
lines changed

src/dotenv/cli.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
)
1818
sys.exit(1)
1919

20-
from .main import dotenv_values, set_key, unset_key
20+
from .main import dotenv_values, generate_template, set_key, unset_key
2121
from .version import __version__
2222

2323

@@ -201,6 +201,37 @@ def run(ctx: click.Context, override: bool, commandline: tuple[str, ...]) -> Non
201201
run_command([*commandline, *ctx.args], dotenv_as_dict)
202202

203203

204+
@cli.command()
205+
@click.pass_context
206+
@click.argument("output", default=None, required=False, type=click.Path())
207+
@click.option(
208+
"--keep-directives",
209+
is_flag=True,
210+
default=False,
211+
help="Keep ::dotenv-template-preserve and ::dotenv-template-exclude directives in output.",
212+
)
213+
def template(ctx: click.Context, output: Any, keep_directives: bool) -> None:
214+
"""Generate a template from the .env file.
215+
216+
Values are replaced with their key names. Use ::dotenv-template-preserve
217+
in a line to keep its value, or ::dotenv-template-exclude to omit it.
218+
219+
If OUTPUT is given, the template is written to that file. Otherwise it is
220+
printed to stdout.
221+
"""
222+
file = ctx.obj["FILE"]
223+
224+
with stream_file(file) as stream:
225+
result = generate_template(stream=stream, keep_directives=keep_directives)
226+
227+
if output:
228+
with open(output, "w") as f:
229+
f.write(result)
230+
click.echo(f"Template written to {output}")
231+
else:
232+
click.echo(result, nl=False)
233+
234+
204235
def run_command(command: List[str], env: Dict[str, str]) -> None:
205236
"""Replace the current process with the specified command.
206237

src/dotenv/main.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -500,12 +500,14 @@ def _is_file_or_fifo(path: StrPath) -> bool:
500500

501501

502502
def _strip_directives(line: str) -> str:
503-
"""Remove directive tokens from a line and trim any resulting trailing whitespace."""
503+
"""Remove directive tokens from a line and clean up artifacts."""
504504
result = _DIRECTIVE_RE.sub("", line)
505505
# Preserve the original line ending
506506
stripped = result.rstrip("\r\n")
507507
ending = result[len(stripped) :]
508-
return stripped.rstrip() + ending
508+
# Remove a bare comment marker left after stripping
509+
cleaned = re.sub(r"[ \t]*#[ \t]*$", "", stripped)
510+
return cleaned.rstrip() + ending
509511

510512

511513
def generate_template(

0 commit comments

Comments
 (0)