Skip to content

Commit 666e203

Browse files
skshetryefiop
andauthored
params/metrics: diff: implement tabulated markdown output (--show-md) (#3757)
* params/metrics: diff: implement tabulated markdown output Via `--show-md` on `diff`s. * table: show None on missing val * tests: use `dedent`, add tests for markdown * diff: add --show-md Co-authored-by: Ruslan Kuprieiev <[email protected]>
1 parent ce946d1 commit 666e203

File tree

8 files changed

+206
-64
lines changed

8 files changed

+206
-64
lines changed

dvc/command/diff.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,21 @@
1111
logger = logging.getLogger(__name__)
1212

1313

14+
def _show_md(diff):
15+
from dvc.utils.diff import table
16+
17+
rows = []
18+
for status in ["added", "deleted", "modified"]:
19+
entries = diff.get(status, [])
20+
if not entries:
21+
continue
22+
paths = sorted([entry["path"] for entry in entries])
23+
for path in paths:
24+
rows.append([status, path])
25+
26+
return table(["Status", "Path"], rows, True)
27+
28+
1429
class CmdDiff(CmdBase):
1530
@staticmethod
1631
def _format(diff):
@@ -103,6 +118,8 @@ def run(self):
103118

104119
if self.args.show_json:
105120
logger.info(json.dumps(diff))
121+
elif self.args.show_md:
122+
logger.info(_show_md(diff))
106123
elif diff:
107124
logger.info(self._format(diff))
108125

@@ -147,4 +164,10 @@ def add_parser(subparsers, parent_parser):
147164
action="store_true",
148165
default=False,
149166
)
167+
diff_parser.add_argument(
168+
"--show-md",
169+
help="Show tabulated output in the Markdown format (GFM).",
170+
action="store_true",
171+
default=False,
172+
)
150173
diff_parser.set_defaults(func=CmdDiff)

dvc/command/metrics.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def run(self):
8787
return 0
8888

8989

90-
def _show_diff(diff):
90+
def _show_diff(diff, markdown=False):
9191
from collections import OrderedDict
9292

9393
from dvc.utils.diff import table
@@ -105,7 +105,7 @@ def _show_diff(diff):
105105
]
106106
)
107107

108-
return table(["Path", "Metric", "Value", "Change"], rows)
108+
return table(["Path", "Metric", "Value", "Change"], rows, markdown)
109109

110110

111111
class CmdMetricsDiff(CmdBase):
@@ -124,7 +124,7 @@ def run(self):
124124

125125
logger.info(json.dumps(diff))
126126
else:
127-
table = _show_diff(diff)
127+
table = _show_diff(diff, self.args.show_md)
128128
if table:
129129
logger.info(table)
130130

@@ -263,6 +263,12 @@ def add_parser(subparsers, parent_parser):
263263
default=False,
264264
help="Show output in JSON format.",
265265
)
266+
metrics_diff_parser.add_argument(
267+
"--show-md",
268+
action="store_true",
269+
default=False,
270+
help="Show tabulated output in the Markdown format (GFM).",
271+
)
266272
metrics_diff_parser.set_defaults(func=CmdMetricsDiff)
267273

268274
METRICS_REMOVE_HELP = "Remove metric mark on a DVC-tracked file."

dvc/command/params.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
logger = logging.getLogger(__name__)
99

1010

11-
def _show_diff(diff):
11+
def _show_diff(diff, markdown=False):
1212
from dvc.utils.diff import table
1313

1414
rows = []
@@ -17,7 +17,7 @@ def _show_diff(diff):
1717
for param, change in sorted_pdiff.items():
1818
rows.append([fname, param, change["old"], change["new"]])
1919

20-
return table(["Path", "Param", "Old", "New"], rows)
20+
return table(["Path", "Param", "Old", "New"], rows, markdown)
2121

2222

2323
class CmdParamsDiff(CmdBase):
@@ -34,7 +34,7 @@ def run(self):
3434

3535
logger.info(json.dumps(diff))
3636
else:
37-
table = _show_diff(diff)
37+
table = _show_diff(diff, self.args.show_md)
3838
if table:
3939
logger.info(table)
4040

@@ -94,4 +94,10 @@ def add_parser(subparsers, parent_parser):
9494
default=False,
9595
help="Show output in JSON format.",
9696
)
97+
params_diff_parser.add_argument(
98+
"--show-md",
99+
action="store_true",
100+
default=False,
101+
help="Show tabulated output in the Markdown format (GFM).",
102+
)
97103
params_diff_parser.set_defaults(func=CmdParamsDiff)

dvc/utils/diff.py

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -85,24 +85,20 @@ def diff(old, new, with_unchanged=False):
8585
return dict(res)
8686

8787

88-
def table(header, rows):
89-
from texttable import Texttable
88+
def table(header, rows, markdown=False):
89+
from tabulate import tabulate
9090

91-
if not rows:
91+
if not rows and not markdown:
9292
return ""
9393

94-
t = Texttable()
95-
96-
# disable automatic formatting
97-
t.set_cols_dtype(["t"] * len(header))
98-
99-
# remove borders to make it easier for users to copy stuff
100-
t.set_chars([""] * len(header))
101-
t.set_deco(0)
102-
103-
t.add_rows([header] + rows)
104-
105-
return t.draw()
94+
return tabulate(
95+
rows,
96+
header,
97+
tablefmt="github" if markdown else "plain",
98+
disable_numparse=True,
99+
# None will be shown as "" by default, overriding
100+
missingval="None",
101+
)
106102

107103

108104
def format_dict(d):

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ def run(self):
7878
"pydot>=1.2.4",
7979
"speedcopy>=2.0.1; python_version < '3.8' and sys_platform == 'win32'",
8080
"flatten_json>=0.1.6",
81-
"texttable>=0.5.2",
81+
"tabulate>=0.8.7",
8282
"pygtrie==2.3.2",
8383
"dpath>=2.0.1,<3",
8484
]

tests/unit/command/test_diff.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import os
44

55
from dvc.cli import parse_args
6+
from dvc.command.diff import _show_md
67

78

89
def test_default(mocker, caplog):
@@ -110,3 +111,32 @@ def info():
110111
cmd = args.func(args)
111112
assert 0 == cmd.run()
112113
assert not info()
114+
115+
116+
def test_show_md_empty():
117+
assert _show_md({}) == ("| Status | Path |\n" "|----------|--------|")
118+
119+
120+
def test_show_md():
121+
diff = {
122+
"deleted": [
123+
{"path": "zoo", "hash": "22222"},
124+
{"path": os.path.join("data", ""), "hash": "XXXXXXXX.dir"},
125+
{"path": os.path.join("data", "foo"), "hash": "11111111"},
126+
{"path": os.path.join("data", "bar"), "hash": "00000000"},
127+
],
128+
"modified": [
129+
{"path": "file", "hash": {"old": "AAAAAAAA", "new": "BBBBBBBB"}}
130+
],
131+
"added": [{"path": "file", "hash": "00000000"}],
132+
}
133+
assert _show_md(diff) == (
134+
"| Status | Path |\n"
135+
"|----------|----------|\n"
136+
"| added | file |\n"
137+
"| deleted | data{sep} |\n"
138+
"| deleted | data{sep}bar |\n"
139+
"| deleted | data{sep}foo |\n"
140+
"| deleted | zoo |\n"
141+
"| modified | file |"
142+
).format(sep=os.path.sep)

tests/unit/command/test_metrics.py

Lines changed: 62 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import textwrap
2+
13
from dvc.cli import parse_args
24
from dvc.command.metrics import CmdMetricsDiff, CmdMetricsShow, _show_diff
35

@@ -37,25 +39,30 @@ def test_metrics_diff(dvc, mocker):
3739
def test_metrics_show_json_diff():
3840
assert _show_diff(
3941
{"metrics.json": {"a.b.c": {"old": 1, "new": 2, "diff": 3}}}
40-
) == (
41-
" Path Metric Value Change\n"
42-
"metrics.json a.b.c 2 3 "
42+
) == textwrap.dedent(
43+
"""\
44+
Path Metric Value Change
45+
metrics.json a.b.c 2 3"""
4346
)
4447

4548

4649
def test_metrics_show_raw_diff():
47-
assert _show_diff({"metrics": {"": {"old": "1", "new": "2"}}}) == (
48-
" Path Metric Value Change \n"
49-
"metrics 2 diff not supported"
50+
assert _show_diff(
51+
{"metrics": {"": {"old": "1", "new": "2"}}}
52+
) == textwrap.dedent(
53+
"""\
54+
Path Metric Value Change
55+
metrics 2 diff not supported"""
5056
)
5157

5258

5359
def test_metrics_diff_no_diff():
5460
assert _show_diff(
5561
{"other.json": {"a.b.d": {"old": "old", "new": "new"}}}
56-
) == (
57-
" Path Metric Value Change \n"
58-
"other.json a.b.d new diff not supported"
62+
) == textwrap.dedent(
63+
"""\
64+
Path Metric Value Change
65+
other.json a.b.d new diff not supported"""
5966
)
6067

6168

@@ -66,18 +73,20 @@ def test_metrics_diff_no_changes():
6673
def test_metrics_diff_new_metric():
6774
assert _show_diff(
6875
{"other.json": {"a.b.d": {"old": None, "new": "new"}}}
69-
) == (
70-
" Path Metric Value Change \n"
71-
"other.json a.b.d new diff not supported"
76+
) == textwrap.dedent(
77+
"""\
78+
Path Metric Value Change
79+
other.json a.b.d new diff not supported"""
7280
)
7381

7482

7583
def test_metrics_diff_deleted_metric():
7684
assert _show_diff(
7785
{"other.json": {"a.b.d": {"old": "old", "new": None}}}
78-
) == (
79-
" Path Metric Value Change \n"
80-
"other.json a.b.d None diff not supported"
86+
) == textwrap.dedent(
87+
"""\
88+
Path Metric Value Change
89+
other.json a.b.d None diff not supported"""
8190
)
8291

8392

@@ -114,9 +123,10 @@ def test_metrics_show(dvc, mocker):
114123
def test_metrics_diff_prec():
115124
assert _show_diff(
116125
{"other.json": {"a.b": {"old": 0.0042, "new": 0.0043, "diff": 0.0001}}}
117-
) == (
118-
" Path Metric Value Change\n"
119-
"other.json a.b 0.0043 0.0001"
126+
) == textwrap.dedent(
127+
"""\
128+
Path Metric Value Change
129+
other.json a.b 0.0043 0.0001"""
120130
)
121131

122132

@@ -129,9 +139,38 @@ def test_metrics_diff_sorted():
129139
"a.b.c": {"old": 1, "new": 2, "diff": 1},
130140
}
131141
}
132-
) == (
133-
" Path Metric Value Change\n"
134-
"metrics.yaml a.b.c 2 1 \n"
135-
"metrics.yaml a.d.e 4 1 \n"
136-
"metrics.yaml x.b 6 1 "
142+
) == textwrap.dedent(
143+
"""\
144+
Path Metric Value Change
145+
metrics.yaml a.b.c 2 1
146+
metrics.yaml a.d.e 4 1
147+
metrics.yaml x.b 6 1"""
148+
)
149+
150+
151+
def test_metrics_diff_markdown_empty():
152+
assert _show_diff({}, markdown=True) == textwrap.dedent(
153+
"""\
154+
| Path | Metric | Value | Change |
155+
|--------|----------|---------|----------|"""
156+
)
157+
158+
159+
def test_metrics_diff_markdown():
160+
assert _show_diff(
161+
{
162+
"metrics.yaml": {
163+
"x.b": {"old": 5, "new": 6},
164+
"a.d.e": {"old": 3, "new": 4, "diff": 1},
165+
"a.b.c": {"old": 1, "new": 2, "diff": 1},
166+
}
167+
},
168+
markdown=True,
169+
) == textwrap.dedent(
170+
"""\
171+
| Path | Metric | Value | Change |
172+
|--------------|----------|---------|--------------------|
173+
| metrics.yaml | a.b.c | 2 | 1 |
174+
| metrics.yaml | a.d.e | 4 | 1 |
175+
| metrics.yaml | x.b | 6 | diff not supported |"""
137176
)

0 commit comments

Comments
 (0)