Skip to content

Commit 514cfce

Browse files
Merge pull request #160 from source-foundry/color-as-default
Convert to ANSI escape code colored diff output as default in terminals
2 parents 75c0948 + 3d63f70 commit 514cfce

File tree

4 files changed

+116
-55
lines changed

4 files changed

+116
-55
lines changed

lib/fdiff/__main__.py

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,7 @@ def run(argv: List[Text]) -> None:
3737
parser = argparse.ArgumentParser(description="An OpenType table diff tool for fonts.")
3838
parser.add_argument("--version", action="version", version=f"fdiff v{__version__}")
3939
parser.add_argument(
40-
"-c",
41-
"--color",
42-
action="store_true",
43-
default=False,
44-
help="ANSI escape code colored diff",
45-
)
46-
parser.add_argument(
47-
"-l", "--lines", type=int, default=3, help="Number of context lines (default 3)"
40+
"-l", "--lines", type=int, default=3, help="Number of context lines (default: 3)"
4841
)
4942
parser.add_argument(
5043
"--include",
@@ -60,10 +53,25 @@ def run(argv: List[Text]) -> None:
6053
)
6154
parser.add_argument("--head", type=int, help="Display first n lines of output")
6255
parser.add_argument("--tail", type=int, help="Display last n lines of output")
56+
parser.add_argument("--external", type=str, help="Run external diff tool command")
6357
parser.add_argument(
64-
"--nomp", action="store_true", help="Do not use multi process optimizations"
58+
"-c",
59+
"--color",
60+
action="store_true",
61+
default=False,
62+
help="Force ANSI escape code color formatting in all environments",
63+
)
64+
parser.add_argument(
65+
"--nomp",
66+
action="store_true",
67+
help="Do not use multi process optimizations (default: on)",
68+
)
69+
parser.add_argument(
70+
"--nocolor",
71+
action="store_true",
72+
default=False,
73+
help="Do not use ANSI escape code colored diff (default: on)",
6574
)
66-
parser.add_argument("--external", type=str, help="Run external diff tool command")
6775
parser.add_argument("PREFILE", help="Font file path/URL 1")
6876
parser.add_argument("POSTFILE", help="Font file path/URL 2")
6977

@@ -154,8 +162,12 @@ def run(argv: List[Text]) -> None:
154162

155163
# write stdout from external tool
156164
for line, exit_code in ext_diff:
157-
# format with color if color flag is entered on command line
158-
if args.color:
165+
# format with color by default unless:
166+
# (1) user entered the --nocolor option
167+
# (2) we are not piping std output to a terminal
168+
# Force formatting with color in all environments if the user includes
169+
# the `-c` / `--color` option
170+
if (not args.nocolor and console.is_terminal) or args.color:
159171
sys.stdout.write(color_unified_diff_line(line))
160172
else:
161173
sys.stdout.write(line)
@@ -194,7 +206,12 @@ def run(argv: List[Text]) -> None:
194206

195207
# print unified diff results to standard output stream
196208
has_diff = False
197-
if args.color:
209+
# format with color by default unless:
210+
# (1) user entered the --nocolor option
211+
# (2) we are not piping std output to a terminal
212+
# Force formatting with color in all environments if the user includes
213+
# the `-c` / `--color` option
214+
if (not args.nocolor and console.is_terminal) or args.color:
198215
for line in iterable:
199216
has_diff = True
200217
sys.stdout.write(color_unified_diff_line(line))
@@ -203,7 +220,7 @@ def run(argv: List[Text]) -> None:
203220
has_diff = True
204221
sys.stdout.write(line)
205222

206-
# if no difference was found, tell the user instead of
207-
# simply closing with zero exit status code.
223+
# if no difference was found, tell the user in addition to the
224+
# the zero exit status code.
208225
if not has_diff:
209-
print("[*] There is no difference between the files.")
226+
print("[*] There is no difference in the tested OpenType tables.")

tests/test_main.py

Lines changed: 50 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,33 @@
22
# -*- coding: utf-8 -*-
33

44
import os
5+
import sys
6+
from unittest.mock import MagicMock
57

68
import pytest
79

810
from fdiff.__main__ import run
911

1012
ROBOTO_BEFORE_PATH = os.path.join("tests", "testfiles", "Roboto-Regular.subset1.ttf")
1113
ROBOTO_AFTER_PATH = os.path.join("tests", "testfiles", "Roboto-Regular.subset2.ttf")
12-
ROBOTO_UDIFF_EXPECTED_PATH = os.path.join("tests", "testfiles", "roboto_udiff_expected.txt")
13-
ROBOTO_UDIFF_COLOR_EXPECTED_PATH = os.path.join("tests", "testfiles", "roboto_udiff_color_expected.txt")
14-
ROBOTO_UDIFF_1CONTEXT_EXPECTED_PATH = os.path.join("tests", "testfiles", "roboto_udiff_1context_expected.txt")
15-
ROBOTO_UDIFF_HEADONLY_EXPECTED_PATH = os.path.join("tests", "testfiles", "roboto_udiff_headonly_expected.txt")
16-
ROBOTO_UDIFF_HEADPOSTONLY_EXPECTED_PATH = os.path.join("tests", "testfiles", "roboto_udiff_headpostonly_expected.txt")
17-
ROBOTO_UDIFF_EXCLUDE_HEADPOST_EXPECTED_PATH = os.path.join("tests", "testfiles", "roboto_udiff_ex_headpost_expected.txt")
14+
ROBOTO_UDIFF_EXPECTED_PATH = os.path.join(
15+
"tests", "testfiles", "roboto_udiff_expected.txt"
16+
)
17+
ROBOTO_UDIFF_COLOR_EXPECTED_PATH = os.path.join(
18+
"tests", "testfiles", "roboto_udiff_color_expected.txt"
19+
)
20+
ROBOTO_UDIFF_1CONTEXT_EXPECTED_PATH = os.path.join(
21+
"tests", "testfiles", "roboto_udiff_1context_expected.txt"
22+
)
23+
ROBOTO_UDIFF_HEADONLY_EXPECTED_PATH = os.path.join(
24+
"tests", "testfiles", "roboto_udiff_headonly_expected.txt"
25+
)
26+
ROBOTO_UDIFF_HEADPOSTONLY_EXPECTED_PATH = os.path.join(
27+
"tests", "testfiles", "roboto_udiff_headpostonly_expected.txt"
28+
)
29+
ROBOTO_UDIFF_EXCLUDE_HEADPOST_EXPECTED_PATH = os.path.join(
30+
"tests", "testfiles", "roboto_udiff_ex_headpost_expected.txt"
31+
)
1832

1933
ROBOTO_BEFORE_URL = "https://github.com/source-foundry/fdiff/raw/master/tests/testfiles/Roboto-Regular.subset1.ttf"
2034
ROBOTO_AFTER_URL = "https://github.com/source-foundry/fdiff/raw/master/tests/testfiles/Roboto-Regular.subset2.ttf"
@@ -80,28 +94,41 @@ def test_main_filepath_validations_false_secondfont(capsys):
8094
# Mutually exclusive argument tests
8195
#
8296

97+
8398
def test_main_include_exclude_defined_simultaneously(capsys):
84-
args = ["--include", "head", "--exclude", "head", ROBOTO_BEFORE_PATH, ROBOTO_AFTER_PATH]
99+
args = [
100+
"--include",
101+
"head",
102+
"--exclude",
103+
"head",
104+
ROBOTO_BEFORE_PATH,
105+
ROBOTO_AFTER_PATH,
106+
]
85107

86108
with pytest.raises(SystemExit) as exit_info:
87109
run(args)
88110

89111
captured = capsys.readouterr()
90-
assert captured.err.startswith("[*] Error: --include and --exclude are mutually exclusive options")
112+
assert captured.err.startswith(
113+
"[*] Error: --include and --exclude are mutually exclusive options"
114+
)
91115
assert exit_info.value.code == 1
92116

93117

94118
#
95119
# Unified diff integration tests
96120
#
97121

122+
98123
def test_main_run_unified_default_local_files_no_diff(capsys):
99124
"""Test default behavior when there is no difference in font files under evaluation"""
100125
args = [ROBOTO_BEFORE_PATH, ROBOTO_BEFORE_PATH]
101126

102127
run(args)
103128
captured = capsys.readouterr()
104-
assert captured.out.startswith("[*] There is no difference between the files.")
129+
assert captured.out.startswith(
130+
"[*] There is no difference in the tested OpenType tables"
131+
)
105132

106133

107134
def test_main_run_unified_default_local_files(capsys):
@@ -180,24 +207,24 @@ def test_main_run_unified_default_404(capsys):
180207

181208

182209
def test_main_run_unified_color(capsys):
183-
args = ["-c", ROBOTO_BEFORE_PATH, ROBOTO_AFTER_PATH]
210+
# prior to v3.0.0, the `-c` / `--color` option was required for color output
211+
# this is the default as of v3.0.0 and the test arguments were
212+
# modified here
213+
args = [ROBOTO_BEFORE_PATH, ROBOTO_AFTER_PATH]
214+
# we also need to mock sys.stdout.isatty because color does not
215+
# show when this returns False
216+
sys.stdout.isatty = MagicMock(return_value=True)
184217

185218
run(args)
186219
captured = capsys.readouterr()
187-
220+
# spot checks for escape code start sequence
188221
res_string_list = captured.out.split("\n")
189-
expected_string_list = ROBOTO_UDIFF_COLOR_EXPECTED.split("\n")
190-
191-
# have to handle the tests for the top two file path lines
192-
# differently than the rest of the comparisons because
193-
# the time is defined using local platform settings
194-
# which makes tests fail on different remote CI testing services
195-
for x, line in enumerate(res_string_list):
196-
# treat top two lines of the diff as comparison of first 10 chars only
197-
if x in (0, 1):
198-
assert line[0:9] == expected_string_list[x][0:9]
199-
else:
200-
assert line == expected_string_list[x]
222+
assert captured.out.startswith("\x1b")
223+
assert res_string_list[10].startswith("\x1b")
224+
assert res_string_list[71].startswith("\x1b")
225+
assert res_string_list[180].startswith("\x1b")
226+
assert res_string_list[200].startswith("\x1b")
227+
assert res_string_list[238].startswith("\x1b")
201228

202229

203230
def test_main_run_unified_context_lines_1(capsys):

tests/test_main_unix_only.py

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,20 @@
22

33
import os
44
import sys
5+
from unittest.mock import MagicMock
56

67
import pytest
78

89
from fdiff.__main__ import run
910

1011
ROBOTO_BEFORE_PATH = os.path.join("tests", "testfiles", "Roboto-Regular.subset1.ttf")
1112
ROBOTO_AFTER_PATH = os.path.join("tests", "testfiles", "Roboto-Regular.subset2.ttf")
12-
ROBOTO_EXTDIFF_EXPECTED_PATH = os.path.join("tests", "testfiles", "roboto_extdiff_expected.txt")
13-
ROBOTO_EXTDIFF_COLOR_EXPECTED_PATH = os.path.join("tests", "testfiles", "roboto_extdiff_color_expected.txt")
13+
ROBOTO_EXTDIFF_EXPECTED_PATH = os.path.join(
14+
"tests", "testfiles", "roboto_extdiff_expected.txt"
15+
)
16+
ROBOTO_EXTDIFF_COLOR_EXPECTED_PATH = os.path.join(
17+
"tests", "testfiles", "roboto_extdiff_color_expected.txt"
18+
)
1419

1520
ROBOTO_BEFORE_URL = "https://github.com/source-foundry/fdiff/raw/master/tests/testfiles/Roboto-Regular.subset1.ttf"
1621
ROBOTO_AFTER_URL = "https://github.com/source-foundry/fdiff/raw/master/tests/testfiles/Roboto-Regular.subset2.ttf"
@@ -72,23 +77,28 @@ def test_main_external_diff_remote(capsys):
7277

7378

7479
def test_main_external_diff_color(capsys):
75-
args = ["--external", "diff -u", "--color", ROBOTO_BEFORE_PATH, ROBOTO_AFTER_PATH]
76-
expected_string_list = ROBOTO_EXTDIFF_COLOR_EXPECTED.split("\n")
80+
# prior to v3.0.0, the `-c` / `--color` option was required for color output
81+
# this is the default as of v3.0.0 and the test arguments were
82+
# modified here
83+
args = ["--external", "diff -u", ROBOTO_BEFORE_PATH, ROBOTO_AFTER_PATH]
84+
# we also need to patch sys.stdout.isatty because color does not
85+
# show when this returns False
86+
sys.stdout.isatty = MagicMock(return_value=True)
87+
# expected_string_list = ROBOTO_EXTDIFF_COLOR_EXPECTED.split("\n")
7788

7889
with pytest.raises(SystemExit):
7990
run(args)
8091

8192
captured = capsys.readouterr()
93+
94+
# spot checks for escape code start sequence
8295
res_string_list = captured.out.split("\n")
83-
for x, line in enumerate(res_string_list):
84-
# treat top two lines of the diff as comparison of first 3 chars only
85-
if x in (0, 1):
86-
assert line[0:2] == expected_string_list[x][0:2]
87-
elif x in range(2, 10):
88-
assert line == expected_string_list[x]
89-
else:
90-
# skip lines beyond first 10
91-
pass
96+
assert captured.out.startswith("\x1b")
97+
assert res_string_list[10].startswith("\x1b")
98+
assert res_string_list[71].startswith("\x1b")
99+
assert res_string_list[180].startswith("\x1b")
100+
assert res_string_list[200].startswith("\x1b")
101+
assert res_string_list[238].startswith("\x1b")
92102

93103

94104
def test_main_external_diff_with_head_fails(capsys):
@@ -114,11 +124,18 @@ def test_main_external_diff_with_tail_fails(capsys):
114124

115125

116126
def test_main_external_diff_with_lines_fails(capsys):
117-
args = ["--external", "diff -u", "--lines", "1", ROBOTO_BEFORE_PATH, ROBOTO_AFTER_PATH]
127+
args = [
128+
"--external",
129+
"diff -u",
130+
"--lines",
131+
"1",
132+
ROBOTO_BEFORE_PATH,
133+
ROBOTO_AFTER_PATH,
134+
]
118135

119136
with pytest.raises(SystemExit) as exit_info:
120137
run(args)
121138

122139
captured = capsys.readouterr()
123140
assert "[ERROR] The lines option is not supported" in captured.err
124-
assert exit_info.value.code == 1
141+
assert exit_info.value.code == 1

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[tox]
2-
envlist = py39
2+
envlist = py310
33

44
[testenv]
55
commands =

0 commit comments

Comments
 (0)