Skip to content

Commit 8f90956

Browse files
committed
--extra feature, closes #38
1 parent 20aa749 commit 8f90956

File tree

4 files changed

+132
-6
lines changed

4 files changed

+132
-6
lines changed

Diff for: README.md

+25
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ Use `--show-unchanged` to include full details of the unchanged values for rows
6666
Unchanged:
6767
name: "Cleo"
6868

69+
### JSON output
70+
6971
You can use the `--json` option to get a machine-readable difference:
7072

7173
$ csv-diff one.csv two.csv --key=id --json
@@ -99,6 +101,29 @@ You can use the `--json` option to get a machine-readable difference:
99101
"columns_removed": []
100102
}
101103

104+
### Adding templated extras
105+
106+
You can specify additional keys to be displayed in the human-readable format using the `--extra` option:
107+
108+
--extra name "Python format string with {id} for variables"
109+
110+
For example, to output a link to `https://news.ycombinator.com/latest?id={id}` for each item with an ID, you could use this:
111+
112+
```bash
113+
csv-diff one.csv two.csv --key=id \
114+
--extra latest "https://news.ycombinator.com/latest?id={id}"
115+
```
116+
These extras display something like this:
117+
```
118+
1 row changed
119+
120+
id: 41459472
121+
points: "24" => "25"
122+
numComments: "5" => "6"
123+
extras:
124+
latest: https://news.ycombinator.com/latest?id=41459472
125+
```
126+
102127
## As a Python library
103128

104129
You can also import the Python library into your own code like so:

Diff for: csv_diff/__init__.py

+20-3
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ def compare(previous, current, show_unchanged=False):
108108
return result
109109

110110

111-
def human_text(result, key=None, singular=None, plural=None, show_unchanged=False):
111+
def human_text(result, key=None, singular=None, plural=None, current=None, extras=None):
112112
singular = singular or "row"
113113
plural = plural or "rows"
114114
title = []
@@ -151,6 +151,9 @@ def human_text(result, key=None, singular=None, plural=None, show_unchanged=Fals
151151
block.append(
152152
' {}: "{}" => "{}"'.format(field, prev_value, current_value)
153153
)
154+
if extras:
155+
current_item = current[details["key"]]
156+
block.append(human_extras(current_item, extras))
154157
block.append("")
155158
change_blocks.append("\n".join(block))
156159
if details.get("unchanged"):
@@ -170,7 +173,10 @@ def human_text(result, key=None, singular=None, plural=None, show_unchanged=Fals
170173
summary.append(fragment + "\n")
171174
rows = []
172175
for row in result["added"]:
173-
rows.append(human_row(row, prefix=" "))
176+
to_append = human_row(row, prefix=" ")
177+
if extras:
178+
to_append += "\n" + human_extras(row, extras)
179+
rows.append(to_append)
174180
summary.append("\n\n".join(rows))
175181
summary.append("")
176182
if result["removed"]:
@@ -182,7 +188,10 @@ def human_text(result, key=None, singular=None, plural=None, show_unchanged=Fals
182188
summary.append(fragment + "\n")
183189
rows = []
184190
for row in result["removed"]:
185-
rows.append(human_row(row, prefix=" "))
191+
to_append = human_row(row, prefix=" ")
192+
if extras:
193+
to_append += "\n" + human_extras(row, extras)
194+
rows.append(to_append)
186195
summary.append("\n\n".join(rows))
187196
summary.append("")
188197
return (", ".join(title) + "\n\n" + ("\n".join(summary))).strip()
@@ -193,3 +202,11 @@ def human_row(row, prefix=""):
193202
for key, value in row.items():
194203
bits.append("{}{}: {}".format(prefix, key, value))
195204
return "\n".join(bits)
205+
206+
207+
def human_extras(row, extras):
208+
bits = []
209+
bits.append(" extras:")
210+
for key, fmt in extras:
211+
bits.append(" {}: {}".format(key, fmt.format(**row)))
212+
return "\n".join(bits)

Diff for: csv_diff/cli.py

+21-3
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,26 @@
4242
is_flag=True,
4343
help="Show unchanged fields for rows with at least one change",
4444
)
45-
def cli(previous, current, key, format, json, singular, plural, show_unchanged):
45+
@click.option(
46+
"extras",
47+
"--extra",
48+
type=(str, str),
49+
multiple=True,
50+
help="key: format string - define extra fields to display",
51+
)
52+
def cli(previous, current, key, format, json, singular, plural, show_unchanged, extras):
4653
"Diff two CSV or JSON files"
4754
dialect = {
4855
"csv": "excel",
4956
"tsv": "excel-tab",
5057
}
5158

59+
if extras and json:
60+
raise click.UsageError(
61+
"Extra fields are not supported in JSON output mode",
62+
ctx=click.get_current_context(),
63+
)
64+
5265
def load(filename):
5366
if format == "json":
5467
return load_json(open(filename), key=key)
@@ -57,8 +70,13 @@ def load(filename):
5770
open(filename, newline=""), key=key, dialect=dialect.get(format)
5871
)
5972

60-
diff = compare(load(previous), load(current), show_unchanged)
73+
previous_data = load(previous)
74+
current_data = load(current)
75+
76+
diff = compare(previous_data, current_data, show_unchanged)
6177
if json:
6278
print(std_json.dumps(diff, indent=4))
6379
else:
64-
print(human_text(diff, key, singular, plural))
80+
print(
81+
human_text(diff, key, singular, plural, current=current_data, extras=extras)
82+
)

Diff for: tests/test_cli.py

+66
Original file line numberDiff line numberDiff line change
@@ -234,3 +234,69 @@ def test_semicolon_delimited(tmpdir):
234234
"columns_added": [],
235235
"columns_removed": [],
236236
} == json.loads(result.output.strip())
237+
238+
239+
def test_diff_with_extras(tmpdir):
240+
one = tmpdir / "one.json"
241+
two = tmpdir / "two.json"
242+
one.write(
243+
json.dumps(
244+
[
245+
{"id": 1, "name": "Cleo", "type": "dog"},
246+
{"id": 2, "name": "Suna", "type": "chicken"},
247+
]
248+
)
249+
)
250+
two.write(
251+
json.dumps(
252+
[
253+
{"id": 2, "name": "Suna", "type": "pretty chicken"},
254+
{"id": 3, "name": "Artie", "type": "bunny"},
255+
]
256+
)
257+
)
258+
result = CliRunner().invoke(
259+
cli.cli,
260+
[
261+
str(one),
262+
str(two),
263+
"--key",
264+
"id",
265+
"--format",
266+
"json",
267+
"--extra",
268+
"search",
269+
"https://www.google.com/search?q={name}",
270+
],
271+
catch_exceptions=False,
272+
)
273+
assert result.exit_code == 0
274+
expected = dedent(
275+
"""
276+
1 row changed, 1 row added, 1 row removed
277+
278+
1 row changed
279+
280+
id: 2
281+
type: "chicken" => "pretty chicken"
282+
extras:
283+
search: https://www.google.com/search?q=Suna
284+
285+
1 row added
286+
287+
id: 3
288+
name: Artie
289+
type: bunny
290+
extras:
291+
search: https://www.google.com/search?q=Artie
292+
293+
1 row removed
294+
295+
id: 1
296+
name: Cleo
297+
type: dog
298+
extras:
299+
search: https://www.google.com/search?q=Cleo
300+
"""
301+
).strip()
302+
assert result.output.strip() == expected

0 commit comments

Comments
 (0)