Skip to content

Commit 914c68e

Browse files
sammcktheskumar
andauthored
feat(cli) add --format= option to list command (#407)
Allows dumping of all variables in various formats. Currently defined formats: simple: Each variable is output as <name>=<value> with no quoting or escaping. The output is not parseable. This is the default format for backwards compatibility. shell: Each variable is output as <name>=<value>, where <value> is quoted/escaped with shell-compatible rules, the result may be imported into a shell script with eval "$(dotenv list --format=shell)" export: Similar to "shell" but prefixes each line with "export " so that when imported into a shell script, the variables are exported. json: The entire set of variables is output as a JSON-serialized object Co-authored-by: Saurabh Kumar <[email protected]>
1 parent 2f36c08 commit 914c68e

File tree

3 files changed

+46
-8
lines changed

3 files changed

+46
-8
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,11 @@ $ dotenv set EMAIL [email protected]
146146
$ dotenv list
147147
USER=foo
148148
149+
$ dotenv list --format=json
150+
{
151+
"USER": "foo",
152+
"EMAIL": "[email protected]"
153+
}
149154
$ dotenv run -- python foo.py
150155
```
151156

src/dotenv/cli.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import json
12
import os
3+
import shlex
24
import sys
35
from subprocess import Popen
46
from typing import Any, Dict, List
@@ -36,7 +38,11 @@ def cli(ctx: click.Context, file: Any, quote: Any, export: Any) -> None:
3638

3739
@cli.command()
3840
@click.pass_context
39-
def list(ctx: click.Context) -> None:
41+
@click.option('--format', default='simple',
42+
type=click.Choice(['simple', 'json', 'shell', 'export']),
43+
help="The format in which to display the list. Default format is simple, "
44+
"which displays name=value without quotes.")
45+
def list(ctx: click.Context, format: bool) -> None:
4046
'''Display all the stored key/value.'''
4147
file = ctx.obj['FILE']
4248
if not os.path.isfile(file):
@@ -45,8 +51,16 @@ def list(ctx: click.Context) -> None:
4551
ctx=ctx
4652
)
4753
dotenv_as_dict = dotenv_values(file)
48-
for k, v in dotenv_as_dict.items():
49-
click.echo('%s=%s' % (k, v))
54+
if format == 'json':
55+
click.echo(json.dumps(dotenv_as_dict, indent=2, sort_keys=True))
56+
else:
57+
prefix = 'export ' if format == 'export' else ''
58+
for k in sorted(dotenv_as_dict):
59+
v = dotenv_as_dict[k]
60+
if v is not None:
61+
if format in ('export', 'shell'):
62+
v = shlex.quote(v)
63+
click.echo('%s%s=%s' % (prefix, k, v))
5064

5165

5266
@cli.command()

tests/test_cli.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,38 @@
22

33
import pytest
44
import sh
5-
5+
from typing import Optional
66
import dotenv
77
from dotenv.cli import cli as dotenv_cli
88
from dotenv.version import __version__
99

1010

11-
def test_list(cli, dotenv_file):
11+
@pytest.mark.parametrize(
12+
"format,content,expected",
13+
(
14+
(None, "x='a b c'", '''x=a b c\n'''),
15+
("simple", "x='a b c'", '''x=a b c\n'''),
16+
("simple", """x='"a b c"'""", '''x="a b c"\n'''),
17+
("simple", '''x="'a b c'"''', '''x='a b c'\n'''),
18+
("json", "x='a b c'", '''{\n "x": "a b c"\n}\n'''),
19+
("shell", "x='a b c'", "x='a b c'\n"),
20+
("shell", """x='"a b c"'""", '''x='"a b c"'\n'''),
21+
("shell", '''x="'a b c'"''', '''x=''"'"'a b c'"'"''\n'''),
22+
("shell", "x='a\nb\nc'", "x='a\nb\nc'\n"),
23+
("export", "x='a b c'", '''export x='a b c'\n'''),
24+
)
25+
)
26+
def test_list(cli, dotenv_file, format: Optional[str], content: str, expected: str):
1227
with open(dotenv_file, "w") as f:
13-
f.write("a=b")
28+
f.write(content + '\n')
29+
30+
args = ['--file', dotenv_file, 'list']
31+
if format is not None:
32+
args.extend(['--format', format])
1433

15-
result = cli.invoke(dotenv_cli, ['--file', dotenv_file, 'list'])
34+
result = cli.invoke(dotenv_cli, args)
1635

17-
assert (result.exit_code, result.output) == (0, result.output)
36+
assert (result.exit_code, result.output) == (0, expected)
1837

1938

2039
def test_list_non_existent_file(cli):

0 commit comments

Comments
 (0)